Claude opencode Skill

reverse-engineer

Inventory an authorized repo, binary, or product. Not for tracing your own repo; that is codebase-recon. Triggers: "reverse-engineer X", "tear down Y", "what should we steal from Z", "evaluate competitor/upstream", "should we fork/adopt/build-native".

LLM Mart · 0 points · 8 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download boshu2-agentops-skills_reverse-engineer-9ac484e.zip · 63 KB
boshu2/agentops 445 41 forks Apache-2.0 Updated 1d ago
Part of boshu2/agentops — 73 skills

Install

skills CLI npx skills add https://github.com/boshu2/agentops/tree/main/skills/reverse-engineer
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install boshu2-agentops@llmmart
Git git clone https://github.com/boshu2/agentops.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole boshu2/agentops collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Reverse Engineer

Reverse-engineer an external system into two things: a mechanically-verifiable teardown (feature inventory + registry + specs, optionally a security audit) and a steal-map — what to adopt into our surfaces, what to leave behind. The teardown is the evidence; the steal-map is the decision. Separating them works because a decision row that must cite a registry entry can be re-checked by anyone, while a decision made from impressions cannot be re-checked by its own author. The original failure mode this skill exists to prevent: reading a competitor's README and "deciding" from vibes.

Triggers: "reverse-engineer X", "tear down Y", "what should we steal from Z", "evaluate competitor/upstream", "should we fork/adopt/build-native".

Prompt

Reverse-engineer the beads CLI (github.com/steveyegge/beads, tag v2.1.0)
in repo mode, then author steal-map.md comparing its dependency-graph
reconciler against our cli/internal/gates/ package. I own this analysis
and have authorization for the clone.

It's working if

Observable in the trace, without reading the prose:

  • feature-registry.yaml and clone-metadata.json land under .agents/scratch/reverse-engineer/<product>/ with the resolved upstream commit recorded.
  • Every steal-map.md row cites a teardown registry entry and our matching surface, using the full have/gap/steal/park/reject set.
  • bash skills/reverse-engineer/scripts/validate-output.sh --output-dir "$output_dir" --phase complete exits 0 before handoff.
  • A one-way-door adoption row is routed to Plan instead of decided inside steal-map.md.

⚠️ Constraints — Hard Guardrails (MANDATORY)

  • Only operate on code/binaries you own or have explicit written authorization to analyze — this matters because unauthorized teardown is the legal/IP line.
  • Do not provide steps to bypass protections/ToS or to extract proprietary source/system prompts.
  • Do not output reconstructed proprietary source or embedded prompts (index only; redact in reports) — to prevent reproducing protected IP.
  • Redact secrets/tokens/keys if encountered; run the secret-scan gate over outputs to prevent credential leakage.
  • Always separate docs say vs code proves vs hosted/control-plane.

Phase 1 — Mechanical teardown (the script)

Produce evidence, not vibes. The script clones (pinned), scans CLI/config/artifact surface, and writes a feature inventory + machine-checkable registry + spec set.

python3 skills/reverse-engineer/scripts/reverse_engineer.py <product> --mode=repo \
  --upstream-repo="https://github.com/org/repo.git" --upstream-ref=v1.0.0 \
  --output-dir=".agents/scratch/reverse-engineer/<product>/"

Binary mode requires --authorized (see Invocation Contract + Self-Test). Use the bundled demo fixture if you lack authorization for a real binary.

Phase 2 — The steal-map (the decision)

Map each capability the teardown found onto our surfaces. This is the part that turns research into a decision. Emit .agents/scratch/reverse-engineer/<product>/steal-map.md with a table; every row cites the teardown evidence and the matching surface in our repo.

The mechanical script intentionally stops after validating Phase 1. It cannot truthfully decide whether our live tree has, lacks, or should adopt a capability. The caller authors steal-map.md from the generated registry plus a fresh read of our repository, then runs the complete-output validator below. A missing or malformed map is therefore an incomplete skill result, not a script success silently relabelled as a decision.

Their capability Our surface today Verdict
<feature> <our file / skill / CLI, or "none"> have / gap / steal / park / reject

Verdict rules (hard-won — apply them, do not skip):

  • steal — we lack it and it advances our core. Steal the pattern, not the storage engine: re-express in our primitives, never vendor their runtime.
  • park — real, but it's substrate we deliberately delegate (e.g. orchestration per ADR-0009) or downstream of an unproven bet. Name it, don't build it.
  • reject — it conflicts with our doctrine (e.g. a self-reported completion edge where we require a verdict — "no verdict = not done").
  • have — we already do this; confirm it still holds, move on.
  • gap — we should have it and don't. These are the steal candidates.

Discipline that makes the map trustworthy:

  • Independently checked, not self-report. Get facts on how they implement each capability from code, cross-checked by a fresh reader — never from a README or one context's summary. Model family is optional metadata, not a trust requirement.
  • Probe the real state, don't argue from stale. Re-verify our side against the live tree before calling something a gap; every "X is missing" carries the search that proved it.
  • The steal is the pattern, not the platform. Their robustness is usually one idea (unification, a gate, a reconcile loop). Steal the idea; leave the scaffolding.

Route one-way-door adoptions into planning

If adopting a steal is a one-way door (an architecture fork, a new bounded context, or a migration), do not decide it here. Hand the steal-map to Plan. Dueling Idea Genies or Premortem may challenge the choice as advisory evidence. Plan alone shapes the selected option in the existing intent source; neither strategy grants readiness or continuation authority.

Invocation Contract

Required: product_name. Common flags: --mode=repo|binary|both, --upstream-repo, --upstream-ref (requires the selected checkout to be at that exact commit and records its resolved SHA in clone-metadata.json), --local-clone-dir (selects that exact tree, including a non-Git tree; it never falls back to the caller's checkout), --output-dir (default .agents/scratch/reverse-engineer/<product>/), --security-audit, --materialize-archives (authorized-only opt-in; embedded-archive extraction is off/index-only by default), --authorized (mandatory for binary mode — refuses without it). Full list: python3 skills/reverse-engineer/scripts/reverse_engineer.py --help.

Output Specification

Phase-1 teardown under output_dir/: feature-inventory.md, feature-registry.yaml, feature-catalog.md, spec-architecture.md, spec-code-map.md, spec-clone-vs-use.md, spec-clone-mvp.md, plus spec-cli-surface.md only when a CLI is detected. clone-metadata.json is written whenever an upstream repo/ref is selected and binds the exact analyzed commit, including an already-present checkout. Security mode adds output_dir/security/: threat-model.md, attack-surface.md, dataflow.md, crypto-review.md, authn-authz.md, findings.md, reproducibility.md, validate-security-audit.sh. Phase-2 adds the caller-authored steal-map.md.

  • Artifact directory: the exact --output-dir, defaulting to $REPO/.agents/scratch/reverse-engineer/<product>/.

  • Filename convention: the fixed phase-1 and phase-2 names above; security files live only in the security/ child directory.

  • Serialization/schema format: registry is YAML, clone metadata is one JSON object, and inventories/specs/steal-map are nonempty Markdown files.

  • Validator command: Phase 1 runs this automatically with --phase teardown. After authoring steal-map.md, validate the complete skill output with $output_dir, $security_audit, $sbom, and $upstream_ref_set (each numeric flag 0|1):

    bash skills/reverse-engineer/scripts/validate-output.sh \
      --output-dir "$output_dir" --phase complete \
      --security-audit "$security_audit" --sbom "$sbom" \
      --upstream-ref-set "$upstream_ref_set"
    
  • Downstream handoff: give the validated steal-map.md to Plan for one-way-door candidates; ordinary have, park, and reject decisions remain evidence-backed terminal rows.

Earlier default compatibility

Existing teardowns under .agents/research/<product>/ remain in place and usable. The script accepts that directory when it is passed explicitly with --output-dir; that flag is caller authorization to write the teardown at the exact selected path. It does not relocate or duplicate existing artifacts. An invocation that omits the flag writes only to the current scratch default and never creates output under the earlier root. Consumers must retain the exact selected output_dir with their evidence references instead of rediscovering outputs by globbing one root. This owning skill contract is the compatibility authority; no separate migration receipt is required.

Reproducibility + fixtures

--upstream-ref binds the selected checkout to one full commit: a new clone is checked out detached at the fetched ref, while an existing checkout must already match or the run refuses before analysis. clone-metadata.json records that resolved commit. Regression test: bash skills/reverse-engineer/scripts/repo_fixture_test.sh. To update a fixture when contracts legitimately change, re-run with the new pinned ref, copy the contract files into fixtures/<product>/, and commit.

Self-Test (acceptance)

bash skills/reverse-engineer/scripts/self_test.sh

Must show: feature inventory and registry generated; the exact Phase-1 validator passes; the complete validator rejects a missing and malformed steal-map and accepts a valid caller-authored fixture; existing-checkout ref mismatch and output symlinks fail closed; in security mode validate-security-audit.sh exits 0 only after the scaffold is completed and the secret scan passes.

Examples

Reverse-engineer an OSS CLI (repo mode) → steal-map

Run Phase 1 for cc-sdd with --mode=repo --upstream-repo="https://github.com/gotalab/cc-sdd.git" --upstream-ref=v1.0.0. It clones the pinned source, scans the surface, writes inventory/registry/specs, and validates the teardown. Then inspect our live surfaces, author each have/gap/steal/park/reject row in steal-map.md, and run the complete-output validator. Supply selected steals to Plan.

Binary analysis with security audit

Run the skill for ao with --authorized --mode=binary --binary-path="$(command -v ao)" --security-audit. It performs authorized static analysis plus the security suite under output_dir/security/; the secret-scan check must pass.

Troubleshooting

Problem Cause Solution
Refuses binary analysis Missing --authorized Add --authorized (explicit written authorization required).
No clone-metadata.json --upstream-repo not passed Pass --upstream-repo (and optionally --upstream-ref).
Fixture diff fails Upstream changed / stale golden Re-run pinned, refresh fixtures/, commit.
Existing teardown is under .agents/research/ It used the earlier default Pass that exact directory with --output-dir; new runs otherwise use the scratch default.
spec-cli-surface.md missing No Node/Python/Go CLI detected Surface is documented in spec-code-map.md instead.
Steal-map is all "steal" Skipped the park/reject rules Substrate we delegate is park; doctrine conflicts are reject — not everything novel is worth adopting.

Quality Rubric

  • Every steal-map row cites teardown evidence and our matching surface (or "none").
  • Verdicts use the full set — have/gap/steal/park/reject — not everything marked "steal".
  • Facts on how they implement come from code and a fresh independent check — not a README.
  • One-way-door adoptions are supplied to Plan, not decided here.
  • Secret-scan gate passed over all outputs; no proprietary source/prompts reproduced.

See Also

  • plan — shape selected steals in the existing intent source
  • idea-genie — optional advisory challenge (duel mode)
  • premortem — optional advisory challenge of the exact plan
  • research — general exploration; this is its external-system specialization

Reference Documents

Files (agentops)
  • agents
    • openai.yaml 296 B
      interface:
        display_name: Reverse Engineer
        short_description: Inspect an external system and compare adoption options
        default_prompt: Evaluate the authorized external system against the caller's question. Separate observed code behavior, documentation claims and unverified hosted features.
      
  • fixtures
    • cc-sdd-v2.1.0
      • cli-surface-contracts.txt 1.4 KB
        # CLI surface contract assertions for cc-sdd v2.1.0
        # Each non-comment line below must appear verbatim in the generated spec-cli-surface.md.
        # The check uses grep -F (fixed-string substring match), so leading spaces matter.
        # Lines starting with # are comments.
        
        # Package identity
        - Node package: `tools/cc-sdd`
        - package name: `cc-sdd`
        - version: `2.1.0`
        
        # Binary entrypoint
        - `cc-sdd` -> `./dist/cli.js`
        
        # Source entry heuristic
        - `tools/cc-sdd/src/cli.ts` (node shebang entry; typically calls `runCli`)
        
        # Key CLI flags (2-space indent as they appear inside the help text code block)
          --agent <claude-code|claude-code-agent|codex|cursor|github-copilot|gemini-cli|windsurf|qwen-code|opencode|opencode-agent>  Select agent
          --lang <ja|en|zh-TW|zh|es|pt|de|fr|ru|it|ko|ar|el>  Language
          --os <auto|mac|windows|linux>               Target OS (auto uses runtime)
          --kiro-dir <path>                           Kiro root dir (default .kiro)
          --overwrite <prompt|skip|force>             Overwrite policy (default: prompt)
          --dry-run                                   Print plan only
          --yes, -y                                   Skip prompts (prompt -> force)
          -h, --help                                  Show help
          -v, --version                               Show version
        
        # Config surface
        - User config file: `.cc-sdd.json` (loaded from CWD).
        - Environment variables: `NO_COLOR`
        
      • clone-metadata.json 156 B
        {
          "upstream_repo": "https://github.com/gotalab/cc-sdd.git",
          "upstream_ref": "v2.1.0",
          "resolved_commit": "6e972c064ac4723bc8ad0181871d07e199af6a9f"
        }
        
      • docs-features.txt 454 B
        docs/README/README_en
        docs/README/README_ja
        docs/README/README_zh-TW
        docs/README
        docs/RELEASE_NOTES/RELEASE_NOTES_en
        docs/RELEASE_NOTES/RELEASE_NOTES_ja
        docs/guides/claude-subagents
        docs/guides/command-reference
        docs/guides/customization-guide
        docs/guides/ja/claude-subagents
        docs/guides/ja/command-reference
        docs/guides/ja/customization-guide
        docs/guides/ja/migration-guide
        docs/guides/ja/spec-driven
        docs/guides/migration-guide
        docs/guides/spec-driven
        
      • feature-registry.yaml 843 B
        schema_version: 1
        product_name: 'cc-sdd'
        docs_features_prefix: 'docs/'
        docs_features:
          - 'docs/README/README_en'
          - 'docs/README/README_ja'
          - 'docs/README/README_zh-TW'
          - 'docs/README'
          - 'docs/RELEASE_NOTES/RELEASE_NOTES_en'
          - 'docs/RELEASE_NOTES/RELEASE_NOTES_ja'
          - 'docs/guides/claude-subagents'
          - 'docs/guides/command-reference'
          - 'docs/guides/customization-guide'
          - 'docs/guides/ja/claude-subagents'
          - 'docs/guides/ja/command-reference'
          - 'docs/guides/ja/customization-guide'
          - 'docs/guides/ja/migration-guide'
          - 'docs/guides/ja/spec-driven'
          - 'docs/guides/migration-guide'
          - 'docs/guides/spec-driven'
        groups:
          README:
            impl: control-plane
            anchors: []
            notes: ""
          RELEASE_NOTES:
            impl: control-plane
            anchors: []
            notes: ""
          guides:
            impl: control-plane
            anchors: []
            notes: ""
        
  • references
    • templates
      • security
        • attack-surface.md.tmpl 310 B · in bundle
        • authn-authz.md.tmpl 294 B · in bundle
        • crypto-review.md.tmpl 283 B · in bundle
        • dataflow.md.tmpl 298 B · in bundle
        • findings.md.tmpl 236 B · in bundle
        • reproducibility.md.tmpl 237 B · in bundle
        • threat-model.md.tmpl 331 B · in bundle
      • postmortem.md.tmpl 433 B · in bundle
      • spec-architecture.md.tmpl 918 B · in bundle
      • spec-clone-mvp.md.tmpl 545 B · in bundle
      • spec-clone-vs-use.md.tmpl 465 B · in bundle
      • spec-code-map.md.tmpl 624 B · in bundle
      • vibe-report.md.tmpl 404 B · in bundle
    • reverse-engineer.feature 2.4 KB · in bundle
  • scripts
    • binary
      • analyze_binary.sh 6 KB
        #!/usr/bin/env bash
        set -euo pipefail
        
        if [[ $# -ne 2 ]]; then
          echo "usage: analyze_binary.sh <binary_path> <out_dir>" >&2
          exit 2
        fi
        
        BIN="$1"
        OUT="$2"
        mkdir -p "$OUT"
        
        if [[ ! -f "$BIN" ]]; then
          echo "error: binary not found: $BIN" >&2
          exit 2
        fi
        
        {
          echo "# Binary Analysis (Best-Effort)"
          echo
          echo "- Target: \`$BIN\`"
          echo "- Generated: $(date +%F)"
          echo
          echo "## file(1)"
          echo
          if command -v file >/dev/null 2>&1; then
            file "$BIN" || true
          else
            echo "_file not available_"
          fi
          echo
          echo "## Linked Libraries (best-effort)"
          echo
          if command -v otool >/dev/null 2>&1; then
            otool -L "$BIN" 2>/dev/null || true
          elif command -v ldd >/dev/null 2>&1; then
            ldd "$BIN" 2>/dev/null || true
          else
            echo "_otool/ldd not available_"
          fi
          echo
          echo "## Language Heuristics (best-effort)"
          echo
          if command -v strings >/dev/null 2>&1; then
            # Cache strings output to a temp file for multiple scans
            _STRINGS_FILE=$(mktemp)
            trap 'rm -f "$_STRINGS_FILE"' EXIT
            strings -a "$BIN" 2>/dev/null >"$_STRINGS_FILE"
        
            # Helper: search strings file with rg falling back to grep -E
            _str_match() {
              local pattern="$1"
              if command -v rg >/dev/null 2>&1; then
                rg -m 1 "$pattern" "$_STRINGS_FILE" 2>/dev/null
              else
                grep -E -m 1 "$pattern" "$_STRINGS_FILE" 2>/dev/null
              fi
            }
        
            # --- Go detection (broad markers for stripped binaries) ---
            GO_DETECTED=false
            GO_MARKER=""
            # Original markers (unstripped binaries)
            if _str_match 'runtime\.morestack|go\.buildid|Go build ID|type\.\*runtime\.' >/dev/null 2>&1; then
              GO_DETECTED=true; GO_MARKER="Go runtime markers"
            # Broader markers for stripped binaries (version strings, GOROOT, module paths)
            elif _str_match 'go1\.[0-9]|GOROOT|github\.com/|golang\.org/' >/dev/null 2>&1; then
              GO_DETECTED=true; GO_MARKER="Go version/module strings"
            fi
        
            # --- Python detection ---
            PYTHON_DETECTED=false
            if _str_match '__pycache__|\.pyc|Py_Initialize|libpython|python[0-9]\.[0-9]' >/dev/null 2>&1; then
              PYTHON_DETECTED=true
            fi
        
            # --- Report language ---
            if $GO_DETECTED && $PYTHON_DETECTED; then
              echo "- Likely language/runtime: Go + Python (Go binary embedding Python code)"
              echo "  - Go detection: $GO_MARKER"
            elif $GO_DETECTED; then
              echo "- Likely language/runtime: Go (heuristic: $GO_MARKER)"
            elif $PYTHON_DETECTED; then
              echo "- Likely language/runtime: Python (heuristic: Python runtime markers in strings)"
            else
              echo "- Likely language/runtime: unknown (no Go or Python markers found)"
            fi
        
            # --- Go details (version, module, packages) ---
            if $GO_DETECTED; then
              echo
              echo "### Go Details"
              echo
              # Go version string (e.g. "go1.23.4") — match lines that ARE the version
              _go_ver=$({
                if command -v rg >/dev/null 2>&1; then
                  rg -m 1 -o '^go1\.[0-9]+\.[0-9]+$' "$_STRINGS_FILE" 2>/dev/null
                else
                  grep -E -m 1 '^go1\.[0-9]+\.[0-9]+$' "$_STRINGS_FILE" 2>/dev/null
                fi
              } || true)
              if [[ -n "$_go_ver" ]]; then
                echo "- Go version: \`$_go_ver\`"
              else
                echo "- Go version: _not found (stripped)_"
              fi
              # Module path — prefer github.com/gitlab.com/golang.org paths first
              _go_mod=$({
                if command -v rg >/dev/null 2>&1; then
                  rg -m 1 -o '^(github|gitlab|bitbucket)\.com/[^\s]+' "$_STRINGS_FILE" 2>/dev/null \
                  || rg -m 1 -o '^golang\.org/[^\s]+' "$_STRINGS_FILE" 2>/dev/null \
                  || rg -m 1 -o '^[a-z][a-z0-9.-]+\.[a-z]{2,}/[^\s]+' "$_STRINGS_FILE" 2>/dev/null
                else
                  grep -E -m 1 -o '^(github|gitlab|bitbucket)\.com/[^ ]+' "$_STRINGS_FILE" 2>/dev/null \
                  || grep -E -m 1 -o '^golang\.org/[^ ]+' "$_STRINGS_FILE" 2>/dev/null \
                  || grep -E -m 1 -o '^[a-z][a-z0-9.-]+\.[a-z]{2,}/[^ ]+' "$_STRINGS_FILE" 2>/dev/null
                fi
              } || true)
              if [[ -n "$_go_mod" ]]; then
                echo "- Module path: \`$_go_mod\`"
              else
                echo "- Module path: _not found_"
              fi
              # Internal package count (unique Go module-style paths)
              _go_pkgs=$({
                if command -v rg >/dev/null 2>&1; then
                  rg -o '^(github|gitlab|bitbucket)\.com/[^\s]+|^golang\.org/[^\s]+' "$_STRINGS_FILE" 2>/dev/null
                else
                  grep -E -o '^(github|gitlab|bitbucket)\.com/[^ ]+|^golang\.org/[^ ]+' "$_STRINGS_FILE" 2>/dev/null
                fi
              } | sort -u | wc -l || echo 0)
              echo "- Internal packages (approx): ${_go_pkgs##* }"
            fi
          else
            echo "- strings not available; cannot run heuristics"
          fi
          echo
          echo "## Embedded Archive Signatures (ZIP, best-effort)"
          echo
          if command -v python3 >/dev/null 2>&1; then
            python3 - "$BIN" <<'PY'
        import sys
        from pathlib import Path
        
        p = Path(sys.argv[1])
        data = p.read_bytes()
        
        sig = b"PK\x03\x04"
        hits = []
        start = 0
        while True:
            i = data.find(sig, start)
            if i < 0:
                break
            hits.append(i)
            start = i + 1
        
        print(f"- ZIP local header occurrences: {len(hits)}")
        for i in hits[:10]:
            print(f"  - offset: {i}")
        if len(hits) > 10:
            print("  - ...")
        PY
          else
            echo "_python3 not available_"
          fi
        } >"$OUT/binary-analysis.md"
        
        # Raw strings (kept under tmp out dir; do not copy into output_dir by default).
        if command -v strings >/dev/null 2>&1; then
          strings -a "$BIN" 2>/dev/null | head -2000 >"$OUT/strings.head.txt" || true
          if command -v rg >/dev/null 2>&1; then
            strings -a "$BIN" 2>/dev/null | rg -n -S 'mcp|prompt|system|tool|openai|anthropic|claude' >"$OUT/strings.ai-hits.txt" 2>/dev/null || true
          else
            strings -a "$BIN" 2>/dev/null | grep -E -in 'mcp|prompt|system|tool|openai|anthropic|claude' >"$OUT/strings.ai-hits.txt" 2>/dev/null || true
          fi
        fi
        
        # Optional disassembly snippet (bounded). Keep under tmp out dir; do not paste into reports by default.
        if command -v otool >/dev/null 2>&1; then
          otool -tvV "$BIN" 2>/dev/null | head -500 >"$OUT/disassembly.head.txt" || true
        elif command -v objdump >/dev/null 2>&1; then
          objdump -d "$BIN" 2>/dev/null | head -500 >"$OUT/disassembly.head.txt" || true
        fi
        
      • capture_cli_help.sh 8.2 KB
        #!/usr/bin/env bash
        # capture_cli_help.sh — Recursively capture --help output from a CLI binary.
        #
        # Usage: capture_cli_help.sh <binary_path> <out_dir>
        #
        # Writes:
        #   <out_dir>/cli-help-tree.txt   — Structured help output per command/subcommand
        #   <out_dir>/cli-commands.txt    — One command path per line
        #
        # Constraints:
        #   - 5-second timeout per invocation
        #   - 120-second total execution cap
        #   - Max recursion depth: 3
        #   - Exit 0 always (best-effort)
        
        set -euo pipefail
        
        BINARY_PATH="${1:?Usage: capture_cli_help.sh <binary_path> <out_dir>}"
        OUT_DIR="${2:?Usage: capture_cli_help.sh <binary_path> <out_dir>}"
        
        BINARY_NAME="$(basename "$BINARY_PATH")"
        PER_CMD_TIMEOUT=5
        TOTAL_TIMEOUT=120
        MAX_DEPTH=3
        
        # Help-like keywords that indicate valid help output.
        HELP_KEYWORDS="Usage|Commands|Available|Flags|Options|usage|commands|available|flags|options|USAGE|COMMANDS|AVAILABLE|FLAGS|OPTIONS|help|HELP|Synopsis|SYNOPSIS|Arguments|ARGUMENTS"
        
        # Resolve timeout command (GNU coreutils `timeout` or macOS `gtimeout`).
        TIMEOUT_CMD=""
        if command -v timeout &>/dev/null; then
            TIMEOUT_CMD="timeout"
        elif command -v gtimeout &>/dev/null; then
            TIMEOUT_CMD="gtimeout"
        fi
        
        mkdir -p "$OUT_DIR"
        
        TREE_FILE="$OUT_DIR/cli-help-tree.txt"
        CMDS_FILE="$OUT_DIR/cli-commands.txt"
        SEEN_PATHS_FILE="$OUT_DIR/.seen-command-paths.tmp"
        VISITED_PREFIX_FILE="$OUT_DIR/.visited-prefixes.tmp"
        
        # Start fresh.
        : > "$TREE_FILE"
        : > "$CMDS_FILE"
        : > "$SEEN_PATHS_FILE"
        : > "$VISITED_PREFIX_FILE"
        
        # Track total elapsed time.
        START_TIME="$(date +%s)"
        
        elapsed() {
            local now
            now="$(date +%s)"
            echo $(( now - START_TIME ))
        }
        
        budget_exceeded() {
            [ "$(elapsed)" -ge "$TOTAL_TIMEOUT" ]
        }
        
        # Run a command with per-invocation timeout. Captures stdout+stderr.
        # Returns the output; exit code 0 on success, non-zero on timeout/failure.
        run_with_timeout() {
            if [ -n "$TIMEOUT_CMD" ]; then
                "$TIMEOUT_CMD" "$PER_CMD_TIMEOUT" "$@" 2>&1 || true
            else
                # Fallback: no timeout command available, just run it.
                "$@" 2>&1 || true
            fi
        }
        
        # Check if text looks like help output.
        looks_like_help() {
            local text="$1"
            if [ -z "$text" ]; then
                return 1
            fi
            if echo "$text" | grep -qE "$HELP_KEYWORDS"; then
                return 0
            fi
            return 1
        }
        
        seen_contains() {
            local file="$1"
            local key="$2"
            grep -Fqx -- "$key" "$file" 2>/dev/null
        }
        
        seen_add() {
            local file="$1"
            local key="$2"
            printf '%s\n' "$key" >> "$file"
        }
        
        record_command_path() {
            local path="$1"
            [ -z "$path" ] && return 0
            if seen_contains "$SEEN_PATHS_FILE" "$path"; then
                return 0
            fi
            seen_add "$SEEN_PATHS_FILE" "$path"
            echo "$path" >> "$CMDS_FILE"
        }
        
        extract_usage_path() {
            local help_text="$1"
            local usage_line
            usage_line="$(echo "$help_text" | awk '
                /^Usage:/ {
                    line=$0
                    sub(/^Usage:[[:space:]]*/, "", line)
                    if (line != "") {
                        print line
                        exit
                    }
                    in_usage=1
                    next
                }
                in_usage {
                    if ($0 ~ /^[[:space:]]*$/) {
                        in_usage=0
                        next
                    }
                    line=$0
                    sub(/^[[:space:]]+/, "", line)
                    if (line != "") {
                        print line
                        exit
                    }
                }
            ')"
            [ -z "$usage_line" ] && return 0
            echo "$usage_line" | awk '
                {
                    out=""
                    for (i=1; i<=NF; i++) {
                        t=$i
                        first = substr(t, 1, 1)
                        if (first == "[" || first == "<" || first == "-" || first == "(" || first == "{") break
                        out = (out ? out " " : "") t
                    }
                    print out
                }
            '
        }
        
        # Extract subcommand names from help output.
        # Looks for lines after "Commands:" or "Available Commands:" header,
        # matching pattern: leading whitespace, then a word (the subcommand name).
        extract_subcommands() {
            local help_text="$1"
            local in_commands_section=0
            local subcmds=()
        
            while IFS= read -r line; do
                # Detect start of commands section.
                if echo "$line" | grep -qiE '^\s*(Available\s+)?Commands\s*:'; then
                    in_commands_section=1
                    continue
                fi
        
                if [ "$in_commands_section" -eq 1 ]; then
                    # Empty line or a new section header ends the commands block.
                    if [ -z "$line" ] || echo "$line" | grep -qE '^[A-Z].*:$'; then
                        in_commands_section=0
                        continue
                    fi
                    # Extract the first word (subcommand name) from indented lines.
                    local cmd
                    cmd="$(echo "$line" | sed -n 's/^[[:space:]]\{1,\}\([a-zA-Z0-9_-]\{1,\}\)[[:space:]].*/\1/p')"
                    if [ -n "$cmd" ]; then
                        # Skip common non-command words that appear in help sections.
                        case "$cmd" in
                            help|completion) ;;  # skip meta-commands
                            *) subcmds+=("$cmd") ;;
                        esac
                    fi
                fi
            done <<< "$help_text"
        
            # Output one per line.
            for sc in "${subcmds[@]+"${subcmds[@]}"}"; do
                echo "$sc"
            done
        }
        
        # Recursive help capture.
        # Args: depth cmd_prefix args...
        #   depth      — current recursion depth (0-based)
        #   cmd_prefix — display prefix for tree (e.g., "forge transcript")
        #   args...    — actual command + args to run
        capture_help() {
            local depth="$1"; shift
            local cmd_prefix="$1"; shift
            # Remaining args are the command to execute.
            if seen_contains "$VISITED_PREFIX_FILE" "$cmd_prefix"; then
                return 0
            fi
            seen_add "$VISITED_PREFIX_FILE" "$cmd_prefix"
        
            if budget_exceeded; then
                return 0
            fi
        
            if [ "$depth" -gt "$MAX_DEPTH" ]; then
                return 0
            fi
        
            local help_output
            help_output="$(run_with_timeout "$@" --help)"
        
            if ! looks_like_help "$help_output"; then
                if [ "$depth" -eq 0 ]; then
                    # Top-level binary doesn't produce help. Write note and bail.
                    echo "# CLI Help Tree" >> "$TREE_FILE"
                    echo "" >> "$TREE_FILE"
                    echo "NOTE: $BINARY_NAME --help did not produce recognizable help output." >> "$TREE_FILE"
                fi
                return 0
            fi
        
            # Write to tree file.
            echo "## $cmd_prefix" >> "$TREE_FILE"
            echo "" >> "$TREE_FILE"
            echo "$help_output" >> "$TREE_FILE"
            echo "" >> "$TREE_FILE"
        
            # Resolve canonical path from Usage: for alias handling and de-noising.
            local usage_path=""
            usage_path="$(extract_usage_path "$help_output" || true)"
        
            # Write to commands file (skip top-level binary name alone).
            if [ "$depth" -gt 0 ]; then
                # Strip first token (binary executable/command name) to compare subcommand paths robustly.
                local subcmd_path="${cmd_prefix#* }"
                local canonical_subcmd_path=""
                if [ -n "$usage_path" ] && [ "$usage_path" != "${usage_path#* }" ]; then
                    canonical_subcmd_path="${usage_path#* }"
                fi
                if [ -n "$canonical_subcmd_path" ]; then
                    record_command_path "$canonical_subcmd_path"
                else
                    record_command_path "$subcmd_path"
                fi
        
                # If Usage path subcommands differ from invocation subcommands, this likely hit
                # an alias/help redirect. Stop recursion to avoid fake paths like "mail inbox inbox".
                if [ -n "$canonical_subcmd_path" ] && [ "$canonical_subcmd_path" != "$subcmd_path" ]; then
                    return 0
                fi
            fi
        
            # Extract and recurse into subcommands.
            local subcmds
            subcmds="$(extract_subcommands "$help_output")"
            if [ -z "$subcmds" ]; then
                return 0
            fi
        
            while IFS= read -r subcmd; do
                [ -z "$subcmd" ] && continue
                if budget_exceeded; then
                    return 0
                fi
                capture_help "$(( depth + 1 ))" "$cmd_prefix $subcmd" "$@" "$subcmd"
            done <<< "$subcmds"
        }
        
        # Write tree header.
        echo "# CLI Help Tree" >> "$TREE_FILE"
        echo "" >> "$TREE_FILE"
        
        # Start recursive capture from the top-level binary.
        capture_help 0 "$BINARY_NAME" "$BINARY_PATH"
        
        # If commands file is empty but tree has content, write the top-level command.
        if [ ! -s "$CMDS_FILE" ] && [ -s "$TREE_FILE" ]; then
            # No subcommands found; the binary itself is the only entry.
            : # cli-commands.txt stays empty — top-level is implicit.
        fi
        
        exit 0
        
      • extract_embedded_archives.py 4 KB
        #!/usr/bin/env python3
        from __future__ import annotations
        
        import argparse
        import hashlib
        import json
        import sys
        import zipfile
        from dataclasses import dataclass
        from io import BytesIO
        from pathlib import Path
        
        
        @dataclass(frozen=True)
        class Candidate:
            offset: int
            file_count: int
            score: int
        
        
        def _sha256_file(path: Path) -> str:
            h = hashlib.sha256()
            with path.open("rb") as f:
                for chunk in iter(lambda: f.read(1024 * 1024), b""):
                    h.update(chunk)
            return h.hexdigest()
        
        
        def _find_offsets(data: bytes, max_hits: int = 5000) -> list[int]:
            sig = b"PK\x03\x04"
            hits: list[int] = []
            start = 0
            while len(hits) < max_hits:
                i = data.find(sig, start)
                if i < 0:
                    break
                hits.append(i)
                start = i + 1
            return hits
        
        
        def _score_names(names: list[str]) -> int:
            exts = {".py": 5, ".js": 4, ".ts": 4, ".go": 4, ".md": 2, ".yaml": 2, ".yml": 2, ".json": 2, ".toml": 2}
            score = 0
            for n in names:
                for ext, w in exts.items():
                    if n.endswith(ext):
                        score += w
                        break
            # Reward file count lightly.
            score += min(len(names), 200)
            return score
        
        
        def main() -> int:
            ap = argparse.ArgumentParser()
            ap.add_argument("--binary", required=True)
            ap.add_argument("--out-dir", required=True, help="Directory to extract archives into.")
            ap.add_argument("--max-candidates", type=int, default=200)
            args = ap.parse_args()
        
            binary = Path(args.binary)
            out_dir = Path(args.out_dir)
            out_dir.mkdir(parents=True, exist_ok=True)
        
            data = binary.read_bytes()
            offsets = _find_offsets(data)
        
            cands: list[Candidate] = []
            opened = 0
            for off in offsets[: args.max_candidates]:
                try:
                    with zipfile.ZipFile(BytesIO(data[off:])) as zf:
                        names = zf.namelist()
                        cands.append(Candidate(offset=off, file_count=len(names), score=_score_names(names)))
                        opened += 1
                except Exception:
                    continue
        
            if not cands:
                (out_dir / "extract.NOOP.md").write_text(
                    f"# Extract Embedded Archives (No-Op)\n\nNo embedded ZIP archives could be opened.\n\nBinary: `{binary}`\n",
                    encoding="utf-8",
                )
                return 0
        
            best = sorted(cands, key=lambda c: (-c.score, -c.file_count, c.offset))[0]
            dest = out_dir / f"zip@{best.offset}"
            dest.mkdir(parents=True, exist_ok=True)
        
            with zipfile.ZipFile(BytesIO(data[best.offset:])) as zf:
                # Bound decompression against zip bombs: this extracts an archive carved
                # from attacker-controlled binary bytes. Refuse an oversized member or
                # total uncompressed size before writing anything to disk.
                max_member = 128 * 1024 * 1024
                max_total = 512 * 1024 * 1024
                total = 0
                for info in zf.infolist():
                    total += info.file_size
                    if info.file_size > max_member or total > max_total:
                        print(
                            f"refusing to extract embedded archive at offset {best.offset}: "
                            "uncompressed size exceeds bounds (possible zip bomb)",
                            file=sys.stderr,
                        )
                        return 1
                # Extract all files. This is an authorized-only operation; do not commit the result.
                zf.extractall(dest)
                names = zf.namelist()
        
            manifest = {
                "binary": str(binary),
                "binary_sha256": _sha256_file(binary),
                "selected_offset": best.offset,
                "selected_file_count": best.file_count,
                "selected_score": best.score,
                "filenames": names[:500],
                "note": "Do not paste or commit extracted content. Reports must reference paths/hashes only.",
            }
            (dest / "manifest.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
        
            # Convenience pointer for downstream scripts.
            (out_dir / "PRIMARY.txt").write_text(str(dest), encoding="utf-8")
        
            print(f"OK: extracted {best.file_count} files to {dest}")
            return 0
        
        
        if __name__ == "__main__":
            raise SystemExit(main())
        
        
      • list_embedded_archives.py 3.7 KB
        #!/usr/bin/env python3
        from __future__ import annotations
        
        import argparse
        import hashlib
        import json
        import zipfile
        from dataclasses import dataclass
        from io import BytesIO
        from pathlib import Path
        
        
        @dataclass(frozen=True)
        class ZipCandidate:
            offset: int
            file_count: int
            names: list[str]
            sha256: str
        
        
        def _sha256_bytes(b: bytes) -> str:
            h = hashlib.sha256()
            h.update(b)
            return h.hexdigest()
        
        
        def _find_zip_offsets(data: bytes, max_hits: int = 5000) -> list[int]:
            sig = b"PK\x03\x04"
            hits: list[int] = []
            start = 0
            while len(hits) < max_hits:
                i = data.find(sig, start)
                if i < 0:
                    break
                hits.append(i)
                start = i + 1
            return hits
        
        
        def _try_open_zip(data: bytes, offset: int) -> ZipCandidate | None:
            tail = data[offset:]
            # zipfile wants central directory present; if it's not, this will fail (that's fine).
            bio = BytesIO(tail)
            try:
                with zipfile.ZipFile(bio) as zf:
                    names = zf.namelist()
                    # Hash just the first ~4MB for stable fingerprint without storing full content.
                    sha = _sha256_bytes(tail[: 4 * 1024 * 1024])
                    return ZipCandidate(offset=offset, file_count=len(names), names=names[:200], sha256=sha)
            except Exception:
                return None
        
        
        def main() -> int:
            ap = argparse.ArgumentParser()
            ap.add_argument("--binary", required=True)
            ap.add_argument("--out-json", required=True)
            ap.add_argument("--out-index-md", required=True)
            args = ap.parse_args()
        
            binary = Path(args.binary)
            data = binary.read_bytes()
        
            hits = _find_zip_offsets(data)
            cands: list[ZipCandidate] = []
            # Try a limited number to keep runtime bounded.
            for off in hits[:200]:
                cand = _try_open_zip(data, off)
                if cand:
                    cands.append(cand)
        
            out_json = Path(args.out_json)
            out_json.parent.mkdir(parents=True, exist_ok=True)
            out_json.write_text(
                json.dumps(
                    {
                        "binary": str(binary),
                        "zip_header_hits": len(hits),
                        "candidates": [
                            {"offset": c.offset, "file_count": c.file_count, "sha256_head_4mb": c.sha256, "names": c.names}
                            for c in sorted(cands, key=lambda x: (-x.file_count, x.offset))
                        ],
                    },
                    indent=2,
                    sort_keys=True,
                )
                + "\n",
                encoding="utf-8",
            )
        
            out_md = Path(args.out_index_md)
            out_md.parent.mkdir(parents=True, exist_ok=True)
            lines: list[str] = []
            lines.append("# Embedded Archive Index (Best-Effort)")
            lines.append("")
            lines.append("Guardrail: this index does not dump reconstructed source or prompts; it only inventories candidate archives.")
            lines.append("")
            lines.append(f"- Binary: `{binary}`")
            lines.append(f"- ZIP header hits: {len(hits)}")
            lines.append(f"- ZIP candidates opened: {len(cands)}")
            lines.append("")
            if not cands:
                lines.append("_No embedded ZIP archives could be opened via the central directory heuristic._")
                lines.append("")
            else:
                for i, c in enumerate(sorted(cands, key=lambda x: (-x.file_count, x.offset))[:5], start=1):
                    lines.append(f"## Candidate {i}")
                    lines.append("")
                    lines.append(f"- Offset: `{c.offset}`")
                    lines.append(f"- File count: `{c.file_count}`")
                    lines.append(f"- SHA256(head_4mb): `{c.sha256}`")
                    lines.append("")
                    lines.append("Top filenames (truncated):")
                    lines.append("")
                    for n in c.names[:30]:
                        lines.append(f"- `{n}`")
                    lines.append("")
        
            out_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
            return 0
        
        
        if __name__ == "__main__":
            raise SystemExit(main())
        
        
    • security
      • generate_sbom.sh 1.3 KB
        #!/usr/bin/env bash
        set -euo pipefail
        
        if [[ $# -ne 2 ]]; then
          echo "usage: generate_sbom.sh <analysis_root_dir> <security_out_dir>" >&2
          exit 2
        fi
        
        ROOT="$1"
        OUT="$2"
        mkdir -p "$OUT"
        
        report="$OUT/dep-risk-report.md"
        
        if command -v syft >/dev/null 2>&1; then
          # Best-effort. Avoid failing the whole workflow if syft has issues.
          if syft "dir:${ROOT}" -o spdx-json >"$OUT/sbom.spdx.json" 2>"$OUT/syft.stderr"; then
            cat >"$report" <<EOF
        # Dependency Risk Report (Best-Effort)
        
        - Generator: syft
        - Input: \`${ROOT}\`
        - Notes: This report is a stub. Pair SBOM output with a vuln scanner (e.g., grype) in an authorized environment.
        EOF
            exit 0
          fi
        fi
        
        # Language-aware no-op outputs (still produces deterministic artifacts).
        if [[ -f "$ROOT/go.mod" ]]; then
          if command -v go >/dev/null 2>&1; then
            (cd "$ROOT" && go list -m -json all) >"$OUT/sbom.go-mod.modules.json" 2>"$OUT/go-list.stderr" || true
          fi
        fi
        
        cat >"$OUT/sbom.NOOP.md" <<EOF
        # SBOM (No-Op)
        
        No supported SBOM generator was available (or it failed).
        
        Input: \`${ROOT}\`
        Created: $(date +%F)
        EOF
        
        cat >"$report" <<EOF
        # Dependency Risk Report (No-Op)
        
        No dependency risk scan was performed (offline / tool unavailable).
        
        Recommended (authorized environments only):
        - Generate a real SBOM with syft
        - Run a vuln scan with grype / osv-scanner / etc.
        EOF
        
        
      • scan_secrets.sh 1.7 KB
        #!/usr/bin/env bash
        set -euo pipefail
        
        if [[ $# -ne 1 ]]; then
          echo "usage: scan_secrets.sh <dir>" >&2
          exit 2
        fi
        
        ROOT="$1"
        if [[ ! -d "$ROOT" ]]; then
          echo "error: not a directory: $ROOT" >&2
          exit 2
        fi
        
        # Conservative patterns. This will produce false positives; treat as a gate to review and redact.
        PATTERNS=(
          'AKIA[0-9A-Z]{16}'
          'ASIA[0-9A-Z]{16}'
          '-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----'
          'xox[baprs]-[0-9A-Za-z-]{10,}'
          'ghp_[0-9A-Za-z]{20,}'
          'github_pat_[0-9A-Za-z_]{20,}'
          'sk-[0-9A-Za-z]{20,}'
          'AIza[0-9A-Za-z\\-_]{20,}'
          '-----BEGIN PGP PRIVATE KEY BLOCK-----'
          '(?i)client_secret\\s*[:=]\\s*[^\\s]+'
          '(?i)api[_-]?key\\s*[:=]\\s*[^\\s]+'
          '(?i)authorization\\s*:\\s*bearer\\s+[^\\s]+'
        )
        
        TMP="$(mktemp -t re_rpi_secrets.XXXXXX)"
        trap 'rm -f "$TMP"' EXIT
        
        FAIL=0
        for pat in "${PATTERNS[@]}"; do
          # ripgrep is faster and supports PCRE2 with -P.
          if command -v rg >/dev/null 2>&1; then
            # Use '--' so patterns beginning with '-' are not treated as flags.
            # Avoid self-matches: this validator embeds some of the patterns it is looking for.
            if rg -n -S -P --hidden --no-ignore \
              --glob '!.git/**' \
              --glob '!.tmp/**' \
              --glob '!**/security/scan-secrets.sh' \
              --glob '!**/security/scan_secrets.sh' \
              --glob '!**/security/validate-security-audit.sh' \
              --glob '!**/security/generate-sbom.sh' \
              -- "$pat" "$ROOT" >>"$TMP"; then
              FAIL=1
            fi
          else
            if grep -RInE -- "$pat" "$ROOT" >>"$TMP" 2>/dev/null; then
              FAIL=1
            fi
          fi
        done
        
        if [[ $FAIL -ne 0 ]]; then
          echo "FAIL: potential secrets detected in $ROOT" >&2
          # Print limited output to avoid copying secrets into logs.
          head -50 "$TMP" >&2
          echo "..." >&2
          exit 1
        fi
        
        echo "OK: secret scan passed ($ROOT)"
        
      • validate_security_audit.sh 2.4 KB
        #!/usr/bin/env bash
        set -euo pipefail
        
        if [[ $# -lt 2 ]]; then
          echo "usage: validate_security_audit.sh <output_dir> (--sbom|--no-sbom)" >&2
          exit 2
        fi
        
        OUTDIR="$1"
        SBOM_FLAG="${2:-}"
        
        SEC="$OUTDIR/security"
        if [[ ! -d "$SEC" ]]; then
          echo "FAIL: missing security dir: $SEC" >&2
          exit 1
        fi
        
        req=(
          "$SEC/threat-model.md"
          "$SEC/attack-surface.md"
          "$SEC/dataflow.md"
          "$SEC/crypto-review.md"
          "$SEC/authn-authz.md"
          "$SEC/findings.md"
          "$SEC/reproducibility.md"
          "$SEC/validate-security-audit.sh"
        )
        
        fail=0
        for f in "${req[@]}"; do
          if [[ ! -f "$f" ]]; then
            echo "FAIL: missing required file: $f" >&2
            fail=1
          fi
        done
        if [[ $fail -ne 0 ]]; then
          exit 1
        fi
        
        # Findings gate: each finding must have Evidence + Fix sections (simple heuristic).
        if ! rg -n -S '^## ' "$SEC/findings.md" >/dev/null 2>&1; then
          echo "FAIL: findings.md has no findings headers (expected '## ...')" >&2
          exit 1
        fi
        
        if ! rg -n -S '(?i)^Evidence:' "$SEC/findings.md" >/dev/null 2>&1; then
          echo "FAIL: findings.md missing Evidence: lines" >&2
          exit 1
        fi
        if ! rg -n -S '(?i)^(Fix|Remediation):' "$SEC/findings.md" >/dev/null 2>&1; then
          echo "FAIL: findings.md missing Fix:/Remediation: lines" >&2
          exit 1
        fi
        # Reject unfilled placeholders across the WHOLE required audit bundle: the
        # shipped templates supply Evidence/Fix/threat/dataflow content as literal _TBD
        # markers, and the presence-only greps above certify them as real. Any _TBD in
        # any required narrative file is an unfilled scaffold and must not certify green
        # (scanning only findings.md would let a _TBD threat-model or dataflow through).
        for f in "${req[@]}"; do
          case "$f" in
            *.md) ;;
            *) continue ;;
          esac
          if grep -Fq '_TBD' "$f"; then
            echo "FAIL: $(basename "$f") still contains _TBD placeholders — fill the audit before certifying" >&2
            exit 1
          fi
        done
        
        # Secret scan gate over outputs.
        SCANDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
        SCANNER="$SCANDIR/scan_secrets.sh"
        if [[ ! -x "$SCANNER" ]]; then
          SCANNER="$SCANDIR/scan-secrets.sh"
        fi
        "$SCANNER" "$OUTDIR"
        
        if [[ "$SBOM_FLAG" == "--sbom" ]]; then
          if [[ ! -f "$SEC/sbom.spdx.json" && ! -f "$SEC/sbom.NOOP.md" ]]; then
            echo "FAIL: --sbom set but no sbom.spdx.json (or sbom.NOOP.md) found in $SEC" >&2
            exit 1
          fi
          if [[ ! -f "$SEC/dep-risk-report.md" ]]; then
            echo "FAIL: --sbom set but missing dep-risk-report.md in $SEC" >&2
            exit 1
          fi
        fi
        
        echo "OK: security audit validated ($OUTDIR)"
        
    • extract_docs_features.sh 839 B
      #!/usr/bin/env bash
      set -euo pipefail
      
      if [[ $# -ne 2 ]]; then
        echo "usage: extract_docs_features.sh <paths.txt> <docs_features_prefix>" >&2
        exit 2
      fi
      
      PATHS_TXT="$1"
      PREFIX_RAW="$2"
      
      # Normalize prefix: "docs/features/" -> "/docs/features"
      PREFIX="/${PREFIX_RAW#/}"
      PREFIX="${PREFIX%/}"
      
      python3 - "$PATHS_TXT" "$PREFIX" <<'PY'
      import sys
      from pathlib import Path
      
      paths_txt = Path(sys.argv[1])
      prefix = sys.argv[2]
      
      out = set()
      for line in paths_txt.read_text(encoding="utf-8", errors="replace").splitlines():
          p = line.strip()
          if not p:
              continue
          if not p.startswith("/"):
              p = "/" + p
          if p.startswith(prefix + "/") or p == prefix:
              # Keep the path *under* docs/features as a slug, without leading slash.
              slug = p.lstrip("/")
              out.add(slug)
      
      for s in sorted(out):
          print(s)
      PY
      
      
    • extract_sitemap_paths.sh 892 B
      #!/usr/bin/env bash
      set -euo pipefail
      
      if [[ $# -ne 1 ]]; then
        echo "usage: extract_sitemap_paths.sh <sitemap.xml>" >&2
        exit 2
      fi
      
      SITEMAP_XML="$1"
      
      python3 - "$SITEMAP_XML" <<'PY'
      import sys
      import urllib.parse
      import xml.etree.ElementTree as ET
      from pathlib import Path
      
      src = Path(sys.argv[1])
      data = src.read_text(encoding="utf-8", errors="replace")
      root = ET.fromstring(data)
      
      paths = set()
      for loc in root.iter():
          if loc.tag.endswith("loc") and loc.text:
              u = loc.text.strip()
              p = urllib.parse.urlparse(u)
              path = p.path or ""
              if not path:
                  continue
              # Normalize: ensure leading slash, drop trailing slash except root.
              if not path.startswith("/"):
                  path = "/" + path
              if len(path) > 1 and path.endswith("/"):
                  path = path[:-1]
              paths.add(path)
      
      for p in sorted(paths):
          print(p)
      PY
      
      
    • fetch_url.py 853 B
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import sys
      import urllib.parse
      import urllib.request
      from pathlib import Path
      
      
      def main() -> int:
          if len(sys.argv) != 3:
              print("usage: fetch_url.py <url> <out_path>", file=sys.stderr)
              return 2
          url = sys.argv[1]
          out_path = Path(sys.argv[2])
          out_path.parent.mkdir(parents=True, exist_ok=True)
      
          parsed = urllib.parse.urlparse(url)
          if parsed.scheme in ("file", ""):
              src = Path(parsed.path if parsed.scheme == "file" else url)
              out_path.write_bytes(src.read_bytes())
              return 0
      
          req = urllib.request.Request(url, headers={"User-Agent": "reverse-engineer/1.0"})
          with urllib.request.urlopen(req, timeout=30) as resp:
              out_path.write_bytes(resp.read())
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
      
    • generate_feature_catalog_md.py 2.9 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import datetime as _dt
      from pathlib import Path
      
      
      def _parse_registry(path: Path) -> dict:
          data = {"docs_features_prefix": "docs/features/", "docs_features": [], "groups": {}}
          cur = None
          in_docs = False
          in_groups = False
          in_anchors = False
          for raw in path.read_text(encoding="utf-8", errors="replace").splitlines():
              line = raw.rstrip("\n")
              if not line.strip() or line.lstrip().startswith("#"):
                  continue
              if line.startswith("docs_features_prefix:"):
                  data["docs_features_prefix"] = line.split(":", 1)[1].strip().strip("'\"")
              if line == "docs_features:":
                  in_docs = True
                  in_groups = False
                  continue
              if line == "groups:":
                  in_docs = False
                  in_groups = True
                  continue
      
              if in_docs and line.startswith("  - "):
                  data["docs_features"].append(line[4:].strip().strip("'\""))
                  continue
      
              if in_groups:
                  if line.startswith("  ") and not line.startswith("    ") and line.endswith(":"):
                      name = line.strip()[:-1]
                      cur = {"impl": None, "anchors": [], "notes": ""}
                      data["groups"][name] = cur
                      in_anchors = False
                      continue
                  if cur is None:
                      continue
                  s = line.strip()
                  if s.startswith("impl:"):
                      cur["impl"] = s.split(":", 1)[1].strip()
                  elif s.startswith("anchors:"):
                      in_anchors = True
                      if s.endswith("[]"):
                          cur["anchors"] = []
                  elif in_anchors and s.startswith("- "):
                      cur["anchors"].append(s[2:].strip().strip("'\""))
                  elif s.startswith("notes:"):
                      cur["notes"] = s.split(":", 1)[1].strip().strip("'\"")
          return data
      
      
      def main() -> int:
          ap = argparse.ArgumentParser()
          ap.add_argument("--registry", required=True)
          ap.add_argument("--out", required=True)
          args = ap.parse_args()
      
          reg = _parse_registry(Path(args.registry))
          groups = reg["groups"]
      
          out = Path(args.out)
          out.parent.mkdir(parents=True, exist_ok=True)
      
          lines: list[str] = []
          lines.append("# Feature Catalog")
          lines.append("")
          lines.append(f"- Generated: {_dt.date.today().isoformat()}")
          lines.append(f"- Groups: {len(groups)}")
          lines.append("")
          lines.append("| Group | impl | anchors | notes |")
          lines.append("|---|---|---:|---|")
          for g in sorted(groups.keys()):
              ent = groups[g]
              impl = ent.get("impl") or ""
              anchors = ent.get("anchors") or []
              notes = (ent.get("notes") or "").replace("\n", " ")
              lines.append(f"| `{g}` | `{impl}` | {len(anchors)} | {notes} |")
          lines.append("")
          out.write_text("\n".join(lines) + "\n", encoding="utf-8")
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
      
    • generate_feature_inventory_md.py 1.4 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import datetime as _dt
      from pathlib import Path
      
      
      def main() -> int:
          ap = argparse.ArgumentParser()
          ap.add_argument("--product-name", required=True)
          ap.add_argument("--docs-features", required=True, help="Text file: one docs/features slug per line (may be empty).")
          ap.add_argument("--out", required=True)
          args = ap.parse_args()
      
          slugs_path = Path(args.docs_features)
          slugs = [ln.strip() for ln in slugs_path.read_text(encoding="utf-8", errors="replace").splitlines() if ln.strip()]
      
          out = Path(args.out)
          out.parent.mkdir(parents=True, exist_ok=True)
      
          lines: list[str] = []
          lines.append(f"# Feature Inventory: {args.product_name}")
          lines.append("")
          lines.append(f"- Generated: {_dt.date.today().isoformat()}")
          lines.append("- Source: docs sitemap inventory (if provided); otherwise empty/incomplete by design.")
          lines.append(f"- Count: {len(slugs)}")
          lines.append("")
          lines.append("## Docs Slugs")
          lines.append("")
          if slugs:
              for s in slugs:
                  lines.append(f"- `{s}`")
          else:
              lines.append("_No docs sitemap provided (or no matching `docs/features/` entries)._")
          lines.append("")
      
          out.write_text("\n".join(lines) + "\n", encoding="utf-8")
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
      
    • repo_fixture_test.sh 14.4 KB
      #!/usr/bin/env bash
      # repo_fixture_test.sh — Golden fixture self-test for cc-sdd repo-mode analysis.
      #
      # Pins to cc-sdd v2.1.0 (commit 6e972c064ac4723bc8ad0181871d07e199af6a9f) and
      # runs repo-mode analysis, then compares key contracts against stored fixtures.
      #
      # Usage:
      #   bash skills/reverse-engineer/scripts/repo_fixture_test.sh
      #
      # Exit codes:
      #   0  All fixture contracts match.
      #   1  One or more contracts drifted (diff output printed to stderr).
      #   2  Prerequisite missing or unexpected error.
      #
      # Requirements:
      #   - Network access (to clone github.com/gotalab/cc-sdd at v2.1.0)
      #   - git, python3
      
      set -euo pipefail
      
      # ---------------------------------------------------------------------------
      # Paths
      # ---------------------------------------------------------------------------
      SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
      SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
      # ROOT = git repo root (trunks/), two levels up from the skill dir
      # (reverse-engineer -> skills -> trunks)
      ROOT="$(cd "$SKILL_DIR/../.." && pwd)"
      FIXTURES_DIR="$SKILL_DIR/fixtures/cc-sdd-v2.1.0"
      
      PINNED_REF="v2.1.0"
      PINNED_COMMIT="6e972c064ac4723bc8ad0181871d07e199af6a9f"
      UPSTREAM_REPO="https://github.com/gotalab/cc-sdd.git"
      
      TMP="$ROOT/.tmp/repo-fixture-test-cc-sdd"
      OUT="$TMP/out"
      CLONE_DIR="$TMP/local-clone"
      
      # ---------------------------------------------------------------------------
      # Helpers
      # ---------------------------------------------------------------------------
      FAILURES=0
      
      _fail() {
        echo "FAIL: $1" >&2
        FAILURES=$((FAILURES + 1))
      }
      
      _ok() {
        echo "OK: $1"
      }
      
      _check_cmd() {
        if ! command -v "$1" >/dev/null 2>&1; then
          echo "error: required command not found: $1" >&2
          exit 2
        fi
      }
      
      # Normalize a YAML/text file: strip generated_at/clone_date/analysis_root lines
      # (which are volatile) so diff is stable across run dates.
      _normalize() {
        local f="$1"
        grep -v '^generated_at:' "$f" \
          | grep -v '^  "clone_date":' \
          | grep -v '^  "analysis_root":' \
          | grep -v '^  "node_package_dir":' \
          | grep -v '^analysis_root:' \
          | sed 's|^- Date: .*|- Date: <DATE>|' \
          | sed 's|^- Analysis root: .*|- Analysis root: <ROOT>|' \
          | sed "s|$(echo "$ROOT" | sed 's|/|\\/|g')|<ROOT>|g"
      }
      
      # ---------------------------------------------------------------------------
      # Prerequisites
      # ---------------------------------------------------------------------------
      _check_cmd git
      _check_cmd python3
      
      if [ ! -d "$FIXTURES_DIR" ]; then
        echo "error: fixtures directory not found: $FIXTURES_DIR" >&2
        echo "       Run with UPDATE_FIXTURES=1 to create it, or check the skill directory." >&2
        exit 2
      fi
      
      # ---------------------------------------------------------------------------
      # UPDATE_FIXTURES mode: regenerate and overwrite golden fixtures.
      # ---------------------------------------------------------------------------
      if [ "${UPDATE_FIXTURES:-0}" = "1" ]; then
        echo "=== UPDATE_FIXTURES=1: regenerating golden fixtures ==="
        rm -rf "$TMP"
        mkdir -p "$OUT" "$CLONE_DIR" "$FIXTURES_DIR"
      
        python3 "$SKILL_DIR/scripts/reverse_engineer.py" cc-sdd \
          --mode=repo \
          --upstream-repo="$UPSTREAM_REPO" \
          --upstream-ref="$PINNED_REF" \
          --local-clone-dir="$CLONE_DIR" \
          --output-dir="$OUT"
      
        # Verify resolved commit matches pin.
        ACTUAL_COMMIT="$(python3 -c "import json; d=json.load(open('$OUT/clone-metadata.json')); print(d['resolved_commit'])")"
        if [ "$ACTUAL_COMMIT" != "$PINNED_COMMIT" ]; then
          echo "WARNING: resolved commit $ACTUAL_COMMIT does not match expected pin $PINNED_COMMIT" >&2
          echo "         Update PINNED_COMMIT in this script if the tag was force-pushed." >&2
        fi
      
        # docs-features.txt — stable (content from repo tree).
        cp "$OUT/docs-features.txt" "$FIXTURES_DIR/docs-features.txt"
      
        # feature-registry.yaml — strip generated_at before storing.
        grep -v '^generated_at:' "$OUT/feature-registry.yaml" > "$FIXTURES_DIR/feature-registry.yaml"
      
        # clone-metadata.json — strip clone_date (volatile).
        python3 - "$OUT/clone-metadata.json" "$FIXTURES_DIR/clone-metadata.json" <<'PYEOF'
      import json, sys
      d = json.load(open(sys.argv[1]))
      d.pop("clone_date", None)
      open(sys.argv[2], "w").write(json.dumps({k: d[k] for k in ("upstream_repo", "upstream_ref", "resolved_commit")}, indent=2) + "\n")
      PYEOF
      
        # cli-surface-contracts.txt — extract key contract lines from spec-cli-surface.md.
        python3 - "$OUT/spec-cli-surface.md" "$FIXTURES_DIR/cli-surface-contracts.txt" <<'PYEOF'
      import sys, re
      
      text = open(sys.argv[1]).read()
      out_lines = [
          "# CLI surface contract assertions for cc-sdd v2.1.0",
          "# Each line below must appear verbatim in the generated spec-cli-surface.md",
          "# (after stripping leading/trailing whitespace).",
          "# Lines starting with # are comments.",
          "",
      ]
      
      # Entrypoints section.
      out_lines.append("# Package identity")
      for pat in [r"- Node package: `.+`", r"- package name: `.+`", r"- version: `.+`"]:
          m = re.search(pat, text)
          if m:
              out_lines.append(m.group(0))
      
      out_lines.append("")
      out_lines.append("# Binary entrypoint")
      m = re.search(r"- `cc-sdd` -> `.+`", text)
      if m:
          out_lines.append(m.group(0))
      
      out_lines.append("")
      out_lines.append("# Source entry heuristic")
      m = re.search(r"- `tools/cc-sdd/src/cli\.ts`.+", text)
      if m:
          out_lines.append(m.group(0))
      
      # Help text flags (extract lines from the code block).
      in_block = False
      flags = []
      for line in text.splitlines():
          if line.strip().startswith("```"):
              in_block = not in_block
              continue
          if in_block and (line.startswith("  -") or line.startswith("-")):
              flags.append(line.rstrip())
      if flags:
          out_lines.append("")
          out_lines.append("# Key CLI flags present in help text")
          out_lines.extend(flags)
      
      # Config surface.
      out_lines.append("")
      out_lines.append("# Config surface")
      for pat in [r"- User config file: `.+`", r"- Environment variables: `.+`"]:
          m = re.search(pat, text)
          if m:
              out_lines.append(m.group(0))
      
      open(sys.argv[2], "w").write("\n".join(out_lines) + "\n")
      print(f"Written {len(out_lines)} lines to {sys.argv[2]}")
      PYEOF
      
        echo "=== Fixtures updated in $FIXTURES_DIR ==="
        exit 0
      fi
      
      # ---------------------------------------------------------------------------
      # Normal mode: run analysis and compare against golden fixtures.
      # ---------------------------------------------------------------------------
      echo "=== repo_fixture_test.sh: cc-sdd v2.1.0 golden fixture test ==="
      echo "    Pinned commit: $PINNED_COMMIT"
      echo "    Fixtures:      $FIXTURES_DIR"
      echo ""
      
      # Clean output dir for reproducible run. The clone dir is preserved across runs
      # to avoid re-downloading (a shallow clone is ~5-10 MB and slow on first run).
      # However, we always delete the clone dir if the resolved SHA does not match the
      # pinned commit (guards against a force-pushed tag).
      if [ -d "$CLONE_DIR/.git" ]; then
        EXISTING_SHA="$(git -C "$CLONE_DIR" rev-parse HEAD 2>/dev/null || true)"
        if [ "$EXISTING_SHA" != "$PINNED_COMMIT" ]; then
          echo "--- Existing clone SHA ($EXISTING_SHA) != pin ($PINNED_COMMIT); re-cloning ---"
          rm -rf "$CLONE_DIR"
        else
          echo "--- Reusing cached clone at $PINNED_COMMIT ---"
        fi
      fi
      
      rm -rf "$OUT"
      mkdir -p "$OUT"
      
      if [ ! -d "$CLONE_DIR/.git" ]; then
        echo "--- Cloning cc-sdd at $PINNED_REF (network required) ---"
        mkdir -p "$CLONE_DIR"
      fi
      
      echo "--- Running repo-mode analysis ---"
      python3 "$SKILL_DIR/scripts/reverse_engineer.py" cc-sdd \
        --mode=repo \
        --upstream-repo="$UPSTREAM_REPO" \
        --upstream-ref="$PINNED_REF" \
        --local-clone-dir="$CLONE_DIR" \
        --output-dir="$OUT"
      
      # clone-metadata.json is only written by reverse_engineer.py during the initial
      # clone. When reusing a cached clone, write it ourselves so downstream checks work.
      if [ ! -f "$OUT/clone-metadata.json" ]; then
        RESOLVED_SHA="$(git -C "$CLONE_DIR" rev-parse HEAD 2>/dev/null || echo "")"
        python3 - "$OUT/clone-metadata.json" "$UPSTREAM_REPO" "$PINNED_REF" "$RESOLVED_SHA" <<'PYEOF'
      import json, sys
      out_path, repo, ref, sha = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
      data = {"upstream_repo": repo, "upstream_ref": ref, "resolved_commit": sha, "clone_date": "cached"}
      open(out_path, "w").write(json.dumps(data, indent=2) + "\n")
      PYEOF
      fi
      
      echo ""
      echo "--- Verifying pinned commit SHA ---"
      ACTUAL_COMMIT="$(python3 -c "import json; d=json.load(open('$OUT/clone-metadata.json')); print(d['resolved_commit'])")"
      if [ "$ACTUAL_COMMIT" != "$PINNED_COMMIT" ]; then
        _fail "resolved commit mismatch: got $ACTUAL_COMMIT, expected $PINNED_COMMIT"
        echo "      This means the tag was force-pushed or the fixture pin is stale." >&2
      else
        _ok "resolved commit matches pin ($PINNED_COMMIT)"
      fi
      
      # ---------------------------------------------------------------------------
      # Contract 1: docs-features.txt (exact match)
      # ---------------------------------------------------------------------------
      echo ""
      echo "--- Contract 1: docs-features.txt ---"
      GOLDEN="$FIXTURES_DIR/docs-features.txt"
      ACTUAL="$OUT/docs-features.txt"
      
      if [ ! -f "$ACTUAL" ]; then
        _fail "docs-features.txt not generated"
      else
        DIFF_OUT="$(diff --unified=3 "$GOLDEN" "$ACTUAL" 2>&1 || true)"
        if [ -n "$DIFF_OUT" ]; then
          _fail "docs-features.txt drifted from golden fixture"
          echo "--- diff (golden vs actual) ---" >&2
          echo "$DIFF_OUT" >&2
          echo "---" >&2
        else
          _ok "docs-features.txt matches golden fixture"
        fi
      fi
      
      # ---------------------------------------------------------------------------
      # Contract 2: feature-registry.yaml (normalized, strip generated_at)
      # ---------------------------------------------------------------------------
      echo ""
      echo "--- Contract 2: feature-registry.yaml (normalized) ---"
      GOLDEN="$FIXTURES_DIR/feature-registry.yaml"
      ACTUAL="$OUT/feature-registry.yaml"
      
      if [ ! -f "$ACTUAL" ]; then
        _fail "feature-registry.yaml not generated"
      else
        GOLDEN_NORM="$(mktemp)"
        ACTUAL_NORM="$(mktemp)"
        grep -v '^generated_at:' "$GOLDEN" > "$GOLDEN_NORM"
        grep -v '^generated_at:' "$ACTUAL" > "$ACTUAL_NORM"
        DIFF_OUT="$(diff --unified=3 "$GOLDEN_NORM" "$ACTUAL_NORM" 2>&1 || true)"
        rm -f "$GOLDEN_NORM" "$ACTUAL_NORM"
        if [ -n "$DIFF_OUT" ]; then
          _fail "feature-registry.yaml drifted from golden fixture"
          echo "--- diff (golden vs actual, generated_at stripped) ---" >&2
          echo "$DIFF_OUT" >&2
          echo "---" >&2
        else
          _ok "feature-registry.yaml matches golden fixture (normalized)"
        fi
      fi
      
      # ---------------------------------------------------------------------------
      # Contract 3: cli-surface-contracts.txt (line-presence check in spec-cli-surface.md)
      # ---------------------------------------------------------------------------
      echo ""
      echo "--- Contract 3: spec-cli-surface.md contract lines ---"
      CLI_SURFACE="$OUT/spec-cli-surface.md"
      CONTRACTS="$FIXTURES_DIR/cli-surface-contracts.txt"
      
      if [ ! -f "$CLI_SURFACE" ]; then
        _fail "spec-cli-surface.md not generated"
      elif [ ! -f "$CONTRACTS" ]; then
        echo "SKIP: cli-surface-contracts.txt fixture not found (non-fatal)"
      else
        CONTRACT_FAILURES=0
        while IFS= read -r line; do
          # Skip blank lines and comments.
          [[ -z "$line" || "$line" == \#* ]] && continue
          # Check verbatim line presence (fixed-string, -- prevents lines starting with
          # '-' being misinterpreted as grep flags).
          if ! grep -qF -- "$line" "$CLI_SURFACE" 2>/dev/null; then
            _fail "contract line not found in spec-cli-surface.md: $line"
            CONTRACT_FAILURES=$((CONTRACT_FAILURES + 1))
          fi
        done < "$CONTRACTS"
        if [ "$CONTRACT_FAILURES" -eq 0 ]; then
          _ok "all spec-cli-surface.md contract lines present"
        fi
      fi
      
      # ---------------------------------------------------------------------------
      # Contract 4: clone-metadata.json (key fields)
      # ---------------------------------------------------------------------------
      echo ""
      echo "--- Contract 4: clone-metadata.json key fields ---"
      GOLDEN="$FIXTURES_DIR/clone-metadata.json"
      ACTUAL="$OUT/clone-metadata.json"
      
      if [ ! -f "$ACTUAL" ]; then
        _fail "clone-metadata.json not generated"
      elif [ ! -f "$GOLDEN" ]; then
        echo "SKIP: clone-metadata.json fixture not found (non-fatal)"
      else
        # Compare only the stable fields (upstream_repo, upstream_ref, resolved_commit).
        GOLDEN_STABLE="$(mktemp)"
        ACTUAL_STABLE="$(mktemp)"
        python3 - "$GOLDEN" "$GOLDEN_STABLE" <<'PYEOF'
      import json, sys
      d = json.load(open(sys.argv[1]))
      out = {k: d[k] for k in ("upstream_repo", "upstream_ref", "resolved_commit") if k in d}
      open(sys.argv[2], "w").write(json.dumps(out, indent=2, sort_keys=True) + "\n")
      PYEOF
        python3 - "$ACTUAL" "$ACTUAL_STABLE" <<'PYEOF'
      import json, sys
      d = json.load(open(sys.argv[1]))
      out = {k: d[k] for k in ("upstream_repo", "upstream_ref", "resolved_commit") if k in d}
      open(sys.argv[2], "w").write(json.dumps(out, indent=2, sort_keys=True) + "\n")
      PYEOF
        DIFF_OUT="$(diff --unified=3 "$GOLDEN_STABLE" "$ACTUAL_STABLE" 2>&1 || true)"
        rm -f "$GOLDEN_STABLE" "$ACTUAL_STABLE"
        if [ -n "$DIFF_OUT" ]; then
          _fail "clone-metadata.json stable fields drifted from golden fixture"
          echo "--- diff (golden vs actual, stable fields only) ---" >&2
          echo "$DIFF_OUT" >&2
          echo "---" >&2
        else
          _ok "clone-metadata.json stable fields match golden fixture"
        fi
      fi
      
      # ---------------------------------------------------------------------------
      # Contract 5: required output files exist
      # ---------------------------------------------------------------------------
      echo ""
      echo "--- Contract 5: required output files exist ---"
      REQUIRED_FILES=(
        feature-inventory.md
        feature-registry.yaml
        feature-catalog.md
        spec-architecture.md
        spec-code-map.md
        spec-clone-vs-use.md
        spec-clone-mvp.md
        spec-cli-surface.md
        spec-artifact-surface.md
        artifact-registry.json
        clone-metadata.json
        docs-features.txt
        validate-feature-registry.py
      )
      
      for f in "${REQUIRED_FILES[@]}"; do
        if [ ! -f "$OUT/$f" ]; then
          _fail "required output file missing: $f"
        else
          _ok "exists: $f"
        fi
      done
      
      # ---------------------------------------------------------------------------
      # Contract 6: feature registry validator passes
      # ---------------------------------------------------------------------------
      echo ""
      echo "--- Contract 6: feature registry validator ---"
      if python3 "$OUT/validate-feature-registry.py" 2>&1; then
        _ok "validate-feature-registry.py exit 0"
      else
        _fail "validate-feature-registry.py exited non-zero"
      fi
      
      # ---------------------------------------------------------------------------
      # Summary
      # ---------------------------------------------------------------------------
      echo ""
      if [ "$FAILURES" -gt 0 ]; then
        echo "RESULT: FAIL — $FAILURES contract(s) drifted. See diff output above." >&2
        exit 1
      else
        echo "RESULT: PASS — all golden fixture contracts match."
        exit 0
      fi
      
    • reverse_engineer.py 79.8 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import datetime as _dt
      import hashlib
      import json
      import os
      import re
      import shutil
      import stat
      import subprocess
      import sys
      from pathlib import Path
      
      
      REPO_ROOT = Path.cwd()
      SKILL_DIR = Path(__file__).resolve().parents[1]
      TEMPLATES_DIR = SKILL_DIR / "references" / "templates"
      
      IGNORED_REPO_SCAN_PARTS = {
          ".agents",
          ".git",
          ".hg",
          ".mypy_cache",
          ".next",
          ".pytest_cache",
          ".svn",
          ".tmp",
          ".venv",
          "__pycache__",
          "build",
          "coverage",
          "dist",
          "node_modules",
          "target",
          "tmp",
          "venv",
          "vendor",
      }
      
      
      def _die(msg: str, code: int = 2) -> None:
          print(f"error: {msg}", file=sys.stderr)
          raise SystemExit(code)
      
      
      def _run(
          cmd: list[str], *, cwd: Path | None = None, check: bool = True
      ) -> subprocess.CompletedProcess:
          return subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=check)
      
      
      def _lexical_absolute(path: Path) -> Path:
          """Return an absolute normalized path without following filesystem links."""
      
          return Path(os.path.abspath(os.fspath(path.expanduser())))
      
      
      def _ensure_real_directory(path: Path) -> tuple[int, int]:
          """Create/traverse *path* one component at a time without following links.
      
          The returned device/inode pair lets the caller detect replacement of the
          selected output root after setup.  Every existing component must be a real
          directory; a symlink or special file is a hard error.
          """
      
          absolute = _lexical_absolute(path)
          flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0)
          nofollow = getattr(os, "O_NOFOLLOW", 0)
          current_fd = os.open(absolute.anchor, flags)
          try:
              for part in absolute.parts[1:]:
                  try:
                      os.mkdir(part, mode=0o755, dir_fd=current_fd)
                  except FileExistsError:
                      pass
                  try:
                      next_fd = os.open(part, flags | nofollow, dir_fd=current_fd)
                  except OSError as exc:
                      _die(f"directory component is not a real directory: {absolute}: {exc}")
                  os.close(current_fd)
                  current_fd = next_fd
              info = os.fstat(current_fd)
              return info.st_dev, info.st_ino
          finally:
              os.close(current_fd)
      
      
      def _assert_directory_identity(
          path: Path, identity: tuple[int, int], label: str
      ) -> None:
          try:
              info = os.lstat(path)
          except OSError as exc:
              _die(f"{label} disappeared during the run: {path}: {exc}")
          if stat.S_ISLNK(info.st_mode) or not stat.S_ISDIR(info.st_mode):
              _die(f"{label} is no longer a real directory: {path}")
          if (info.st_dev, info.st_ino) != identity:
              _die(f"{label} was replaced during the run: {path}")
      
      
      def _assert_no_symlinks(root: Path) -> None:
          """Reject pre-existing or concurrently introduced links below *root*."""
      
          if not root.exists():
              return
          root_info = os.lstat(root)
          if stat.S_ISLNK(root_info.st_mode) or not stat.S_ISDIR(root_info.st_mode):
              _die(f"output root must be a real directory: {root}")
          for directory, dirnames, filenames in os.walk(root, followlinks=False):
              base = Path(directory)
              for name in [*dirnames, *filenames]:
                  child = base / name
                  info = os.lstat(child)
                  if stat.S_ISLNK(info.st_mode):
                      _die(f"refusing symlink inside managed output tree: {child}")
      
      
      def _ensure_dirs(paths: list[Path]) -> None:
          for p in paths:
              _ensure_real_directory(p)
      
      
      def _today_ymd() -> str:
          return _dt.date.today().isoformat()
      
      
      def _slugify(s: str) -> str:
          out = []
          for ch in s.strip().lower():
              if ch.isalnum():
                  out.append(ch)
              elif ch in (" ", "-", "_", "/"):
                  out.append("-")
          slug = "".join(out)
          while "--" in slug:
              slug = slug.replace("--", "-")
          return slug.strip("-") or "product"
      
      
      def _detect_docs_prefix_for_repo(analysis_root: Path) -> str:
          """
          Choose a sensible docs slug prefix for repos that do not use docs/features/.
          Returns a prefix with trailing slash.
          """
          candidates = [
              "docs/features/",
              "docs/code-map/",
              "docs/workflows/",
              "docs/levels/",
              "docs/",
          ]
          best = "docs/features/"
          best_count = -1
          for cand in candidates:
              base = analysis_root / cand.strip("/")
              if not base.exists() or not base.is_dir():
                  continue
              count = 0
              for p in base.rglob("*"):
                  if p.is_file() and p.suffix.lower() in (".md", ".mdx"):
                      count += 1
              if count > best_count:
                  best = cand
                  best_count = count
          if best_count >= 0:
              return best
          return "docs/features/"
      
      
      def _detect_docs_prefix_from_paths(paths: list[str]) -> str:
          """
          Choose docs prefix from sitemap-style path inventory.
          """
          normalized: list[str] = []
          for raw in paths:
              p = raw.strip()
              if not p:
                  continue
              if not p.startswith("/"):
                  p = "/" + p
              normalized.append(p)
      
          if not normalized:
              return "docs/features/"
      
          candidates = [
              "docs/features/",
              "docs/code-map/",
              "docs/workflows/",
              "docs/levels/",
              "docs/",
          ]
          best = "docs/features/"
          best_count = -1
          for cand in candidates:
              prefix = "/" + cand.strip("/").rstrip("/")
              count = sum(1 for p in normalized if p == prefix or p.startswith(prefix + "/"))
              if count > best_count:
                  best = cand
                  best_count = count
          return best
      
      
      def _render_template(src: Path, dst: Path, vars: dict[str, str]) -> None:
          text = src.read_text(encoding="utf-8")
          for k, v in vars.items():
              text = text.replace("{{" + k + "}}", v)
          dst.write_text(text, encoding="utf-8")
      
      
      def _read_text(p: Path) -> str:
          return p.read_text(encoding="utf-8", errors="replace")
      
      
      def _should_skip_repo_scan_path(path: Path, repo_root: Path) -> bool:
          try:
              rel_parts = path.relative_to(repo_root).parts
          except ValueError:
              rel_parts = path.parts
          for part in rel_parts:
              if part in IGNORED_REPO_SCAN_PARTS:
                  return True
          return False
      
      
      def _extract_ts_backtick_const(src: Path, const_name: str) -> str | None:
          # Best-effort: extract `const <name> = `...`;` blocks (common for CLI help text).
          if not src.exists():
              return None
          text = _read_text(src)
          m = re.search(
              rf"\bconst\s+{re.escape(const_name)}\s*=\s*`([\s\S]*?)`;",
              text,
              flags=re.MULTILINE,
          )
          return m.group(1) if m else None
      
      
      def _extract_ts_string_const(src: Path, const_name: str) -> str | None:
          if not src.exists():
              return None
          text = _read_text(src)
          m = re.search(rf"\b{re.escape(const_name)}\s*=\s*'([^']*)';", text)
          if m:
              return m.group(1)
          m = re.search(rf'\b{re.escape(const_name)}\s*=\s*"([^"]*)";', text)
          if m:
              return m.group(1)
          return None
      
      
      def _extract_agents_from_registry_ts(
          registry_ts: Path,
      ) -> tuple[list[str], list[str]] | None:
          """
          Best-effort parser for agent keys + alias flags from a TS registry.
          Intended to resolve help text interpolations like `${agentKeys.join('|')}`.
          """
          if not registry_ts.exists():
              return None
      
          text = _read_text(registry_ts)
          start = text.find("export const agentDefinitions")
          if start < 0:
              return None
          tail = text[start:]
      
          # Limit to the agentDefinitions object body to reduce false matches.
          end = tail.find("} as const")
          if end > 0:
              tail = tail[:end]
      
          agent_keys: list[str] = []
          seen_keys: set[str] = set()
      
          for line in tail.splitlines():
              # Top-level agent keys in the registry are consistently 2-space indented. This avoids
              # accidentally matching nested object keys like `layout:` or `commands:`.
              m = re.match(r"^  (?:'([^']+)'|([A-Za-z0-9_-]+))\s*:\s*\{\s*$", line)
              if not m:
                  continue
              key = (m.group(1) or m.group(2) or "").strip()
              if not key:
                  continue
              if key not in seen_keys:
                  agent_keys.append(key)
                  seen_keys.add(key)
      
          alias_flags: set[str] = set()
          for m in re.finditer(r"aliasFlags:\s*\[([^\]]*)\]", tail, flags=re.MULTILINE):
              blob = m.group(1)
              for s in re.findall(r"'([^']+)'", blob):
                  alias_flags.add(s)
              for s in re.findall(r"\"([^\"]+)\"", blob):
                  alias_flags.add(s)
      
          return agent_keys, sorted(alias_flags)
      
      
      def _find_node_cli_package(
          repo_root: Path, product_slug: str, product_name: str
      ) -> dict[str, object] | None:
          # Detect Node CLI packages by locating a package.json with a "bin" field and matching name/bin key.
          product_name_lc = product_name.strip().lower()
          candidates: list[tuple[int, Path, dict[str, object]]] = []
      
          for pkg_json in sorted(repo_root.rglob("package.json")):
              if _should_skip_repo_scan_path(pkg_json, repo_root):
                  continue
              try:
                  data = json.loads(_read_text(pkg_json))
              except Exception:
                  continue
      
              bin_field = data.get("bin")
              if not bin_field:
                  continue
      
              name = str(data.get("name") or "")
              score = 0
              if name.lower() == product_slug or name.lower() == product_name_lc:
                  score += 100
      
              # Normalize bin mapping.
              bin_map: dict[str, str] = {}
              if isinstance(bin_field, str):
                  if name:
                      bin_map[name] = bin_field
              elif isinstance(bin_field, dict):
                  for k, v in bin_field.items():
                      if isinstance(k, str) and isinstance(v, str):
                          bin_map[k] = v
              if product_slug in bin_map:
                  score += 80
              if product_name_lc in (k.lower() for k in bin_map.keys()):
                  score += 60
      
              # Prefer shallower packages when score ties (often the main package vs nested deps).
              depth = len(pkg_json.relative_to(repo_root).parts)
              score -= depth
      
              candidates.append((score, pkg_json, data))
      
          if not candidates:
              return None
      
          candidates.sort(key=lambda t: t[0], reverse=True)
          score, pkg_json, data = candidates[0]
      
          # Return a normalized payload for downstream rendering.
          bin_field = data.get("bin")
          bin_map: dict[str, str] = {}
          if isinstance(bin_field, str):
              name = str(data.get("name") or "")
              if name:
                  bin_map[name] = bin_field
          elif isinstance(bin_field, dict):
              for k, v in bin_field.items():
                  if isinstance(k, str) and isinstance(v, str):
                      bin_map[k] = v
      
          return {
              "score": score,
              "package_json": str(pkg_json),
              "package_dir": str(pkg_json.parent),
              "name": str(data.get("name") or ""),
              "version": str(data.get("version") or ""),
              "bin": bin_map,
          }
      
      
      def _find_python_cli(repo_root: Path) -> dict[str, object] | None:
          """Detect Python CLI packages via pyproject.toml or setup.cfg entry_points."""
          result: dict[str, object] = {
              "language": "python",
              "bin": {},
              "framework": None,
              "entry_module": None,
          }
      
          # Try pyproject.toml first (modern standard).
          for pyproject in sorted(repo_root.rglob("pyproject.toml")):
              if _should_skip_repo_scan_path(pyproject, repo_root):
                  continue
              text = _read_text(pyproject)
              # [project.scripts] section (PEP 621).
              m = re.search(r"\[project\.scripts\]\s*\n((?:[^\[].+\n)*)", text)
              if m:
                  for line in m.group(1).strip().splitlines():
                      parts = line.split("=", 1)
                      if len(parts) == 2:
                          name = parts[0].strip().strip('"').strip("'")
                          entry = parts[1].strip().strip('"').strip("'")
                          result["bin"][name] = entry  # type: ignore[index]
                          if not result["entry_module"]:
                              result["entry_module"] = (
                                  entry.split(":")[0] if ":" in entry else entry
                              )
              # [tool.poetry.scripts] section.
              m2 = re.search(r"\[tool\.poetry\.scripts\]\s*\n((?:[^\[].+\n)*)", text)
              if m2:
                  for line in m2.group(1).strip().splitlines():
                      parts = line.split("=", 1)
                      if len(parts) == 2:
                          name = parts[0].strip().strip('"').strip("'")
                          entry = parts[1].strip().strip('"').strip("'")
                          result["bin"][name] = entry  # type: ignore[index]
              if result["bin"]:
                  break
      
          # Try setup.cfg if pyproject didn't find scripts.
          if not result["bin"]:
              for setup_cfg in sorted(repo_root.rglob("setup.cfg")):
                  if _should_skip_repo_scan_path(setup_cfg, repo_root):
                      continue
                  text = _read_text(setup_cfg)
                  m = re.search(
                      r"\[options\.entry_points\]\s*\nconsole_scripts\s*=\s*\n((?:\s+.+\n)*)",
                      text,
                  )
                  if m:
                      for line in m.group(1).strip().splitlines():
                          parts = line.strip().split("=", 1)
                          if len(parts) == 2:
                              result["bin"][parts[0].strip()] = parts[1].strip()  # type: ignore[index]
                      if result["bin"]:
                          break
      
          if not result["bin"]:
              return None
      
          # Detect CLI framework via source scan (best-effort, cap file count).
          scanned = 0
          for py_file in sorted(repo_root.rglob("*.py")):
              if _should_skip_repo_scan_path(py_file, repo_root):
                  continue
              scanned += 1
              if scanned > 200:
                  break
              text = _read_text(py_file)
              if "@click.command" in text or "@click.group" in text:
                  result["framework"] = "click"
                  break
              if "typer.Typer" in text or "@app.command" in text:
                  result["framework"] = "typer"
                  break
              if "ArgumentParser(" in text and "add_argument" in text:
                  result["framework"] = "argparse"
      
          return result
      
      
      def _find_go_cli(repo_root: Path) -> dict[str, object] | None:
          """Detect Go CLI packages via go.mod + main.go + flag/cobra usage."""
          result: dict[str, object] = {
              "language": "go",
              "bin": {},
              "framework": None,
              "module": None,
          }
      
          # Find go.mod for module name.
          go_mod = repo_root / "go.mod"
          if not go_mod.exists():
              # Check one level deeper (monorepo).
              for gm in sorted(repo_root.rglob("go.mod")):
                  if _should_skip_repo_scan_path(gm, repo_root):
                      continue
                  go_mod = gm
                  break
          if go_mod.exists():
              text = _read_text(go_mod)
              m = re.search(r"^module\s+(.+)$", text, re.MULTILINE)
              if m:
                  result["module"] = m.group(1).strip()
      
          # Find main.go files (entry points).
          main_files: list[Path] = []
          for mg in sorted(repo_root.rglob("main.go")):
              if _should_skip_repo_scan_path(mg, repo_root) or "testdata" in mg.parts:
                  continue
              main_files.append(mg)
      
          if not main_files and not result["module"]:
              return None
      
          # Derive binary names from cmd/ pattern or root main.go.
          for mf in main_files:
              rel = mf.relative_to(repo_root)
              parts = rel.parts
              if len(parts) >= 3 and parts[-3] == "cmd":
                  # cmd/<name>/main.go pattern.
                  result["bin"][parts[-2]] = str(rel)  # type: ignore[index]
              elif len(parts) == 1:
                  # Root main.go — use module basename or directory name.
                  mod = str(result.get("module") or "")
                  name = mod.rsplit("/", 1)[-1] if mod else repo_root.name
                  result["bin"][name] = str(rel)  # type: ignore[index]
      
          if not result["bin"]:
              return None
      
          # Detect CLI framework (cobra vs stdlib flag).
          scanned = 0
          for go_file in sorted(repo_root.rglob("*.go")):
              if (
                  _should_skip_repo_scan_path(go_file, repo_root)
                  or "testdata" in go_file.parts
              ):
                  continue
              scanned += 1
              if scanned > 200:
                  break
              text = _read_text(go_file)
              if "cobra.Command" in text or '"github.com/spf13/cobra"' in text:
                  result["framework"] = "cobra"
                  break
              if "flag.String" in text or "flag.Bool" in text or "flag.Int" in text:
                  result["framework"] = "flag"
      
          return result
      
      
      def _sha256_file(p: Path) -> str:
          h = hashlib.sha256()
          with p.open("rb") as f:
              for chunk in iter(lambda: f.read(1024 * 1024), b""):
                  h.update(chunk)
          return h.hexdigest()
      
      
      def _render_placeholders(s: str, vars: dict[str, str]) -> str:
          out = s
          for k, v in vars.items():
              out = out.replace("{{" + k + "}}", v)
          return out
      
      
      def _enrich_registry_with_binary_evidence(
          registry_yaml: Path,
          tmp_dir: Path,
          output_dir: Path,
          *,
          product_name: str,
          date: str,
      ) -> bool:
          """Enrich feature-registry.yaml with binary string evidence.
      
          Reads cli-commands.txt and binary strings to create evidence-backed groups.
          Also generates binary-symbols.txt in the output dir.
          Returns True if enrichment was applied.
          """
          commands_file = tmp_dir / "binary" / "cli-commands.txt"
          _strings_file = tmp_dir / "binary" / "strings.head.txt"
          _ba_file = tmp_dir / "binary" / "binary-analysis.md"
      
          # Generate binary-symbols.txt from strings
          full_strings = tmp_dir / "binary" / "strings.head.txt"
          symbols_out = output_dir / "binary-symbols.txt"
          if full_strings.exists() and not symbols_out.exists():
              shutil.copyfile(full_strings, symbols_out)
      
          # Gather command groups from cli-commands.txt
          cmd_groups: dict[str, list[str]] = {}
          if commands_file.exists():
              for line in commands_file.read_text(encoding="utf-8").splitlines():
                  line = line.strip()
                  if not line:
                      continue
                  parts = line.split()
                  group = parts[0]
                  cmd_groups.setdefault(group, []).append(line)
      
          if not cmd_groups:
              return False
      
          # Try to load the existing registry (manual parse, no yaml dep)
          reg: dict = {"groups": {}}
          try:
              text = registry_yaml.read_text(encoding="utf-8")
              for raw_line in text.splitlines():
                  stripped = raw_line.strip()
                  if stripped.startswith("docs_features_prefix:"):
                      reg["docs_features_prefix"] = (
                          stripped.split(":", 1)[1].strip().strip("'\"")
                      )
                  elif stripped.startswith("docs_features:"):
                      reg.setdefault("docs_features", [])
                  elif (
                      raw_line.startswith("  - ")
                      and "docs_features" in reg
                      and "groups"
                      not in text.split(raw_line)[0].rsplit("docs_features:", 1)[-1]
                  ):
                      reg.setdefault("docs_features", []).append(
                          stripped[2:].strip().strip("'\"")
                      )
              # Parse groups using the same logic as the validator
              cur = None
              in_groups = False
              in_anchors = False
              for raw_line in text.splitlines():
                  line = raw_line.rstrip()
                  if not line.strip() or line.lstrip().startswith("#"):
                      continue
                  if line == "groups:":
                      in_groups = True
                      continue
                  if not in_groups:
                      continue
                  if (
                      line.startswith("  ")
                      and not line.startswith("    ")
                      and line.endswith(":")
                  ):
                      name = line.strip()[:-1]
                      cur = {"impl": None, "anchors": [], "notes": ""}
                      reg["groups"][name] = cur
                      in_anchors = False
                      continue
                  if cur is None:
                      continue
                  s = line.strip()
                  if s.startswith("impl:"):
                      cur["impl"] = s.split(":", 1)[1].strip()
                  elif s.startswith("anchors:"):
                      in_anchors = True
                      if s.endswith("[]"):
                          cur["anchors"] = []
                  elif in_anchors and s.startswith("- "):
                      cur["anchors"].append(s[2:].strip().strip("'\""))
                  elif s.startswith("notes:"):
                      cur["notes"] = s.split(":", 1)[1].strip().strip("'\"")
          except Exception:
              return False
      
          groups = reg.get("groups", {})
      
          # Check if registry is already populated (has non-empty groups with notes)
          has_content = any(g.get("notes") for g in groups.values()) if groups else False
          if has_content:
              # Already enriched or populated — don't overwrite
              return False
      
          # Build new groups from binary command data
          new_groups: dict[str, dict] = {}
          for grp_name, cmds in sorted(cmd_groups.items()):
              slug = grp_name.replace("-", "_")
              subcmds = [c for c in cmds if c != grp_name]
              sub_str = ", ".join(subcmds) if subcmds else "no subcommands"
              new_groups[slug] = {
                  "impl": "client",
                  "anchors": ["binary-symbols.txt"],
                  "notes": f"{grp_name} ({len(cmds)} commands: {sub_str})",
              }
      
          # Write registry in the manual format expected by validate_feature_registry.py
          lines: list[str] = []
          lines.append("schema_version: 1")
          lines.append(f"product_name: {product_name!r}")
          lines.append(f"generated_at: {date!r}")
          lines.append("evidence_source: 'binary --help + string extraction'")
          # Preserve docs_features_prefix if present
          dfp = reg.get("docs_features_prefix", "docs/features/")
          lines.append(f"docs_features_prefix: {dfp!r}")
          # Preserve docs_features list if present
          docs_feats = reg.get("docs_features", [])
          if docs_feats:
              lines.append("docs_features:")
              for df in docs_feats:
                  lines.append(f"  - {df!r}")
          lines.append("groups:")
          for slug, grp in new_groups.items():
              lines.append(f"  {slug}:")
              lines.append(f"    impl: {grp['impl']}")
              lines.append("    anchors:")
              for a in grp["anchors"]:
                  lines.append(f"      - {a}")
              lines.append(f"    notes: {grp['notes']!r}")
      
          registry_yaml.write_text("\n".join(lines) + "\n", encoding="utf-8")
          return True
      
      
      def _write_binary_cli_surface_spec(
          output_dir: Path,
          tmp_dir: Path,
          *,
          product_name: str,
          date: str,
      ) -> bool:
          """Write spec-cli-surface.md from binary --help output or binary strings.
      
          Returns True if a spec was written.
          """
          help_tree = tmp_dir / "binary" / "cli-help-tree.txt"
          commands_file = tmp_dir / "binary" / "cli-commands.txt"
          strings_file = tmp_dir / "binary" / "strings.head.txt"
      
          lines: list[str] = []
          lines.append(f"# CLI Surface Spec: {product_name}")
          lines.append("")
          lines.append(f"- Date: {date}")
          lines.append(
              "- Source: binary --help output"
              if help_tree.exists()
              else "- Source: binary string extraction"
          )
          lines.append("")
      
          cmd_count = 0
          if commands_file.exists():
              cmds = [
                  c.strip()
                  for c in commands_file.read_text(encoding="utf-8").splitlines()
                  if c.strip()
              ]
              cmd_count = len(cmds)
      
          if help_tree.exists():
              tree_text = help_tree.read_text(encoding="utf-8")
              lines.append("## Command Count")
              lines.append("")
              lines.append(
                  f"- **{cmd_count} commands** discovered via recursive `--help` execution"
              )
              lines.append("")
      
              # Extract top-level commands and subcommands
              if commands_file.exists():
                  top_level = sorted(set(c.split()[0] for c in cmds if c.strip()))
                  lines.append("## Top-Level Commands")
                  lines.append("")
                  lines.append("| Command | Subcommands |")
                  lines.append("|---------|-------------|")
                  for top in top_level:
                      subs = [c for c in cmds if c.startswith(top + " ") and c != top]
                      sub_names = [c.split(maxsplit=1)[1] if " " in c else "" for c in subs]
                      sub_str = (
                          ", ".join(f"`{s}`" for s in sub_names if s) if sub_names else "—"
                      )
                      lines.append(f"| `{top}` | {sub_str} |")
                  lines.append("")
      
              lines.append("## Full Help Tree")
              lines.append("")
              lines.append("```")
              # Truncate to avoid massive output
              tree_lines = tree_text.splitlines()
              if len(tree_lines) > 500:
                  lines.extend(tree_lines[:500])
                  lines.append(f"... ({len(tree_lines) - 500} more lines)")
              else:
                  lines.extend(tree_lines)
              lines.append("```")
              lines.append("")
          elif strings_file.exists():
              # Fallback: extract command-like patterns from strings
              raw = strings_file.read_text(encoding="utf-8", errors="replace")
              usage_lines = [
                  line.strip()
                  for line in raw.splitlines()
                  if "usage" in line.lower() or "Usage" in line
              ]
              lines.append("## CLI Surface (from binary strings, best-effort)")
              lines.append("")
              if usage_lines:
                  for u in usage_lines[:20]:
                      lines.append(f"- `{u[:200]}`")
              else:
                  lines.append("_No usage patterns found in binary strings._")
              lines.append("")
          else:
              return False
      
          out = output_dir / "spec-cli-surface.md"
          out.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
          return True
      
      
      def _write_cli_surface_spec(
          output_dir: Path,
          *,
          product_name: str,
          product_slug: str,
          date: str,
          analysis_root: Path,
      ) -> bool:
          """
          Return True if a CLI was detected and spec-cli-surface.md was written.
      
          Repo-mode only. This is best-effort and aims to capture a mechanically-verifiable contract:
          - entrypoints (package.json bin)
          - help/usage text (static extraction, with interpolation resolved when possible)
          - config/env surface
          """
          if not analysis_root.exists():
              return False
      
          node_cli = _find_node_cli_package(analysis_root, product_slug, product_name)
          python_cli = _find_python_cli(analysis_root) if not node_cli else None
          go_cli = _find_go_cli(analysis_root) if not node_cli and not python_cli else None
      
          if not node_cli and not python_cli and not go_cli:
              return False
      
          # If Python or Go CLI detected (non-Node), write a language-appropriate spec.
          if python_cli or go_cli:
              cli_info = python_cli or go_cli
              assert cli_info is not None
              out = output_dir / "spec-cli-surface.md"
              lines: list[str] = []
              lang = str(cli_info["language"]).capitalize()
              lines.append(f"# CLI Surface Spec: {product_name}")
              lines.append("")
              lines.append(f"- Date: {date}")
              lines.append(f"- Language: {lang}")
              lines.append(f"- Analysis root: `{analysis_root}`")
              if cli_info.get("framework"):
                  lines.append(f"- Framework: {cli_info['framework']}")
              if cli_info.get("module"):
                  lines.append(f"- Module: `{cli_info['module']}`")
              if cli_info.get("entry_module"):
                  lines.append(f"- Entry module: `{cli_info['entry_module']}`")
              lines.append("")
              lines.append("## Entrypoints (Code-Proven)")
              lines.append("")
              bin_map = cli_info.get("bin") or {}
              if isinstance(bin_map, dict) and bin_map:
                  for k in sorted(bin_map.keys()):
                      lines.append(f"- `{k}` -> `{bin_map[k]}`")
              else:
                  lines.append("- _No entrypoints extracted._")
              lines.append("")
              lines.append("## Notes For 1:1 Fidelity")
              lines.append("")
              lines.append(
                  "- Run `<binary> --help` to capture the full CLI contract as a golden test fixture."
              )
              if lang == "Python":
                  lines.append(
                      "- For Click/Typer apps, consider `<binary> --help` per subcommand for full coverage."
                  )
              elif lang == "Go":
                  lines.append(
                      "- For Cobra apps, consider `<binary> help <subcommand>` for full coverage."
                  )
              out.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
              return True
      
          out = output_dir / "spec-cli-surface.md"
          pkg_dir = Path(str(node_cli["package_dir"]))
      
          pkg_json_rel = (
              Path(str(node_cli["package_json"])).relative_to(analysis_root).as_posix()
          )
          src_index = pkg_dir / "src" / "index.ts"
          src_cli = pkg_dir / "src" / "cli.ts"
          src_store = pkg_dir / "src" / "cli" / "store.ts"
          src_agents = pkg_dir / "src" / "agents" / "registry.ts"
      
          help_text = _extract_ts_backtick_const(src_index, "helpText")
          config_file = _extract_ts_string_const(src_store, "CONFIG_FILE")
      
          # Resolve common interpolations in helpText for higher-fidelity output.
          if help_text and src_agents.exists():
              extracted = _extract_agents_from_registry_ts(src_agents)
              if extracted:
                  agent_keys, alias_flags = extracted
                  if agent_keys:
                      help_text = help_text.replace(
                          "${agentKeys.join('|')}", "|".join(agent_keys)
                      )
                  alias_line = ""
                  if alias_flags:
                      alias_line = f"  {' | '.join(alias_flags)}  Agent alias flags\n"
                  help_text = help_text.replace("${agentAliasLine}", alias_line)
      
          # Env vars: scan the src tree for process.env.<NAME> patterns.
          env_vars: list[str] = []
          src_root = pkg_dir / "src"
          if src_root.exists():
              pat = re.compile(r"\bprocess\.env\.([A-Z][A-Z0-9_]*)\b")
              found = set()
              for p in sorted(src_root.rglob("*")):
                  if not p.is_file() or p.suffix.lower() not in (
                      ".ts",
                      ".tsx",
                      ".js",
                      ".jsx",
                      ".mjs",
                      ".cjs",
                  ):
                      continue
                  for m in pat.finditer(_read_text(p)):
                      found.add(m.group(1))
              env_vars = sorted(found)
      
          lines: list[str] = []
          lines.append(f"# CLI Surface Spec: {product_name}")
          lines.append("")
          lines.append(f"- Date: {date}")
          lines.append(f"- Analysis root: `{analysis_root}`")
          lines.append("")
          lines.append("## Entrypoints (Code-Proven)")
          lines.append("")
          lines.append(f"- Node package: `{pkg_dir.relative_to(analysis_root).as_posix()}`")
          lines.append(f"- package.json: `{pkg_json_rel}`")
          if node_cli.get("name"):
              lines.append(f"- package name: `{node_cli['name']}`")
          if node_cli.get("version"):
              lines.append(f"- version: `{node_cli['version']}`")
          lines.append("")
          lines.append("### Binaries")
          lines.append("")
          bin_map = node_cli.get("bin") or {}
          if isinstance(bin_map, dict) and bin_map:
              for k in sorted(bin_map.keys()):
                  v = str(bin_map[k])
                  lines.append(f"- `{k}` -> `{v}`")
          else:
              lines.append("- _No `bin` mapping extracted (unexpected)._")
      
          if src_cli.exists():
              lines.append("")
              lines.append("### Source Entry (Heuristic)")
              lines.append("")
              lines.append(
                  f"- `{src_cli.relative_to(analysis_root).as_posix()}` (node shebang entry; typically calls `runCli`)"
              )
      
          lines.append("")
          lines.append("## Usage / Help (Code-Proven Where Possible)")
          lines.append("")
          if help_text:
              lines.append("```text")
              lines.append(help_text.rstrip("\n"))
              lines.append("```")
              lines.append("")
              lines.append("Evidence:")
              lines.append(
                  f"- `{src_index.relative_to(analysis_root).as_posix()}` (`helpText`)"
              )
          else:
              lines.append("- _Help text not extracted (pattern not found)._")
              lines.append("Evidence:")
              lines.append(f"- `{src_index.relative_to(analysis_root).as_posix()}`")
      
          lines.append("")
          lines.append("## Config / Env (Code-Proven Where Possible)")
          lines.append("")
          wrote_any = False
          if config_file:
              lines.append(f"- User config file: `{config_file}` (loaded from CWD).")
              lines.append(f"  Evidence: `{src_store.relative_to(analysis_root).as_posix()}`")
              wrote_any = True
          if env_vars:
              lines.append(f"- Environment variables: `{', '.join(env_vars)}`")
              lines.append(
                  f"  Evidence: scan of `{src_root.relative_to(analysis_root).as_posix()}` for `process.env.<NAME>`."
              )
              wrote_any = True
          if not wrote_any:
              lines.append("- _No config/env surface extracted._")
      
          lines.append("")
          lines.append("## Notes For 1:1 Fidelity")
          lines.append("")
          lines.append(
              "- Treat `--help` output as the CLI contract; include it as a golden test fixture for regressions."
          )
          lines.append(
              "- If the repo does not ship built artifacts (ex: `dist/`), building may be required to execute the CLI directly."
          )
      
          out.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
          return True
      
      
      def _write_artifact_surface_spec(
          output_dir: Path,
          *,
          product_name: str,
          product_slug: str,
          date: str,
          analysis_root: Path,
      ) -> None:
          """
          Higher-fidelity extraction of "what the product writes/installs" for template-driven CLIs.
      
          Emits:
          - spec-artifact-surface.md (human summary)
          - artifact-registry.json (machine-usable: manifests + template file hashes)
          """
          out_md = output_dir / "spec-artifact-surface.md"
          out_json = output_dir / "artifact-registry.json"
      
          if not analysis_root.exists():
              out_md.write_text(
                  f"# Artifact Surface Spec: {product_name}\n\n- Date: {date}\n\n- _No repo content available to analyze._\n",
                  encoding="utf-8",
              )
              return
      
          node_cli = _find_node_cli_package(analysis_root, product_slug, product_name)
          if not node_cli:
              out_md.write_text(
                  f"# Artifact Surface Spec: {product_name}\n\n- Date: {date}\n\n- _No Node CLI package detected; artifact extraction not implemented for this repo._\n",
                  encoding="utf-8",
              )
              return
      
          pkg_dir = Path(str(node_cli["package_dir"]))
          manifests_dir = pkg_dir / "templates" / "manifests"
          if not manifests_dir.exists():
              out_md.write_text(
                  f"# Artifact Surface Spec: {product_name}\n\n- Date: {date}\n\n"
                  f"- _No `templates/manifests/` directory found under `{pkg_dir.relative_to(analysis_root).as_posix()}`._\n",
                  encoding="utf-8",
              )
              return
      
          manifest_files = sorted(manifests_dir.glob("*.json"))
          manifests: list[dict[str, object]] = []
          resolved_sources: list[dict[str, object]] = []
      
          for mf in manifest_files:
              try:
                  data = json.loads(_read_text(mf))
              except Exception:
                  continue
      
              agent = None
              artifacts = data.get("artifacts") if isinstance(data, dict) else None
              if isinstance(artifacts, list):
                  for a in artifacts:
                      if isinstance(a, dict):
                          when = a.get("when")
                          if isinstance(when, dict) and isinstance(when.get("agent"), str):
                              agent = when.get("agent")
                              break
      
              manifests.append(
                  {
                      "path": mf.relative_to(analysis_root).as_posix(),
                      "agent": agent,
                      "raw": data,
                  }
              )
      
              # Build resolved source inventory (what files are copied from templates).
              if not isinstance(artifacts, list):
                  continue
      
              placeholder_vars = {"AGENT": agent} if isinstance(agent, str) else {}
              for a in artifacts:
                  if not isinstance(a, dict):
                      continue
                  source = a.get("source")
                  if not isinstance(source, dict):
                      continue
                  stype = source.get("type")
                  if stype == "templateDir":
                      from_dir = source.get("fromDir")
                      if not isinstance(from_dir, str):
                          continue
                      from_dir_res = (
                          _render_placeholders(from_dir, placeholder_vars)
                          if placeholder_vars
                          else from_dir
                      )
                      abs_from = pkg_dir / from_dir_res
                      if abs_from.exists() and abs_from.is_dir():
                          for fp in sorted(abs_from.rglob("*")):
                              if not fp.is_file():
                                  continue
                              resolved_sources.append(
                                  {
                                      "manifest": mf.relative_to(analysis_root).as_posix(),
                                      "artifact_id": a.get("id"),
                                      "source_type": "templateDir",
                                      "from": from_dir_res,
                                      "file": fp.relative_to(pkg_dir).as_posix(),
                                      "sha256": _sha256_file(fp),
                                  }
                              )
                  elif stype == "templateFile":
                      from_file = source.get("from")
                      if not isinstance(from_file, str):
                          continue
                      from_file_res = (
                          _render_placeholders(from_file, placeholder_vars)
                          if placeholder_vars
                          else from_file
                      )
                      abs_from = pkg_dir / from_file_res
                      if abs_from.exists() and abs_from.is_file():
                          resolved_sources.append(
                              {
                                  "manifest": mf.relative_to(analysis_root).as_posix(),
                                  "artifact_id": a.get("id"),
                                  "source_type": "templateFile",
                                  "from": from_file_res,
                                  "file": abs_from.relative_to(pkg_dir).as_posix(),
                                  "sha256": _sha256_file(abs_from),
                              }
                          )
      
          out_json.write_text(
              json.dumps(
                  {
                      "schema_version": 1,
                      "product_name": product_name,
                      "generated_at": date,
                      "analysis_root": str(analysis_root),
                      "node_package_dir": pkg_dir.relative_to(analysis_root).as_posix(),
                      "manifests": manifests,
                      "resolved_template_files": resolved_sources,
                  },
                  indent=2,
                  sort_keys=True,
              )
              + "\n",
              encoding="utf-8",
          )
      
          lines: list[str] = []
          lines.append(f"# Artifact Surface Spec: {product_name}")
          lines.append("")
          lines.append(f"- Date: {date}")
          lines.append(f"- Analysis root: `{analysis_root}`")
          lines.append(f"- Node package: `{pkg_dir.relative_to(analysis_root).as_posix()}`")
          lines.append(
              f"- Manifests dir: `{manifests_dir.relative_to(analysis_root).as_posix()}`"
          )
          lines.append(f"- Machine registry: `{out_json.relative_to(output_dir).as_posix()}`")
          lines.append("")
          lines.append("## Manifest Inventory (Code-Proven)")
          lines.append("")
          if manifest_files:
              for mf in manifest_files:
                  rel = mf.relative_to(analysis_root).as_posix()
                  agent = None
                  for m in manifests:
                      if m.get("path") == rel:
                          agent = m.get("agent")
                          break
                  agent_note = f" (agent={agent})" if agent else ""
                  lines.append(f"- `{rel}`{agent_note}")
          else:
              lines.append("- _No manifest JSON files found._")
      
          lines.append("")
          lines.append("## Template Source File Inventory (Hashed)")
          lines.append("")
          lines.append(f"- Files hashed: `{len(resolved_sources)}`")
          lines.append(
              "- Use `artifact-registry.json` as the source of truth for 1:1 template content equivalence."
          )
      
          out_md.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
      
      
      def _get_upstream_commit(analysis_root: Path) -> str | None:
          """Return the HEAD commit SHA if analysis_root is a git repo, else None."""
          git_dir = analysis_root / ".git"
          if not git_dir.exists():
              return None
          try:
              sha = subprocess.check_output(
                  ["git", "-C", str(analysis_root), "rev-parse", "HEAD"],
                  text=True,
                  stderr=subprocess.DEVNULL,
              ).strip()
              return sha if sha else None
          except Exception:
              return None
      
      
      def _collect_env_vars_with_evidence(
          analysis_root: Path,
      ) -> list[dict[str, object]]:
          """
          Scan source files for environment variable references and return a sorted list
          with per-var file evidence.  Covers:
            - TypeScript/JavaScript: process.env.VAR_NAME
            - Python: os.environ['VAR'] / os.environ.get('VAR') / os.getenv('VAR')
            - Go: os.Getenv("VAR") / os.LookupEnv("VAR")
            - Shell: $VAR_NAME (upper-snake only, cap at 300 files)
          """
          var_files: dict[str, set[str]] = {}
      
          patterns: list[tuple[re.Pattern[str], set[str]]] = [
              (
                  re.compile(r"\bprocess\.env\.([A-Z][A-Z0-9_]+)\b"),
                  {".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"},
              ),
              (
                  re.compile(
                      r"""os\.environ(?:\.get)?\s*\(\s*['"]([A-Z][A-Z0-9_]+)['"]\s*\)"""
                  ),
                  {".py"},
              ),
              (
                  re.compile(r"""\bos\.getenv\s*\(\s*['"]([A-Z][A-Z0-9_]+)['"]\s*\)"""),
                  {".py"},
              ),
              (
                  re.compile(
                      r"""\bos\.(?:Getenv|LookupEnv)\s*\(\s*"([A-Z][A-Z0-9_]+)"\s*\)"""
                  ),
                  {".go"},
              ),
              (
                  re.compile(r"\$\{?([A-Z][A-Z0-9_]{2,})\}?"),
                  {".sh", ".bash", ".env", ".envrc"},
              ),
          ]
      
          scanned = 0
          for p in sorted(analysis_root.rglob("*")):
              if not p.is_file():
                  continue
              # Skip irrelevant dirs
              skip_dirs = {
                  "node_modules",
                  ".git",
                  ".venv",
                  "vendor",
                  "testdata",
                  "__pycache__",
              }
              if any(part in skip_dirs for part in p.parts):
                  continue
              suffix = p.suffix.lower()
              matching_pats = [pat for pat, suffixes in patterns if suffix in suffixes]
              if not matching_pats:
                  continue
              scanned += 1
              if scanned > 500:
                  break
              try:
                  text = _read_text(p)
              except Exception:
                  continue
              rel = p.relative_to(analysis_root).as_posix()
              for pat in matching_pats:
                  for m in pat.finditer(text):
                      name = m.group(1)
                      var_files.setdefault(name, set()).add(rel)
      
          result: list[dict[str, object]] = []
          for var_name in sorted(var_files.keys()):
              result.append(
                  {
                      "name": var_name,
                      "files": sorted(var_files[var_name]),
                  }
              )
          return result
      
      
      def _collect_schema_files(analysis_root: Path) -> list[str]:
          """
          Return sorted relative paths of schema-like files in the repo.
          Matches: *.schema.json, *schema*.json, openapi*.json/yaml, swagger*.json/yaml,
                   *.proto, *.avsc, *.thrift, graphql schema files.
          """
          schema_patterns = [
              "**/*.schema.json",
              "**/*schema*.json",
              "**/openapi*.json",
              "**/openapi*.yaml",
              "**/openapi*.yml",
              "**/swagger*.json",
              "**/swagger*.yaml",
              "**/swagger*.yml",
              "**/*.proto",
              "**/*.avsc",
              "**/*.thrift",
              "**/schema.graphql",
              "**/*.graphql",
          ]
          skip_dirs = {"node_modules", ".git", ".venv", "vendor", "testdata", "__pycache__"}
          found: set[str] = set()
          for pattern in schema_patterns:
              for p in analysis_root.glob(pattern):
                  if not p.is_file():
                      continue
                  if any(part in skip_dirs for part in p.relative_to(analysis_root).parts):
                      continue
                  found.add(p.relative_to(analysis_root).as_posix())
          return sorted(found)
      
      
      def _collect_config_files(analysis_root: Path) -> list[str]:
          """
          Return sorted relative paths of config files commonly read at runtime.
          Matches common config naming patterns at any depth (capped at 300 files).
          """
          config_name_patterns = re.compile(
              r"^(config|configuration|settings|\.env|app\.config|appsettings"
              r"|pyproject|setup\.cfg|cargo\.toml|go\.mod|tsconfig|jest\.config"
              r"|webpack\.config|vite\.config|babel\.config|eslint.*|\.eslintrc.*"
              r"|prettier.*|\.prettierrc.*)(\.(json|yaml|yml|toml|ini|cfg|js|ts|cjs|mjs))?$",
              re.IGNORECASE,
          )
          skip_dirs = {"node_modules", ".git", ".venv", "vendor", "testdata", "__pycache__"}
          found: set[str] = set()
          count = 0
          for p in sorted(analysis_root.rglob("*")):
              if not p.is_file():
                  continue
              if any(part in skip_dirs for part in p.relative_to(analysis_root).parts):
                  continue
              if config_name_patterns.match(p.name):
                  found.add(p.relative_to(analysis_root).as_posix())
                  count += 1
                  if count >= 300:
                      break
          return sorted(found)
      
      
      def _write_repo_contract_json(
          output_dir: Path,
          analysis_root: Path,
          *,
          product_name: str,
          product_slug: str,
      ) -> Path:
          """
          Write a deterministic, machine-checkable contract JSON to
          output_dir/contracts/repo-contract.json.
      
          Contract includes:
          - upstream_commit (if analysis_root is a git repo)
          - cli surface: bin map, help text (static extraction), config file, env vars with file evidence
          - manifest inventory + template file hashes (from artifact-registry.json if present)
          - schema-like files
          - config files discovered in repo
      
          No absolute paths, no dates — stable across runs on the same commit.
          """
          contracts_dir = output_dir / "contracts"
          contracts_dir.mkdir(parents=True, exist_ok=True)
          out_path = contracts_dir / "repo-contract.json"
      
          contract: dict[str, object] = {
              "schema_version": 1,
              "product_name": product_name,
          }
      
          # upstream_commit
          upstream_commit = _get_upstream_commit(analysis_root)
          if upstream_commit:
              contract["upstream_commit"] = upstream_commit
      
          # --- CLI surface ---
          cli_surface: dict[str, object] = {}
      
          node_cli = _find_node_cli_package(analysis_root, product_slug, product_name)
          python_cli_info = _find_python_cli(analysis_root) if node_cli is None else None
          go_cli_info = (
              _find_go_cli(analysis_root)
              if node_cli is None and python_cli_info is None
              else None
          )
      
          if node_cli:
              pkg_dir = Path(str(node_cli["package_dir"]))
              # bin map with relative paths
              raw_bin = node_cli.get("bin") or {}
              bin_map: dict[str, str] = {}
              if isinstance(raw_bin, dict):
                  for k, v in raw_bin.items():
                      bin_map[k] = v
              cli_surface["language"] = "node"
              cli_surface["package_json"] = (
                  Path(str(node_cli["package_json"])).relative_to(analysis_root).as_posix()
              )
              cli_surface["package_dir"] = pkg_dir.relative_to(analysis_root).as_posix()
              cli_surface["package_name"] = str(node_cli.get("name") or "")
              cli_surface["bin"] = {k: bin_map[k] for k in sorted(bin_map)}
      
              # Help text (static extraction)
              src_index = pkg_dir / "src" / "index.ts"
              src_agents = pkg_dir / "src" / "agents" / "registry.ts"
              help_text = _extract_ts_backtick_const(src_index, "helpText")
              if help_text and src_agents.exists():
                  extracted = _extract_agents_from_registry_ts(src_agents)
                  if extracted:
                      agent_keys, alias_flags = extracted
                      if agent_keys:
                          help_text = help_text.replace(
                              "${agentKeys.join('|')}", "|".join(agent_keys)
                          )
                      alias_line = ""
                      if alias_flags:
                          alias_line = f"  {' | '.join(alias_flags)}  Agent alias flags\n"
                      help_text = help_text.replace("${agentAliasLine}", alias_line)
              if help_text is not None:
                  cli_surface["help_text"] = help_text
                  cli_surface["help_text_source"] = (
                      src_index.relative_to(analysis_root).as_posix()
                      if src_index.exists()
                      else None
                  )
      
              # Config file from store.ts
              src_store = pkg_dir / "src" / "cli" / "store.ts"
              config_file = _extract_ts_string_const(src_store, "CONFIG_FILE")
              if config_file:
                  cli_surface["config_file"] = config_file
                  cli_surface["config_file_source"] = (
                      src_store.relative_to(analysis_root).as_posix()
                      if src_store.exists()
                      else None
                  )
      
          elif python_cli_info:
              raw_bin_py = python_cli_info.get("bin") or {}
              cli_surface["language"] = "python"
              cli_surface["framework"] = python_cli_info.get("framework")
              cli_surface["entry_module"] = python_cli_info.get("entry_module")
              cli_surface["bin"] = (
                  {k: str(raw_bin_py[k]) for k in sorted(raw_bin_py)}
                  if isinstance(raw_bin_py, dict)
                  else {}
              )
      
          elif go_cli_info:
              raw_bin_go = go_cli_info.get("bin") or {}
              cli_surface["language"] = "go"
              cli_surface["framework"] = go_cli_info.get("framework")
              cli_surface["module"] = go_cli_info.get("module")
              cli_surface["bin"] = (
                  {k: str(raw_bin_go[k]) for k in sorted(raw_bin_go)}
                  if isinstance(raw_bin_go, dict)
                  else {}
              )
      
          contract["cli"] = cli_surface
      
          # --- Env vars with per-var file evidence ---
          contract["env_vars"] = _collect_env_vars_with_evidence(analysis_root)
      
          # --- Manifest inventory + template file hashes ---
          artifact_registry_path = output_dir / "artifact-registry.json"
          if artifact_registry_path.exists():
              try:
                  artifact_data = json.loads(_read_text(artifact_registry_path))
                  manifests_raw = artifact_data.get("manifests") or []
                  template_files_raw = artifact_data.get("resolved_template_files") or []
      
                  # Manifests: keep only path and agent (drop raw JSON for contract stability)
                  manifests_clean: list[dict[str, object]] = []
                  for m in manifests_raw:
                      entry: dict[str, object] = {"path": m.get("path")}
                      if m.get("agent"):
                          entry["agent"] = m["agent"]
                      manifests_clean.append(entry)
      
                  # Template files: keep path, sha256 (no absolute paths; already relative in artifact-registry)
                  template_hashes: list[dict[str, object]] = []
                  for tf in template_files_raw:
                      template_hashes.append(
                          {
                              "file": tf.get("file"),
                              "manifest": tf.get("manifest"),
                              "sha256": tf.get("sha256"),
                              "source_type": tf.get("source_type"),
                          }
                      )
      
                  contract["manifests"] = sorted(
                      manifests_clean, key=lambda x: str(x.get("path", ""))
                  )
                  contract["template_files"] = sorted(
                      template_hashes, key=lambda x: str(x.get("file", ""))
                  )
              except Exception:
                  pass
      
          # --- Schema-like files ---
          contract["schema_files"] = _collect_schema_files(analysis_root)
      
          # --- Config files ---
          contract["config_files"] = _collect_config_files(analysis_root)
      
          out_path.write_text(
              json.dumps(contract, indent=2, sort_keys=True) + "\n",
              encoding="utf-8",
          )
          return out_path
      
      
      def _write_comparison_report(
          output_dir: Path,
          tmp_dir: Path,
          *,
          product_name: str,
          date: str,
      ) -> bool:
          """Write comparison-report.md contrasting binary vs repo analysis results.
      
          Returns True if a report was written.
          """
          # --- Command discovery ---
          binary_cmds: list[str] = []
          commands_file = tmp_dir / "binary" / "cli-commands.txt"
          if commands_file.exists():
              binary_cmds = [
                  c.strip()
                  for c in commands_file.read_text(encoding="utf-8").splitlines()
                  if c.strip()
              ]
      
          repo_cmds: list[str] = []
          repo_cli_spec = output_dir / "spec-cli-surface.md"
          if repo_cli_spec.exists():
              # Extract command names from the table rows (| `cmd` | ... |)
              text = repo_cli_spec.read_text(encoding="utf-8")
              for m in re.finditer(r"^\|\s*`([^`]+)`\s*\|", text, re.MULTILINE):
                  cmd = m.group(1).strip()
                  if cmd and cmd not in ("Command",):
                      repo_cmds.append(cmd)
      
          binary_set = set(binary_cmds)
          repo_set = set(repo_cmds)
          only_binary = sorted(binary_set - repo_set)
          only_repo = sorted(repo_set - binary_set)
          delta = len(binary_cmds) - len(repo_cmds)
      
          # --- Registry groups ---
          binary_groups = 0
          _repo_groups = 0
          registry_yaml = output_dir / "feature-registry.yaml"
          if registry_yaml.exists():
              text = registry_yaml.read_text(encoding="utf-8")
              in_groups = False
              for raw_line in text.splitlines():
                  line = raw_line.rstrip()
                  if line == "groups:":
                      in_groups = True
                      continue
                  if not in_groups:
                      continue
                  # Group entries are 2-space indented, end with ':'
                  if (
                      line.startswith("  ")
                      and not line.startswith("    ")
                      and line.rstrip().endswith(":")
                  ):
                      # Determine source from notes field
                      binary_groups += 1
      
              # For the comparison we count total groups; binary-enriched have "binary-symbols.txt" anchor
              binary_enriched = 0
              repo_scaffold = 0
              for raw_line in text.splitlines():
                  stripped = raw_line.strip()
                  if stripped == "- binary-symbols.txt":
                      binary_enriched += 1
      
              # Groups without binary anchor are repo-scaffolded
              repo_scaffold = binary_groups - binary_enriched
      
          # --- Coverage percentage ---
          if repo_cmds:
              coverage_pct = round(len(binary_set & repo_set) / len(repo_set) * 100)
              coverage_line = (
                  f"Binary analysis found {coverage_pct}% of repo-discovered commands."
              )
          elif binary_cmds:
              coverage_line = f"Binary analysis found {len(binary_cmds)} commands; repo analysis found none (no CLI detected in repo)."
          else:
              coverage_line = "Neither source discovered CLI commands."
      
          # --- Write report ---
          lines: list[str] = []
          lines.append(f"# Comparison Report: {product_name}")
          lines.append("")
          lines.append(f"**Date:** {date}")
          lines.append("**Mode:** both (binary + repo)")
          lines.append("")
          lines.append("## Command Discovery")
          lines.append("")
          lines.append("| Source | Commands Found |")
          lines.append("|--------|---------------|")
          lines.append(f"| Binary --help | {len(binary_cmds)} |")
          lines.append(f"| Repo analysis | {len(repo_cmds)} |")
          delta_str = f"+{delta}" if delta > 0 else str(delta)
          lines.append(f"| Delta | {delta_str} |")
          lines.append("")
      
          lines.append("## Commands Only in Binary")
          lines.append("")
          if only_binary:
              for cmd in only_binary:
                  lines.append(f"- `{cmd}`")
          else:
              lines.append("_None._")
          lines.append("")
      
          lines.append("## Commands Only in Repo")
          lines.append("")
          if only_repo:
              for cmd in only_repo:
                  lines.append(f"- `{cmd}`")
          else:
              lines.append("_None._")
          lines.append("")
      
          lines.append("## Registry Groups")
          lines.append("")
          lines.append("| Source | Groups |")
          lines.append("|--------|--------|")
          lines.append(f"| Binary enriched | {binary_enriched} |")
          lines.append(f"| Repo scaffold | {repo_scaffold} |")
          lines.append("")
      
          lines.append("## Summary")
          lines.append("")
          lines.append(coverage_line)
      
          out = output_dir / "comparison-report.md"
          out.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
          return True
      
      
      def _write_wrapper_validate_feature_registry(output_dir: Path) -> None:
          skill_validate_path = (
              SKILL_DIR / "scripts" / "validate_feature_registry.py"
          ).resolve()
          wrapper = output_dir / "validate-feature-registry.py"
          wrapper.write_text(
              f"""#!/usr/bin/env python3
      from __future__ import annotations
      
      import os
      import subprocess
      import sys
      from pathlib import Path
      
      HERE = Path(__file__).resolve().parent
      SKILL_VALIDATE_CANDIDATES = [
          Path({str(skill_validate_path)!r}),
          Path(__file__).resolve().parents[3] / "skills" / "reverse-engineer" / "scripts" / "validate_feature_registry.py",
          Path(__file__).resolve().parents[2] / "skills" / "reverse-engineer" / "scripts" / "validate_feature_registry.py",
          Path.cwd() / "skills" / "reverse-engineer" / "scripts" / "validate_feature_registry.py",
      ]
      
      def _resolve_validator() -> Path:
          for cand in SKILL_VALIDATE_CANDIDATES:
              if cand.exists():
                  return cand
          raise FileNotFoundError("Could not locate validate_feature_registry.py")
      
      def main() -> int:
          # Delegate to the canonical validator, but default paths to this output dir.
          args = sys.argv[1:]
          if not args:
              root_path = HERE / "analysis-root-path.txt"
              local_root = (root_path.read_text(encoding="utf-8").strip() if root_path.exists() else str(HERE / "analysis-root"))
              args = [
                  "--feature-registry", str(HERE / "feature-registry.yaml"),
                  "--docs-features", str(HERE / "docs-features.txt"),
                  "--local-clone-dir", local_root,
              ]
          validator = _resolve_validator()
          p = subprocess.run([sys.executable, str(validator), *args])
          return p.returncode
      
      if __name__ == "__main__":
          raise SystemExit(main())
      """,
              encoding="utf-8",
          )
          wrapper.chmod(0o755)
      
      
      def _copy_security_validators(output_dir: Path) -> None:
          sec_dir = output_dir / "security"
          _ensure_dirs([sec_dir])
      
          # Copy validator + secret scan + sbom generator so the audit folder is self-validating.
          for rel in [
              "scripts/security/validate_security_audit.sh",
              "scripts/security/scan_secrets.sh",
              "scripts/security/generate_sbom.sh",
          ]:
              src = SKILL_DIR / rel
              dst = sec_dir / Path(rel).name.replace("_", "-")
              dst.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
              dst.chmod(0o755)
      
      
      def _git_text(repo: Path, *args: str) -> str:
          return subprocess.check_output(
              ["git", "-C", str(repo), *args], text=True, stderr=subprocess.STDOUT
          ).strip()
      
      
      def _is_git_checkout(path: Path) -> bool:
          try:
              return _git_text(path, "rev-parse", "--is-inside-work-tree") == "true"
          except (OSError, subprocess.CalledProcessError):
              return False
      
      
      def _write_source_metadata(
          output_dir: Path,
          *,
          upstream_repo: str | None,
          upstream_ref: str | None,
          resolved_commit: str,
          source_kind: str,
      ) -> None:
          payload = {
              "upstream_repo": upstream_repo,
              "upstream_ref": upstream_ref,
              "resolved_commit": resolved_commit,
              "source_kind": source_kind,
              "clone_date": _today_ymd(),
          }
          (output_dir / "clone-metadata.json").write_te
    • scaffold_feature_registry.py 2.2 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import datetime as _dt
      from pathlib import Path
      
      
      def _group_from_slug(slug: str, docs_features_prefix: str) -> str | None:
          prefix = docs_features_prefix.strip("/").rstrip("/") + "/"
          s = slug.strip().lstrip("/")
          if not s.startswith(prefix):
              return None
          rest = s[len(prefix) :]
          if not rest:
              return None
          group = rest.split("/", 1)[0].strip()
          return group or None
      
      
      def main() -> int:
          ap = argparse.ArgumentParser()
          ap.add_argument("--product-name", required=True)
          ap.add_argument("--docs-features-prefix", required=True)
          ap.add_argument("--docs-features", required=True)
          ap.add_argument("--out", required=True)
          args = ap.parse_args()
      
          docs_features_prefix = args.docs_features_prefix
          slugs = [ln.strip() for ln in Path(args.docs_features).read_text(encoding="utf-8", errors="replace").splitlines() if ln.strip()]
      
          groups: list[str] = []
          seen = set()
          for slug in slugs:
              g = _group_from_slug(slug, docs_features_prefix)
              if not g:
                  continue
              if g not in seen:
                  groups.append(g)
                  seen.add(g)
      
          out = Path(args.out)
          out.parent.mkdir(parents=True, exist_ok=True)
      
          # Minimal YAML that is still easy to mechanically validate.
          lines: list[str] = []
          lines.append("schema_version: 1")
          lines.append(f"product_name: {args.product_name!r}")
          lines.append(f"generated_at: {_dt.date.today().isoformat()!r}")
          lines.append(f"docs_features_prefix: {docs_features_prefix!r}")
          lines.append("docs_features:")
          for s in slugs:
              lines.append(f"  - {s!r}")
          lines.append("groups:")
          if not groups and slugs:
              # Slugs existed but no groups parsed; keep explicit empty mapping to fail validation loudly later.
              lines.append("  {}")
          elif not groups:
              lines.append("  {}")
          else:
              for g in groups:
                  lines.append(f"  {g!s}:")
                  lines.append("    impl: control-plane")
                  lines.append("    anchors: []")
                  lines.append("    notes: \"\"")
      
          out.write_text("\n".join(lines) + "\n", encoding="utf-8")
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
      
    • self_test.sh 14.5 KB
      #!/usr/bin/env bash
      set -euo pipefail
      
      ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
      SKILL="$ROOT/skills/reverse-engineer"
      
      if ! command -v go >/dev/null 2>&1; then
        echo "error: go is required for the demo fixture build" >&2
        exit 2
      fi
      
      TMP="$ROOT/.tmp/reverse-engineer-self-test"
      OUT1="$TMP/out-core"
      OUT2="$TMP/out-sec"
      SRC="$TMP/fixture-src"
      BIN="$TMP/demo_bin"
      SITEMAP="$TMP/sitemap.xml"
      
      rm -rf "$TMP"
      mkdir -p "$SRC" "$OUT1" "$OUT2"
      
      HELP="$(python3 "$SKILL/scripts/reverse_engineer.py" --help)"
      grep -Fq '.agents/scratch/reverse-engineer/<product>/' <<<"$HELP"
      grep -Fq '.agents/research/<product>/ path remains' <<<"$HELP"
      grep -Fq 'are never moved automatically.' <<<"$HELP"
      grep -Fq -- "- '.agents/scratch/reverse-engineer/*/'" "$SKILL/SKILL.md"
      echo "OK: output-path migration contract is visible in --help"
      
      python3 - "$SRC" <<'PY'
      import sys, zipfile
      from pathlib import Path
      
      src = Path(sys.argv[1])
      (src / "payload.zip").parent.mkdir(parents=True, exist_ok=True)
      with zipfile.ZipFile(src / "payload.zip", "w", compression=zipfile.ZIP_DEFLATED) as zf:
          zf.writestr("agent/main.py", "print('hello from demo agent')\n")
          zf.writestr("agent/README.md", "# Demo Agent\n")
          zf.writestr("agent/SYSTEM_PROMPT.txt", "DEMO PROMPT (do not dump in reports)\n")
      PY
      
      cat >"$SRC/main.go" <<'EOF'
      package main
      
      import _ "embed"
      import "fmt"
      
      //go:embed payload.zip
      var payload []byte
      
      func main() {
      	// Ensure the bytes are referenced so the ZIP signature is present in the binary.
      	fmt.Printf("demo binary; embedded payload bytes=%d\n", len(payload))
      }
      EOF
      
      (cat >"$SRC/go.mod" <<'EOF'
      module demo_embedded_zip
      
      go 1.22
      EOF
      )
      
      (cd "$SRC" && go build -o "$BIN" .)
      
      cat >"$SITEMAP" <<'EOF'
      <?xml version="1.0" encoding="UTF-8"?>
      <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
        <url><loc>https://example.test/docs/features/alpha/overview</loc></url>
        <url><loc>https://example.test/docs/features/alpha/howto</loc></url>
        <url><loc>https://example.test/docs/features/beta/overview</loc></url>
      </urlset>
      EOF
      
      python3 "$SKILL/scripts/reverse_engineer.py" demo \
        --authorized \
        --mode=binary \
        --binary-path="$BIN" \
        --docs-sitemap-url="file://$SITEMAP" \
        --materialize-archives \
        --local-clone-dir="$TMP/local-demo" \
        --output-dir="$OUT1"
      
      python3 "$OUT1/validate-feature-registry.py"
      
      VALIDATE_OUTPUT="$SKILL/scripts/validate-output.sh"
      "$VALIDATE_OUTPUT" --output-dir "$OUT1" --phase teardown \
        --security-audit 0 --sbom 0 --upstream-ref-set 0
      if "$VALIDATE_OUTPUT" --output-dir "$OUT1" --phase complete \
        --security-audit 0 --sbom 0 --upstream-ref-set 0 >/dev/null 2>&1; then
        echo "FAIL: complete validator accepted a missing steal-map.md" >&2
        exit 1
      fi
      cat >"$OUT1/steal-map.md" <<'EOF'
      # Steal map: demo
      
      | Their capability | Our surface today | Verdict |
      |---|---|---|
      | Embedded archive inventory (`feature-registry.yaml`) | `skills/reverse-engineer/` | **have** |
      EOF
      "$VALIDATE_OUTPUT" --output-dir "$OUT1" --phase complete \
        --security-audit 0 --sbom 0 --upstream-ref-set 0
      cp "$OUT1/steal-map.md" "$OUT1/steal-map.valid"
      printf '# malformed map\n' >"$OUT1/steal-map.md"
      if "$VALIDATE_OUTPUT" --output-dir "$OUT1" --phase complete \
        --security-audit 0 --sbom 0 --upstream-ref-set 0 >/dev/null 2>&1; then
        echo "FAIL: complete validator accepted a malformed steal-map.md" >&2
        exit 1
      fi
      mv "$OUT1/steal-map.valid" "$OUT1/steal-map.md"
      echo "OK: exact output validator distinguishes teardown from complete decision output"
      
      # --- Binary mode capability assertions ---
      
      echo "--- binary mode capability checks ---"
      
      # 1. Help capture output exists (may be empty if binary doesn't support --help)
      if [ ! -f "$OUT1/cli-commands.txt" ]; then
        echo "FAIL: cli-commands.txt not created by binary mode" >&2
        exit 1
      fi
      echo "OK: cli-commands.txt exists"
      
      # 2. CLI surface spec exists (generated from --help tree or binary strings fallback)
      if [ ! -f "$OUT1/spec-cli-surface.md" ]; then
        echo "FAIL: spec-cli-surface.md not created by binary mode" >&2
        exit 1
      fi
      echo "OK: spec-cli-surface.md exists"
      
      # 3. binary-symbols.txt exists
      if [ ! -f "$OUT1/binary-symbols.txt" ]; then
        echo "FAIL: binary-symbols.txt not created by binary mode" >&2
        exit 1
      fi
      echo "OK: binary-symbols.txt exists"
      
      # 4. Registry enrichment: if cli-commands.txt has content, groups should have impl: client
      if [ -s "$OUT1/cli-commands.txt" ]; then
        if ! grep -q 'impl: client' "$OUT1/feature-registry.yaml"; then
          echo "FAIL: feature-registry.yaml should contain 'impl: client' when CLI commands are found" >&2
          exit 1
        fi
        echo "OK: feature-registry.yaml enriched with impl: client"
      else
        echo "OK: cli-commands.txt empty (demo binary has no subcommands); skipping impl: client check"
      fi
      
      python3 "$SKILL/scripts/reverse_engineer.py" demo \
        --authorized \
        --mode=binary \
        --binary-path="$BIN" \
        --docs-sitemap-url="file://$SITEMAP" \
        --output-dir="$OUT2" \
        --materialize-archives \
        --local-clone-dir="$TMP/local-demo" \
        --security-audit \
        --sbom
      
      # The freshly generated audit is a scaffold whose files carry _TBD
      # placeholders; the gate must now REFUSE to certify it (fail-closed).
      if "$OUT2/security/validate-security-audit.sh" "$OUT2" --sbom >/dev/null 2>&1; then
        echo "FAIL: security gate certified an unfilled _TBD scaffold (should fail-closed)" >&2
        exit 1
      fi
      echo "OK: security gate rejects the unfilled _TBD scaffold"
      
      # Fill EVERY required narrative file (no placeholders). The other files just
      # need real content; findings.md additionally needs the Evidence/Fix shape.
      for name in threat-model attack-surface dataflow crypto-review authn-authz reproducibility; do
        printf '# %s\n\nReviewed for the demo binary; no items of concern.\n' "$name" > "$OUT2/security/$name.md"
      done
      cat >"$OUT2/security/findings.md" <<'EOF'
      # Findings: demo
      
      - Date: self-test
      
      ## Finding F-001: Embedded demo prompt present in binary
      
      Severity: Low
      Impact: Informational; the embedded demo prompt is not a secret.
      Likelihood: Low
      
      Evidence: payload.zip/agent/SYSTEM_PROMPT.txt embedded via go:embed (see binary-embedded-archives.md).
      Fix: None required for the demo; production binaries should not embed plaintext prompts.
      Validation: Re-ran the secret scan over outputs; no credentials present.
      EOF
      
      "$OUT2/security/validate-security-audit.sh" "$OUT2" --sbom
      echo "OK: security gate certifies a completed audit"
      
      # Prove the _TBD gate scans BEYOND findings.md: seed a placeholder into a
      # different required file and the gate must fail-closed again.
      printf '# threat-model\n\n- _TBD_\n' > "$OUT2/security/threat-model.md"
      if "$OUT2/security/validate-security-audit.sh" "$OUT2" --sbom >/dev/null 2>&1; then
        echo "FAIL: security gate certified an audit with _TBD in threat-model.md (should fail-closed)" >&2
        exit 1
      fi
      echo "OK: security gate rejects _TBD in a non-findings required file"
      
      # --- Negative tests ---
      
      # Test: invalid --mode should fail
      echo "--- negative test: invalid --mode ---"
      if python3 "$SKILL/scripts/reverse_engineer.py" demo --mode=invalid --output-dir="$TMP/out-neg" 2>/dev/null; then
        echo "FAIL: expected non-zero exit for --mode=invalid" >&2
        exit 1
      fi
      echo "OK: invalid --mode correctly rejected"
      
      # --- Upstream ref pinning test ---
      
      echo "--- upstream-ref pinning test ---"
      OUT_REF="$TMP/out-ref"
      mkdir -p "$OUT_REF"
      # Use file:// protocol on the current repo to avoid network dependency.
      REPO_URL="file://$ROOT"
      python3 "$SKILL/scripts/reverse_engineer.py" self-ref-test \
        --mode=repo \
        --upstream-repo="$REPO_URL" \
        --upstream-ref=HEAD \
        --local-clone-dir="$TMP/local-ref" \
        --output-dir="$OUT_REF"
      
      if [ ! -f "$OUT_REF/clone-metadata.json" ]; then
        echo "FAIL: clone-metadata.json not created with --upstream-ref" >&2
        exit 1
      fi
      echo "OK: clone-metadata.json created with --upstream-ref"
      
      echo "--- existing-checkout ref mismatch test ---"
      WRONG_REPO="$TMP/local-wrong-ref"
      WRONG_OUT="$TMP/out-wrong-ref"
      mkdir -p "$WRONG_REPO"
      git -C "$WRONG_REPO" init -q
      git -C "$WRONG_REPO" config user.name reverse-self-test
      git -C "$WRONG_REPO" config user.email reverse-self-test@example.invalid
      printf 'one\n' >"$WRONG_REPO/unique.txt"
      git -C "$WRONG_REPO" add unique.txt
      git -C "$WRONG_REPO" commit -qm one
      first_commit="$(git -C "$WRONG_REPO" rev-parse HEAD)"
      printf 'two\n' >"$WRONG_REPO/unique.txt"
      git -C "$WRONG_REPO" commit -qam two
      second_commit="$(git -C "$WRONG_REPO" rev-parse HEAD)"
      git -C "$WRONG_REPO" checkout -q --detach "$first_commit"
      if python3 "$SKILL/scripts/reverse_engineer.py" wrong-ref \
        --mode=repo --local-clone-dir="$WRONG_REPO" \
        --upstream-ref="$second_commit" --output-dir="$WRONG_OUT" >/dev/null 2>&1; then
        echo "FAIL: existing checkout at the wrong commit was analyzed" >&2
        exit 1
      fi
      if [ -e "$WRONG_OUT/feature-registry.yaml" ]; then
        echo "FAIL: ref mismatch wrote trusted teardown artifacts" >&2
        exit 1
      fi
      echo "OK: existing checkout must match the requested ref"
      
      echo "--- explicit non-Git root test ---"
      EXPLICIT_TREE="$TMP/explicit-nongit"
      EXPLICIT_OUT="$TMP/out-explicit-nongit"
      mkdir -p "$EXPLICIT_TREE"
      printf 'only-in-explicit-tree\n' >"$EXPLICIT_TREE/unique-source.txt"
      python3 "$SKILL/scripts/reverse_engineer.py" explicit-nongit \
        --mode=repo --local-clone-dir="$EXPLICIT_TREE" --output-dir="$EXPLICIT_OUT"
      if ! grep -Fqx "$EXPLICIT_TREE" "$EXPLICIT_OUT/analysis-root-path.txt"; then
        echo "FAIL: explicit non-Git tree was replaced by the caller checkout" >&2
        exit 1
      fi
      echo "OK: explicit non-Git analysis root wins"
      
      echo "--- output symlink refusal tests ---"
      SYMLINK_CASE="$TMP/symlink-case"
      SYMLINK_OUTSIDE="$TMP/symlink-outside"
      mkdir -p "$SYMLINK_CASE/.agents" "$SYMLINK_OUTSIDE" "$SYMLINK_CASE/local"
      printf 'outside sentinel\n' >"$SYMLINK_OUTSIDE/sentinel"
      ln -s "$SYMLINK_OUTSIDE" "$SYMLINK_CASE/.agents/scratch"
      if (
        cd "$SYMLINK_CASE"
        python3 "$SKILL/scripts/reverse_engineer.py" escaped \
          --mode=repo --local-clone-dir="$SYMLINK_CASE/local" >/dev/null 2>&1
      ); then
        echo "FAIL: default output followed a symlinked scratch parent" >&2
        exit 1
      fi
      if ! grep -Fqx 'outside sentinel' "$SYMLINK_OUTSIDE/sentinel" \
        || [ -e "$SYMLINK_OUTSIDE/reverse-engineer" ]; then
        echo "FAIL: symlinked parent allowed an outside write" >&2
        exit 1
      fi
      
      MANAGED_OUT="$TMP/out-managed-link"
      MANAGED_OUTSIDE="$TMP/managed-outside.yaml"
      mkdir -p "$MANAGED_OUT"
      printf 'outside registry\n' >"$MANAGED_OUTSIDE"
      ln -s "$MANAGED_OUTSIDE" "$MANAGED_OUT/feature-registry.yaml"
      if python3 "$SKILL/scripts/reverse_engineer.py" managed-link \
        --mode=repo --local-clone-dir="$EXPLICIT_TREE" \
        --output-dir="$MANAGED_OUT" >/dev/null 2>&1; then
        echo "FAIL: managed artifact symlink was followed" >&2
        exit 1
      fi
      if ! grep -Fqx 'outside registry' "$MANAGED_OUTSIDE"; then
        echo "FAIL: managed artifact symlink changed the outside target" >&2
        exit 1
      fi
      echo "OK: output parent and managed-file symlinks fail closed"
      
      # --- Multi-language CLI graceful degradation test ---
      
      echo "--- multi-language CLI degradation test ---"
      OUT_NONCLI="$TMP/out-noncli"
      mkdir -p "$OUT_NONCLI" "$TMP/local-noncli"
      # Create a minimal repo with no CLI markers.
      mkdir -p "$TMP/local-noncli/.git"
      touch "$TMP/local-noncli/README.md"
      python3 "$SKILL/scripts/reverse_engineer.py" no-cli-demo \
        --mode=repo \
        --local-clone-dir="$TMP/local-noncli" \
        --output-dir="$OUT_NONCLI" \
        --docs-sitemap-url="file://$SITEMAP"
      
      # spec-cli-surface.md should NOT exist (no CLI detected), and the note should be in spec-code-map.md
      if [ -f "$OUT_NONCLI/spec-cli-surface.md" ]; then
        echo "FAIL: spec-cli-surface.md should not exist for non-CLI repo" >&2
        exit 1
      fi
      if ! grep -q "no CLI surface detected" "$OUT_NONCLI/spec-code-map.md" 2>/dev/null; then
        echo "FAIL: spec-code-map.md should note that no CLI surface was detected" >&2
        exit 1
      fi
      echo "OK: multi-language CLI graceful degradation works"
      
      echo "--- default output-path parity test ---"
      DEFAULT_OUT="$TMP/.agents/scratch/reverse-engineer/default-demo"
      (
        cd "$TMP"
        python3 "$SKILL/scripts/reverse_engineer.py" default-demo \
          --mode=repo \
          --local-clone-dir="$TMP/local-noncli" \
          --docs-sitemap-url="file://$SITEMAP"
      )
      if [ ! -s "$DEFAULT_OUT/feature-registry.yaml" ] \
        || [ ! -s "$DEFAULT_OUT/contracts/repo-contract.json" ] \
        || [ ! -s "$DEFAULT_OUT/reports/$(date +%F)-vibe-default-demo.md" ] \
        || [ ! -s "$DEFAULT_OUT/docs-features.txt" ] \
        || [ ! -s "$DEFAULT_OUT/validate-feature-registry.py" ]; then
        echo "FAIL: executable default did not emit the declared product output directory" >&2
        exit 1
      fi
      echo "OK: frontmatter output directory matches the executable default"
      
      echo "--- earlier output-path compatibility test ---"
      LEGACY_OUT="$TMP/.agents/research/legacy-demo"
      LEGACY_EXPECTED="$TMP/legacy-sentinel.expected"
      LEGACY_DEFAULT="$TMP/.agents/scratch/reverse-engineer/legacy-demo"
      mkdir -p "$LEGACY_OUT"
      printf 'caller-owned sentinel\n\n' > "$LEGACY_OUT/caller-sentinel.txt"
      cp "$LEGACY_OUT/caller-sentinel.txt" "$LEGACY_EXPECTED"
      (
        cd "$TMP"
        python3 "$SKILL/scripts/reverse_engineer.py" legacy-demo \
          --mode=repo \
          --local-clone-dir="$TMP/local-noncli" \
          --output-dir="$LEGACY_OUT" \
          --docs-sitemap-url="file://$SITEMAP"
      )
      if [ ! -s "$LEGACY_OUT/feature-registry.yaml" ]; then
        echo "FAIL: explicit earlier-default output directory was not honored" >&2
        exit 1
      fi
      if ! cmp -s "$LEGACY_EXPECTED" "$LEGACY_OUT/caller-sentinel.txt"; then
        echo "FAIL: explicit earlier-default invocation changed a pre-existing artifact" >&2
        exit 1
      fi
      if [ -e "$LEGACY_DEFAULT" ]; then
        echo "FAIL: explicit earlier-default invocation also wrote to the scratch default" >&2
        exit 1
      fi
      echo "OK: explicit earlier-default output directory remains supported"
      
      echo "--- generated-tree hygiene regression test ---"
      HYGIENE_REPO="$TMP/local-hygiene"
      HYGIENE_OUT="$TMP/out-hygiene"
      mkdir -p "$HYGIENE_REPO/.tmp/compound-engineer" "$HYGIENE_OUT"
      (cd "$HYGIENE_REPO" && git init >/dev/null 2>&1)
      cat >"$HYGIENE_REPO/package.json" <<'EOF'
      {
        "name": "agentops",
        "version": "0.0.1",
        "bin": {
          "agentops": "bin/agentops.js"
        }
      }
      EOF
      cat >"$HYGIENE_REPO/.tmp/compound-engineer/package.json" <<'EOF'
      {
        "name": "@every-env/compound-plugin",
        "version": "9.9.9",
        "bin": {
          "compound-plugin": "bin/index.js"
        }
      }
      EOF
      python3 "$SKILL/scripts/reverse_engineer.py" agentops \
        --mode=repo \
        --local-clone-dir="$HYGIENE_REPO" \
        --output-dir="$HYGIENE_OUT"
      if grep -q "\.tmp/compound-engineer" "$HYGIENE_OUT/spec-cli-surface.md"; then
        echo "FAIL: generated-tree package leaked into CLI surface spec" >&2
        exit 1
      fi
      if ! grep -q "package name: \`agentops\`" "$HYGIENE_OUT/spec-cli-surface.md"; then
        echo "FAIL: root package did not win CLI surface detection" >&2
        exit 1
      fi
      echo "OK: generated-tree hygiene regression holds"
      
      echo "OK: self-test passed (all positive + negative tests)"
      
    • validate-output.sh 4.2 KB
      #!/usr/bin/env bash
      set -euo pipefail
      
      usage() {
        cat >&2 <<'EOF'
      usage: validate-output.sh --output-dir DIR [--phase teardown|complete]
                                [--security-audit 0|1] [--sbom 0|1]
                                [--upstream-ref-set 0|1]
      EOF
        exit 2
      }
      
      output_dir=""
      phase="complete"
      security_audit=0
      sbom=0
      upstream_ref_set=0
      while (($#)); do
        case "$1" in
          --output-dir) (($# >= 2)) || usage; output_dir=$2; shift 2 ;;
          --phase) (($# >= 2)) || usage; phase=$2; shift 2 ;;
          --security-audit) (($# >= 2)) || usage; security_audit=$2; shift 2 ;;
          --sbom) (($# >= 2)) || usage; sbom=$2; shift 2 ;;
          --upstream-ref-set) (($# >= 2)) || usage; upstream_ref_set=$2; shift 2 ;;
          -h|--help) usage ;;
          *) usage ;;
        esac
      done
      
      [[ -n "$output_dir" ]] || usage
      [[ "$phase" == teardown || "$phase" == complete ]] || usage
      [[ "$security_audit" =~ ^[01]$ ]] || usage
      [[ "$sbom" =~ ^[01]$ ]] || usage
      [[ "$upstream_ref_set" =~ ^[01]$ ]] || usage
      [[ -d "$output_dir" && ! -L "$output_dir" ]] || {
        echo "error: output directory must be a real directory: $output_dir" >&2
        exit 1
      }
      
      required=(
        feature-inventory.md
        feature-registry.yaml
        feature-catalog.md
        spec-architecture.md
        spec-code-map.md
        spec-clone-vs-use.md
        spec-clone-mvp.md
        analysis-root-path.txt
        validate-feature-registry.py
      )
      for name in "${required[@]}"; do
        path="$output_dir/$name"
        [[ -f "$path" && ! -L "$path" && -s "$path" ]] || {
          echo "error: required regular nonempty artifact missing: $path" >&2
          exit 1
        }
      done
      
      [[ -f "$output_dir/docs-features.txt" && ! -L "$output_dir/docs-features.txt" ]] || {
        echo "error: docs-features.txt must be a regular file" >&2
        exit 1
      }
      if [[ -e "$output_dir/spec-cli-surface.md" || -L "$output_dir/spec-cli-surface.md" ]]; then
        [[ -f "$output_dir/spec-cli-surface.md" && ! -L "$output_dir/spec-cli-surface.md" && -s "$output_dir/spec-cli-surface.md" ]] || {
          echo "error: spec-cli-surface.md must be a regular nonempty file when present" >&2
          exit 1
        }
      fi
      
      python3 "$output_dir/validate-feature-registry.py"
      
      if [[ "$upstream_ref_set" == 1 ]]; then
        metadata="$output_dir/clone-metadata.json"
        [[ -f "$metadata" && ! -L "$metadata" && -s "$metadata" ]] || {
          echo "error: --upstream-ref requires clone-metadata.json" >&2
          exit 1
        }
        python3 - "$metadata" <<'PY'
      import json, pathlib, re, sys
      path = pathlib.Path(sys.argv[1])
      data = json.loads(path.read_text(encoding="utf-8"))
      if not isinstance(data, dict):
          raise SystemExit("clone metadata must be an object")
      commit = data.get("resolved_commit")
      if not isinstance(commit, str) or not re.fullmatch(r"[0-9a-fA-F]{40,64}", commit):
          raise SystemExit("clone metadata lacks a full resolved commit OID")
      if not data.get("upstream_ref"):
          raise SystemExit("clone metadata lacks upstream_ref")
      PY
      fi
      
      if [[ "$phase" == complete ]]; then
        steal_map="$output_dir/steal-map.md"
        [[ -f "$steal_map" && ! -L "$steal_map" && -s "$steal_map" ]] || {
          echo "error: complete output requires a regular nonempty steal-map.md" >&2
          exit 1
        }
        grep -Fqx '| Their capability | Our surface today | Verdict |' "$steal_map" || {
          echo "error: steal-map.md lacks the required table header" >&2
          exit 1
        }
        awk -F'|' '
          BEGIN { found = 0 }
          /^\|/ {
            capability=$2; ours=$3; verdict=$4
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", capability)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", ours)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", verdict)
            gsub(/\*\*/, "", verdict)
            if (capability != "" && capability != "Their capability" && capability !~ /^-+$/ &&
                ours != "" && verdict ~ /^(have|gap|steal|park|reject)$/) found = 1
          }
          END { exit found ? 0 : 1 }
        ' "$steal_map" || {
          echo "error: steal-map.md needs at least one nonempty row with a valid verdict" >&2
          exit 1
        }
      fi
      
      if [[ "$security_audit" == 1 ]]; then
        gate="$output_dir/security/validate-security-audit.sh"
        [[ -x "$gate" && ! -L "$gate" ]] || {
          echo "error: security validator is missing or unsafe" >&2
          exit 1
        }
        if [[ "$sbom" == 1 ]]; then
          "$gate" "$output_dir" --sbom
        else
          "$gate" "$output_dir" --no-sbom
        fi
      else
        [[ "$sbom" == 0 ]] || {
          echo "error: --sbom requires --security-audit 1" >&2
          exit 1
        }
      fi
      
      echo "PASS: reverse-engineer $phase output is structurally valid"
      
    • validate.sh 2 KB
      #!/usr/bin/env bash
      set -euo pipefail
      
      SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
      
      # Syntax-check the shipped Python without writing __pycache__/*.pyc into the
      # package (py_compile writes the default cfile even with cfile=None); ast.parse
      # validates syntax and writes nothing.
      python3 - \
        "$SKILL_DIR/scripts/reverse_engineer.py" \
        "$SKILL_DIR/scripts/fetch_url.py" \
        "$SKILL_DIR/scripts/generate_feature_inventory_md.py" \
        "$SKILL_DIR/scripts/scaffold_feature_registry.py" \
        "$SKILL_DIR/scripts/generate_feature_catalog_md.py" \
        "$SKILL_DIR/scripts/validate_feature_registry.py" \
        "$SKILL_DIR/scripts/binary/list_embedded_archives.py" \
        "$SKILL_DIR/scripts/binary/extract_embedded_archives.py" <<'PY'
      import ast
      import sys
      for path in sys.argv[1:]:
          with open(path, encoding="utf-8") as fh:
              ast.parse(fh.read(), filename=path)
      PY
      
      # Hermetic behavioral witness: the security-audit gate must fail-closed on an
      # unfilled findings scaffold. The shipped template supplies Evidence/Fix as
      # literal _TBD markers; a presence-only grep used to certify them green. Build a
      # minimal security dir whose findings.md still carries _TBD and assert the gate
      # refuses it. (No go, network, or scanners needed — the _TBD check trips before
      # the secret scan.)
      tmp="$(mktemp -d "${TMPDIR:-/tmp}/re-validate.XXXXXX")"
      trap 'rm -rf "$tmp"' EXIT
      sec="$tmp/security"
      mkdir -p "$sec"
      for name in threat-model attack-surface dataflow crypto-review authn-authz reproducibility; do
        printf '# %s\n' "$name" > "$sec/$name.md"
      done
      cp "$SKILL_DIR/scripts/security/validate_security_audit.sh" "$sec/validate-security-audit.sh"
      chmod +x "$sec/validate-security-audit.sh"
      printf '## Finding F-1: scaffold\nEvidence: _TBD_\nFix: _TBD_\n' > "$sec/findings.md"
      
      if bash "$sec/validate-security-audit.sh" "$tmp" --no-sbom >/dev/null 2>&1; then
        echo "FAIL: security-audit gate certified an unfilled _TBD scaffold (should fail-closed)" >&2
        exit 1
      fi
      
      echo "OK: reverse-engineer validate.sh passed (syntax + security gate rejects _TBD scaffold)"
      
    • validate_feature_registry.py 5.8 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import os
      import re
      import sys
      from pathlib import Path
      
      
      ALLOWED_IMPL = {"client", "mixed", "control-plane"}
      
      
      def _group_from_slug(slug: str, docs_features_prefix: str) -> str | None:
          prefix = docs_features_prefix.strip("/").rstrip("/") + "/"
          s = slug.strip().lstrip("/")
          if not s.startswith(prefix):
              return None
          rest = s[len(prefix) :]
          if not rest:
              return None
          return rest.split("/", 1)[0] or None
      
      
      def _parse_registry(path: Path) -> dict:
          data = {"docs_features_prefix": "docs/features/", "groups": {}}
          cur = None
          in_groups = False
          in_anchors = False
          for raw in path.read_text(encoding="utf-8", errors="replace").splitlines():
              line = raw.rstrip("\n")
              if not line.strip() or line.lstrip().startswith("#"):
                  continue
              if line.startswith("docs_features_prefix:"):
                  data["docs_features_prefix"] = line.split(":", 1)[1].strip().strip("'\"")
              if line == "groups:":
                  in_groups = True
                  continue
              if not in_groups:
                  continue
      
              if line.startswith("  ") and not line.startswith("    ") and line.endswith(":"):
                  name = line.strip()[:-1]
                  cur = {"impl": None, "anchors": [], "notes": ""}
                  data["groups"][name] = cur
                  in_anchors = False
                  continue
      
              if cur is None:
                  continue
      
              s = line.strip()
              if s.startswith("impl:"):
                  cur["impl"] = s.split(":", 1)[1].strip()
              elif s.startswith("anchors:"):
                  in_anchors = True
                  if s.endswith("[]"):
                      cur["anchors"] = []
              elif in_anchors and s.startswith("- "):
                  cur["anchors"].append(s[2:].strip().strip("'\""))
              elif s.startswith("notes:"):
                  cur["notes"] = s.split(":", 1)[1].strip().strip("'\"")
          return data
      
      
      def main() -> int:
          ap = argparse.ArgumentParser()
          ap.add_argument("--feature-registry", required=True)
          ap.add_argument("--docs-features", required=True)
          ap.add_argument("--local-clone-dir", required=True)
          args = ap.parse_args()
      
          feature_registry_path = Path(args.feature_registry).resolve()
          artifact_dir = feature_registry_path.parent
          reg = _parse_registry(feature_registry_path)
          prefix = reg["docs_features_prefix"]
          groups = reg["groups"]
          docs_slugs = [ln.strip() for ln in Path(args.docs_features).read_text(encoding="utf-8", errors="replace").splitlines() if ln.strip()]
          root = Path(args.local_clone_dir).resolve()
      
          errs: list[str] = []
      
          # Rule: every docs/features slug maps to a group.
          for slug in docs_slugs:
              g = _group_from_slug(slug, prefix)
              if not g:
                  errs.append(f"docs slug not under prefix {prefix!r}: {slug!r}")
                  continue
              if g not in groups:
                  errs.append(f"docs slug group missing from registry: group={g!r} slug={slug!r}")
      
          # Rule: every group has impl; client/mixed must have anchors.
          for g, ent in groups.items():
              impl = (ent.get("impl") or "").strip()
              if impl not in ALLOWED_IMPL:
                  errs.append(f"group {g!r} has invalid impl {impl!r} (allowed: {sorted(ALLOWED_IMPL)})")
              anchors = ent.get("anchors") or []
              if impl in ("client", "mixed") and len(anchors) < 1:
                  errs.append(f"group {g!r} impl={impl!r} requires >=1 anchor")
      
              for a in anchors:
                  # Allow line/col suffix like "path/to/file.py:123"
                  p = a.split(":", 1)[0]
                  if p.startswith("/"):
                      abs_path = Path(p).resolve()
                      if not abs_path.exists():
                          errs.append(f"group {g!r} anchor missing: {a!r} (checked {abs_path})")
                      continue
      
                  # Relative anchors may reference either the analysis root or the artifact bundle dir.
                  candidates = [
                      (artifact_dir, (artifact_dir / p).resolve(), "artifact_dir"),
                      (root, (root / p).resolve(), "analysis_root"),
                  ]
                  path_ok = False
                  missing_paths: list[str] = []
                  for base, resolved, _label in candidates:
                      base_resolved = base.resolve()
                      if not (resolved == base_resolved or str(resolved).startswith(str(base_resolved) + os.sep)):
                          continue
                      if resolved.exists():
                          path_ok = True
                          break
                      missing_paths.append(str(resolved))
      
                  if not path_ok:
                      checked = ", ".join(missing_paths) if missing_paths else "(no safe candidate paths)"
                      errs.append(f"group {g!r} anchor missing: {a!r} (checked {checked})")
      
          # Completeness guard: if docs/ exists with markdown content, empty docs feature inventory is likely bad prefix selection.
          docs_dir = root / "docs"
          if docs_dir.exists():
              has_docs_markdown = any(docs_dir.rglob("*.md")) or any(docs_dir.rglob("*.mdx"))
              if has_docs_markdown and len(docs_slugs) == 0:
                  errs.append(
                      "docs-features inventory is empty while docs/ contains markdown; "
                      "likely wrong docs_features_prefix or extraction failure"
                  )
      
          # Completeness guard: reject unresolved placeholder code-map specs.
          spec_code_map = artifact_dir / "spec-code-map.md"
          if spec_code_map.exists():
              text = spec_code_map.read_text(encoding="utf-8", errors="replace")
              if "_TBD_" in text or re.search(r"\|\s*_TBD_\s*\|", text):
                  errs.append(f"spec-code-map contains unresolved placeholders: {spec_code_map}")
      
          if errs:
              for e in errs:
                  print(f"FAIL: {e}", file=sys.stderr)
              return 1
          print("OK: feature registry validated")
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
  • .gitignore 19 B · in bundle
  • SKILL.md 13 KB
    ---
    name: reverse-engineer
    description: 'Tear down an authorized competitor repo, binary or product into a feature inventory and adoption choices. Use when: comparing an external system; local questions go to Research.'
    practices:
    - legacy-code-seams
    - ddd-bounded-context
    - adr
    hexagonal_role: supporting
    consumes: []
    produces:
    - '.agents/scratch/reverse-engineer/*/'
    context_rel: []
    skill_api_version: 1
    user-invocable: true
    context:
      window: fork
      intent:
        mode: task
      sections:
        exclude:
        - HISTORY
    metadata:
      dependencies: []
      capabilities: [reverse_engineer]
      effects: [clone_upstream_repo, authorized_binary_execution, write_teardown_artifacts]
      canonical_status: canonical
      disposition: keep_specialist
      tier: execution
      internal: false
    output_contract: validated phase-1 teardown directory, followed by a caller-authored and validated phase-2 steal-map.md
    ---
    # Reverse Engineer
    
    Reverse-engineer an external system into two things: a **mechanically-verifiable teardown** (feature inventory + registry + specs, optionally a security audit) and a **steal-map** — what to adopt into our surfaces, what to leave behind. The teardown is the evidence; the steal-map is the decision. Separating them works because a decision row that must cite a registry entry can be re-checked by anyone, while a decision made from impressions cannot be re-checked by its own author. The original failure mode this skill exists to prevent: reading a competitor's README and "deciding" from vibes.
    
    **Triggers:** "reverse-engineer X", "tear down Y", "what should we steal from Z", "evaluate competitor/upstream", "should we fork/adopt/build-native".
    
    ## Prompt
    
    ```text
    Reverse-engineer the beads CLI (github.com/steveyegge/beads, tag v2.1.0)
    in repo mode, then author steal-map.md comparing its dependency-graph
    reconciler against our cli/internal/gates/ package. I own this analysis
    and have authorization for the clone.
    ```
    
    ## It's working if
    
    Observable in the trace, without reading the prose:
    
    - `feature-registry.yaml` and `clone-metadata.json` land under
      `.agents/scratch/reverse-engineer/<product>/` with the resolved
      upstream commit recorded.
    - Every `steal-map.md` row cites a teardown registry entry and our
      matching surface, using the full `have`/`gap`/`steal`/`park`/`reject`
      set.
    - `bash skills/reverse-engineer/scripts/validate-output.sh --output-dir
      "$output_dir" --phase complete` exits 0 before handoff.
    - A one-way-door adoption row is routed to Plan instead of decided
      inside `steal-map.md`.
    
    ## ⚠️ Constraints — Hard Guardrails (MANDATORY)
    
    - Only operate on code/binaries you own or have **explicit written authorization** to analyze — this matters because unauthorized teardown is the legal/IP line.
    - Do not provide steps to bypass protections/ToS or to extract proprietary source/system prompts.
    - Do not output reconstructed proprietary source or embedded prompts (index only; redact in reports) — to prevent reproducing protected IP.
    - Redact secrets/tokens/keys if encountered; run the secret-scan gate over outputs to prevent credential leakage.
    - Always separate **docs say** vs **code proves** vs **hosted/control-plane**.
    
    ## Phase 1 — Mechanical teardown (the script)
    
    Produce evidence, not vibes. The script clones (pinned), scans CLI/config/artifact surface, and writes a feature inventory + machine-checkable registry + spec set.
    
    ```bash
    python3 skills/reverse-engineer/scripts/reverse_engineer.py <product> --mode=repo \
      --upstream-repo="https://github.com/org/repo.git" --upstream-ref=v1.0.0 \
      --output-dir=".agents/scratch/reverse-engineer/<product>/"
    ```
    
    Binary mode requires `--authorized` (see Invocation Contract + Self-Test). Use the bundled demo fixture if you lack authorization for a real binary.
    
    ## Phase 2 — The steal-map (the decision)
    
    Map each capability the teardown found onto **our** surfaces. This is the part that turns research into a decision. Emit `.agents/scratch/reverse-engineer/<product>/steal-map.md` with a table; every row cites the teardown evidence **and** the matching surface in our repo.
    
    The mechanical script intentionally stops after validating Phase 1. It cannot
    truthfully decide whether our live tree has, lacks, or should adopt a capability.
    The caller authors `steal-map.md` from the generated registry plus a fresh read
    of our repository, then runs the complete-output validator below. A missing or
    malformed map is therefore an incomplete skill result, not a script success
    silently relabelled as a decision.
    
    | Their capability | Our surface today | Verdict |
    |---|---|---|
    | `<feature>` | `<our file / skill / CLI, or "none">` | **have** / **gap** / **steal** / **park** / **reject** |
    
    Verdict rules (hard-won — apply them, do not skip):
    
    - **steal** — we lack it and it advances our core. Steal the *pattern*, not the storage engine: re-express in our primitives, never vendor their runtime.
    - **park** — real, but it's substrate we deliberately delegate (e.g. orchestration per ADR-0009) or downstream of an unproven bet. Name it, don't build it.
    - **reject** — it conflicts with our doctrine (e.g. a self-reported completion edge where we require a verdict — "no verdict = not done").
    - **have** — we already do this; confirm it still holds, move on.
    - **gap** — we should have it and don't. These are the steal candidates.
    
    Discipline that makes the map trustworthy:
    
    - **Independently checked, not self-report.** Get facts on *how* they implement
      each capability from code, cross-checked by a fresh reader — never from a
      README or one context's summary. Model family is optional metadata, not a
      trust requirement.
    - **Probe the real state, don't argue from stale.** Re-verify our side against the live tree before calling something a gap; every "X is missing" carries the search that proved it.
    - **The steal is the pattern, not the platform.** Their robustness is usually one idea (unification, a gate, a reconcile loop). Steal the idea; leave the scaffolding.
    
    ## Route one-way-door adoptions into planning
    
    If adopting a steal is a **one-way door** (an architecture fork, a new bounded
    context, or a migration), do not decide it here. Hand the steal-map to Plan.
    Dueling Idea Genies or Premortem may challenge the choice as advisory
    evidence. Plan alone shapes the selected option in the existing intent source;
    neither strategy grants readiness or continuation authority.
    
    ## Invocation Contract
    
    Required: `product_name`. Common flags: `--mode=repo|binary|both`, `--upstream-repo`, `--upstream-ref` (requires the selected checkout to be at that exact commit and records its resolved SHA in `clone-metadata.json`), `--local-clone-dir` (selects that exact tree, including a non-Git tree; it never falls back to the caller's checkout), `--output-dir` (default `.agents/scratch/reverse-engineer/<product>/`), `--security-audit`, `--materialize-archives` (authorized-only opt-in; embedded-archive extraction is off/index-only by default), `--authorized` (mandatory for binary mode — refuses without it). Full list: `python3 skills/reverse-engineer/scripts/reverse_engineer.py --help`.
    
    ## Output Specification
    
    Phase-1 teardown under `output_dir/`: `feature-inventory.md`, `feature-registry.yaml`, `feature-catalog.md`, `spec-architecture.md`, `spec-code-map.md`, `spec-clone-vs-use.md`, `spec-clone-mvp.md`, plus `spec-cli-surface.md` only when a CLI is detected. `clone-metadata.json` is written whenever an upstream repo/ref is selected and binds the exact analyzed commit, including an already-present checkout. Security mode adds `output_dir/security/`: `threat-model.md`, `attack-surface.md`, `dataflow.md`, `crypto-review.md`, `authn-authz.md`, `findings.md`, `reproducibility.md`, `validate-security-audit.sh`. Phase-2 adds the caller-authored `steal-map.md`.
    
    - **Artifact directory:** the exact `--output-dir`, defaulting to
      `$REPO/.agents/scratch/reverse-engineer/<product>/`.
    - **Filename convention:** the fixed phase-1 and phase-2 names above; security
      files live only in the `security/` child directory.
    - **Serialization/schema format:** registry is YAML, clone metadata is one JSON
      object, and inventories/specs/steal-map are nonempty Markdown files.
    - **Validator command:** Phase 1 runs this automatically with
      `--phase teardown`. After authoring `steal-map.md`, validate the complete
      skill output with `$output_dir`, `$security_audit`, `$sbom`, and
      `$upstream_ref_set` (each numeric flag `0|1`):
    
      ```bash
      bash skills/reverse-engineer/scripts/validate-output.sh \
        --output-dir "$output_dir" --phase complete \
        --security-audit "$security_audit" --sbom "$sbom" \
        --upstream-ref-set "$upstream_ref_set"
      ```
    - **Downstream handoff:** give the validated `steal-map.md` to Plan for
      one-way-door candidates; ordinary `have`, `park`, and
      `reject` decisions remain evidence-backed terminal rows.
    
    ### Earlier default compatibility
    
    Existing teardowns under `.agents/research/<product>/` remain in place and
    usable. The script accepts that directory when it is passed explicitly with
    `--output-dir`; that flag is caller authorization to write the teardown at the
    exact selected path. It does not relocate or duplicate existing artifacts. An
    invocation that omits the flag writes only to the current scratch default and
    never creates output under the earlier root.
    Consumers must retain the exact selected `output_dir` with their evidence
    references instead of rediscovering outputs by globbing one root. This owning
    skill contract is the compatibility authority; no separate migration receipt
    is required.
    
    ## Reproducibility + fixtures
    
    `--upstream-ref` binds the selected checkout to one full commit: a new clone is
    checked out detached at the fetched ref, while an existing checkout must already
    match or the run refuses before analysis. `clone-metadata.json` records that
    resolved commit. Regression test: `bash skills/reverse-engineer/scripts/repo_fixture_test.sh`. To update a fixture when contracts legitimately change, re-run with the new pinned ref, copy the contract files into `fixtures/<product>/`, and commit.
    
    ## Self-Test (acceptance)
    
    ```bash
    bash skills/reverse-engineer/scripts/self_test.sh
    ```
    
    Must show: feature inventory and registry generated; the exact Phase-1 validator
    passes; the complete validator rejects a missing and malformed steal-map and
    accepts a valid caller-authored fixture; existing-checkout ref mismatch and
    output symlinks fail closed; in security mode `validate-security-audit.sh`
    exits 0 only after the scaffold is completed and the secret scan passes.
    
    ## Examples
    
    ### Reverse-engineer an OSS CLI (repo mode) → steal-map
    
    Run Phase 1 for `cc-sdd` with `--mode=repo --upstream-repo="https://github.com/gotalab/cc-sdd.git" --upstream-ref=v1.0.0`. It clones the pinned source, scans the surface, writes inventory/registry/specs, and validates the teardown. Then inspect our live surfaces, author each `have`/`gap`/`steal`/`park`/`reject` row in `steal-map.md`, and run the complete-output validator. Supply selected steals to Plan.
    
    ### Binary analysis with security audit
    
    Run the skill for `ao` with `--authorized --mode=binary --binary-path="$(command -v ao)" --security-audit`. It performs authorized static analysis plus the security suite under `output_dir/security/`; the secret-scan check must pass.
    
    ## Troubleshooting
    
    | Problem | Cause | Solution |
    |---|---|---|
    | Refuses binary analysis | Missing `--authorized` | Add `--authorized` (explicit written authorization required). |
    | No `clone-metadata.json` | `--upstream-repo` not passed | Pass `--upstream-repo` (and optionally `--upstream-ref`). |
    | Fixture diff fails | Upstream changed / stale golden | Re-run pinned, refresh `fixtures/`, commit. |
    | Existing teardown is under `.agents/research/` | It used the earlier default | Pass that exact directory with `--output-dir`; new runs otherwise use the scratch default. |
    | `spec-cli-surface.md` missing | No Node/Python/Go CLI detected | Surface is documented in `spec-code-map.md` instead. |
    | Steal-map is all "steal" | Skipped the park/reject rules | Substrate we delegate is **park**; doctrine conflicts are **reject** — not everything novel is worth adopting. |
    
    ## Quality Rubric
    
    - [ ] Every steal-map row cites teardown evidence **and** our matching surface (or "none").
    - [ ] Verdicts use the full set — `have`/`gap`/`steal`/`park`/`reject` — not everything marked "steal".
    - [ ] Facts on *how* they implement come from code and a fresh independent check — not a README.
    - [ ] One-way-door adoptions are supplied to Plan, not decided here.
    - [ ] Secret-scan gate passed over all outputs; no proprietary source/prompts reproduced.
    
    ## See Also
    
    - [plan](../plan/SKILL.md) — shape selected steals in the existing intent source
    - [idea-genie](../idea-genie/SKILL.md) — optional advisory challenge (duel mode)
    - [premortem](../premortem/SKILL.md) — optional advisory challenge of the exact plan
    - [research](../research/SKILL.md) — general exploration; this is its external-system specialization
    
    ## Reference Documents
    
    - [references/reverse-engineer.feature](references/reverse-engineer.feature) — executable spec: repo-mode feature catalog + code map, binary-mode security audit, durable spec artifacts
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related