- Use FlexLib's API source code to create a program that can reliably generate API documentation in markdown format.
- Categorize the APIs, creating a separate markdown page for each category. Categories should be defined by a JSON file in the models folder.
- Describe the APIs at the TCP command level, including commands and options, responses and know status messages that are sent as a result of the command.
- Support hand-written supplementary markdown files that are merged into the final generated document for a given API category, allowing additional context, usage notes, or corrections to be authored manually and preserved across regeneration runs.
- Generate the markdown documents using a standard format defined in a file in the models folder. The file example-api-format.md describes this format.
- The format model must cover three message types:
- Commands — sent by the client:
C[D]<seq>|<command> [parameters] - Responses — sent by the radio in reply:
R<seq>|<result_code>|<data> - Status messages — unsolicited broadcasts sent by the radio:
S<client_handle>|<object_type> [id] <key>=<value> ...
- Commands — sent by the client:
- (Future — may be discarded) The
example-api-format.mdfile could be updated to include a "Where:" field breakdown of the status message wire format. However, the radio emits hundreds of distinct status messages with widely varying formats, and it is often impossible to determine whether a given status message is a direct response to a command or a firmware-triggered event. This documentation may instead be deferred to hand-written supplementary text on a per-command basis.
- The program will be implemented in Python.
- The
example_docs/folder contains existing hand-written markdown files covering approximately 30 command categories. - These files serve as an initial reference for the structure and content that the program should produce.
- The program now produces output for all 34 categories defined in
models/categories.json; the generated pages live indocs/. Theexample_docs/files are retained for comparison only and are not regenerated.
- The program writes all final generated markdown files directly to
docs/. docs/is the authoritative output location for the build pipeline; the pipeline does not use a staging folder.- A separate, optional step (
src/publish_wiki.py) transformsdocs/into a GitHub-wiki-ready page set underwiki/for publishing.wiki/is a transient build artifact and is gitignored. See Wiki Publishing.
Supplementary content allows hand-authored notes to be preserved across regeneration runs and merged into the generated pages.
- Supplement files live in
supplementary/at the repo root. - Each file is named to match the category output file it augments (e.g.,
supplementary/Slice-Commands.mdfordocs/Slice-Commands.md). - Both the
supplementary/folder and the generateddocs/output folder are committed to git. Theflexlib/source tree and the transientwiki/build folder are not (see.gitignore).
- A supplement file is a plain markdown file.
- Each
###heading must exactly match a generated command heading (the text after the##on the generated page), with no backtick wrapper. - Generated headings are derived from the command template with each
<placeholder>rendered as..., and any trailing...dropped. For example:slice tune <index> <freq>→ heading### slice tune ... ...slice set <index> active=<active>→ grouped heading### slice set(trailing...dropped)
- When in doubt, run
build_docs.pyand copy the exact## ...heading from the generateddocs/page. - Everything under that heading (until the next
###or end of file) is the supplement body for that command. - The supplement body may contain any markdown: prose, code blocks, tables, sub-headings (
####and below). - Within the
###section of a grouped key=value command (e.g.slice set), a#### <key>sub-heading supplies a per-parameter description override for that key, keyed by the parameter name. The override replaces the auto-generated description for that row in the parameter table.
- Supplement content is injected after the auto-generated command description and before the Syntax block.
- If a supplement exists for a command, its full body is inserted at that point verbatim.
- If no supplement file exists for a category, or no heading matches a given command, the page renders as if no supplement were present.
- A supplement heading that matches no generated command is not silently dropped: the renderer prints a
[WARN] supplement heading '<heading>' in <output_file> matched no command; its content was not included.so stale or mistyped headings are caught (e.g. after a FlexLib command is renamed).
### slice create
Creates a new receiver slice. The radio returns the index of the created slice
in the response data field.
### slice set
Sets one or more operational parameters on the specified slice receiver.
#### active
Only one slice can be active at a time. Setting this to `1` on one slice
automatically deactivates the previously active slice.- The program lives in a
src/folder and is invoked aspython src/build_docs.py. - It accepts a
--versionargument (e.g.,--version 4.2.18) that selects the FlexLib source tree underflexlib/v<version>/FlexLib/. Defaults to the only version present if omitted. - It runs the full pipeline on every invocation: extract → intermediate → generate → index.
- For each source class listed in
models/categories.json, open the corresponding.csfile. - Scan for calls to
SendCommand(),SendReplyCommand(), andSendCommandAsync()using regex. - Extract the string argument passed to each call. The argument may be:
- A string literal:
"slice lock " + _index - A C# interpolated string:
$"display pan set 0x{_streamID:X} pan_position={value}" - A local variable built in a prior conditional block (harder — best-effort only)
- A string literal:
- Normalize the extracted string into a command template by replacing C# variable expressions with
<placeholder>tokens derived from the variable name where possible (e.g.,{_streamID:X}→<streamID>,+ _index +→<index>). - The first word(s) of the string form the TCP verb used for category routing.
- Scan
ParseStatus()andStatusUpdate()methods to extract key names fromswitchcases,ifconditions, and string comparisons (e.g.,case "freq":,if (kv == "mode")). - Scan
public enumdefinitions and record member names for use as parameter valid-value lists.
- Route each extracted command to a category by matching its TCP verb against each category's
tcp_verbslist incategories.json. - Write one JSON file per source class to
models/intermediate/<ClassName>.json. - Write unmatched commands to
models/intermediate/unmatched.jsonand print a warning per unmatched command.
- For each category in
categories.json, merge the intermediate JSON files listed insource_classes. - Render one markdown page per category to
docs/<output_file>using the format defined inmodels/example-api-format.md. - Each command becomes a
##section. Commands that share a prefix and end inkey=<value>form (e.g. allslice set <index> <key>=<value>variants) are consolidated into a single##section with one parameter table. Parameters and a response example are rendered beneath each section. - Merge any matching per-category supplement from
supplementary/<output_file>(see Supplementary Content). - Include a version tag in the page header (e.g.,
> Generated from FlexLib v4.2.18).
- Regenerate
docs/Command-Index.mdfrommodels/categories.json, grouped bygroup, with relative links to eachoutput_file.
- Use regex, not a full C# AST parser. This is intentional: the source patterns are consistent enough for regex, and a full parser would add significant complexity.
- String concatenation and interpolation mean many extracted command strings will be partial templates. This is acceptable — the goal is to capture the verb, sub-command, and parameter key names, not to produce executable strings.
- Commands built dynamically across multiple lines (e.g.,
cmd = "slice lock " + _index; if (!_lock) cmd = "slice unlock ...") may only capture one branch. Flag these for manual review in the intermediate JSON using an"extraction_note"field.
- The renderer populates the
{Description of command}field automatically — no<!-- TODO -->stubs. - Primary source: scan backwards from each
SendCommandcall for the nearest/// <summary>XML doc comment block; clean up the text (strip///markers, collapse whitespace). - Fallback (when no doc comment is found): derive a description from the enclosing property or method name by splitting on camelCase and underscores and capitalising the result (e.g.
level_32Hz→"Level 32Hz",EQ_enabled→"EQ enabled"). - Store the derived description in the intermediate JSON under
"description". The renderer writes it verbatim.
- All program source lives under
src/. - Module breakdown:
build_docs.py— CLI entry point and pipeline orchestrationextractor.py— C# source parsing (Send* calls, ParseStatus, enums)categorizer.py— verb-to-category routing and intermediate JSON outputrenderer.py— intermediate JSON → markdown page renderingsupplements.py— per-category supplement file loader (parses###/####sections)indexer.py—Command-Index.mdgeneration fromcategories.jsonpublish_wiki.py— transformsdocs/into a GitHub-wiki-ready page set (see Wiki Publishing); not part of the build pipeline
The generated docs/ pages are published to this repository's own GitHub wiki (the companion <repo>.wiki.git repository, surfaced under the repo's Wiki tab). Publishing is a separate concern from the build pipeline.
- Reads
docs/*.mdplusmodels/categories.jsonand writes a staging directory (wiki/by default, gitignored). - GitHub wikis are flat and use extensionless links, so the transform:
- rewrites inter-page links from
](Name.md)to](Name)(preserving any#anchor); - renames
Command-Index.mdtoHome.md(the wiki landing page) and retargets links to it; - generates
_Sidebar.mdfromcategories.json, grouped like the index, so every wiki page shows category navigation.
- rewrites inter-page links from
- Stale
.mdpages from a previous run are cleared so renames and removals propagate. The transform is idempotent. - Runnable locally for preview:
python3 src/publish_wiki.py(writes./wiki/).
- Runs on push to
maintouchingsrc/,models/,supplementary/, ordocs/, and on manualworkflow_dispatch. - Rebuilds docs (when the FlexLib source tree is present), runs the transform, then clones the
*.wiki.gitrepo, replaces its pages, and pushes. Commits only when there is a change. Aconcurrencygroup serialises wiki pushes.
- Initialize the wiki: create a first page via the repo's Wiki tab. The
*.wiki.gitrepository does not exist (and cannot be cloned/pushed) until at least one page exists. WIKI_TOKENsecret: the defaultGITHUB_TOKENcannot push to wiki repositories. Add a Personal Access Token (classicreposcope, or fine-grained Contents read/write) as theWIKI_TOKENActions secret.- For a public repo, enable Settings → Features → Wikis → "Restrict editing to collaborators only" so the public can read but not edit the wiki.
1. Create a JSON file that represents the command categories and store it in the models folder. (Complete)
- The file is
models/categories.json. It has been created and contains all current categories. - Each entry in the
categoriesarray has the following fields:name— display name used in the command index (e.g.,"Slice Commands")description— one-line description used in the command index link listgroup— section heading inCommand-Index.md(e.g.,"Core Radio Control")output_file— filename written todocs/(e.g.,"Slice-Commands.md")source_classes— list of.csfilenames whose commands contribute to this categorytcp_verbs— list of TCP command verb prefixes (e.g.,["slice"]) used to assign extracted commands to this category
Radio.csSlice.csPanadapter.csWaterfall.csTuner.cs(ATU)Amplifier.csEqualizer.csFilter.csTNF.csSpot.csMemory.csMeter.csWaveform.csXvtr.csCWX.csDVK.csGUIClient.csHAAPI.cs
ALE2G.csALE3G.csALE4G.csALEComposite.cs
UsbCable.csUsbBitCable.csUsbBcdCable.csUsbCatCable.csUsbPassthroughCable.csUsbLdpaCable.csUsbOtherCable.cs
DAXIQStream.csDAXRXAudioStream.csDAXTXAudioStream.csDAXMICAudioStream.csNetCWStream.csRXAudioStream.csRXRemoteAudioStream.csTXRemoteAudioStream.cs
SendCommand()— immediate commands (fire and forget)SendReplyCommand()— commands requiring a response callbackSendCommandAsync()— asynchronous variant used in some classes (e.g.,DVK.cs)
ParseStatus()— parses unsolicited key=value status messages broadcast by the radioStatusUpdate()— variant of the same pattern used in some classes
These methods reveal the full set of key=value pairs the radio broadcasts for each object type and must be extracted alongside the command strings.
- If a
SendCommand,SendReplyCommand, orSendCommandAsynccall contains a TCP verb that does not match any category'stcp_verbslist, the command must not be silently dropped. - Write all unmatched commands to
models/intermediate/unmatched.jsonusing the same schema as other intermediate files, with an additionaltcp_verbfield showing the unrecognised verb. - The program should also print a warning to stdout for each unmatched command found.
- Enum definitions in each source file (e.g.,
AGCMode,InterlockState,PTTSource) represent the valid discrete values for command parameters. - The program should extract enum names and their members and associate them with the parameters that reference them.
- The program must create the
models/intermediate/folder if it does not exist. - Store intermediate data in
models/intermediate/as JSON files, one per source class. - Each per-class file is an object with these top-level keys:
source_class— originating class name (e.g."Slice")commands— list of command objects (see below)status_keys— key names parsed inParseStatus/StatusUpdateenums— map of enum name → list of member names, used as parameter valid-value lists
- Each entry in
commandsincludes:template— the TCP command string template, with<placeholder>tokens for variable parts (e.g."slice set <index> active=<active>")verb— the first word of the template, used for category routingdescription— auto-generated description (from the nearest/// <summary>or derived from the member name)parameters— list of parameter names extracted from the template's placeholderscategory— the category the command was routed toextraction_note— (optional) present only when extraction captured a partial/ambiguous template that warrants manual review
- Unmatched commands are written to
models/intermediate/unmatched.jsonusing the same command schema plus atcp_verbfield showing the unrecognised verb.
- The FlexLib source at
flexlib/v4_2_18/corresponds to version 4.2.18. - Every generated markdown file should include a version tag in its header indicating the FlexLib version it was produced from.
- The version should also be stored in the intermediate JSON files so the full pipeline is traceable.
- When new FlexLib versions are added under
flexlib/, the program should accept a version argument to target the correct source tree.
USB-Cable-Commandshas been added tomodels/categories.jsoncovering the sevenUsb*Cable.csclasses and theusb_cableverb (usb_cable set,usb_cable setbit,usb_cable write).- The corresponding markdown page will be generated by the program on first run.
docs/is initially empty.docs/Command-Index.mddoes not pre-exist and will be created by this task on first run.- The program must regenerate
docs/Command-Index.mdfrom the category JSON (Task 1) as part of every build run. - Links must use relative paths to local markdown files (e.g.,
Slice-Commands.md), not absolute or external URLs.