feed-the-machine 1.3.0 → 1.3.1

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.
@@ -25,125 +25,451 @@ Before starting, load context from the blackboard:
25
25
 
26
26
  If index.json is empty or no matches found, proceed normally without experience-informed shortcuts.
27
27
 
28
- ## Execution Protocol
28
+ # FTM Audit — Wiring Verification
29
29
 
30
- 1. Phase 0 detect project patterns (framework, router, state, API layer)
31
- 2. Layer 1 — knip static analysis
32
- 3. Layer 2 — adversarial audit, calibrated to detected patterns
33
- 4. Combine findings, deduplicate
34
- 5. Layer 3 — auto-fix each finding
35
- 6. Re-verify (re-run Layers 1+2)
36
- 7. Phase 3 — runtime wiring via ftm-browse (if prerequisites met)
37
- 8. Produce final changelog report
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
38
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.
39
35
 
40
36
  ## Phase 0: Detect Project Patterns
41
37
 
42
- Scan the project to calibrate which wiring dimensions apply. Read `references/protocols/PROJECT-PATTERNS.md` for the full detection table and dimension activation matrix.
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.
43
56
 
44
- **Quick scan:** `package.json` deps + `next.config.*`, `vite.config.*`, `app/` directory, `pages/` directory.
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.
45
70
 
46
- **Output:** Store detected context for all subsequent layers.
47
71
  ```
48
72
  Project detected: React 18 + Vite + react-router v6 + Zustand + TanStack Query
49
73
  Dimensions active: D1 ✓ D2 ✓ D3 (router config) D4 (Zustand) D5 (TanStack Query)
50
74
  ```
51
75
 
52
- ---
53
-
54
76
  ## Layer 1: Static Analysis (knip)
55
77
 
56
- Detects unused files, exports, dependencies, and unreachable modules from the import graph.
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)
57
83
 
58
- **Prerequisites:** Requires `package.json` — skip and note if absent. Use `npx knip` if not locally installed.
84
+ **Execution:**
59
85
 
86
+ Run knip with JSON output for machine parsing:
60
87
  ```bash
61
88
  npx knip --reporter json 2>/dev/null
62
89
  ```
63
90
 
64
- **Output:** `files` (unused files) + `issues` array (`type`/`filePath`/`symbol`). Issue types: `exports`, `types`, `duplicates`, `dependencies`, `devDependencies`, `unlisted`, `binaries`, `unresolved`.
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.).
65
92
 
66
- Fix actions by finding type: see `references/strategies/AUTO-FIX-STRATEGIES.md`. Helper script: `scripts/run-knip.sh`.
93
+ **Parsing the JSON output:**
67
94
 
68
- ---
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.
69
123
 
70
124
  ## Layer 2: LLM Adversarial Audit
71
125
 
72
- **Mindset:** Prove code is dead, not confirm it works. Every new/modified export is guilty until you find a complete chain from entry point to it.
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.
73
127
 
74
- **Scope:** `git diff HEAD~1` (or current task diff). For each new or modified export, check all five wiring dimensions.
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:
75
129
 
76
- ### The 5 Wiring Dimensions
130
+ **The 5 Wiring Dimensions:**
77
131
 
78
- Calibrate each dimension to the detected project pattern from Phase 0. Framework-specific method variations are in `references/protocols/PROJECT-PATTERNS.md`.
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.
79
133
 
80
- | Dim | Name | Trace | GUILTY if | Evidence required |
81
- |---|---|---|---|---|
82
- | D1 | Import Chain | `export` `import` ... → entry point | No importer found, OR importing file itself unimported | Full chain with file:line each link |
83
- | D2 | JSX Rendering | Component parent JSX root (React/Vue/Svelte only) | Imported but absent from every JSX return | Parent file:line where rendered, or "NOT FOUND" |
84
- | D3 | Route Registration | View → route config → router (method varies by router type) | View exists but no route points to it | Route config file:line, or "NOT FOUND" |
85
- | D4 | Store Consumption | Store field defined → selector/hook → component | Field written but never read | Consumer file:line, or "NOT FOUND" |
86
- | D5 | API Invocation | API function → call site → used in app | Exported but never called | Call site file:line, or "NOT FOUND" |
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
87
139
 
88
- **D2 valid rendering:** lazy imports, conditional rendering (`{cond && <C/>}`), render props, HOCs — all count.
89
- **D3 nav link check:** A route with no nav link might be orphaned — flag as a warning.
90
- **Non-React projects:** Skip D2-D3. Focus on D1, D4 (adapted to state management pattern), D5.
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
91
146
 
92
- **Key principle:** File:line evidence for EVERY finding. Show the grep results and the missing chain link — "I think this might be unused" is not acceptable.
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
93
153
 
94
- ---
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"
95
159
 
96
- ## Layer 3: Auto-Fix and Changelog
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"
97
165
 
98
- **Purpose:** When Layers 1 or 2 find unwired code, generate fixes, apply them, re-verify, and produce a structured changelog.
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).
99
167
 
100
- For fix strategies by finding type and the conditions that block auto-fix, see `references/strategies/AUTO-FIX-STRATEGIES.md`.
168
+ **Output format for this layer:**
169
+ ```
170
+ Layer 2 findings:
171
+ - [UNWIRED_COMPONENT] src/components/NewWidget.tsx — imported 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
+ ```
101
176
 
102
- **Fix protocol for each finding:** Report determine fix (check wiring contract for WHERE) show proposed change apply re-verify log to changelog.
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
103
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:
104
220
  ```
105
- FIX: [UNWIRED_COMPONENT] NewWidget in Dashboard.tsx
106
- Proposed: Add <NewWidget /> to Dashboard.tsx return JSX after line 45
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)
107
225
  ```
108
226
 
109
- **When auto-fix is not possible:** Flag with `MANUAL_INTERVENTION_NEEDED`, a suggested action, and the reason skipped.
227
+ **Re-verification after all fixes:**
110
228
 
111
- **Re-verification:** After all fixes, re-run Layers 1+2. Maximum 3 iterations to prevent loops.
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)
112
233
 
113
- ---
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
+ ```
114
258
 
115
259
  ## Wiring Contracts
116
260
 
117
- A wiring contract is a YAML block in a plan task declaring expected wiring for code produced by that task. See `references/protocols/WIRING-CONTRACTS.md` for full schema, examples (React component, API functions, route/view), and per-field verification checks.
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.
118
262
 
119
- **Fields:** `exports`, `imported_by`, `rendered_in`, `route_path`, `nav_link`, `store_reads`, `store_writes`, `api_calls` — all optional.
263
+ **Schema:**
120
264
 
121
- **Graceful degradation:** Full contract → check every wire. Partial → check what's declared. No contract → pure Layer 1 + Layer 2 analysis.
265
+ ```yaml
266
+ Wiring:
267
+ exports:
268
+ - symbol: ComponentName # What's being exported
269
+ from: src/components/Thing.tsx # From which file
122
270
 
123
- ---
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]`
124
380
 
125
381
  ## Phase 3: Runtime Wiring (Optional)
126
382
 
127
- Verify that components and routes that passed static analysis actually render in the running application. See `references/protocols/RUNTIME-WIRING.md` for full prerequisites, process, and what runtime wiring catches that static analysis misses.
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
128
387
 
129
- **Prerequisites (all required):** ftm-browse binary at `$HOME/.claude/skills/ftm-browse/bin/ftm-browse` + dev server running + wiring contracts include `route_path` entries.
388
+ If any prerequisite is not met, skip this phase with a note explaining which condition failed.
130
389
 
131
- **If any prerequisite fails:** Log the reason and skip. Do NOT fail the overall audit.
390
+ **What it checks**: Components and routes that passed static analysis (Phases 1-2) actually render in the running application.
132
391
 
133
- ---
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
134
439
 
135
440
  ## Blackboard Write
136
441
 
137
- After completing:
442
+ After completing, update the blackboard:
138
443
 
139
- 1. Update `~/.claude/ftm-state/blackboard/context.json` — set current_task complete, append to recent_decisions (cap 10), update session_metadata
140
- 2. Write experience file to `~/.claude/ftm-state/blackboard/experiences/YYYY-MM-DD_task-slug.json` — findings count, fix count, dimensions fired, manual interventions
141
- 3. Update `experiences/index.json`
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
142
450
  4. Emit `audit_complete` event
143
451
 
144
452
  ## Report Format
145
453
 
146
- See `references/templates/REPORT-FORMAT.md` for the full report template (summary, changelog table, layer-by-layer finding format with examples).
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
+ ```
147
473
 
148
474
  ## Requirements
149
475