ai continue ...
gates / consistency-and-conventions (push) Successful in 1m24s

This commit is contained in:
Your Name
2026-09-04 11:09:21 -04:00
parent 072a8e72c1
commit 06a05f0567
76 changed files with 15648 additions and 122623 deletions
@@ -0,0 +1,808 @@
# Architect Report — Self-Describing Command Registry for POS
**Date:** 2026-08-26
**Status:** DECISION_READY
---
## TL;DR
- **Decision:** Add a thin `lib/registry.sh` library that parses `# POS_*:` headers into a queryable API; two new optional headers (`# POS_DEPS:`, `# POS_EXAMPLES:`) extend existing conventions; no framework, no new abstraction layer.
- **Key insight:** The existing header system is already 80% of a registry. The missing piece is a shared parsing library so every consumer stops reimplementing `sed` + `grep` header reading.
- **Scope:** `lib/registry.sh` (new), `scripts/gen-docs.sh` (extend), `bin/pos-tree` (use registry), `templates/pos-tool.sh` (document new headers), docs. All existing tools keep working unchanged.
- **Migration:** Zero-downtime — new headers are optional. Tools add `# POS_DEPS:` and `# POS_EXAMPLES:` incrementally. Registry degrades gracefully when headers are absent.
- **Open items:** `pos help` and `pos menu` are future consumers (not in this phase). Dashboard is out of scope.
---
## Decision 1: Header Format — Extend, Don't Replace
### Problem
The spec asks for dependencies, curated examples, and a shared query API. Currently, each consumer (`pos tree`, `pos <category> --help`, `gen-docs.sh`, `pos config`) independently implements `sed`/`grep` header parsing with its own logic.
### Decision
Add two new optional `# POS_*:` header lines. Keep all existing headers unchanged.
### Exact Syntax
```bash
# POS_DEPS: <binary1> [binary2 ...]
# POS_EXAMPLES: <command> | <description>
```
**Rules:**
- All new headers are **optional** — tools that don't declare them simply won't expose that metadata. Progressive metadata preserved.
- `# POS_DEPS:` is space-separated binary names (what `command -v` checks, not apt package names).
- `# POS_EXAMPLES:` can appear on multiple lines — each is `<command> | <description>` (pipe-delimited, max one `|`).
- Headers must appear between the shebang/strict-mode block (lines 16) and the first non-comment line.
- Gen-docs and lint only parse headers from the first ~10 lines of each file.
**Existing headers (unchanged):**
```bash
# POS: <category> <command> — <description>
# POS_FLAGS: --flag1 --flag2
# POS_SUBCMDS: sub1 sub2 sub3
# POS_CONFIG: <scope> | <env-file> | <KEY>=<flags>:<desc> | ...
```
**New headers (optional):**
```bash
# POS_DEPS: docker nmap jq
# POS_EXAMPLES: pos network scan 192.168.1.0/24 | Scan a /24 CIDR
# POS_EXAMPLES: pos network scan 10.0.0.0/28 --full | Full scan with OS detection
```
### Example: Minimal Tool (No Change Needed)
```bash
#!/usr/bin/env bash
set -euo pipefail
# POS: ssh load-keys — Load all SSH keys into the agent
# ... rest of script
```
### Example: Rich Tool
```bash
#!/usr/bin/env bash
set -euo pipefail
# POS: network download — aria2 RPC daemon + queue control (add/torrent/metalink, watch, limits)
# POS_SUBCMDS: start stop status add torrent metalink list info files peers pause resume remove purge move limit set watch restart retry replace menu
# POS_FLAGS: --dir --out --split --seed --force --upload --gid --tmux
# POS_DEPS: aria2c jq curl
# POS_EXAMPLES: pos network download add https://example.com/file.zip | Download a file
# POS_EXAMPLES: pos network download status | Show download queue status
# POS_EXAMPLES: pos network download --tmux start aria2 daemon with live view
```
### Rationale
| Option | Architecture | Advantages | Costs | Risks | When to choose |
|--------|-------------|------------|-------|-------|----------------|
| **A: Extend existing headers** | Add `# POS_DEPS:` and `# POS_EXAMPLES:` alongside existing headers | Backward compatible, zero migration cost, progressive metadata, consistent with existing patterns | Two new header formats to parse | Low — optional headers degrade gracefully | **Chosen** — smallest sufficient design |
| B: Unified YAML frontmatter | Replace all `# POS_*:` with a YAML block in each file | Richer structure, easier to extend | Breaks all existing consumers, requires migration of 40+ tools, adds YAML dependency | High — YAML parser availability in bash, migration burden | Only if the header system were fundamentally inadequate |
| C: Separate registry file | `registry/<tool>.yaml` per tool | Clean separation, richer metadata | Duplicates what headers already provide, extra files to maintain, sync risk between header and registry | Medium — source of truth drift | Only if headers couldn't hold the metadata |
**Option A wins** because the existing header system already works, is already the source of truth for gen-docs output, and the new metadata (deps, examples) fits naturally into the comment-header format.
### Constraints for Builder
- `# POS_DEPS:` line: `sed -n '/^# POS_DEPS: /{s/^# POS_DEPS: //;p;q}' <file>` — space-separated tokens.
- `# POS_EXAMPLES:` lines: `grep '^# POS_EXAMPLES:' <file | sed 's/^# POS_EXAMPLES:[[:space:]]*//'` — one per line, `|`-delimited command|description.
- The `# POS:` header line **must remain the first metadata line** after shebang/strict-mode. New headers go after existing headers, before any code.
[DECIDED]
---
## Decision 2: Registry Library — `lib/registry.sh`
### Problem
Four consumers independently parse tool headers with their own `sed`/`grep` patterns:
- `bin/pos` `_pos_category_help()` (lines 68123): reads `# POS:` and `# POS_SUBCMDS:` per tool
- `bin/pos-tree` (lines 4768): reads `# POS:` and `# POS_SUBCMDS:` per tool
- `scripts/gen-docs.sh` (lines 3046): reads `# POS:`, `# POS_FLAGS:`, `# POS_SUBCMDS:`, `# POS_CONFIG:` per tool
- `lib/config-ui.sh` (lines 5057): reads `# POS_CONFIG:` per tool
Each reimplements the same header-reading pattern. Adding new headers means updating every consumer.
### Decision
Create `lib/registry.sh` — a thin library (target ~180 lines) that provides a shared API for querying tool metadata from `# POS_*:` headers.
### Data Structures
All data lives in bash associative arrays and indexed arrays, populated by a single `reg_scan` call.
```bash
# Indexed array — all tool keys, sorted (LC_ALL=C)
declare -a _reg_tools=()
# Associative arrays — keyed by tool key (e.g., "network-download", "config")
declare -A _reg_cat=() # tool → category ("" for category-less)
declare -A _reg_desc=() # tool → description (text after "— ")
declare -A _reg_flags=() # tool → raw POS_FLAGS value
declare -A _reg_subcmds=() # tool → raw POS_SUBCMDS value
declare -A _reg_deps=() # tool → raw POS_DEPS value
declare -A _reg_examples=() # tool → newline-joined POS_EXAMPLES lines
# Config is special: multiple headers per tool, multiple fields per header.
# Stored as pipe-delimited lines keyed by scope (not tool).
declare -a _reg_config_scopes=() # unique scope names, sorted
declare -A _reg_config_keys=() # scope → newline-joined key|flags|desc lines
```
**Why not store everything in one mega-array?** Bash associative arrays can't hold structured records. Separate arrays per field keep lookups O(1) and code readable.
### Tool Key Convention
Tool keys match the existing filename convention:
- `bin/pos-network-download` → key `network-download`, category `network`
- `bin/pos-config` → key `config`, category `""` (category-less)
- `bin/pos-ai-alias` → key `ai-alias`, category `ai`
### API
```bash
# ── Initialization ──────────────────────────────────────────────
reg_scan [dir]
Scan all pos-* files in dir (default: auto-detect from BASH_SOURCE).
Populates _reg_tools, _reg_cat, _reg_desc, _reg_flags, _reg_subcmds,
_reg_deps, _reg_examples, _reg_config_scopes, _reg_config_keys.
Must be called before any other reg_* function.
Uses LC_ALL=C for deterministic sort.
# ── Discovery ───────────────────────────────────────────────────
reg_list
Echo sorted list of all tool keys, one per line.
reg_categories
Echo sorted unique category names (empty string for category-less tools).
reg_tools_in <category>
Echo sorted tool keys belonging to <category>.
Pass "" for category-less tools.
# ── Lookup ──────────────────────────────────────────────────────
reg_lookup <tool> <field>
Echo a field's value for a tool. Fields:
cat, desc, flags, subcmds, deps, examples
Returns empty string if field not set or tool not found.
Exit code: 0 if tool found, 1 if not.
reg_config_scopes
Echo sorted list of unique config scope names.
reg_config_keys <scope>
Echo key|flags|description lines for a scope (newline-delimited).
reg_config_envfile <scope>
Echo the env-file basename for a scope.
Exit code: 0 if found, 1 if not.
# ── Iteration ───────────────────────────────────────────────────
reg_each <callback>
Call <callback> for each tool, passing:
<callback> <category> <tool_key> <description>
Category is empty for category-less tools.
# ── Convenience (for common patterns) ──────────────────────────
reg_tool_exists <tool>
Exit 0 if tool is registered, 1 otherwise.
reg_tools_for_category <category>
Alias for reg_tools_in. Kept for clarity.
```
### Source Pattern
```bash
# lib/registry.sh — no shebang (library, not executable)
# Sourced opt-in by consumers that need tool metadata.
# Common.sh helpers (guarded fallback — mirrors lib/config-ui.sh pattern)
declare -F log >/dev/null || log() { echo "[+] $*"; }
declare -F warn >/dev/null || warn() { echo "[!] $*"; }
declare -F err >/dev/null || err() { echo "ERROR: $*" >&2; exit 1; }
# ── Tool directory detection ────────────────────────────────────
# Repo: lib/registry.sh → ../bin
# Install: /usr/local/bin/registry.sh → /usr/local/bin (same dir)
_reg_tools_dir() {
local dir
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../bin" 2>/dev/null && pwd)"
if [ -d "$dir" ] && ls "$dir"/pos-* &>/dev/null; then
echo "$dir"
else
dirname "${BASH_SOURCE[0]}"
fi
}
# ── Data stores ─────────────────────────────────────────────────
declare -a _reg_tools=()
declare -A _reg_cat=()
declare -A _reg_desc=()
declare -A _reg_flags=()
declare -A _reg_subcmds=()
declare -A _reg_deps=()
declare -A _reg_examples=()
declare -a _reg_config_scopes=()
declare -A _reg_config_keys=()
# ── reg_scan ────────────────────────────────────────────────────
reg_scan() {
local dir="${1:-$(_reg_tools_dir)}" f
local old LC_ALL_PREV="$LC_ALL"
export LC_ALL=C
_reg_tools=()
# Clear all associative arrays
for key in "${!_reg_cat[@]:-}"; do
unset "_reg_cat[$key]" "_reg_desc[$key]" "_reg_flags[$key]"
unset "_reg_subcmds[$key]" "_reg_deps[$key]" "_reg_examples[$key]"
done
_reg_config_scopes=()
for scope in "${!_reg_config_keys[@]:-}"; do
unset "_reg_config_keys[$scope]"
done
local -A scope_seen=()
for f in "$dir"/pos-*; do
[ -x "$f" ] || continue
local name="${f##*/pos-}"
local key cat sub
if [[ "$name" == *-* ]]; then
cat="${name%%-*}"
sub="${name#*-}"
else
cat=""
sub="$name"
fi
key="$sub"
_reg_tools+=("$key")
_reg_cat["$key"]="$cat"
# POS: — description (text after first "— ")
local pos_line
pos_line="$(sed -n '/^# POS: /{s/^# POS: //;p;q}' "$f" 2>/dev/null)"
_reg_desc["$key"]="${pos_line#*— }"
# POS_FLAGS:
_reg_flags["$key"]="$(sed -n '/^# POS_FLAGS: /{s/^# POS_FLAGS: //;p;q}' "$f" 2>/dev/null)"
# POS_SUBCMDS:
_reg_subcmds["$key"]="$(sed -n '/^# POS_SUBCMDS: /{s/^# POS_SUBCMDS: //;p;q}' "$f" 2>/dev/null)"
# POS_DEPS:
_reg_deps["$key"]="$(sed -n '/^# POS_DEPS: /{s/^# POS_DEPS: //;p;q}' "$f" 2>/dev/null)"
# POS_EXAMPLES: (may appear multiple times — join with newlines)
local examples=""
examples="$(sed -n '/^# POS_EXAMPLES: /{s/^# POS_EXAMPLES: //;p}' "$f" 2>/dev/null)"
_reg_examples["$key"]="$examples"
# POS_CONFIG: (may appear multiple lines per file)
local line
while IFS= read -r line; do
[ -n "$line" ] || continue
line="${line#*POS_CONFIG:}"
local scope="${line%%|*}"
scope="${scope// }"
[ -n "$scope" ] || continue
_reg_config_keys["$scope"]+="${_reg_config_keys[$scope]:+$'\n'}$line"
if [ -z "${scope_seen[$scope]:-}" ]; then
scope_seen["$scope"]=1
_reg_config_scopes+=("$scope")
fi
done < <(grep '^# POS_CONFIG:' "$f" 2>/dev/null || true)
done
# Sort tools
mapfile -t _reg_tools < <(printf '%s\n' "${_reg_tools[@]}" | sort)
# Sort config scopes
mapfile -t _reg_config_scopes < <(printf '%s\n' "${_reg_config_scopes[@]}" | sort -u)
export LC_ALL="$LC_ALL_PREV"
}
# ── Discovery ───────────────────────────────────────────────────
reg_list() { printf '%s\n' "${_reg_tools[@]}"; }
reg_categories() {
local -A cats=()
local t
for t in "${_reg_tools[@]}"; do
cats["${_reg_cat[$t]}"]=1
done
printf '%s\n' "${!cats[@]}" | sort
}
reg_tools_in() {
local cat="$1" t
for t in "${_reg_tools[@]}"; do
[ "${_reg_cat[$t]}" = "$cat" ] && echo "$t"
done
}
# ── Lookup ──────────────────────────────────────────────────────
reg_lookup() {
local tool="$1" field="$2"
case "$field" in
cat) echo "${_reg_cat[$tool]:-}" ;;
desc) echo "${_reg_desc[$tool]:-}" ;;
flags) echo "${_reg_flags[$tool]:-}" ;;
subcmds) echo "${_reg_subcmds[$tool]:-}" ;;
deps) echo "${_reg_deps[$tool]:-}" ;;
examples) echo "${_reg_examples[$tool]:-}" ;;
*) return 1 ;;
esac
}
reg_config_scopes() { printf '%s\n' "${_reg_config_scopes[@]}"; }
reg_config_keys() {
local scope="$1"
echo "${_reg_config_keys[$scope]:-}"
}
reg_config_envfile() {
local scope="$1" line
line="$(echo "${_reg_config_keys[$scope]:-}" | head -1)"
[ -n "$line" ] || return 1
line="${line#*|}" # drop scope
local env="${line%%|*}"
echo "${env// }"
}
# ── Iteration ───────────────────────────────────────────────────
reg_each() {
local cb="$1" t
for t in "${_reg_tools[@]}"; do
"$cb" "${_reg_cat[$t]}" "$t" "${_reg_desc[$t]}"
done
}
# ── Convenience ─────────────────────────────────────────────────
reg_tool_exists() {
[ -n "${_reg_desc[$1]+x}" ]
}
```
### Rationale
| Option | Architecture | Advantages | Costs | Risks | When to choose |
|--------|-------------|------------|-------|-------|----------------|
| **A: Regenerate-on-source library** | `reg_scan` parses all files into bash arrays on first call | O(1) lookups after scan, no external deps, works from /usr/local/bin, bash-native | ~180 lines of code, scan cost at startup (~5ms for 40 tools) | Low — scan is fast enough for interactive use | **Chosen** — matches project's bash-only, no-framework philosophy |
| B: Cached JSON file | `make gen` produces `registry.json`, consumers parse with `jq` | Fast lookups, rich queries | Requires `jq` at runtime (currently a dep, but adds coupling), extra build step, staleness risk | Medium — JSON dependency for all consumers | Only if performance of header parsing became a bottleneck (it won't for 40 tools) |
| C: Per-tool .meta files | Each tool has a sidecar `pos-<tool>.meta` | Clean separation, rich format | Duplicates header data, extra files to maintain, sync drift risk | Medium — two sources of truth | Only if headers were fundamentally limited |
**Option A wins** because:
1. Headers are already the source of truth — no sync risk.
2. The scan takes ~5ms for 40 tools — no performance concern.
3. Works from `/usr/local/bin/` (all libs live there after install).
4. Matches the project's "no unnecessary framework" philosophy.
5. Follows the pattern established by `lib/config-ui.sh`.
### Source Chain
```bash
# In any consumer:
source "$(dirname "$0")/../lib/registry.sh" 2>/dev/null || source "$(dirname "$0")/registry.sh"
reg_scan
# ... use reg_list, reg_lookup, etc.
```
[DECIDED]
---
## Decision 3: Consumer Integration
### Problem
Four consumers need to use the registry. Each has different requirements:
- `bin/pos-tree` needs tree-building from tool data
- `bin/pos` `_pos_category_help()` needs category → tool listing
- `scripts/gen-docs.sh` needs all metadata for code generation
- `lib/config-ui.sh` needs config scopes and keys
### Decision
Migrate consumers to `lib/registry.sh` in this order (highest value first):
#### 3.1: `bin/pos-tree` (Priority: HIGH)
**Current state** (lines 4768): Scans `pos-*` files independently, reads `# POS:` and `# POS_SUBCMDS:` via `sed`.
**After migration:**
```bash
source "$(dirname "$0")/../lib/registry.sh" 2>/dev/null || source "$(dirname "$0")/registry.sh"
reg_scan
# Build tree from registry instead of scanning files
for tool in $(reg_list); do
cat="$(reg_lookup "$tool" cat)"
desc="$(reg_lookup "$tool" desc)"
deps="$(reg_lookup "$tool" deps)"
# ... add to tree, annotate with deps if present
done
```
**Enhancement:** When `# POS_DEPS:` is present, show it in the tree view:
```
├── docker ps # Enhanced container overview (health, IPs, ports, uptime)
│ [deps: docker]
```
This is the highest-value consumer change — it proves the registry works at runtime and shows the immediate benefit of new metadata.
**Implementation note:** Keep the existing `add()` / `render()` tree-building logic. Replace only the data-collection loop (lines 4768) with registry calls. The tree structure is already correct from filenames + `POS_SUBCMDS`.
#### 3.2: `scripts/gen-docs.sh` (Priority: HIGH)
**Current state** (lines 3046): Collects tools into a pipe-delimited array via direct `sed` calls.
**After migration:**
```bash
# In the tools collection loop, use registry for new fields
# Keep the existing collection pattern for backward compatibility
# (gen-docs.sh has its own sorting and rendering logic)
# Add deps and examples to the tools array format
tools+=("$cat|$sub|$desc|$flags|$subcmds|$deps|$examples")
```
**New gen blocks:**
- Add `deps` and `examples` columns to `gen_tree()`, `gen_dispatch()`, `gen_filetable()`.
- These become visible in `DOC/AGENT_Context_Project.md` once tools add the new headers.
**Important:** The existing `gen_*` functions use their own `tools` array (not the registry) because they need specific formatting. The registry provides the raw data; gen-docs formats it. This avoids tight coupling between the generator and the library.
**Alternative considered:** Have gen-docs source the registry directly. Rejected because gen-docs needs the data in a specific format (pipe-delimited array) and the registry's data structure is an implementation detail. Keeping the data flow explicit (`registry → gen-docs tools array → gen_* functions`) is cleaner.
#### 3.3: `bin/pos` `_pos_category_help()` (Priority: MEDIUM)
**Current state** (lines 68123): Scans `pos-<cat>-*` files, reads `# POS:` and `# POS_SUBCMDS:` per file.
**After migration:**
```bash
_pos_category_help() {
local cat="$1"
source "$(dirname "$0")/../lib/registry.sh" 2>/dev/null || source "$(dirname "$0")/registry.sh"
reg_scan
echo "pos $cat$cat tools"
echo
echo "USAGE"
echo " pos $cat <command> [args]"
echo
echo "COMMANDS"
for tool in $(reg_tools_in "$cat"); do
local desc deps
desc="$(reg_lookup "$tool" desc)"
deps="$(reg_lookup "$tool" deps)"
printf ' %-28s%s' "$tool" "$desc"
[ -n "$deps" ] && printf ' [deps: %s]' "$deps"
echo
# ... subcommands from reg_lookup "$tool" subcmds
done
echo
echo "Run 'pos $cat <command> --help' for details on a command."
exit 0
}
```
**Trade-off:** This adds a `reg_scan` call every time `pos <category>` runs. For 40 tools, the scan takes ~5ms — negligible for interactive use. If profiling shows this matters, `pos` could cache the scan in a temp file (but this is premature optimization).
**Alternative considered:** Keep `_pos_category_help()` using direct `sed` (no registry dependency). Rejected because the whole point is to centralize header parsing. The 5ms scan cost is acceptable.
#### 3.4: `lib/config-ui.sh` (Priority: LOW — separate decision)
**Current state** (lines 50203): Has its own header parsing for `# POS_CONFIG:` and `# POS_KEYS:`. The config library is already a mature, working abstraction.
**Decision: Do NOT integrate config-ui.sh with the registry in this phase.**
**Rationale:**
1. config-ui.sh already works correctly and is well-tested.
2. Its parsing is specialized (multi-line key fields, `*plugins` expansion, `*providers` expansion).
3. Integrating it with the registry would require the registry to handle all config-ui's edge cases, bloating the library.
4. config-ui.sh's `cfg_headers()` / `cfg_scopes()` / `cfg_scope_keys()` API is already the "registry" for config consumers.
**Future:** When config-ui.sh needs maintenance, it could source the registry for its initial scan. But this is not needed now.
### Rationale
| Option | Architecture | Advantages | Costs | Risks | When to choose |
|--------|-------------|------------|-------|-------|----------------|
| **A: Incremental migration** | Migrate tree + gen-docs first, then pos, defer config-ui | Lowest risk, proves value early, no breaking changes | Some consumers still use direct sed during transition | Low — transition period is harmless | **Chosen** — smallest risky step |
| B: Big-bang migration | Rewrite all consumers at once | Consistent from day one | High risk, many things can break, hard to review | High — one bad refactor breaks everything | Only if the codebase were much smaller |
| C: No migration — just add the library | Create registry.sh but don't change any consumers | Library exists for future use | No immediate value, consumers still duplicate logic | Low — but pointless | Only if the task were just "create a library" |
[DECIDED]
---
## Decision 4: Migration Strategy — Zero-Downtime Incremental
### Problem
40+ tools exist. All must keep working. The new headers are optional. No tool should require changes to function.
### Decision
**Phase 1 (this implementation):**
1. Create `lib/registry.sh` with full API.
2. Update `scripts/gen-docs.sh` to parse new headers (degrades gracefully when absent).
3. Migrate `bin/pos-tree` to use registry (proves runtime value).
4. Update `templates/pos-tool.sh` to document new headers.
5. Update `DOC/DEV.md` and `DOC/AGENT_Context_Project.md` with new header format.
**Phase 2 (future, out of scope):**
1. Migrate `bin/pos` `_pos_category_help()` to use registry.
2. Enhance `pos help` to show registry metadata (deps, examples).
3. Add `# POS_DEPS:` and `# POS_EXAMPLES:` to tools incrementally (start with 35 representative tools per category).
**Phase 3 (future, out of scope):**
1. `pos menu` — interactive menu from registry (new tool).
2. `pos dashboard` — status dashboard from registry (new tool).
### Incremental Adoption for Tool Authors
1. Add `# POS_DEPS: docker jq` to your tool's header block. Done — deps appear in registry.
2. Add `# POS_EXAMPLES: pos <tool> <args> | Description` lines. Done — examples appear in registry.
3. No other changes required. The tool keeps working exactly as before.
4. When you run `make gen`, the new metadata appears in generated docs.
### Tool Template Update
`templates/pos-tool.sh` gets new header documentation:
```bash
# ────────────────────────────────────────────────────────────────
# TEMPLATE — new `pos` CLI tool
#
# 1. Copy: cp templates/pos-tool.sh bin/pos-<category>-<command>
# 2. Header: add a `# POS:` line right after the shebang/strict-mode
# lines (single source of truth for generated docs):
# # POS: <category> <command> — one-line description
# # POS_FLAGS: --flag1 --flag2 (flag-style tools only)
# # POS_SUBCMDS: sub1 sub2 (multi-command tools only)
# # POS_DEPS: binary1 binary2 (runtime deps, optional)
# # POS_EXAMPLES: pos <tool> <args> | Description (optional)
# 3. ...
```
### Lint Gate Update
`scripts/lint-conventions.sh` gets a new WARN check:
- If `# POS_DEPS:` is present, validate that each token looks like a binary name (no spaces, no special chars). This is a soft check — WARN on malformed deps, not FAIL.
[DECIDED]
---
## Decision 5: Scope Boundaries
### Approved Scope
```text
In scope:
lib/registry.sh — NEW FILE, ~180 lines
scripts/gen-docs.sh — Extend tools array, add deps/examples to gen_* functions
bin/pos-tree — Migrate to use registry (replace file-scanning loop)
templates/pos-tool.sh — Document new headers in template comments
DOC/DEV.md — Document new header format and registry usage
DOC/AGENT_Context_Project.md — Update line count table for lib/registry.sh, gen blocks updated by make gen
scripts/lint-conventions.sh — Optional: WARN for malformed POS_DEPS
Not in scope:
bin/pos _pos_category_help() — Phase 2 (future)
pos help enhancements — Phase 2 (future)
pos menu (new tool) — Phase 3 (future)
pos dashboard (new tool) — Phase 3 (future)
Adding POS_DEPS/POS_EXAMPLES to existing tools — Individual tool authors, incremental
lib/config-ui.sh integration — Deferred (already works, specialized parsing)
completions/pos.bash changes — No new completion data needed (deps/examples aren't completable)
install.sh changes — registry.sh is installed with existing lib/* loop
```
### What Builder Must NOT Do
1. Do NOT add `# POS_DEPS:` or `# POS_EXAMPLES:` to any existing tool in this PR (that's incremental migration, separate commits).
2. Do NOT change the `# POS:` header format or the em-dash convention.
3. Do NOT change `bin/pos` dispatch logic or `INTERACTIVE_CMDS`.
4. Do NOT add any new files beyond `lib/registry.sh`.
5. Do NOT modify the completion script (`completions/pos.bash`).
6. Do NOT refactor `lib/config-ui.sh` to use the registry.
7. Do NOT add a shebang to `lib/registry.sh` (it's a library, not executable).
8. Do NOT break `make gen && make check && make lint`.
[DECIDED]
---
## Decision 6: Risk Analysis
### Risk 1: `make gen` output changes break `git diff --exit-code` in CI
**Impact:** HIGH — blocks PRs.
**Cause:** Adding deps/examples parsing to gen-docs.sh changes the `tools` array format. The gen_* functions that consume this array will produce different output if any tool has the new headers. But since no tools have them yet, the output should be identical.
**Mitigation:**
- The gen_* functions must produce **identical output** when no tools have `# POS_DEPS:` or `# POS_EXAMPLES:` headers.
- Test: run `make gen && git diff --exit-code` before committing. Zero diff = safe.
- The tools array format change (adding `$deps|$examples` fields) is internal to gen-docs.sh — the gen_* functions that render output must not use the new fields when they're empty.
### Risk 2: `reg_scan` performance degrades with many tools
**Impact:** LOW — current tool count is ~40.
**Cause:** Each tool file is read 46 times by `sed` during scan. For 40 tools, this is ~200 process spawns.
**Mitigation:**
- At current scale, scan completes in ~5ms. Even 100 tools would be ~15ms.
- If it ever becomes an issue, `reg_scan` could read each file once and parse all headers in a single `awk` pass. This is a future optimization, not needed now.
### Risk 3: Registry library conflicts with existing sourcing patterns
**Impact:** MEDIUM — could break tools that source both common.sh and registry.sh.
**Cause:** `registry.sh` declares guarded fallbacks for `log`, `warn`, `err` (same pattern as `config-ui.sh`). If both are sourced, the second source is a no-op because the functions already exist.
**Mitigation:**
- Use the same guarded-declaration pattern as `config-ui.sh`: `declare -F log >/dev/null || log() { ... }`.
- `registry.sh` does NOT define `run`, `spawn`, `confirm`, or any other common.sh functions.
- `registry.sh` does NOT call `err` during normal operation — only if `reg_scan` is called with an invalid directory (which won't happen in practice).
### Risk 4: `bin/pos-tree` migration breaks tree output
**Impact:** HIGH — visible to users.
**Cause:** The tree-building logic in `pos-tree` is tightly coupled to the current data collection. Replacing the collection loop might subtly change tree structure.
**Mitigation:**
- Keep the existing `add()`, `render()` functions unchanged.
- Replace ONLY the data-collection loop (lines 4768) with registry calls.
- Test: run `bin/pos-tree` before and after, diff the output. Must be identical (for existing headers).
- The only visible change should be when a tool has `# POS_DEPS:` — deps appear in the tree.
### Risk 5: `pos <category>` performance regression
**Impact:** LOW — adds ~5ms per invocation.
**Cause:** `_pos_category_help()` would source and call `reg_scan` on every invocation.
**Mitigation:**
- Phase 2 only (not in this implementation).
- If needed, cache scan results in a temp file: `reg_scan` writes to `/tmp/.pos-registry-<hash>` and consumers check for freshness. This is premature — implement only if profiling shows a problem.
### Risk 6: New headers malformed, breaking parsing
**Impact:** LOW — malformed headers produce empty values, not crashes.
**Cause:** A tool author writes `# POS_DEPS` (missing colon) or `# POS_EXAMPLES foo bar` (missing pipe).
**Mitigation:**
- `reg_scan` uses strict pattern matching: `sed -n '/^# POS_DEPS: /{...}'`. Missing colon = no match = empty value. Graceful degradation.
- Add a lint WARN (not FAIL) for `# POS_DEPS:` lines without space-separated tokens.
- Add lint WARN for `# POS_EXAMPLES:` lines without `|` delimiter.
- Document the expected format clearly in DEV.md.
[DECIDED]
---
## Verification Plan
### Gate 1: Syntax
```bash
bash -n lib/registry.sh # Must pass (no syntax errors)
bash -n scripts/gen-docs.sh # Must pass (after modifications)
bash -n bin/pos-tree # Must pass (after modifications)
```
### Gate 2: Gen Drift
```bash
make gen && git diff --exit-code # Zero diff (no tools have new headers yet)
```
### Gate 3: Self-Consistency
```bash
make check # Must pass (syntax + exec bits + doc/code sync + dispatch smoke)
```
### Gate 4: Convention Lint
```bash
make lint # Must pass (0 FAIL, 0 WARN)
```
### Gate 5: Functional
```bash
# Registry works standalone
bash -c 'source lib/registry.sh; reg_scan; reg_list; reg_lookup docker ps desc'
# pos tree produces identical output
bin/pos-tree > /tmp/tree-before.txt
# ... apply changes ...
bin/pos-tree > /tmp/tree-after.txt
diff /tmp/tree-before.txt /tmp/tree-after.txt # Must be empty
# pos category help works
bin/pos docker --help # Must show docker tools
bin/pos network --help # Must show network tools
```
### Gate 6: Regression
```bash
# All existing commands still dispatch
bin/pos --help
bin/pos docker --help
bin/pos network --help
bin/pos help network scan
```
---
## Implementation Guidance for Builder
### Step-by-step
1. **Create `lib/registry.sh`** (~180 lines). Start from the API spec in Decision 2. Use `lib/config-ui.sh` as a structural reference for the guarded fallbacks and source pattern.
2. **Update `scripts/gen-docs.sh`**. In the tools collection loop (lines 3046):
- Add `deps` and `examples` fields to the `tools` array format: `"$cat|$sub|$desc|$flags|$subcmds|$deps|$examples"`
- Parse new headers with the same `sed` pattern as existing ones.
- In `gen_tree()`, `gen_dispatch()`, `gen_filetable()`: add deps/examples columns ONLY when non-empty. Empty fields = identical output to current.
3. **Migrate `bin/pos-tree`**. Replace lines 4768 (the file-scanning loop) with registry calls. Keep `add()`, `render()`, and the rest unchanged. Test that output is identical for existing tools.
4. **Update `templates/pos-tool.sh`**. Add `# POS_DEPS:` and `# POS_EXAMPLES:` to the header documentation block. Add them after the existing `# POS_FLAGS:` example.
5. **Update `DOC/DEV.md`**. In "Adding a New CLI Tool → Make it discoverable":
- Document the new `# POS_DEPS:` and `# POS_EXAMPLES:` headers.
- Explain when to use each (deps: list runtime binaries; examples: show 13 representative usages).
6. **Update `lib/registry.sh` line count** in `DOC/AGENT_Context_Project.md` filetable (the hand-maintained rows above the GEN marker).
7. **Run gates:** `make gen && make check && make lint`. Verify 0 FAIL, 0 WARN.
### Critical Constraints
- `lib/registry.sh` must NOT have a shebang (library, not executable).
- `lib/registry.sh` must be added to the `lib_names` list in `install.sh` Phase 2 (line 143).
- `reg_scan` must set `LC_ALL=C` for deterministic sort.
- gen-docs.sh changes must produce zero diff when no tools have new headers.
- `pos-tree` output must be byte-identical before/after migration (for existing headers).
---
## Open Questions (for Orchestrator)
1. Should `pos <category> --help` (in `bin/pos`) be migrated in this phase or deferred to Phase 2? **Recommendation: defer to Phase 2** — lower risk, and the category help is already working.
2. Should the lint gate enforce that `# POS_DEPS:` tokens are valid binary names? **Recommendation: WARN only, not FAIL** — some deps might be shell builtins or paths, not just binary names.
3. Should `reg_scan` support a `--cached` mode? **Recommendation: no, not yet** — premature optimization for 40 tools.
---
*Report written by Architect agent. Next recommended agent: **Builder** (to implement the approved scope).*