feed-the-machine 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +170 -170
  3. package/bin/generate-manifest.mjs +463 -463
  4. package/bin/install.mjs +491 -491
  5. package/docs/HOOKS.md +243 -243
  6. package/docs/INBOX.md +233 -233
  7. package/ftm/SKILL.md +122 -122
  8. package/ftm-audit/SKILL.md +623 -541
  9. package/ftm-audit/references/protocols/PROJECT-PATTERNS.md +91 -91
  10. package/ftm-audit/references/protocols/RUNTIME-WIRING.md +66 -66
  11. package/ftm-audit/references/protocols/WIRING-CONTRACTS.md +135 -135
  12. package/ftm-audit/references/strategies/AUTO-FIX-STRATEGIES.md +69 -69
  13. package/ftm-audit/references/templates/REPORT-FORMAT.md +96 -96
  14. package/ftm-audit/scripts/run-knip.sh +23 -23
  15. package/ftm-audit.yml +2 -2
  16. package/ftm-brainstorm/SKILL.md +498 -498
  17. package/ftm-brainstorm/evals/evals.json +100 -100
  18. package/ftm-brainstorm/evals/promptfoo.yaml +109 -109
  19. package/ftm-brainstorm/references/agent-prompts.md +224 -224
  20. package/ftm-brainstorm/references/plan-template.md +121 -121
  21. package/ftm-brainstorm.yml +2 -2
  22. package/ftm-browse/SKILL.md +454 -454
  23. package/ftm-browse/daemon/browser-manager.ts +206 -206
  24. package/ftm-browse/daemon/bun.lock +30 -30
  25. package/ftm-browse/daemon/cli.ts +347 -347
  26. package/ftm-browse/daemon/commands.ts +410 -410
  27. package/ftm-browse/daemon/main.ts +357 -357
  28. package/ftm-browse/daemon/package.json +17 -17
  29. package/ftm-browse/daemon/server.ts +189 -189
  30. package/ftm-browse/daemon/snapshot.ts +519 -519
  31. package/ftm-browse/daemon/tsconfig.json +22 -22
  32. package/ftm-browse.yml +4 -4
  33. package/ftm-capture/SKILL.md +370 -370
  34. package/ftm-capture.yml +4 -4
  35. package/ftm-codex-gate/SKILL.md +361 -361
  36. package/ftm-codex-gate.yml +2 -2
  37. package/ftm-config/SKILL.md +345 -345
  38. package/ftm-config.default.yml +82 -80
  39. package/ftm-config.yml +2 -2
  40. package/ftm-council/SKILL.md +416 -416
  41. package/ftm-council/references/prompts/CLAUDE-INVESTIGATION.md +60 -60
  42. package/ftm-council/references/prompts/CODEX-INVESTIGATION.md +58 -58
  43. package/ftm-council/references/prompts/GEMINI-INVESTIGATION.md +58 -58
  44. package/ftm-council/references/prompts/REBUTTAL-TEMPLATE.md +57 -57
  45. package/ftm-council/references/protocols/PREREQUISITES.md +47 -47
  46. package/ftm-council/references/protocols/STEP-0-FRAMING.md +46 -46
  47. package/ftm-council.yml +2 -2
  48. package/ftm-dashboard/SKILL.md +163 -163
  49. package/ftm-dashboard.yml +4 -4
  50. package/ftm-debug/SKILL.md +1037 -1037
  51. package/ftm-debug/references/phases/PHASE-0-INTAKE.md +58 -58
  52. package/ftm-debug/references/phases/PHASE-1-TRIAGE.md +46 -46
  53. package/ftm-debug/references/phases/PHASE-2-WAR-ROOM-AGENTS.md +279 -279
  54. package/ftm-debug/references/phases/PHASE-3-TO-6-EXECUTION.md +436 -436
  55. package/ftm-debug/references/protocols/BLACKBOARD.md +86 -86
  56. package/ftm-debug/references/protocols/EDGE-CASES.md +103 -103
  57. package/ftm-debug.yml +2 -2
  58. package/ftm-diagram/SKILL.md +277 -277
  59. package/ftm-diagram.yml +2 -2
  60. package/ftm-executor/SKILL.md +777 -767
  61. package/ftm-executor/references/STYLE-TEMPLATE.md +73 -73
  62. package/ftm-executor/references/phases/PHASE-0-VERIFICATION.md +62 -62
  63. package/ftm-executor/references/phases/PHASE-2-AGENT-ASSEMBLY.md +34 -34
  64. package/ftm-executor/references/phases/PHASE-3-WORKTREES.md +38 -38
  65. package/ftm-executor/references/phases/PHASE-4-5-AUDIT.md +72 -72
  66. package/ftm-executor/references/phases/PHASE-4-DISPATCH.md +66 -66
  67. package/ftm-executor/references/phases/PHASE-5-5-CODEX-GATE.md +73 -73
  68. package/ftm-executor/references/protocols/DOCUMENTATION-BOOTSTRAP.md +36 -36
  69. package/ftm-executor/references/protocols/MODEL-PROFILE.md +59 -44
  70. package/ftm-executor/references/protocols/PROGRESS-TRACKING.md +66 -66
  71. package/ftm-executor/runtime/ftm-runtime.mjs +252 -252
  72. package/ftm-executor/runtime/package.json +8 -8
  73. package/ftm-executor.yml +2 -2
  74. package/ftm-git/SKILL.md +441 -441
  75. package/ftm-git/evals/evals.json +26 -26
  76. package/ftm-git/evals/promptfoo.yaml +75 -75
  77. package/ftm-git/hooks/post-commit-experience.sh +92 -92
  78. package/ftm-git/references/patterns/SECRET-PATTERNS.md +104 -104
  79. package/ftm-git/references/protocols/REMEDIATION.md +139 -139
  80. package/ftm-git/scripts/pre-commit-secrets.sh +110 -110
  81. package/ftm-git.yml +2 -2
  82. package/ftm-inbox/backend/adapters/_retry.py +64 -64
  83. package/ftm-inbox/backend/adapters/base.py +230 -230
  84. package/ftm-inbox/backend/adapters/freshservice.py +104 -104
  85. package/ftm-inbox/backend/adapters/gmail.py +125 -125
  86. package/ftm-inbox/backend/adapters/jira.py +136 -136
  87. package/ftm-inbox/backend/adapters/registry.py +192 -192
  88. package/ftm-inbox/backend/adapters/slack.py +110 -110
  89. package/ftm-inbox/backend/db/connection.py +54 -54
  90. package/ftm-inbox/backend/db/schema.py +78 -78
  91. package/ftm-inbox/backend/executor/__init__.py +7 -7
  92. package/ftm-inbox/backend/executor/engine.py +149 -149
  93. package/ftm-inbox/backend/executor/step_runner.py +98 -98
  94. package/ftm-inbox/backend/main.py +103 -103
  95. package/ftm-inbox/backend/models/__init__.py +1 -1
  96. package/ftm-inbox/backend/models/unified_task.py +36 -36
  97. package/ftm-inbox/backend/planner/__init__.py +6 -6
  98. package/ftm-inbox/backend/planner/generator.py +127 -127
  99. package/ftm-inbox/backend/planner/schema.py +34 -34
  100. package/ftm-inbox/backend/requirements.txt +5 -5
  101. package/ftm-inbox/backend/routes/execute.py +186 -186
  102. package/ftm-inbox/backend/routes/health.py +52 -52
  103. package/ftm-inbox/backend/routes/inbox.py +68 -68
  104. package/ftm-inbox/backend/routes/plan.py +271 -271
  105. package/ftm-inbox/bin/launchagent.mjs +91 -91
  106. package/ftm-inbox/bin/setup.mjs +188 -188
  107. package/ftm-inbox/bin/start.sh +10 -10
  108. package/ftm-inbox/bin/status.sh +17 -17
  109. package/ftm-inbox/bin/stop.sh +8 -8
  110. package/ftm-inbox/config.example.yml +55 -55
  111. package/ftm-inbox/package-lock.json +2898 -2898
  112. package/ftm-inbox/package.json +26 -26
  113. package/ftm-inbox/postcss.config.js +6 -6
  114. package/ftm-inbox/src/app.css +199 -199
  115. package/ftm-inbox/src/app.html +18 -18
  116. package/ftm-inbox/src/lib/api.ts +166 -166
  117. package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -81
  118. package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -143
  119. package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -271
  120. package/ftm-inbox/src/lib/components/PlanView.svelte +206 -206
  121. package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -99
  122. package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -190
  123. package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -63
  124. package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -86
  125. package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -106
  126. package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -67
  127. package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -149
  128. package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -80
  129. package/ftm-inbox/src/lib/theme.ts +47 -47
  130. package/ftm-inbox/src/routes/+layout.svelte +76 -76
  131. package/ftm-inbox/src/routes/+page.svelte +401 -401
  132. package/ftm-inbox/svelte.config.js +12 -12
  133. package/ftm-inbox/tailwind.config.ts +63 -63
  134. package/ftm-inbox/tsconfig.json +13 -13
  135. package/ftm-inbox/vite.config.ts +6 -6
  136. package/ftm-intent/SKILL.md +241 -241
  137. package/ftm-intent.yml +2 -2
  138. package/ftm-manifest.json +3794 -3794
  139. package/ftm-map/SKILL.md +291 -291
  140. package/ftm-map/scripts/db.py +712 -712
  141. package/ftm-map/scripts/index.py +415 -415
  142. package/ftm-map/scripts/parser.py +224 -224
  143. package/ftm-map/scripts/queries/go-tags.scm +20 -20
  144. package/ftm-map/scripts/queries/javascript-tags.scm +35 -35
  145. package/ftm-map/scripts/queries/python-tags.scm +31 -31
  146. package/ftm-map/scripts/queries/ruby-tags.scm +19 -19
  147. package/ftm-map/scripts/queries/rust-tags.scm +37 -37
  148. package/ftm-map/scripts/queries/typescript-tags.scm +41 -41
  149. package/ftm-map/scripts/query.py +301 -301
  150. package/ftm-map/scripts/ranker.py +377 -377
  151. package/ftm-map/scripts/requirements.txt +5 -5
  152. package/ftm-map/scripts/setup-hooks.sh +27 -27
  153. package/ftm-map/scripts/setup.sh +56 -56
  154. package/ftm-map/scripts/test_db.py +364 -364
  155. package/ftm-map/scripts/test_parser.py +174 -174
  156. package/ftm-map/scripts/test_query.py +183 -183
  157. package/ftm-map/scripts/test_ranker.py +199 -199
  158. package/ftm-map/scripts/views.py +591 -591
  159. package/ftm-map.yml +2 -2
  160. package/ftm-mind/SKILL.md +1943 -1943
  161. package/ftm-mind/evals/promptfoo.yaml +142 -142
  162. package/ftm-mind/references/blackboard-schema.md +328 -328
  163. package/ftm-mind/references/complexity-guide.md +110 -110
  164. package/ftm-mind/references/event-registry.md +319 -319
  165. package/ftm-mind/references/mcp-inventory.md +296 -296
  166. package/ftm-mind/references/protocols/COMPLEXITY-SIZING.md +72 -72
  167. package/ftm-mind/references/protocols/MCP-HEURISTICS.md +32 -32
  168. package/ftm-mind/references/protocols/PLAN-APPROVAL.md +80 -80
  169. package/ftm-mind/references/reflexion-protocol.md +249 -249
  170. package/ftm-mind/references/routing/SCENARIOS.md +22 -22
  171. package/ftm-mind/references/routing-scenarios.md +35 -35
  172. package/ftm-mind.yml +2 -2
  173. package/ftm-pause/SKILL.md +395 -395
  174. package/ftm-pause/references/protocols/SKILL-RESTORE-PROTOCOLS.md +186 -186
  175. package/ftm-pause/references/protocols/VALIDATION.md +80 -80
  176. package/ftm-pause.yml +2 -2
  177. package/ftm-researcher/SKILL.md +275 -275
  178. package/ftm-researcher/evals/agent-diversity.yaml +17 -17
  179. package/ftm-researcher/evals/synthesis-quality.yaml +12 -12
  180. package/ftm-researcher/evals/trigger-accuracy.yaml +39 -39
  181. package/ftm-researcher/references/adaptive-search.md +116 -116
  182. package/ftm-researcher/references/agent-prompts.md +193 -193
  183. package/ftm-researcher/references/council-integration.md +193 -193
  184. package/ftm-researcher/references/output-format.md +203 -203
  185. package/ftm-researcher/references/synthesis-pipeline.md +165 -165
  186. package/ftm-researcher/scripts/score_credibility.py +234 -234
  187. package/ftm-researcher/scripts/validate_research.py +92 -92
  188. package/ftm-researcher.yml +2 -2
  189. package/ftm-resume/SKILL.md +518 -518
  190. package/ftm-resume/references/protocols/VALIDATION.md +172 -172
  191. package/ftm-resume.yml +2 -2
  192. package/ftm-retro/SKILL.md +380 -380
  193. package/ftm-retro/references/protocols/SCORING-RUBRICS.md +89 -89
  194. package/ftm-retro/references/templates/REPORT-FORMAT.md +109 -109
  195. package/ftm-retro.yml +2 -2
  196. package/ftm-routine/SKILL.md +170 -170
  197. package/ftm-routine.yml +4 -4
  198. package/ftm-state/blackboard/capabilities.json +5 -5
  199. package/ftm-state/blackboard/capabilities.schema.json +27 -27
  200. package/ftm-state/blackboard/context.json +23 -23
  201. package/ftm-state/blackboard/experiences/index.json +9 -9
  202. package/ftm-state/blackboard/patterns.json +6 -6
  203. package/ftm-state/schemas/context.schema.json +130 -130
  204. package/ftm-state/schemas/experience-index.schema.json +77 -77
  205. package/ftm-state/schemas/experience.schema.json +78 -78
  206. package/ftm-state/schemas/patterns.schema.json +44 -44
  207. package/ftm-upgrade/SKILL.md +194 -194
  208. package/ftm-upgrade/scripts/check-version.sh +76 -76
  209. package/ftm-upgrade/scripts/upgrade.sh +143 -143
  210. package/ftm-upgrade.yml +2 -2
  211. package/ftm-verify.yml +2 -2
  212. package/ftm.yml +2 -2
  213. package/hooks/ftm-blackboard-enforcer.sh +93 -93
  214. package/hooks/ftm-discovery-reminder.sh +90 -90
  215. package/hooks/ftm-drafts-gate.sh +61 -61
  216. package/hooks/ftm-event-logger.mjs +107 -107
  217. package/hooks/ftm-map-autodetect.sh +79 -79
  218. package/hooks/ftm-pending-sync-check.sh +22 -22
  219. package/hooks/ftm-plan-gate.sh +92 -92
  220. package/hooks/ftm-post-commit-trigger.sh +57 -57
  221. package/hooks/settings-template.json +81 -81
  222. package/install.sh +363 -363
  223. package/package.json +84 -84
  224. package/uninstall.sh +25 -25
@@ -1,541 +1,623 @@
1
- ---
2
- name: ftm-audit
3
- description: Dual-purpose wiring audit that verifies all code is actually connected to the running application. Combines static analysis (knip) with adversarial LLM audit and auto-fixes anything it finds. Use when user says "audit", "wiring check", "verify wiring", "dead code", "check imports", "unused code", "find dead code", or "audit wiring". Also auto-invoked by ftm-executor after each task.
4
- ---
5
-
6
- ## Events
7
-
8
- ### Emits
9
- - `audit_complete` — when all three audit layers finish and the final changelog is produced
10
- - `issue_found` — when Layer 1 (knip) or Layer 2 (adversarial audit) identifies an unwired or dead-code problem
11
- - `task_completed` — when an audit-initiated fix cycle finishes and the audited scope is verified clean
12
-
13
- ### Listens To
14
- - `code_committed` — run post-commit verification: trigger Layer 1 and Layer 2 against the committed diff
15
- - `review_complete` — validate that review findings align with static analysis results; flag discrepancies
16
-
17
- ## Blackboard Read
18
-
19
- Before starting, load context from the blackboard:
20
-
21
- 1. Read `~/.claude/ftm-state/blackboard/context.json` — check current_task, recent_decisions, active_constraints
22
- 2. Read `~/.claude/ftm-state/blackboard/experiences/index.json` — filter entries by task_type="test" or tags matching "audit" or "wiring"
23
- 3. Load top 3-5 matching experience files for commonly found issues and effective fix strategies
24
- 4. Read `~/.claude/ftm-state/blackboard/patterns.json` — check recurring_issues for common wiring failures and execution_patterns for what types of code changes need more scrutiny
25
-
26
- If index.json is empty or no matches found, proceed normally without experience-informed shortcuts.
27
-
28
- # FTM Audit — Wiring Verification
29
-
30
- Three-layer verification system: knip for import-graph dead code, LLM adversarial trace for semantic wiring, and auto-fix with changelog.
31
-
32
- ## Why This Exists
33
-
34
- Code that compiles but isn't wired into the running application is worse than code that doesn't compile — it silently wastes space, confuses readers, and creates false confidence. This skill catches unwired code through two complementary lenses and fixes it automatically.
35
-
36
- ## Phase 0: Detect Project Patterns
37
-
38
- Before running any checks, scan the project to calibrate which wiring dimensions apply and how to check them. This takes seconds and prevents false positives from applying React patterns to a Vue project (or Next.js App Router patterns to a Pages Router project).
39
-
40
- **Read `package.json` and check for:**
41
-
42
- | Signal | Detection | Impact on Audit |
43
- |---|---|---|
44
- | **Framework** | `react`, `next`, `vue`, `svelte`, `angular` in deps | Determines which dimensions are relevant |
45
- | **Router** | `react-router-dom`, `@tanstack/react-router`, `next` (file-based), `vue-router` | Changes how Dimension 3 (Route Registration) works |
46
- | **State management** | `zustand`, `@reduxjs/toolkit`, `pinia`, `jotai`, `recoil` | Changes how Dimension 4 (Store Consumption) works |
47
- | **API layer** | `@tanstack/react-query`, `swr`, `trpc`, `@apollo/client`, `axios` | Changes how Dimension 5 (API Invocation) works |
48
- | **Build tool** | `vite`, `next`, `webpack`, `turbopack` | Affects knip entry point detection |
49
-
50
- **Quick file checks:**
51
-
52
- - `next.config.*` exists → Next.js project. Check for `app/` directory (App Router) vs `pages/` (Pages Router). This completely changes route checking.
53
- - `vite.config.*` exists → Vite project. Entry point is usually `index.html` → `src/main.tsx`.
54
- - `app/layout.tsx` or `app/page.tsx` exists → Next.js App Router. Routes are file-based (`app/dashboard/page.tsx` = `/dashboard`). No router config file to check — Dimension 3 checks directory structure instead.
55
- - `src/router.*` or `src/routes.*` exists → Explicit router config. Dimension 3 checks this file.
56
-
57
- **Framework-specific dimension adjustments:**
58
-
59
- | Framework | D1 (Import) | D2 (JSX) | D3 (Routes) | D4 (Store) | D5 (API) |
60
- |---|---|---|---|---|---|
61
- | React + react-router | Standard | Standard | Check router config file | Standard | Standard |
62
- | Next.js App Router | Check `app/` tree | Standard | File-based: `page.tsx` in directory = route | Standard | Check for Server Actions too |
63
- | Next.js Pages Router | Check `pages/` tree | Standard | File-based: `pages/foo.tsx` = `/foo` | Standard | Check `getServerSideProps`/`getStaticProps` |
64
- | Remix | Check `app/routes/` | Standard | File-based + `remix.config` | Standard | Check `loader`/`action` exports |
65
- | Vue + vue-router | Standard | `<template>` instead of JSX | Check router config | Pinia: `defineStore` | Standard |
66
- | Svelte | Standard | Svelte components | SvelteKit: file-based routes | Svelte stores | Standard |
67
- | No framework (Node.js) | Standard | Skip D2 | Skip D3 | Skip D4 | Standard |
68
-
69
- **Output:** Store the detected pattern as context for all subsequent layers. Don't include it in the report unless something unusual was detected.
70
-
71
- ```
72
- Project detected: React 18 + Vite + react-router v6 + Zustand + TanStack Query
73
- Dimensions active: D1 ✓ D2 ✓ D3 (router config) D4 (Zustand) D5 (TanStack Query)
74
- ```
75
-
76
- ## Layer 1: Static Analysis (knip)
77
-
78
- **What it does:** Runs [knip](https://knip.dev/) against the target project to detect unused files, exports, dependencies, and unreachable modules from the import graph.
79
-
80
- **Prerequisites check:**
81
- 1. Check if the project has a `package.json` — if not, skip Layer 1 entirely and note "No package.json found — knip layer skipped" in the report
82
- 2. Check if knip is installed (`node_modules/.bin/knip`) — if not, use `npx knip` (no install needed)
83
-
84
- **Execution:**
85
-
86
- Run knip with JSON output for machine parsing:
87
- ```bash
88
- npx knip --reporter json 2>/dev/null
89
- ```
90
-
91
- If the project has a `knip.json` or `knip.config.ts`, knip uses it automatically. If not, knip auto-detects entry points from the framework (Vite, Next.js, React Router, etc.).
92
-
93
- **Parsing the JSON output:**
94
-
95
- Knip's JSON output contains these categories:
96
- - `files` — completely unused files (not imported by anything)
97
- - `issues` — array of issues, each with:
98
- - `type`: "exports", "types", "duplicates", "dependencies", "devDependencies", "optionalPeerDependencies", "unlisted", "binaries", "unresolved"
99
- - `filePath`: the file containing the issue
100
- - `symbol`: the unused export name (for export issues)
101
- - `parentSymbol`: the re-exporting module (for re-export chains)
102
-
103
- **Categorize findings:**
104
-
105
- | Finding Type | Fix Action |
106
- |---|---|
107
- | Unused file | Remove file OR add import from appropriate parent |
108
- | Unused export | Remove export OR wire it into consumer |
109
- | Unused dependency | Remove from package.json OR add usage |
110
- | Unlisted dependency | Add to package.json |
111
- | Unresolved import | Fix import path OR install missing package |
112
-
113
- **Output format for this layer:**
114
- ```
115
- Layer 1 findings:
116
- - [UNUSED_FILE] src/components/OldWidget.tsx — not imported anywhere
117
- - [UNUSED_EXPORT] src/utils/helpers.ts:42 — export `formatDate` not used
118
- - [UNUSED_DEP] package.json — `lodash` listed but never imported
119
- - [UNLISTED_DEP] src/api/client.ts — imports `axios` but it's not in package.json
120
- ```
121
-
122
- **Helper script:** `scripts/run-knip.sh` handles execution and returns structured JSON output.
123
-
124
- ## Layer 2: LLM Adversarial Audit
125
-
126
- **Mindset:** You are an adversary trying to PROVE code is dead. Not "confirm it works" — PROVE it's dead. Every new/modified export is guilty until proven innocent. You must find a complete chain from app entry point to the code in question, or it's flagged.
127
-
128
- **Scope:** Analyze the current git diff (`git diff HEAD~1` or the diff from the current task's changes). For each new or modified export:
129
-
130
- **The 5 Wiring Dimensions:**
131
-
132
- For each export, check ALL five. A component might be imported but never rendered. A function might be exported but never called. Check the full chain.
133
-
134
- ### Dimension 1: Import Chain
135
- - Trace: `export` `import` → ... → entry point (`main.tsx`, `App.tsx`, `index.ts`)
136
- - Method: Use `grep -r "import.*{.*ExportName.*}.*from" src/` or equivalent
137
- - **GUILTY if:** No file imports this export, OR the importing file itself is not imported (broken chain)
138
- - Evidence required: Full import chain with file:line for each link
139
-
140
- ### Dimension 2: JSX Rendering (React/Vue/Svelte projects)
141
- - Trace: Component rendered in parent JSX ... → root component
142
- - Method: Search for `<ComponentName` in JSX/TSX files
143
- - **GUILTY if:** Component is imported but never appears in any JSX return statement
144
- - Evidence required: The parent component file:line where it's rendered (or "NOT FOUND")
145
- - Special cases: Lazy imports (`React.lazy(() => import(...))`), conditional rendering (`{condition && <Component/>}`), render props, HOCs — all count as valid rendering
146
-
147
- ### Dimension 3: Route Registration
148
- - Trace: View/page component route config → router entry point
149
- - Method: Search router config files (react-router `createBrowserRouter`, Next.js pages/, etc.)
150
- - **GUILTY if:** A view/page component exists but no route points to it
151
- - Evidence required: Route config file:line showing the route, or "NOT FOUND"
152
- - Also check: Does the route have a navigation link (sidebar, navbar, menu)? A route with no nav link might be intentionally hidden (deep link) or might be orphaned
153
-
154
- ### Dimension 4: Store Field Consumption (Redux/Zustand/Pinia/etc.)
155
- - Trace: Store field defined → selector/hook reads it → component uses the value
156
- - Method: Search for store selectors, useSelector calls, useStore hooks that reference the field
157
- - **GUILTY if:** Store field is written but never read anywhere
158
- - Evidence required: Component file:line where the field is consumed, or "NOT FOUND"
159
-
160
- ### Dimension 5: API Function Invocation
161
- - Trace: API function defined called by hook/component/other function → used in app
162
- - Method: Search for function call sites
163
- - **GUILTY if:** API function is exported but never called anywhere
164
- - Evidence required: Call site file:line, or "NOT FOUND"
165
-
166
- **Non-React projects:** Skip Dimensions 2-3 if no JSX framework detected. Focus on import chain (D1), data flow (D4 adapted to the project's state management), and function invocation (D5).
167
-
168
- **Output format for this layer:**
169
- ```
170
- Layer 2 findings:
171
- - [UNWIRED_COMPONENT] src/components/NewWidget.tsximported in Dashboard.tsx:5 but never rendered in JSX (Dimension 2 FAIL)
172
- - [ORPHAN_ROUTE] src/views/SettingsView.tsx no route in router config points to this view (Dimension 3 FAIL)
173
- - [DEAD_STORE_FIELD] src/store/userSlice.ts:23 — `userPreferences` written in reducer but never read by any selector (Dimension 4 FAIL)
174
- - [UNCALLED_API] src/api/billing.ts:15 — `fetchInvoices()` exported but never called (Dimension 5 FAIL)
175
- ```
176
-
177
- **Key principle:** File:line evidence for EVERY finding. "I think this might be unused" is NOT acceptable. Show the grep results, show the missing link in the chain.
178
-
179
- ## Layer 3: Auto-Fix and Changelog
180
-
181
- **Purpose:** When Layer 1 or Layer 2 finds unwired code, this layer generates fixes, applies them, re-verifies, and produces a structured changelog.
182
-
183
- **Fix Strategies by Finding Type:**
184
-
185
- | Finding Type | Fix Strategy | Fallback |
186
- |---|---|---|
187
- | `UNUSED_FILE` | If the file was created by the current task, add import from the appropriate parent module. If it's pre-existing dead code, flag for removal. | Flag for manual review — might be intentionally standalone (config, script) |
188
- | `UNUSED_EXPORT` | If another module should consume it (check wiring contract), add the import. If truly unnecessary, remove the export keyword. | Flag for manual review |
189
- | `UNWIRED_COMPONENT` | Add `<ComponentName />` to the parent component's JSX return. Determine placement from component name and parent structure. | Flag — can't determine correct placement |
190
- | `ORPHAN_ROUTE` | Add route entry to the router config. Infer path from component name (e.g., `SettingsView` → `/settings`). Add nav link to sidebar/navbar if one exists. | Flag — route path ambiguous |
191
- | `DEAD_STORE_FIELD` | If a component should read this field (check wiring contract), add the selector/hook usage. If truly unused, remove the field. | Flag — store design decision needed |
192
- | `UNCALLED_API` | If a hook or component should call this (check wiring contract), add the invocation. If truly unused, remove the function. | Flag API integration decision needed |
193
- | `UNUSED_DEP` | Remove from package.json `dependencies` or `devDependencies`. | Flag if it might be used in scripts, config files, or CLI |
194
- | `UNLISTED_DEP` | Run `npm install <package>` (or appropriate package manager command). | Flag if the import might be wrong |
195
-
196
- **Fix Protocol (for each finding):**
197
-
198
- 1. **Report** — Log the finding with type, file:line, and evidence
199
- 2. **Determine fix**Match finding type to fix strategy above. Check wiring contract if available for guidance on WHERE to wire.
200
- 3. **Show proposed fix** — Display the exact code change before applying:
201
- ```
202
- FIX: [UNWIRED_COMPONENT] NewWidget in Dashboard.tsx
203
- Proposed: Add <NewWidget /> to Dashboard.tsx return JSX after line 45
204
- ```
205
- 4. **Apply fix** Use Edit tool to make the change
206
- 5. **Re-verify** — Run the specific check that found the issue:
207
- - For knip findings: re-run `npx knip --reporter json`
208
- - For adversarial findings: re-trace the specific wiring dimension
209
- 6. **Log to changelog** Record: timestamp, finding, fix applied, verification result
210
-
211
- **When auto-fix is NOT possible:**
212
-
213
- Some findings can't be auto-fixed safely:
214
- - Ambiguous placement (where exactly should the component render?)
215
- - Design decisions needed (should this store field exist at all?)
216
- - Cross-cutting changes (fix requires modifying 5+ files)
217
- - Test-only code (might be intentionally not wired into app)
218
-
219
- For these, flag them clearly:
220
- ```
221
- MANUAL_INTERVENTION_NEEDED:
222
- - [ORPHAN_ROUTE] src/views/AdminPanel.tsx cannot determine route path or nav placement
223
- Suggested action: Add route to router config and nav link to sidebar
224
- Reason auto-fix skipped: Multiple possible route paths (/admin, /settings/admin, /dashboard/admin)
225
- ```
226
-
227
- **Re-verification after all fixes:**
228
-
229
- After all auto-fixes are applied:
230
- 1. Re-run Layer 1 (knip) confirm no new unused code introduced by fixes
231
- 2. Re-run Layer 2 (adversarial audit on the fix diff) confirm fixes actually wire correctly
232
- 3. If re-verification finds new issues, fix those too (max 3 iterations to prevent loops)
233
-
234
- **Changelog format:**
235
-
236
- ```
237
- ### FTM Audit Changelog [YYYY-MM-DD HH:MM]
238
-
239
- #### Findings
240
- | # | Type | Location | Description |
241
- |---|------|----------|-------------|
242
- | 1 | UNWIRED_COMPONENT | src/components/Widget.tsx | Imported but not rendered in Dashboard |
243
- | 2 | ORPHAN_ROUTE | src/views/Settings.tsx | No route config entry |
244
-
245
- #### Fixes Applied
246
- | # | Finding | Fix | Verified |
247
- |---|---------|-----|----------|
248
- | 1 | UNWIRED_COMPONENT Widget | Added <Widget /> to Dashboard.tsx:47 | ✅ PASS |
249
- | 2 | ORPHAN_ROUTE Settings | Added /settings route to router.tsx:23 | ✅ PASS |
250
-
251
- #### Manual Intervention Required
252
- | # | Finding | Reason | Suggested Action |
253
- |---|---------|--------|-----------------|
254
- | (none) | | | |
255
-
256
- #### Final Status: PASS (0 remaining issues)
257
- ```
258
-
259
- ## Wiring Contracts
260
-
261
- **What:** A wiring contract is a YAML block in a plan task that declares the expected wiring for code produced by that task. It tells ftm-audit exactly what to verify — instead of guessing, the audit checks specific expectations.
262
-
263
- **Schema:**
264
-
265
- ```yaml
266
- Wiring:
267
- exports:
268
- - symbol: ComponentName # What's being exported
269
- from: src/components/Thing.tsx # From which file
270
-
271
- imported_by:
272
- - file: src/views/Dashboard.tsx # Which file should import it
273
- line_hint: "import section" # Approximate location (optional)
274
-
275
- rendered_in: # For React components
276
- - parent: Dashboard # Parent component name
277
- placement: "main content area" # Where in the JSX (descriptive)
278
-
279
- route_path: /dashboard/thing # For routed views (optional)
280
-
281
- nav_link: # For views that need navigation (optional)
282
- - location: sidebar # Where the nav link goes
283
- label: "Thing" # Display text
284
-
285
- store_reads: # Store fields this code reads (optional)
286
- - store: useAppStore
287
- field: user.preferences
288
-
289
- store_writes: # Store fields this code writes (optional)
290
- - store: useAppStore
291
- field: user.preferences
292
- action: setPreferences
293
-
294
- api_calls: # API functions this code invokes (optional)
295
- - function: fetchUserPrefs
296
- from: src/api/user.ts
297
- ```
298
-
299
- **All fields are optional.** Graceful degradation:
300
- - Full contract audit checks every declared wire
301
- - Partial contract audit checks what's declared, uses heuristics for the rest
302
- - No contract → audit falls back to pure Layer 1 + Layer 2 analysis
303
-
304
- **Example: React Component Task**
305
-
306
- ```yaml
307
- ### Task 3: Build UserPreferences component
308
- **Files:** Create src/components/UserPreferences.tsx
309
- **Wiring:**
310
- exports:
311
- - symbol: UserPreferences
312
- from: src/components/UserPreferences.tsx
313
- imported_by:
314
- - file: src/views/SettingsView.tsx
315
- rendered_in:
316
- - parent: SettingsView
317
- placement: "below profile section"
318
- store_reads:
319
- - store: useAppStore
320
- field: user.preferences
321
- api_calls:
322
- - function: updatePreferences
323
- from: src/api/user.ts
324
- ```
325
-
326
- **Example: API Client Function Task**
327
-
328
- ```yaml
329
- ### Task 5: Add billing API functions
330
- **Files:** Create src/api/billing.ts
331
- **Wiring:**
332
- exports:
333
- - symbol: fetchInvoices
334
- from: src/api/billing.ts
335
- - symbol: createSubscription
336
- from: src/api/billing.ts
337
- imported_by:
338
- - file: src/hooks/useBilling.ts
339
- api_calls: [] # These ARE the API functions — nothing to call downstream
340
- ```
341
-
342
- **Example: New Route/View Task**
343
-
344
- ```yaml
345
- ### Task 7: Build AnalyticsDashboard view
346
- **Files:** Create src/views/AnalyticsDashboard.tsx
347
- **Wiring:**
348
- exports:
349
- - symbol: AnalyticsDashboard
350
- from: src/views/AnalyticsDashboard.tsx
351
- imported_by:
352
- - file: src/router.tsx
353
- rendered_in:
354
- - parent: RouterConfig
355
- placement: "route element"
356
- route_path: /analytics
357
- nav_link:
358
- - location: sidebar
359
- label: "Analytics"
360
- icon: BarChart
361
- store_reads:
362
- - store: useAppStore
363
- field: analytics.dateRange
364
- ```
365
-
366
- **How ftm-audit checks contracts:**
367
-
368
- For each field in the wiring contract:
369
-
370
- 1. **exports** → Verify the symbol exists as a named export in the specified file. Use `grep "export.*ComponentName"` or AST-level check.
371
- 2. **imported_by** Verify the importing file contains `import { Symbol } from './path'`. Check the actual import statement exists.
372
- 3. **rendered_in** → Verify the parent component's JSX contains `<Symbol`. If `placement` is specified, verify approximate location.
373
- 4. **route_path** → Verify the router config contains a route with this path pointing to this component.
374
- 5. **nav_link** → Verify the navigation component (sidebar/navbar) contains a link with matching label and path.
375
- 6. **store_reads** → Verify a selector/hook call reads this field in the component.
376
- 7. **store_writes** Verify a dispatch/action call writes this field.
377
- 8. **api_calls**Verify the function is imported and called somewhere in the component or its hooks.
378
-
379
- Each check produces: `✅ VERIFIED file:line` or `❌ NOT FOUND [what was expected] [where it was expected]`
380
-
381
- ## Phase 3: Runtime Wiring (Optional)
382
-
383
- **Prerequisite**: This phase runs only when ALL of these conditions are met:
384
- 1. The ftm-browse binary exists at `$HOME/.claude/skills/ftm-browse/bin/ftm-browse`
385
- 2. A dev server is running (detected via `lsof -i :3000` or `lsof -i :5173` or `lsof -i :8080`)
386
- 3. The wiring contracts for the audited tasks include `route_path` entries
387
-
388
- If any prerequisite is not met, skip this phase with a note explaining which condition failed.
389
-
390
- **What it checks**: Components and routes that passed static analysis (Phases 1-2) actually render in the running application.
391
-
392
- ### Process
393
-
394
- For each wiring contract that includes a `route_path`:
395
-
396
- 1. **Navigate**: `$PB goto <dev_server_url><route_path>`
397
- 2. **Snapshot**: `$PB snapshot -i` to get the ARIA tree of interactive elements
398
- 3. **Verify components render**: Check that the expected components from the wiring contract appear in the ARIA tree. Look for:
399
- - Expected buttons, links, inputs by their labels/roles
400
- - Expected headings and landmarks
401
- - Expected form fields
402
- 4. **Screenshot**: `$PB screenshot` as evidence of the render state
403
- 5. **Report findings**:
404
- - PASS: All expected components found in ARIA tree
405
- - WARN: Page renders but some expected components are missing
406
- - FAIL: Page doesn't render (blank, error page, 404)
407
-
408
- Where `$PB` is `$HOME/.claude/skills/ftm-browse/bin/ftm-browse`.
409
-
410
- ### Integration with Phases 1-2
411
-
412
- Runtime wiring catches a category of bugs that static analysis cannot:
413
- - Component is imported and used in JSX but conditionally rendered (and the condition is false)
414
- - Route is registered but the page component crashes on mount
415
- - Component renders but is hidden via CSS (visibility: hidden, display: none)
416
- - Server-side data dependency fails, leaving the component in an error state
417
-
418
- If Phase 3 finds issues that Phases 1-2 missed, flag them as **runtime-only findings** in the audit report.
419
-
420
- ### Graceful Degradation
421
-
422
- If ftm-browse is not installed or the dev server is not running:
423
- - Log: "Phase 3 (Runtime Wiring) skipped — [reason: no browse binary | no dev server | no route_path in contracts]"
424
- - Do NOT fail the overall audit
425
- - Phases 1-2 results stand on their own
426
-
427
- ## Execution Protocol
428
-
429
- When invoked (manually via `/ftm-audit` or automatically post-task):
430
-
431
- 1. Run Phase 0 (detect project patterns — framework, router, state, API layer)
432
- 2. Run Layer 1 (knip static analysis)
433
- 3. Run Layer 2 (LLM adversarial audit, calibrated to detected patterns)
434
- 4. Combine findings, deduplicate
435
- 5. Run Layer 3 (auto-fix) for each finding
436
- 6. Re-verify (re-run Layers 1+2)
437
- 7. Run Phase 3 (runtime wiring via ftm-browse, if prerequisites met)
438
- 8. Produce final changelog report
439
-
440
- ## Blackboard Write
441
-
442
- After completing, update the blackboard:
443
-
444
- 1. Update `~/.claude/ftm-state/blackboard/context.json`:
445
- - Set current_task status to "complete"
446
- - Append decision summary to recent_decisions (cap at 10)
447
- - Update session_metadata.skills_invoked and last_updated
448
- 2. Write an experience file to `~/.claude/ftm-state/blackboard/experiences/YYYY-MM-DD_task-slug.json` capturing findings count, fix count, which wiring dimensions fired, and any manual interventions required
449
- 3. Update `~/.claude/ftm-state/blackboard/experiences/index.json` with the new entry
450
- 4. Emit `audit_complete` event
451
-
452
- ## Report Format
453
-
454
- ```
455
- ## FTM Audit Report — [timestamp]
456
-
457
- ### Layer 1: Static Analysis (knip)
458
- - Findings: [N]
459
- - [list each finding with file:line]
460
-
461
- ### Layer 2: Adversarial Audit
462
- - Findings: [N]
463
- - [list each finding with file:line and evidence]
464
-
465
- ### Layer 3: Auto-Fix Results
466
- - Fixed: [N]
467
- - Manual intervention needed: [N]
468
- - [list each fix applied]
469
-
470
- ### Final Status: PASS / FAIL
471
- - Remaining issues: [list if any]
472
- ```
473
-
474
- ## Requirements
475
-
476
- - tool: `knip` | optional | static dead-code and unused-export analysis (Layer 1)
477
- - tool: `node` | required | runtime for knip via npx
478
- - config: `knip.config.ts` | optional | custom knip configuration at project root
479
- - reference: `references/protocols/PROJECT-PATTERNS.md` | required | framework detection table and dimension activation matrix
480
- - reference: `references/strategies/AUTO-FIX-STRATEGIES.md` | required | fix actions by finding type
481
- - reference: `references/protocols/WIRING-CONTRACTS.md` | optional | wiring contract schema for plan-driven audits
482
- - reference: `references/protocols/RUNTIME-WIRING.md` | optional | runtime verification protocol
483
- - reference: `references/templates/REPORT-FORMAT.md` | required | structured report template
484
- - tool: `$HOME/.claude/skills/ftm-browse/bin/ftm-browse` | optional | runtime wiring verification via browser (Phase 3)
485
-
486
- ## Risk
487
-
488
- - level: medium_write
489
- - scope: modifies source files to fix wiring issues (auto-fix layer); also adds/removes imports and route registrations; reads codebase broadly
490
- - rollback: git checkout on auto-fixed files; all changes are tracked in the changelog report before being applied
491
-
492
- ## Approval Gates
493
-
494
- - trigger: auto-fix proposed for a finding | action: report proposed change before applying (show "Proposed: ..." format)
495
- - trigger: finding flagged MANUAL_INTERVENTION_NEEDED | action: surface to user with suggested action, do not auto-fix
496
- - trigger: re-verification still fails after 3 iterations | action: stop and report remaining issues to user
497
- - complexity_routing: micro → auto | small → auto | medium → plan_first | large → plan_first | xl → always_ask
498
-
499
- ## Fallbacks
500
-
501
- - condition: knip not installed and npx unavailable | action: skip Layer 1, run Layer 2 adversarial audit only
502
- - condition: no package.json found | action: skip knip entirely, run adversarial audit only
503
- - condition: ftm-browse not installed | action: skip Phase 3 runtime wiring check, log reason and continue
504
- - condition: dev server not running | action: skip Phase 3 runtime wiring check, log reason and continue
505
- - condition: wiring contracts absent | action: run pure Layer 1 + Layer 2 analysis without contract checking
506
- - condition: project has no identifiable entry point | action: skip knip, run adversarial audit only
507
-
508
- ## Capabilities
509
-
510
- - cli: `knip` | optional | dead code detection via npx knip
511
- - cli: `node` | required | JavaScript runtime for npx
512
- - cli: `$HOME/.claude/skills/ftm-browse/bin/ftm-browse` | optional | headless browser for runtime wiring
513
- - mcp: `git` | optional | diff scope for Layer 2 adversarial audit
514
-
515
- ## Event Payloads
516
-
517
- ### audit_complete
518
- - skill: string — "ftm-audit"
519
- - findings_count: number — total issues found across all layers
520
- - auto_fixed_count: number issues auto-remediated by Layer 3
521
- - manual_count: number — issues requiring manual intervention
522
- - scope: string[] — file paths audited
523
- - duration_ms: number total audit duration
524
- - layers_run: string[] which layers executed (e.g., ["layer1", "layer2", "layer3"])
525
-
526
- ### issue_found
527
- - skill: string "ftm-audit"
528
- - layer: string — "layer1" | "layer2" | "layer3"
529
- - dimension: string — D1 | D2 | D3 | D4 | D5 (for Layer 2 findings)
530
- - finding_type: string — exports | types | duplicates | UNWIRED_COMPONENT | etc.
531
- - file_path: string — affected file
532
- - symbol: string — affected symbol name
533
- - severity: stringCRITICAL | HIGH | MEDIUM | LOW
534
- - auto_fixable: boolean — whether Layer 3 can fix this automatically
535
-
536
- ### task_completed
537
- - skill: string "ftm-audit"
538
- - result: string — "pass" | "pass_with_fixes" | "fail"
539
- - findings_count: number — total findings
540
- - auto_fixed_count: number — auto-remediated count
541
- - manual_count: numbermanual intervention needed
1
+ ---
2
+ name: ftm-audit
3
+ description: Triple-layer wiring audit that verifies all code is actually connected to the running application and documented in INTENT.md. Combines static analysis (knip), documentation coverage checking (INTENT.md entries for every changed function), and adversarial LLM audit auto-fixes anything it finds. Use when user says "audit", "wiring check", "verify wiring", "dead code", "check imports", "unused code", "find dead code", "audit wiring", "check docs", or "documentation coverage". Also auto-invoked by ftm-executor after each task.
4
+ ---
5
+
6
+ ## Events
7
+
8
+ ### Emits
9
+ - `audit_complete` — when all three audit layers finish and the final changelog is produced
10
+ - `issue_found` — when Layer 1 (knip) or Layer 2 (adversarial audit) identifies an unwired or dead-code problem
11
+ - `task_completed` — when an audit-initiated fix cycle finishes and the audited scope is verified clean
12
+
13
+ ### Listens To
14
+ - `code_committed` — run post-commit verification: trigger Layer 1 and Layer 2 against the committed diff
15
+ - `review_complete` — validate that review findings align with static analysis results; flag discrepancies
16
+
17
+ ## Blackboard Read
18
+
19
+ Before starting, load context from the blackboard:
20
+
21
+ 1. Read `~/.claude/ftm-state/blackboard/context.json` — check current_task, recent_decisions, active_constraints
22
+ 2. Read `~/.claude/ftm-state/blackboard/experiences/index.json` — filter entries by task_type="test" or tags matching "audit" or "wiring"
23
+ 3. Load top 3-5 matching experience files for commonly found issues and effective fix strategies
24
+ 4. Read `~/.claude/ftm-state/blackboard/patterns.json` — check recurring_issues for common wiring failures and execution_patterns for what types of code changes need more scrutiny
25
+
26
+ If index.json is empty or no matches found, proceed normally without experience-informed shortcuts.
27
+
28
+ # FTM Audit — Wiring Verification
29
+
30
+ Three-layer verification system: knip for import-graph dead code, LLM adversarial trace for semantic wiring, and auto-fix with changelog.
31
+
32
+ ## Why This Exists
33
+
34
+ Code that compiles but isn't wired into the running application is worse than code that doesn't compile — it silently wastes space, confuses readers, and creates false confidence. This skill catches unwired code through two complementary lenses and fixes it automatically.
35
+
36
+ ## Phase 0: Detect Project Patterns
37
+
38
+ Before running any checks, scan the project to calibrate which wiring dimensions apply and how to check them. This takes seconds and prevents false positives from applying React patterns to a Vue project (or Next.js App Router patterns to a Pages Router project).
39
+
40
+ **Read `package.json` and check for:**
41
+
42
+ | Signal | Detection | Impact on Audit |
43
+ |---|---|---|
44
+ | **Framework** | `react`, `next`, `vue`, `svelte`, `angular` in deps | Determines which dimensions are relevant |
45
+ | **Router** | `react-router-dom`, `@tanstack/react-router`, `next` (file-based), `vue-router` | Changes how Dimension 3 (Route Registration) works |
46
+ | **State management** | `zustand`, `@reduxjs/toolkit`, `pinia`, `jotai`, `recoil` | Changes how Dimension 4 (Store Consumption) works |
47
+ | **API layer** | `@tanstack/react-query`, `swr`, `trpc`, `@apollo/client`, `axios` | Changes how Dimension 5 (API Invocation) works |
48
+ | **Build tool** | `vite`, `next`, `webpack`, `turbopack` | Affects knip entry point detection |
49
+
50
+ **Quick file checks:**
51
+
52
+ - `next.config.*` exists → Next.js project. Check for `app/` directory (App Router) vs `pages/` (Pages Router). This completely changes route checking.
53
+ - `vite.config.*` exists → Vite project. Entry point is usually `index.html` → `src/main.tsx`.
54
+ - `app/layout.tsx` or `app/page.tsx` exists → Next.js App Router. Routes are file-based (`app/dashboard/page.tsx` = `/dashboard`). No router config file to check — Dimension 3 checks directory structure instead.
55
+ - `src/router.*` or `src/routes.*` exists → Explicit router config. Dimension 3 checks this file.
56
+
57
+ **Framework-specific dimension adjustments:**
58
+
59
+ | Framework | D1 (Import) | D2 (JSX) | D3 (Routes) | D4 (Store) | D5 (API) |
60
+ |---|---|---|---|---|---|
61
+ | React + react-router | Standard | Standard | Check router config file | Standard | Standard |
62
+ | Next.js App Router | Check `app/` tree | Standard | File-based: `page.tsx` in directory = route | Standard | Check for Server Actions too |
63
+ | Next.js Pages Router | Check `pages/` tree | Standard | File-based: `pages/foo.tsx` = `/foo` | Standard | Check `getServerSideProps`/`getStaticProps` |
64
+ | Remix | Check `app/routes/` | Standard | File-based + `remix.config` | Standard | Check `loader`/`action` exports |
65
+ | Vue + vue-router | Standard | `<template>` instead of JSX | Check router config | Pinia: `defineStore` | Standard |
66
+ | Svelte | Standard | Svelte components | SvelteKit: file-based routes | Svelte stores | Standard |
67
+ | No framework (Node.js) | Standard | Skip D2 | Skip D3 | Skip D4 | Standard |
68
+
69
+ **Output:** Store the detected pattern as context for all subsequent layers. Don't include it in the report unless something unusual was detected.
70
+
71
+ ```
72
+ Project detected: React 18 + Vite + react-router v6 + Zustand + TanStack Query
73
+ Dimensions active: D1 ✓ D2 ✓ D3 (router config) D4 (Zustand) D5 (TanStack Query)
74
+ ```
75
+
76
+ ## Layer 1: Static Analysis (knip)
77
+
78
+ **What it does:** Runs [knip](https://knip.dev/) against the target project to detect unused files, exports, dependencies, and unreachable modules from the import graph.
79
+
80
+ **Prerequisites check:**
81
+ 1. Check if the project has a `package.json` — if not, skip Layer 1 entirely and note "No package.json found — knip layer skipped" in the report
82
+ 2. Check if knip is installed (`node_modules/.bin/knip`) — if not, use `npx knip` (no install needed)
83
+
84
+ **Execution:**
85
+
86
+ Run knip with JSON output for machine parsing:
87
+ ```bash
88
+ npx knip --reporter json 2>/dev/null
89
+ ```
90
+
91
+ If the project has a `knip.json` or `knip.config.ts`, knip uses it automatically. If not, knip auto-detects entry points from the framework (Vite, Next.js, React Router, etc.).
92
+
93
+ **Parsing the JSON output:**
94
+
95
+ Knip's JSON output contains these categories:
96
+ - `files` — completely unused files (not imported by anything)
97
+ - `issues` — array of issues, each with:
98
+ - `type`: "exports", "types", "duplicates", "dependencies", "devDependencies", "optionalPeerDependencies", "unlisted", "binaries", "unresolved"
99
+ - `filePath`: the file containing the issue
100
+ - `symbol`: the unused export name (for export issues)
101
+ - `parentSymbol`: the re-exporting module (for re-export chains)
102
+
103
+ **Categorize findings:**
104
+
105
+ | Finding Type | Fix Action |
106
+ |---|---|
107
+ | Unused file | Remove file OR add import from appropriate parent |
108
+ | Unused export | Remove export OR wire it into consumer |
109
+ | Unused dependency | Remove from package.json OR add usage |
110
+ | Unlisted dependency | Add to package.json |
111
+ | Unresolved import | Fix import path OR install missing package |
112
+
113
+ **Output format for this layer:**
114
+ ```
115
+ Layer 1 findings:
116
+ - [UNUSED_FILE] src/components/OldWidget.tsx — not imported anywhere
117
+ - [UNUSED_EXPORT] src/utils/helpers.ts:42 — export `formatDate` not used
118
+ - [UNUSED_DEP] package.json — `lodash` listed but never imported
119
+ - [UNLISTED_DEP] src/api/client.ts — imports `axios` but it's not in package.json
120
+ ```
121
+
122
+ **Helper script:** `scripts/run-knip.sh` handles execution and returns structured JSON output.
123
+
124
+ ## Layer 1.5: Documentation Coverage Check
125
+
126
+ **Purpose:** Verify that every new or changed function has a corresponding INTENT.md entry. This catches the most common documentation skip agents write code and tests but forget to update INTENT.md, leaving the documentation layer stale.
127
+
128
+ **When to run:** After Layer 1, before Layer 2. Only runs if the project has an INTENT.md documentation layer (root INTENT.md exists). If no INTENT.md exists anywhere in the project, skip this layer silently — it's not a documentation-first project.
129
+
130
+ **Scope:** Analyze the current git diff to find new or changed functions/classes/methods.
131
+
132
+ **Check protocol:**
133
+
134
+ 1. **Find changed files:** `git diff HEAD~1 --name-only -- '*.py' '*.ts' '*.tsx' '*.js' '*.jsx'` (or equivalent for the project's language)
135
+ 2. **For each changed source file:**
136
+ - Identify the module directory (e.g., `src/risk/` for `src/risk/position_sizer.py`)
137
+ - Check if `[module_dir]/INTENT.md` exists
138
+ - If it exists, read it and extract all documented function signatures
139
+ - Parse the changed file for public function/class/method definitions
140
+ - Compare: flag any public function that exists in code but has NO entry in INTENT.md
141
+ 3. **For new module directories** (directories created in this diff):
142
+ - Check if `[new_module_dir]/INTENT.md` was created
143
+ - Check if root INTENT.md module map has a row for the new module
144
+ - Flag if either is missing
145
+
146
+ **Finding types:**
147
+
148
+ | Finding | Severity | Auto-fixable? |
149
+ |---------|----------|---------------|
150
+ | `MISSING_INTENT_ENTRY` function exists in code but no INTENT.md entry | HARD FAIL | Yes — generate entry from code |
151
+ | `STALE_INTENT_ENTRY` INTENT.md entry for a function that no longer exists | WARN | Yes — remove entry |
152
+ | `MISSING_MODULE_INTENT` new module directory has no INTENT.md file | HARD FAIL | Yes create from template |
153
+ | `MISSING_MODULE_MAP_ROW` — new module not in root INTENT.md module map | HARD FAIL | Yes — add row |
154
+
155
+ **Output format:**
156
+ ```
157
+ Layer 1.5 findings:
158
+ - [MISSING_INTENT_ENTRY] src/risk/position_sizer.py:45 `calculate_dynamic_size()` has no entry in src/risk/INTENT.md
159
+ - [STALE_INTENT_ENTRY] src/risk/INTENT.md — entry for `old_function()` but function no longer exists in code
160
+ - [MISSING_MODULE_INTENT] src/newmodule/ directory created but no INTENT.md file
161
+ - [MISSING_MODULE_MAP_ROW] src/newmodule/ not listed in root INTENT.md module map
162
+ ```
163
+
164
+ **Auto-fix strategy for `MISSING_INTENT_ENTRY`:**
165
+
166
+ Generate the entry from the function's code. Read the function body and produce:
167
+ ```markdown
168
+ ### function_name(param1: Type, param2: Type) -> ReturnType
169
+ - **Does**: [infer from function body — one sentence]
170
+ - **Why**: [infer from context and callers — one sentence, or "Why unknown — inferred from usage: [inference]"]
171
+ - **Relationships**: [grep for callers/calleesone sentence]
172
+ - **Decisions**: [note any non-obvious choices, or "None"]
173
+ ```
174
+
175
+ Append the entry to the module's INTENT.md under the `## Functions` section.
176
+
177
+ **Auto-fix strategy for `MISSING_MODULE_INTENT`:**
178
+
179
+ Create `[module_dir]/INTENT.md` with:
180
+ ```markdown
181
+ # [Module Name] Intent
182
+
183
+ ## Functions
184
+
185
+ [generate entries for all public functions in the module]
186
+ ```
187
+
188
+ **Auto-fix strategy for `MISSING_MODULE_MAP_ROW`:**
189
+
190
+ Append a row to the root INTENT.md module map table:
191
+ ```markdown
192
+ | [module_path] | [infer purpose from code] | [infer relationships from imports] |
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Layer 2: LLM Adversarial Audit
198
+
199
+ **Mindset:** You are an adversary trying to PROVE code is dead. Not "confirm it works" PROVE it's dead. Every new/modified export is guilty until proven innocent. You must find a complete chain from app entry point to the code in question, or it's flagged.
200
+
201
+ **Scope:** Analyze the current git diff (`git diff HEAD~1` or the diff from the current task's changes). For each new or modified export:
202
+
203
+ **The 5 Wiring Dimensions:**
204
+
205
+ For each export, check ALL five. A component might be imported but never rendered. A function might be exported but never called. Check the full chain.
206
+
207
+ ### Dimension 1: Import Chain
208
+ - Trace: `export` `import` ... entry point (`main.tsx`, `App.tsx`, `index.ts`)
209
+ - Method: Use `grep -r "import.*{.*ExportName.*}.*from" src/` or equivalent
210
+ - **GUILTY if:** No file imports this export, OR the importing file itself is not imported (broken chain)
211
+ - Evidence required: Full import chain with file:line for each link
212
+
213
+ ### Dimension 2: JSX Rendering (React/Vue/Svelte projects)
214
+ - Trace: Component rendered in parent JSX → ... → root component
215
+ - Method: Search for `<ComponentName` in JSX/TSX files
216
+ - **GUILTY if:** Component is imported but never appears in any JSX return statement
217
+ - Evidence required: The parent component file:line where it's rendered (or "NOT FOUND")
218
+ - Special cases: Lazy imports (`React.lazy(() => import(...))`), conditional rendering (`{condition && <Component/>}`), render props, HOCs — all count as valid rendering
219
+
220
+ ### Dimension 3: Route Registration
221
+ - Trace: View/page component → route config → router entry point
222
+ - Method: Search router config files (react-router `createBrowserRouter`, Next.js pages/, etc.)
223
+ - **GUILTY if:** A view/page component exists but no route points to it
224
+ - Evidence required: Route config file:line showing the route, or "NOT FOUND"
225
+ - Also check: Does the route have a navigation link (sidebar, navbar, menu)? A route with no nav link might be intentionally hidden (deep link) or might be orphaned
226
+
227
+ ### Dimension 4: Store Field Consumption (Redux/Zustand/Pinia/etc.)
228
+ - Trace: Store field defined → selector/hook reads it → component uses the value
229
+ - Method: Search for store selectors, useSelector calls, useStore hooks that reference the field
230
+ - **GUILTY if:** Store field is written but never read anywhere
231
+ - Evidence required: Component file:line where the field is consumed, or "NOT FOUND"
232
+
233
+ ### Dimension 5: API Function Invocation
234
+ - Trace: API function defined → called by hook/component/other function → used in app
235
+ - Method: Search for function call sites
236
+ - **GUILTY if:** API function is exported but never called anywhere
237
+ - Evidence required: Call site file:line, or "NOT FOUND"
238
+
239
+ **Non-React projects:** Skip Dimensions 2-3 if no JSX framework detected. Focus on import chain (D1), data flow (D4 adapted to the project's state management), and function invocation (D5).
240
+
241
+ **Output format for this layer:**
242
+ ```
243
+ Layer 2 findings:
244
+ - [UNWIRED_COMPONENT] src/components/NewWidget.tsx — imported in Dashboard.tsx:5 but never rendered in JSX (Dimension 2 FAIL)
245
+ - [ORPHAN_ROUTE] src/views/SettingsView.tsx — no route in router config points to this view (Dimension 3 FAIL)
246
+ - [DEAD_STORE_FIELD] src/store/userSlice.ts:23 `userPreferences` written in reducer but never read by any selector (Dimension 4 FAIL)
247
+ - [UNCALLED_API] src/api/billing.ts:15 — `fetchInvoices()` exported but never called (Dimension 5 FAIL)
248
+ ```
249
+
250
+ **Key principle:** File:line evidence for EVERY finding. "I think this might be unused" is NOT acceptable. Show the grep results, show the missing link in the chain.
251
+
252
+ ## Layer 3: Auto-Fix and Changelog
253
+
254
+ **Purpose:** When Layer 1 or Layer 2 finds unwired code, this layer generates fixes, applies them, re-verifies, and produces a structured changelog.
255
+
256
+ **Fix Strategies by Finding Type:**
257
+
258
+ | Finding Type | Fix Strategy | Fallback |
259
+ |---|---|---|
260
+ | `UNUSED_FILE` | If the file was created by the current task, add import from the appropriate parent module. If it's pre-existing dead code, flag for removal. | Flag for manual review — might be intentionally standalone (config, script) |
261
+ | `UNUSED_EXPORT` | If another module should consume it (check wiring contract), add the import. If truly unnecessary, remove the export keyword. | Flag for manual review |
262
+ | `UNWIRED_COMPONENT` | Add `<ComponentName />` to the parent component's JSX return. Determine placement from component name and parent structure. | Flag — can't determine correct placement |
263
+ | `ORPHAN_ROUTE` | Add route entry to the router config. Infer path from component name (e.g., `SettingsView` → `/settings`). Add nav link to sidebar/navbar if one exists. | Flag — route path ambiguous |
264
+ | `DEAD_STORE_FIELD` | If a component should read this field (check wiring contract), add the selector/hook usage. If truly unused, remove the field. | Flag — store design decision needed |
265
+ | `UNCALLED_API` | If a hook or component should call this (check wiring contract), add the invocation. If truly unused, remove the function. | Flag — API integration decision needed |
266
+ | `UNUSED_DEP` | Remove from package.json `dependencies` or `devDependencies`. | Flag if it might be used in scripts, config files, or CLI |
267
+ | `UNLISTED_DEP` | Run `npm install <package>` (or appropriate package manager command). | Flag if the import might be wrong |
268
+ | `MISSING_INTENT_ENTRY` | Generate INTENT.md entry from function code (Does/Why/Relationships/Decisions). Append to module's INTENT.md. | Flag if function purpose is ambiguous |
269
+ | `STALE_INTENT_ENTRY` | Remove the entry from INTENT.md. | Flag if unsure whether function was renamed vs deleted |
270
+ | `MISSING_MODULE_INTENT` | Create module INTENT.md with entries for all public functions. | Flag if module has no clear public API |
271
+ | `MISSING_MODULE_MAP_ROW` | Add row to root INTENT.md module map table. | Flag if module purpose is unclear |
272
+
273
+ **Fix Protocol (for each finding):**
274
+
275
+ 1. **Report** — Log the finding with type, file:line, and evidence
276
+ 2. **Determine fix** Match finding type to fix strategy above. Check wiring contract if available for guidance on WHERE to wire.
277
+ 3. **Show proposed fix** Display the exact code change before applying:
278
+ ```
279
+ FIX: [UNWIRED_COMPONENT] NewWidget in Dashboard.tsx
280
+ Proposed: Add <NewWidget /> to Dashboard.tsx return JSX after line 45
281
+ ```
282
+ 4. **Apply fix** Use Edit tool to make the change
283
+ 5. **Re-verify** Run the specific check that found the issue:
284
+ - For knip findings: re-run `npx knip --reporter json`
285
+ - For adversarial findings: re-trace the specific wiring dimension
286
+ 6. **Log to changelog** — Record: timestamp, finding, fix applied, verification result
287
+
288
+ **When auto-fix is NOT possible:**
289
+
290
+ Some findings can't be auto-fixed safely:
291
+ - Ambiguous placement (where exactly should the component render?)
292
+ - Design decisions needed (should this store field exist at all?)
293
+ - Cross-cutting changes (fix requires modifying 5+ files)
294
+ - Test-only code (might be intentionally not wired into app)
295
+
296
+ For these, flag them clearly:
297
+ ```
298
+ MANUAL_INTERVENTION_NEEDED:
299
+ - [ORPHAN_ROUTE] src/views/AdminPanel.tsx cannot determine route path or nav placement
300
+ Suggested action: Add route to router config and nav link to sidebar
301
+ Reason auto-fix skipped: Multiple possible route paths (/admin, /settings/admin, /dashboard/admin)
302
+ ```
303
+
304
+ **Re-verification after all fixes:**
305
+
306
+ After all auto-fixes are applied:
307
+ 1. Re-run Layer 1 (knip) — confirm no new unused code introduced by fixes
308
+ 2. Re-run Layer 2 (adversarial audit on the fix diff) — confirm fixes actually wire correctly
309
+ 3. If re-verification finds new issues, fix those too (max 3 iterations to prevent loops)
310
+
311
+ **Changelog format:**
312
+
313
+ ```
314
+ ### FTM Audit Changelog — [YYYY-MM-DD HH:MM]
315
+
316
+ #### Findings
317
+ | # | Type | Location | Description |
318
+ |---|------|----------|-------------|
319
+ | 1 | UNWIRED_COMPONENT | src/components/Widget.tsx | Imported but not rendered in Dashboard |
320
+ | 2 | ORPHAN_ROUTE | src/views/Settings.tsx | No route config entry |
321
+
322
+ #### Fixes Applied
323
+ | # | Finding | Fix | Verified |
324
+ |---|---------|-----|----------|
325
+ | 1 | UNWIRED_COMPONENT Widget | Added <Widget /> to Dashboard.tsx:47 | ✅ PASS |
326
+ | 2 | ORPHAN_ROUTE Settings | Added /settings route to router.tsx:23 | PASS |
327
+
328
+ #### Manual Intervention Required
329
+ | # | Finding | Reason | Suggested Action |
330
+ |---|---------|--------|-----------------|
331
+ | (none) | | | |
332
+
333
+ #### Final Status: PASS (0 remaining issues)
334
+ ```
335
+
336
+ ## Wiring Contracts
337
+
338
+ **What:** A wiring contract is a YAML block in a plan task that declares the expected wiring for code produced by that task. It tells ftm-audit exactly what to verify — instead of guessing, the audit checks specific expectations.
339
+
340
+ **Schema:**
341
+
342
+ ```yaml
343
+ Wiring:
344
+ exports:
345
+ - symbol: ComponentName # What's being exported
346
+ from: src/components/Thing.tsx # From which file
347
+
348
+ imported_by:
349
+ - file: src/views/Dashboard.tsx # Which file should import it
350
+ line_hint: "import section" # Approximate location (optional)
351
+
352
+ rendered_in: # For React components
353
+ - parent: Dashboard # Parent component name
354
+ placement: "main content area" # Where in the JSX (descriptive)
355
+
356
+ route_path: /dashboard/thing # For routed views (optional)
357
+
358
+ nav_link: # For views that need navigation (optional)
359
+ - location: sidebar # Where the nav link goes
360
+ label: "Thing" # Display text
361
+
362
+ store_reads: # Store fields this code reads (optional)
363
+ - store: useAppStore
364
+ field: user.preferences
365
+
366
+ store_writes: # Store fields this code writes (optional)
367
+ - store: useAppStore
368
+ field: user.preferences
369
+ action: setPreferences
370
+
371
+ api_calls: # API functions this code invokes (optional)
372
+ - function: fetchUserPrefs
373
+ from: src/api/user.ts
374
+ ```
375
+
376
+ **All fields are optional.** Graceful degradation:
377
+ - Full contract audit checks every declared wire
378
+ - Partial contract → audit checks what's declared, uses heuristics for the rest
379
+ - No contract audit falls back to pure Layer 1 + Layer 2 analysis
380
+
381
+ **Example: React Component Task**
382
+
383
+ ```yaml
384
+ ### Task 3: Build UserPreferences component
385
+ **Files:** Create src/components/UserPreferences.tsx
386
+ **Wiring:**
387
+ exports:
388
+ - symbol: UserPreferences
389
+ from: src/components/UserPreferences.tsx
390
+ imported_by:
391
+ - file: src/views/SettingsView.tsx
392
+ rendered_in:
393
+ - parent: SettingsView
394
+ placement: "below profile section"
395
+ store_reads:
396
+ - store: useAppStore
397
+ field: user.preferences
398
+ api_calls:
399
+ - function: updatePreferences
400
+ from: src/api/user.ts
401
+ ```
402
+
403
+ **Example: API Client Function Task**
404
+
405
+ ```yaml
406
+ ### Task 5: Add billing API functions
407
+ **Files:** Create src/api/billing.ts
408
+ **Wiring:**
409
+ exports:
410
+ - symbol: fetchInvoices
411
+ from: src/api/billing.ts
412
+ - symbol: createSubscription
413
+ from: src/api/billing.ts
414
+ imported_by:
415
+ - file: src/hooks/useBilling.ts
416
+ api_calls: [] # These ARE the API functions nothing to call downstream
417
+ ```
418
+
419
+ **Example: New Route/View Task**
420
+
421
+ ```yaml
422
+ ### Task 7: Build AnalyticsDashboard view
423
+ **Files:** Create src/views/AnalyticsDashboard.tsx
424
+ **Wiring:**
425
+ exports:
426
+ - symbol: AnalyticsDashboard
427
+ from: src/views/AnalyticsDashboard.tsx
428
+ imported_by:
429
+ - file: src/router.tsx
430
+ rendered_in:
431
+ - parent: RouterConfig
432
+ placement: "route element"
433
+ route_path: /analytics
434
+ nav_link:
435
+ - location: sidebar
436
+ label: "Analytics"
437
+ icon: BarChart
438
+ store_reads:
439
+ - store: useAppStore
440
+ field: analytics.dateRange
441
+ ```
442
+
443
+ **How ftm-audit checks contracts:**
444
+
445
+ For each field in the wiring contract:
446
+
447
+ 1. **exports** → Verify the symbol exists as a named export in the specified file. Use `grep "export.*ComponentName"` or AST-level check.
448
+ 2. **imported_by** Verify the importing file contains `import { Symbol } from './path'`. Check the actual import statement exists.
449
+ 3. **rendered_in** Verify the parent component's JSX contains `<Symbol`. If `placement` is specified, verify approximate location.
450
+ 4. **route_path** Verify the router config contains a route with this path pointing to this component.
451
+ 5. **nav_link** → Verify the navigation component (sidebar/navbar) contains a link with matching label and path.
452
+ 6. **store_reads** → Verify a selector/hook call reads this field in the component.
453
+ 7. **store_writes** → Verify a dispatch/action call writes this field.
454
+ 8. **api_calls** → Verify the function is imported and called somewhere in the component or its hooks.
455
+
456
+ Each check produces: `✅ VERIFIED file:line` or `❌ NOT FOUND — [what was expected] [where it was expected]`
457
+
458
+ ## Phase 3: Runtime Wiring (Optional)
459
+
460
+ **Prerequisite**: This phase runs only when ALL of these conditions are met:
461
+ 1. The ftm-browse binary exists at `$HOME/.claude/skills/ftm-browse/bin/ftm-browse`
462
+ 2. A dev server is running (detected via `lsof -i :3000` or `lsof -i :5173` or `lsof -i :8080`)
463
+ 3. The wiring contracts for the audited tasks include `route_path` entries
464
+
465
+ If any prerequisite is not met, skip this phase with a note explaining which condition failed.
466
+
467
+ **What it checks**: Components and routes that passed static analysis (Phases 1-2) actually render in the running application.
468
+
469
+ ### Process
470
+
471
+ For each wiring contract that includes a `route_path`:
472
+
473
+ 1. **Navigate**: `$PB goto <dev_server_url><route_path>`
474
+ 2. **Snapshot**: `$PB snapshot -i` to get the ARIA tree of interactive elements
475
+ 3. **Verify components render**: Check that the expected components from the wiring contract appear in the ARIA tree. Look for:
476
+ - Expected buttons, links, inputs by their labels/roles
477
+ - Expected headings and landmarks
478
+ - Expected form fields
479
+ 4. **Screenshot**: `$PB screenshot` as evidence of the render state
480
+ 5. **Report findings**:
481
+ - PASS: All expected components found in ARIA tree
482
+ - WARN: Page renders but some expected components are missing
483
+ - FAIL: Page doesn't render (blank, error page, 404)
484
+
485
+ Where `$PB` is `$HOME/.claude/skills/ftm-browse/bin/ftm-browse`.
486
+
487
+ ### Integration with Phases 1-2
488
+
489
+ Runtime wiring catches a category of bugs that static analysis cannot:
490
+ - Component is imported and used in JSX but conditionally rendered (and the condition is false)
491
+ - Route is registered but the page component crashes on mount
492
+ - Component renders but is hidden via CSS (visibility: hidden, display: none)
493
+ - Server-side data dependency fails, leaving the component in an error state
494
+
495
+ If Phase 3 finds issues that Phases 1-2 missed, flag them as **runtime-only findings** in the audit report.
496
+
497
+ ### Graceful Degradation
498
+
499
+ If ftm-browse is not installed or the dev server is not running:
500
+ - Log: "Phase 3 (Runtime Wiring) skipped — [reason: no browse binary | no dev server | no route_path in contracts]"
501
+ - Do NOT fail the overall audit
502
+ - Phases 1-2 results stand on their own
503
+
504
+ ## Execution Protocol
505
+
506
+ When invoked (manually via `/ftm-audit` or automatically post-task):
507
+
508
+ 1. Run Phase 0 (detect project patterns — framework, router, state, API layer)
509
+ 2. Run Layer 1 (knip static analysis)
510
+ 3. Run Layer 1.5 (documentation coverage check INTENT.md entries for changed functions)
511
+ 4. Run Layer 2 (LLM adversarial audit, calibrated to detected patterns)
512
+ 5. Combine findings, deduplicate
513
+ 6. Run Layer 3 (auto-fix) for each finding (including missing INTENT.md entries)
514
+ 7. Re-verify (re-run Layers 1+1.5+2)
515
+ 8. Run Phase 3 (runtime wiring via ftm-browse, if prerequisites met)
516
+ 9. Produce final changelog report
517
+
518
+ ## Blackboard Write
519
+
520
+ After completing, update the blackboard:
521
+
522
+ 1. Update `~/.claude/ftm-state/blackboard/context.json`:
523
+ - Set current_task status to "complete"
524
+ - Append decision summary to recent_decisions (cap at 10)
525
+ - Update session_metadata.skills_invoked and last_updated
526
+ 2. Write an experience file to `~/.claude/ftm-state/blackboard/experiences/YYYY-MM-DD_task-slug.json` capturing findings count, fix count, which wiring dimensions fired, and any manual interventions required
527
+ 3. Update `~/.claude/ftm-state/blackboard/experiences/index.json` with the new entry
528
+ 4. Emit `audit_complete` event
529
+
530
+ ## Report Format
531
+
532
+ ```
533
+ ## FTM Audit Report [timestamp]
534
+
535
+ ### Layer 1: Static Analysis (knip)
536
+ - Findings: [N]
537
+ - [list each finding with file:line]
538
+
539
+ ### Layer 1.5: Documentation Coverage
540
+ - Findings: [N]
541
+ - [list each finding missing entries, stale entries, missing module docs]
542
+
543
+ ### Layer 2: Adversarial Audit
544
+ - Findings: [N]
545
+ - [list each finding with file:line and evidence]
546
+
547
+ ### Layer 3: Auto-Fix Results
548
+ - Fixed: [N]
549
+ - Manual intervention needed: [N]
550
+ - [list each fix applied]
551
+
552
+ ### Final Status: PASS / FAIL
553
+ - Remaining issues: [list if any]
554
+ ```
555
+
556
+ ## Requirements
557
+
558
+ - tool: `knip` | optional | static dead-code and unused-export analysis (Layer 1)
559
+ - tool: `node` | required | runtime for knip via npx
560
+ - config: `knip.config.ts` | optional | custom knip configuration at project root
561
+ - reference: `references/protocols/PROJECT-PATTERNS.md` | required | framework detection table and dimension activation matrix
562
+ - reference: `references/strategies/AUTO-FIX-STRATEGIES.md` | required | fix actions by finding type
563
+ - reference: `references/protocols/WIRING-CONTRACTS.md` | optional | wiring contract schema for plan-driven audits
564
+ - reference: `references/protocols/RUNTIME-WIRING.md` | optional | runtime verification protocol
565
+ - reference: `references/templates/REPORT-FORMAT.md` | required | structured report template
566
+ - tool: `$HOME/.claude/skills/ftm-browse/bin/ftm-browse` | optional | runtime wiring verification via browser (Phase 3)
567
+
568
+ ## Risk
569
+
570
+ - level: medium_write
571
+ - scope: modifies source files to fix wiring issues (auto-fix layer); also adds/removes imports and route registrations; reads codebase broadly
572
+ - rollback: git checkout on auto-fixed files; all changes are tracked in the changelog report before being applied
573
+
574
+ ## Approval Gates
575
+
576
+ - trigger: auto-fix proposed for a finding | action: report proposed change before applying (show "Proposed: ..." format)
577
+ - trigger: finding flagged MANUAL_INTERVENTION_NEEDED | action: surface to user with suggested action, do not auto-fix
578
+ - trigger: re-verification still fails after 3 iterations | action: stop and report remaining issues to user
579
+ - complexity_routing: micro → auto | small → auto | medium → plan_first | large → plan_first | xl → always_ask
580
+
581
+ ## Fallbacks
582
+
583
+ - condition: knip not installed and npx unavailable | action: skip Layer 1, run Layer 2 adversarial audit only
584
+ - condition: no package.json found | action: skip knip entirely, run adversarial audit only
585
+ - condition: ftm-browse not installed | action: skip Phase 3 runtime wiring check, log reason and continue
586
+ - condition: dev server not running | action: skip Phase 3 runtime wiring check, log reason and continue
587
+ - condition: wiring contracts absent | action: run pure Layer 1 + Layer 2 analysis without contract checking
588
+ - condition: project has no identifiable entry point | action: skip knip, run adversarial audit only
589
+
590
+ ## Capabilities
591
+
592
+ - cli: `knip` | optional | dead code detection via npx knip
593
+ - cli: `node` | required | JavaScript runtime for npx
594
+ - cli: `$HOME/.claude/skills/ftm-browse/bin/ftm-browse` | optional | headless browser for runtime wiring
595
+ - mcp: `git` | optional | diff scope for Layer 2 adversarial audit
596
+
597
+ ## Event Payloads
598
+
599
+ ### audit_complete
600
+ - skill: string — "ftm-audit"
601
+ - findings_count: number — total issues found across all layers
602
+ - auto_fixed_count: number — issues auto-remediated by Layer 3
603
+ - manual_count: number — issues requiring manual intervention
604
+ - scope: string[] — file paths audited
605
+ - duration_ms: number — total audit duration
606
+ - layers_run: string[] — which layers executed (e.g., ["layer1", "layer2", "layer3"])
607
+
608
+ ### issue_found
609
+ - skill: string — "ftm-audit"
610
+ - layer: string — "layer1" | "layer2" | "layer3"
611
+ - dimension: string — D1 | D2 | D3 | D4 | D5 (for Layer 2 findings)
612
+ - finding_type: string — exports | types | duplicates | UNWIRED_COMPONENT | etc.
613
+ - file_path: string — affected file
614
+ - symbol: string — affected symbol name
615
+ - severity: string — CRITICAL | HIGH | MEDIUM | LOW
616
+ - auto_fixable: boolean — whether Layer 3 can fix this automatically
617
+
618
+ ### task_completed
619
+ - skill: string — "ftm-audit"
620
+ - result: string — "pass" | "pass_with_fixes" | "fail"
621
+ - findings_count: number — total findings
622
+ - auto_fixed_count: number — auto-remediated count
623
+ - manual_count: number — manual intervention needed