Files
Linux_post_install/AgentsReport/builder/2026-08-26_registry-phase2.md
T
Your Name 06a05f0567
gates / consistency-and-conventions (push) Successful in 1m24s
ai continue ...
2026-09-04 11:09:21 -04:00

13 KiB
Raw Blame History

Builder Report — POS Command Registry, Phase 2

Date: 2026-08-26 Status: COMPLETE


TL;DR

  • Status: All 3 steps implemented and verified
  • Files changed (this phase): bin/pos (_pos_category_help() migration), bin/pos-network-download, bin/pos-media-sync, bin/pos-system-backup, bin/pos-docker-ps (headers), scripts/gen-docs.sh (2 cell-rendering fixes), gen output in DOC/AGENT_Context_Project.md
  • Gates: make check OK · make lint 0 FAIL, 0 WARN · fast dispatch path unchanged (26 ms)
  • Key finding: pos help <command> is a pure exec-redirect — no per-command dispatcher surface exists; per brief, nothing invented

Baseline

  • Phase 1 state on disk; make checkcheck-sync: OK
  • Maintainer's uncommitted fixes present (ai-alias added to INTERACTIVE_CMDS at bin/pos:262, DOC updates) — untouched
  • BEFORE category-help captured for all 10 categories → /tmp/pos-phase2/h-*-before.txt

[DONE]


Step 1: Migrate _pos_category_help()

File: bin/pos, _pos_category_help() (~lines 68100 post-edit)

Changes:

  • Registry sourced + reg_scan "$self" called lazily inside the function — plain dispatch paths never pay scan cost (verified: fast path 26 ms before and after)
  • Tool list from reg_tools_in "$cat", stripped of leading $cat- → identical short display names
  • All sed header reads replaced by reg_lookup "<cat>-<short>" desc|subcmds|deps
  • Nested-tool enrichment loop, is_nested skip, printf layout: byte-for-byte preserved

Preservation proof (pre-headers): diff of before/mid captures across all 10 categories → zero differences.

New behavior (fires only when headers exist): second indented line [deps: <deps>] directly under a tool's description line.

[DONE]


Step 2: Registry metadata in pos help

Finding: the help meta-command (bin/pos ~lines 233245) joins args into pos-<cat>-<cmd> and execs the tool's own --help. There is no per-command rendering surface in the dispatcher — examples/deps shown there come from each tool's usage() heredoc. Per the brief's instruction ("do NOT invent new UX"), this step is limited to that finding: no dispatcher-side extension made. The category-help surface (Step 1) is where registry metadata surfaces.

[DONE]


Step 3: Representative headers on existing tools

Guards re-verified by reading source before annotating:

Tool Hard guards found Headers added
bin/pos-network-download (lines 1012: err ×3) aria2c, jq, curl # POS_DEPS: + 3 real-subcommand EXAMPLES (add/status/watch)
bin/pos-media-sync (lines 1516: err ×2) lsblk, jq # POS_DEPS: + 2 EXAMPLES (--mp3, --mp4 --dry-run — straight from its usage())
bin/pos-system-backup (line 60 err; 7273 warn; 179 conditional err) tar # POS_DEPS: tar only
bin/pos-docker-ps (line 16: single err) docker # POS_DEPS: docker

Decision note (system-backup): lsblk/jq are warn-only soft guards ("USB copy skipped", returns 0); gpg errors only when encryption is requested (--no-encrypt bypasses). Deps header = hard requirements only → tar.

4 tools total (cap 5 respected). Placement rule followed: metadata headers in comment block, # POS: first, new headers after existing POS_* lines, before any code.

[DONE]


Verification (per budget)

bash -n bin/pos                                    ✅
category --help before/mid diff (10 categories)    ✅ zero diff (Step 1 preservation)
category --help before/after diff                  ✅ only [deps: …] lines added:
                                                     network +1, docker +1, system +1, media +1; others 0
make gen && git diff --stat                        ✅ GEN changes confined to expected blocks
make check                                         ✅ check-sync: OK
make lint                                          ✅ 0 FAIL, 0 WARN
bin/pos-tree | grep deps                           ✅ 4 annotations visible
bin/pos network --help | grep -i deps              ✅ [deps: aria2c jq curl]
fast path timing                                   ✅ 26 ms (no reg_scan cost)
completions/pos.bash                               ✅ untouched by gen

GEN block inspection (as instructed):

  • Tree gains [deps: …] annotation lines under annotated tools ✓
  • Dispatch table flipped to 6-column format (_has_deps_examples=1) ✓
  • Two rendering defects found & fixed in gen-docs.sh during inspection:
    1. Examples cell contained raw inner pipes from the cmd | desc grammar → broke markdown column count. Fixed: inner | rendered as within cells.
    2. Intended // example joiner actually produced single / (paste -d'//' cycles chars). Fixed: awk-based literal // join.
  • Resulting cell sample: …| lsblk jq | pos media sync --mp3 → Sync only MP3 files to USB//pos media sync --mp4 --dry-run → Preview MP4 sync without copying | — valid 6-column row, readable ✓
  • Docmap line ranges shifted accordingly (auto-generated) ✓

Artifacts Summary

Modified this phase

File Change
bin/pos _pos_category_help() → registry API + lazy source + deps line (Maintainer's INTERACTIVE_CMDS line preserved verbatim)
bin/pos-network-download DEPS + 3 EXAMPLES headers
bin/pos-media-sync DEPS + 2 EXAMPLES headers
bin/pos-system-backup DEPS header (hard dep only)
bin/pos-docker-ps DEPS header
scripts/gen-docs.sh Examples-cell pipe escaping () + correct // joiner
DOC/AGENT_Context_Project.md Gen output only (tree annotations, 6-col dispatch, docmap shift, filetable line counts)

Not touched (constraint compliance)

lib/config-ui.sh · completions/pos.bash · INTERACTIVE_CMDS · Maintainer's fixes · bin/pos-tree rendering · no new files except report

Findings / deferred

  • reg_scan ≈ 1.3 s on this box (≈250 process forks / 41 tools) — acceptable on lazy help-only paths; single-pass awk scan remains the anticipated future optimization (Phase 1 Risk 2)
  • pos help <command> has no dispatcher-side surface to enrich (pure redirect)

Review-fix round

Two findings from the Phase 2 adversarial review; both fixed minimally.

Finding 1 (MEDIUM): stale hand-written table header above the dispatch GEN block — FIXED

Exact lines removed from DOC/AGENT_Context_Project.md (were lines 275276):

| Category | Command | Script | Description |
|----------|---------|--------|-------------|

The orphaned 4-column header + its underline row sat between ### Available Commands and <!-- GEN:START dispatch -->, duplicating the GEN block's own 6-column header. Nothing else removed — the section now reads ### Available Commands → blank line → GEN:START dispatch directly.

Finding 2 (LOW): // example joiner collides with URL slashes — FIXED

New separator: " · " (space-middle-dot-space), replacing the literal // in scripts/gen-docs.sh line ~48. Awk literal-join approach unchanged.

Rationale for ·: cannot appear in a URL path, reads as a list separator in a markdown cell, and stays clear of both the pipe grammar (| inside a single example, rendered as in cells) and the em-dash used by # POS: descriptions. Live case now renders: pos network download add https://example.com/file.zip → Enqueue an HTTP download (auto-starts daemon) · pos network download status → Daemon health + global transfer stats · pos network download watch → Live progress view — the URL's // survives intact and each example is cleanly delimited.

Verification

bash -n scripts/gen-docs.sh                        ✅
make gen && make gen → md5 of generated files identical
                                                   ✅ double-gen idempotence: ZERO drift after second run
grep -c 'Category.*Command' DOC/…Project.md        ✅ = 1 (only the GEN block's own header)
sed -n '/GEN:START dispatch/,+4p'                  ✅ GEN header is first row in block
lines above GEN:START dispatch                     ✅ only "### Available Commands" + blank
make check                                         ✅ check-sync: OK
make lint                                          ✅ 0 FAIL, 0 WARN

Note: the earlier bare git diff --exit-code after gen exits 1 because ALL Phase 1+2 work is still uncommitted vs HEAD — checksum comparison of run-1 vs run-2 outputs is the correct idempotence measure and passes.

Constraint compliance

Touched ONLY DOC/AGENT_Context_Project.md (2 stale lines) and scripts/gen-docs.sh (separator string). No bin/, lib/, or completions/ changes. Everything else regenerated via make gen.

[DONE]


Alias generator quoting fix

File: bin/pos-ai-alias (_alias_regen(), new _alias_quote_cmd() helper, _alias_show() command line)

Chosen mechanism + rationale

printf '%q' — the canonical bash mechanism — applied per layer:

  1. _alias_quote_cmd <provider> <session> <prompt> builds the pos-ai command with %q-quoted prompt → the prompt is ONE shell word on expansion.
  2. _alias_regen then %qs the ENTIRE value before emitting alias name=<value> → the assignment survives any bytes.

Justified deviation from the brief's literal snippet: the prescribed single-%q-over-whole-string form expands to multi-word args after --system, but bin/pos-ai's parser takes exactly one value (SYSTEM_PROMPT="$2"; shift 2, bin/pos-ai:640642) — multi-word prompts would truncate to their first word with the remainder leaking into question positionals. The original code had the identical latent defect (single-quote fragments never survive alias-expansion re-parsing). Double-%q is the same single mechanism, applied once per quoting layer — no manual fragment concatenation, no mixing of approaches. Verified by exact round-trip (A4/A7).

Diff summary

Function Change
_alias_quote_cmd() (new, ~10 lines) Shared builder: %q prompt as one word; empty prompt → no --system; used by regen + show
_alias_regen() Naive '…'<escaped>'…' concatenation replaced by helper + whole-value %q; collects names; keeps bash -n "$tmp"; ADDS semantic per-name check in fresh shell: bash --norc -c 'shopt -s expand_aliases; source "$1" && [[ $(type -t "$2") == alias ]]' _ "$tmp" "$n"; failure → warn(alias name) + keep previous + rc 1 (mirrors existing path). Note: expand_aliases required — non-interactive shells define aliases but type -t only reports them when expansion is on (found during self-test)
_alias_show() Command line now rendered via _alias_quote_cmd (display-only; structure untouched) — what users copy pastes verbatim

Unchanged per constraints: ENV_FILE format, | ban, name/session regexes, chmod/mv semantics, interactive flows.

Harness (exact commands)

export CONFIG_DIR=/tmp/pos-alias-test
rm -rf "$CONFIG_DIR"; mkdir -p "$CONFIG_DIR"
cat > "$CONFIG_DIR/ai-aliases.env" <<'EOF'          # fixture as specified
# AI aliases — managed by pos ai alias (do not hand-edit)
assist|gemini|assist|You are assist, a concise and practical AI assistant.
evil|gemini|evil|It's a 'quoted' $(rm -rf /) `backtick` \backslash "dq" ;semi|pipe-less
plain|openrouter|plain|
EOF
# regen via function extraction (sanctioned approach):
bash -c 'source lib/common.sh
  ENV_FILE="$CONFIG_DIR/ai-aliases.env"; SH_FILE="$CONFIG_DIR/ai-aliases.sh"
  eval "$(awk "/^_alias_quote_cmd\(\)/,/^}/" bin/pos-ai-alias)"
  eval "$(awk "/^_alias_regen\(\)/,/^}/" bin/pos-ai-alias)"
  _alias_regen'

Fixture nuance: evil's prompt contains |; the loader's IFS='|' read … _rest splits it off (pre-existing behavior — this is why prompts ban |). Round-trip asserts against the loader-stored string (…;semi), not the hand-edited tail.

Assertion results

# Assert Result
A1 bash --norc -c "source $SH" → rc 0, zero stderr
A2 No execution while sourcing ($CONFIG_DIR/pwned absent)
A3 type -t assist/evil/plain == alias (fresh shell) all three
A4 evil expansion round-trip via eval "set -- ${BASH_ALIASES[evil]}" (no execution): post---system words join to exact original prompt, delivered as one arg byte-exact
A5 plain emits no --system fragment
A6 Regen idempotence (second run md5-identical)
A7 pos ai alias show evil Command line, evaluated via eval set -- $(cat …), yields exact prompt byte-exact

Also verified end-to-end: bin/pos-ai-alias list, bin/pos ai alias show plain (dispatcher path) behave normally.

Gates

bash -n bin/pos-ai-alias   ✅ syntax OK (563 lines)
make gen                   ✅ (filetable row for pos-ai-alias updated automatically)
make check                 ✅ check-sync: OK
make lint                  ✅ 0 FAIL, 0 WARN

[DONE]