@renzrollon/interlock 1.0.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 (125) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.claude-plugin/plugin.json +58 -0
  3. package/CHANGELOG.md +380 -0
  4. package/LICENSE +21 -0
  5. package/README.md +358 -0
  6. package/agents/ping.md +8 -0
  7. package/agents/worker.md +8 -0
  8. package/bin/interlock +2655 -0
  9. package/bin/interlock-graph +6 -0
  10. package/bin/interlock-run +508 -0
  11. package/bin/interlock-ship-acp +39 -0
  12. package/hooks/_shared.mjs +162 -0
  13. package/hooks/guard-commit.mjs +77 -0
  14. package/hooks/guard-tasks.mjs +107 -0
  15. package/hooks/guard-tests.mjs +63 -0
  16. package/hooks/preflight.mjs +107 -0
  17. package/lib/artifacts.mjs +316 -0
  18. package/lib/autonomy.mjs +149 -0
  19. package/lib/conformance.mjs +132 -0
  20. package/lib/doctor.mjs +1176 -0
  21. package/lib/drift.mjs +409 -0
  22. package/lib/eval-history.mjs +304 -0
  23. package/lib/evals-calibrate.mjs +317 -0
  24. package/lib/evals-capture.mjs +437 -0
  25. package/lib/evals-promote.mjs +257 -0
  26. package/lib/evals-triage.mjs +216 -0
  27. package/lib/findings.mjs +340 -0
  28. package/lib/graph/cli.mjs +266 -0
  29. package/lib/graph/extract/config-json.mjs +74 -0
  30. package/lib/graph/extract/create-extractor.mjs +35 -0
  31. package/lib/graph/extract/memory.mjs +99 -0
  32. package/lib/graph/extract/openspec.mjs +80 -0
  33. package/lib/graph/extract/python.mjs +137 -0
  34. package/lib/graph/extract/regex-fallback.mjs +187 -0
  35. package/lib/graph/extract/shell.mjs +71 -0
  36. package/lib/graph/extract/typescript.mjs +217 -0
  37. package/lib/graph/extract/walk.mjs +272 -0
  38. package/lib/graph/format.mjs +51 -0
  39. package/lib/graph/graph/build.mjs +374 -0
  40. package/lib/graph/graph/query.mjs +249 -0
  41. package/lib/graph/graph/report.mjs +122 -0
  42. package/lib/graph/graph/update.mjs +47 -0
  43. package/lib/graph/paths.mjs +66 -0
  44. package/lib/graph/retrieve/context.mjs +86 -0
  45. package/lib/graph/retrieve/docs-index.mjs +116 -0
  46. package/lib/graph/retrieve/markdown.mjs +246 -0
  47. package/lib/host/acp.mjs +551 -0
  48. package/lib/host/claude-cli.mjs +285 -0
  49. package/lib/host/codex.mjs +231 -0
  50. package/lib/host/model-map.mjs +129 -0
  51. package/lib/host/qwen.mjs +176 -0
  52. package/lib/host/registry.mjs +213 -0
  53. package/lib/host.mjs +393 -0
  54. package/lib/ledger.mjs +444 -0
  55. package/lib/limits.mjs +525 -0
  56. package/lib/locators.mjs +99 -0
  57. package/lib/merge-lanes.mjs +148 -0
  58. package/lib/metrics.mjs +99 -0
  59. package/lib/notify.mjs +229 -0
  60. package/lib/outcomes.mjs +560 -0
  61. package/lib/plan-fingerprint.mjs +570 -0
  62. package/lib/project-slug.mjs +18 -0
  63. package/lib/prompts/commit.mjs +26 -0
  64. package/lib/prompts/dimensions.mjs +70 -0
  65. package/lib/prompts/handoff.mjs +86 -0
  66. package/lib/prompts/implementer.mjs +150 -0
  67. package/lib/prompts/index.mjs +250 -0
  68. package/lib/prompts/planner.mjs +70 -0
  69. package/lib/prompts/remediate.mjs +137 -0
  70. package/lib/prompts/replan.mjs +23 -0
  71. package/lib/prompts/review.mjs +107 -0
  72. package/lib/prompts/schemas.mjs +206 -0
  73. package/lib/prompts/stage.mjs +48 -0
  74. package/lib/prompts/verify.mjs +88 -0
  75. package/lib/ready.mjs +812 -0
  76. package/lib/receipt.mjs +434 -0
  77. package/lib/remediate.mjs +275 -0
  78. package/lib/report-html.mjs +623 -0
  79. package/lib/report.mjs +1124 -0
  80. package/lib/review-core.mjs +713 -0
  81. package/lib/risk.mjs +370 -0
  82. package/lib/run-log.mjs +1004 -0
  83. package/lib/run-paths.mjs +239 -0
  84. package/lib/run.mjs +2921 -0
  85. package/lib/ship-stage.mjs +223 -0
  86. package/lib/spill.mjs +150 -0
  87. package/lib/surface.mjs +195 -0
  88. package/lib/verify.mjs +1074 -0
  89. package/lib/waves.mjs +2661 -0
  90. package/package.json +40 -0
  91. package/shared/CONTEXT-HYGIENE.md +84 -0
  92. package/shared/DECISION-LEDGER.md +141 -0
  93. package/shared/EXPLORE-BRIEF.md +96 -0
  94. package/shared/INVARIANT-SWEEP.md +57 -0
  95. package/shared/SESSION-SHAPES.md +261 -0
  96. package/shared/SKILL-CANDIDATE-BRIEF.md +106 -0
  97. package/shared/TEST-PROFILE.md +204 -0
  98. package/shared/TOOL-ECONOMY.md +155 -0
  99. package/skills/bootstrap/SKILL.md +226 -0
  100. package/skills/commit/SKILL.md +99 -0
  101. package/skills/dispatch/SKILL.md +78 -0
  102. package/skills/docs-digest/SKILL.md +109 -0
  103. package/skills/evals/SKILL.md +160 -0
  104. package/skills/explain-code/SKILL.md +42 -0
  105. package/skills/explain-code/commit-teachin.md +136 -0
  106. package/skills/explain-code/module-walkthrough.md +358 -0
  107. package/skills/explore/SKILL.md +159 -0
  108. package/skills/fix-tests/SKILL.md +120 -0
  109. package/skills/graph/SKILL.md +136 -0
  110. package/skills/manual-test-plan/SKILL.md +121 -0
  111. package/skills/mr/SKILL.md +170 -0
  112. package/skills/report/SKILL.md +121 -0
  113. package/skills/review-artifacts/SKILL.md +127 -0
  114. package/skills/review-code/REVIEW.template.md +49 -0
  115. package/skills/review-code/SKILL.md +156 -0
  116. package/skills/review-code/dimensions/architecture.md +10 -0
  117. package/skills/review-code/dimensions/devops.md +12 -0
  118. package/skills/review-code/dimensions/language.md +12 -0
  119. package/skills/review-code/dimensions/qa.md +12 -0
  120. package/skills/review-code/dimensions/security.md +12 -0
  121. package/skills/review-code/dimensions/technical-lead.md +19 -0
  122. package/skills/ship/SKILL.md +67 -0
  123. package/skills/spec/SKILL.md +264 -0
  124. package/skills/spec/continuity.md +66 -0
  125. package/workflows/ship.js +565 -0
@@ -0,0 +1,358 @@
1
+ # Module walkthrough — explain-code
2
+
3
+ Teach TypeScript/React/Next.js code to a frontend beginner. Not reviewing — teaching.
4
+
5
+ ## Scope
6
+
7
+ **Ecosystems covered:**
8
+ - TypeScript (strict mode, generics, utility types, Zod, discriminated unions)
9
+ - React 19 (hooks, components, context, refs, suspense, error boundaries)
10
+ - Next.js 15 App Router (server components, client components, server actions, route handlers, middleware, layouts, streaming)
11
+ - Related dependencies: Prisma ORM, Zustand, Radix UI, Tailwind CSS, better-auth, class-variance-authority
12
+
13
+ **Infrastructure & cloud services — always include when called:**
14
+ - PostgreSQL (via Prisma)
15
+ - AWS S3 (file storage/retrieval)
16
+ - AWS SQS (message queues)
17
+ - Any HTTP/REST calls to external services
18
+ - Authentication services (better-auth sessions)
19
+
20
+ **Context sources:**
21
+ - If `.claude/graph/DOCS_DIGEST.md` exists, Read it once as the domain primer (agent-only; see `/interlock:docs-digest`)
22
+ - Then use `interlock-graph context "<scope terms>" --budget 2000` for task-specific prose — do **not** read all of `docs/` upfront (see `${CLAUDE_PLUGIN_ROOT}/shared/TOOL-ECONOMY.md` Rule 0.5)
23
+ - If graph missing: `interlock-graph docs "<terms>" --budget 800`, or grep headings in `docs/` then Read spans
24
+
25
+ **Files to explain:**
26
+ - **Module walkthrough:** every `.ts` / `.tsx` in the scoped tree (`src/`, `app/`, `lib/`, …)
27
+ - **Commit teach-in:** every source file touched in the commit range (skip binaries/lockfiles; cap ~25)
28
+
29
+ **Skip:**
30
+ - Test files (`*.test.ts`, `*.test.tsx`, `__tests__/`) — unless they are the only files in a commit teach-in and the user asked about them
31
+ - `node_modules/`, generated files, config files outside source trees
32
+ - Boilerplate: import statements, simple variable declarations, trivial type aliases, basic for loops
33
+
34
+ **This mode:** every `.ts` / `.tsx` in the scoped tree (`src/`, `app/`, `lib/`, …).
35
+
36
+ ## Steps
37
+
38
+ 1. Retrieve domain context on demand — Read `.claude/graph/DOCS_DIGEST.md` if present; then `interlock-graph context "<scope>" --budget 2000` (or `docs` with `--budget 800` if no graph). Do not preload every `docs/*.md`.
39
+ 2. If `$ARGUMENTS` specifies a directory or file, explain only that scope; pass paths to `--changed` when using `context`.
40
+ 3. If no arguments, use the graph to list modules/files in `app/`, `lib/`, `components/`, `types/`, and `prisma/` instead of blind `find`.
41
+ 4. Graph-backed scope and call links: if `.claude/graph/graph.json` exists, use `interlock-graph explain <file>` and `interlock-graph consumers <symbol>` for **Called by** / **Calls into**. If missing and scope is large, `interlock-graph build .` once first.
42
+ 5. Read each in-scope file; extract functions and logic blocks worth explaining (Read with offset/limit when locating via graph/grep).
43
+ 6. Trace imports only where the graph is incomplete.
44
+ 7. Order output: foundational files → data layer → components → pages/routes.
45
+ 8. Build flow diagrams last; write to the output path resolved in `SKILL.md` — `docs/CODE_EXPLANATION.md` for an ad-hoc walkthrough, or `openspec/changes/<change-name>/code-explanation.md` when the walkthrough is scoped to an OpenSpec change.
46
+
47
+ ## Per-file structure
48
+
49
+ ### File Header (module walkthrough)
50
+
51
+ ```markdown
52
+ ## `src/path/to/file.ts`
53
+
54
+ **Role:** [one sentence — what this file's job is in the app]
55
+ **Called by:** [files that import from this one]
56
+ **Calls into:** [files this one imports, excluding node_modules]
57
+ **Infra/services touched:** [PostgreSQL, S3, SQS, external APIs — or "None"]
58
+ ```
59
+
60
+ ### Function/Block Breakdown
61
+
62
+ For each exported function, named function, or significant logic block:
63
+
64
+ ```markdown
65
+ ### `functionName(params)` → returnType
66
+
67
+ **What it does:** [1-2 sentences in plain language]
68
+
69
+ **How it works:**
70
+ - [Step-by-step walkthrough of the logic, skipping boilerplate]
71
+ - [Highlight conditionals, transformations, and side effects]
72
+
73
+ **Cross-file calls:**
74
+ - `helperFunction()` from `src/lib/helpers.ts` — [why it's called here]
75
+ - `prisma.user.findMany()` → PostgreSQL — [what data it fetches]
76
+ - `s3Client.send(PutObjectCommand)` → AWS S3 — [what it uploads and when]
77
+
78
+ **Pattern:** [Name the pattern if one applies]
79
+ ```
80
+
81
+ ## API & Data Flows (Required for route handlers and server actions)
82
+
83
+ ### Route Handler Flow
84
+
85
+ ```markdown
86
+ ### API Flow: `POST /api/v1/batches`
87
+
88
+ **Request:**
89
+ - Method: POST
90
+ - Body: `{ name: string, type: "BILLING" | "ACCRUAL", groupId: string }`
91
+ - Auth: Session cookie (better-auth)
92
+ - Headers: `Content-Type: application/json`
93
+
94
+ **Processing:**
95
+ 1. Authenticate → `auth.api.getSession()` → checks session cookie
96
+ 2. Authorize → `makeAuthorizationChecker()` → verifies user belongs to group
97
+ 3. Validate → Zod schema `CreateBatchSchema` → rejects malformed input
98
+ 4. Execute → `prisma.batch.create()` → **PostgreSQL** INSERT
99
+ 5. Side effect → `sendToQueue()` → **AWS SQS** sends processing message
100
+ 6. Respond → return created batch with 201
101
+
102
+ **Response:**
103
+ - 201: `{ id, name, type, status, createdAt }`
104
+ - 400: `{ error: "Validation failed", details: [...] }`
105
+ - 401: `{ error: "Unauthorized" }`
106
+ - 403: `{ error: "Forbidden" }`
107
+
108
+ **Flow diagram:**
109
+ ```
110
+ Browser (fetch POST)
111
+ → Next.js Route Handler (src/app/api/v1/batches/route.ts)
112
+ → better-auth session check
113
+ → Zod validation
114
+ → Prisma ORM
115
+ → [PostgreSQL] INSERT INTO batches
116
+ → SQS client
117
+ → [AWS SQS] SendMessage to processing queue
118
+ ← 201 JSON response
119
+ ← UI updates with new batch
120
+ ```
121
+ ```
122
+
123
+ ### Server Action Flow
124
+
125
+ ```markdown
126
+ ### Server Action: `submitBatch(batchId)`
127
+
128
+ **Trigger:** Called from `SubmitButton` component via form action or onClick
129
+ **Input:** `batchId: string`
130
+ **Output:** `ActionResponse<{ batch: Batch }>` — `{ data }` or `{ error }`
131
+
132
+ **Flow:**
133
+ 1. Client component calls `submitBatch(id)` — crosses network boundary
134
+ 2. `'use server'` — executes on the server process, not the browser
135
+ 3. `auth.api.getSession()` → validates session cookie
136
+ 4. `makeAuthorizationChecker()` → checks SUBMIT permission on this batch
137
+ 5. `prisma.batch.findUnique()` → **PostgreSQL** SELECT — verifies batch is DRAFT
138
+ 6. `prisma.batch.update({ status: 'SUBMITTED' })` → **PostgreSQL** UPDATE
139
+ 7. `sendToQueue(batch)` → **AWS SQS** — triggers async processing pipeline
140
+ 8. Returns `{ data: { batch } }` to client component
141
+ 9. Client re-renders with updated batch status
142
+
143
+ **Infra calls and when they happen:**
144
+ | Step | Service | Operation | Trigger condition |
145
+ |------|---------|-----------|-------------------|
146
+ | 3 | better-auth | Session lookup | Every call (authentication) |
147
+ | 5 | PostgreSQL | SELECT batch | After auth passes |
148
+ | 6 | PostgreSQL | UPDATE status | Only if batch is DRAFT |
149
+ | 7 | AWS SQS | SendMessage | Only after successful UPDATE |
150
+
151
+ **Error scenarios:**
152
+ - No session → returns `{ error: { message: "Unauthorized" } }`
153
+ - No permission → returns `{ error: { message: "Forbidden" } }`
154
+ - Batch not DRAFT → returns `{ error: { message: "Batch already submitted" } }`
155
+ - DB failure → caught by try/catch, logged via `logError()`, returns generic error
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Infrastructure & Cloud Service Documentation
161
+
162
+ Whenever a file interacts with an external service, document:
163
+
164
+ ```markdown
165
+ ### Infrastructure Call: `uploadToS3(file, key)`
166
+
167
+ **Service:** AWS S3
168
+ **SDK:** `@aws-sdk/client-s3` → `S3Client` + `PutObjectCommand`
169
+ **When called:** User uploads a file via the batch item form
170
+ **How called:**
171
+ - Client: form submission → server action `uploadFile()`
172
+ - Server: validates file type/size → generates S3 key → calls `s3Client.send()`
173
+ **Configuration:** Bucket name from `process.env.S3_BUCKET`, region from `AWS_REGION`
174
+ **What flows in:** File buffer + content type + generated key path
175
+ **What flows out:** S3 URL (constructed from bucket + key)
176
+ **Error handling:** Wrapped in try/catch → logs error → returns `{ error }` to client
177
+ ```
178
+
179
+ Document these services whenever they appear:
180
+
181
+ | Service | Look for | What to document |
182
+ |---------|----------|------------------|
183
+ | PostgreSQL | `prisma.*` calls | Which model, which operation (SELECT/INSERT/UPDATE/DELETE), what data |
184
+ | AWS S3 | `s3Client`, `PutObjectCommand`, `GetObjectCommand` | Upload/download, bucket, key pattern, when triggered |
185
+ | AWS SQS | `sqsClient`, `SendMessageCommand` | Queue name, message shape, what triggers the send |
186
+ | External HTTP | `fetch()` to external URLs | URL pattern, method, request/response shape |
187
+ | better-auth | `auth.api.*` | Which auth operation, what it returns, session shape |
188
+
189
+ ---
190
+
191
+ ## Design Patterns to Detect and Explain
192
+
193
+ ### TypeScript Patterns
194
+ | Pattern | What to look for | Why it matters |
195
+ |---------|-----------------|----------------|
196
+ | Discriminated Unions | `type Result = { ok: true; data: T } \| { ok: false; error: E }` | Type-safe branching without runtime checks |
197
+ | Type Guards | `function isX(val): val is X` | Narrows types so TS trusts your logic |
198
+ | Generics | `function fetch<T>(url): Promise<T>` | One function works with any type safely |
199
+ | Zod Schema Inference | `z.infer<typeof Schema>` | Single source of truth for runtime + compile-time types |
200
+ | Utility Types | `Partial<T>`, `Pick<T, K>`, `Omit<T, K>` | Transform types without rewriting them |
201
+ | Const Assertions | `as const` | Locks values to their literal types |
202
+ | Branded Types | `type Id = string & { __brand: 'Id' }` | Prevents mixing up IDs that are all strings |
203
+
204
+ ### React Patterns
205
+ | Pattern | What to look for | Why it matters |
206
+ |---------|-----------------|----------------|
207
+ | Custom Hooks | `function useX()` | Reusable stateful logic extracted from components |
208
+ | Provider Pattern | Context + `useX()` hook | Dependency injection without prop drilling |
209
+ | Compound Components | Related components sharing implicit state | Flexible API for complex UI (like Radix) |
210
+ | Controlled Components | State owned by parent, passed via props | Predictable form behavior |
211
+ | Composition | Small components assembled via children/slots | Flexible, testable, reusable UI |
212
+ | Optimistic Updates | Update UI before server confirms | Feels instant to the user |
213
+ | Memoization | `useMemo`, `useCallback`, `React.memo` | Avoids expensive re-renders |
214
+ | Ref Forwarding | `forwardRef` | Lets parent access child's DOM node |
215
+ | Error Boundaries | Class component with `getDerivedStateFromError` | Catches render crashes gracefully |
216
+
217
+ ### Next.js App Router Patterns
218
+ | Pattern | What to look for | Why it matters |
219
+ |---------|-----------------|----------------|
220
+ | Server Components | No `'use client'`, no hooks, no browser APIs | Zero JS shipped to client, fast initial load |
221
+ | Client Components | `'use client'` at top | Interactive, stateful, has access to browser |
222
+ | Server Actions | `'use server'` functions | Server-side mutations callable directly from client |
223
+ | Route Handlers | `route.ts` with `GET`/`POST`/etc exports | API endpoints within App Router |
224
+ | Layout Nesting | `layout.tsx` wrapping child routes | Persistent UI across navigation |
225
+ | Loading Convention | `loading.tsx` | Automatic Suspense boundary per route segment |
226
+ | Error Convention | `error.tsx` | Automatic error boundary per route segment |
227
+ | Middleware | `middleware.ts` at project root | Request interception (auth, redirects, headers) |
228
+ | Streaming | `<Suspense>` wrapping async server components | Progressive rendering — show what's ready |
229
+ | Metadata API | `export const metadata` or `generateMetadata()` | Per-route SEO and head management |
230
+
231
+ ### Architectural Patterns
232
+ | Pattern | What to look for | Why it matters |
233
+ |---------|-----------------|----------------|
234
+ | Repository | Functions in `lib/db.ts` abstracting Prisma | Swap DB without touching business logic |
235
+ | Singleton | Single `prisma` or `auth` instance exported | One connection pool, not one per request |
236
+ | Adapter | Wrappers around S3/SQS SDK calls | Consistent interface; easy to mock/swap |
237
+ | Middleware/Pipeline | Chained checks (auth → authz → validate → execute) | Each step has one job; easy to reorder |
238
+ | Factory | Functions returning configured objects | Encapsulate complex creation logic |
239
+ | Strategy | Behavior varies by parameter/config | Same interface, different implementations |
240
+
241
+ ## Important Scenarios to Cover
242
+
243
+ For each file, identify and explain the scenarios that matter:
244
+
245
+ - **Happy path** — normal successful flow from trigger to result
246
+ - **Error propagation** — where errors are caught, how they travel back to the UI
247
+ - **Edge cases** — empty arrays, null values, missing env vars, expired sessions
248
+ - **State transitions** — DRAFT → SUBMITTED → APPROVED (explain the state machine)
249
+ - **Permission gates** — who can call this, what roles/groups are checked
250
+ - **Async behavior** — loading states, what happens during the await, cleanup on unmount
251
+ - **Infra failure** — what if S3 is down? What if SQS rejects? What if DB times out?
252
+
253
+ ## Output Format
254
+
255
+ ### Module walkthrough
256
+
257
+ ```markdown
258
+ # Code Explanation: [directory or feature area]
259
+
260
+ ## Overview
261
+ [2-3 sentences: what this area of the codebase does, in plain language]
262
+
263
+ ## Tech & Services Involved
264
+ | Layer | Technology | Role |
265
+ |-------|-----------|------|
266
+ | Frontend | React 19 + Next.js 15 | UI rendering, routing, server components |
267
+ | State | Zustand | Client-side state management |
268
+ | Validation | Zod | Runtime type checking |
269
+ | Database | Prisma + PostgreSQL | Data persistence |
270
+ | Storage | AWS S3 | File uploads |
271
+ | Queue | AWS SQS | Async processing |
272
+ | Auth | better-auth | Session management |
273
+
274
+ ## File Map
275
+ [Simple tree showing which files exist and their one-line role]
276
+
277
+ ---
278
+
279
+ ## `src/path/to/file.ts`
280
+
281
+ **Role:** ...
282
+ **Called by:** ...
283
+ **Calls into:** ...
284
+ **Infra/services touched:** ...
285
+
286
+ ### `functionName(params)` → returnType
287
+ [explanation]
288
+
289
+ ### Patterns Used
290
+ - **Pattern Name:** one-line explanation of why it's used here
291
+
292
+ ---
293
+
294
+ [...repeat for each file...]
295
+
296
+ ---
297
+
298
+ ## Full Data Flow Diagram
299
+
300
+ ```
301
+ User clicks "Submit Batch"
302
+ → SubmitButton (client component, src/app/.../components/SubmitButton.tsx)
303
+ → submitBatch(batchId) — server action (src/app/.../actions.ts)
304
+ → [better-auth] session validation
305
+ → [PostgreSQL] SELECT batch WHERE id = ?
306
+ → [PostgreSQL] UPDATE batch SET status = 'SUBMITTED'
307
+ → [AWS SQS] SendMessage { batchId, action: 'process' }
308
+ ← { data: { batch: { ...updatedBatch } } }
309
+ ← Re-render with new status badge showing "SUBMITTED"
310
+
311
+ Meanwhile, async:
312
+ [AWS SQS] → Processing Worker (separate service)
313
+ → [PostgreSQL] UPDATE batch SET status = 'PROCESSING'
314
+ → validate each batch item
315
+ → [PostgreSQL] UPDATE batch SET status = 'APPROVED' or 'REJECTED'
316
+ ```
317
+
318
+ ## Key Patterns Summary
319
+
320
+ | Pattern | Where Used | Why |
321
+ |---------|-----------|-----|
322
+ | Server Action | `actions.ts` | Secure server-side mutations callable from client |
323
+ | Discriminated Union | `ActionResponse<T>` | Type-safe success/error without exceptions |
324
+ | Singleton | `src/lib/prisma.ts` | One DB connection pool for the whole app |
325
+ | Adapter | `src/lib/s3.ts`, `src/lib/sqs.ts` | Wrap AWS SDK behind simple functions |
326
+ | ... | ... | ... |
327
+
328
+ ## Concepts You Learned
329
+ [Bulleted list of every concept explained, with one-line definition — becomes a reference card]
330
+ ```
331
+
332
+ ## Tone & Style
333
+
334
+ ## Tone & Style
335
+
336
+ - **Friendly, encouraging, zero condescension** — "we" language, like a senior sitting next to you
337
+ - **Skip the obvious** — don't explain what `const x = 5` does
338
+ - **Explain the non-obvious** — why this pattern? why server vs client? why does this call SQS here?
339
+ - **Name every external service call** — never skip an infra interaction, these are critical for understanding the system
340
+ - **Use analogies** — "Think of SQS like a to-do list for a background worker — we drop a note saying 'process this batch', and a separate service picks it up later"
341
+ - **Show the full journey** — from user action → through the code → to the database/service → back to the UI
342
+ - **Keep it scannable** — headers, bullet points, tables, code blocks. No walls of text.
343
+
344
+ ---
345
+
346
+ ## What NOT to Do
347
+
348
+ - Don't review or critique the code
349
+ - Don't suggest improvements (unless asked)
350
+ - Don't explain import statements, simple variable declarations, or trivial types
351
+ - Don't explain test files
352
+ - Don't use jargon without defining it on first use
353
+ - Don't be condescending ("as you probably know" / "this is simple")
354
+ - Don't explain node_modules or third-party library internals (just say what the library does)
355
+ - Don't skip infrastructure calls — every DB query, S3 operation, SQS message, and auth check must appear in the flow
356
+ - Don't use subagents or workflows — this skill requires sequential understanding across files
357
+ - In commit teach-in, don't skip Change Context fields — every changed source file needs Why / What / Blast radius / If left out
358
+ - Don't invent blast-radius callers or breakage modes not evidenced by the diff or reference search
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: explore
3
+ description: Deep codebase reconnaissance with parallel read-only investigators — fans out automatically when a question spans multiple subsystems, options, or cross-cutting concerns, then synthesizes one coherent picture. Use before designing a change, when tracing how something works end to end, or when you need to know the blast radius of touching a shared value.
4
+ license: MIT
5
+ compatibility: Optional openspec CLI for change awareness. Node.js >= 18 for the bundled interlock-graph CLI.
6
+ argument-hint: "[question] [--autonomous]"
7
+ allowed-tools: Bash(interlock-graph *) Bash(openspec *) Agent Read Grep Glob
8
+ metadata:
9
+ type: discovery
10
+ outputs:
11
+ - .claude/handoff/explore-<slug>-<timestamp>.md
12
+ - openspec/changes/<name>/decisions.md
13
+ ---
14
+
15
+ Think deeply. Fan out when breadth is needed. Synthesize into understanding.
16
+
17
+ **Explore is for thinking, not implementing.** Read files, search code, investigate freely — but never write code and never implement a feature here. If asked to implement, say so and point at `/interlock:spec`. Writing OpenSpec artifacts or an explore brief is capturing thinking, not implementing, and is allowed.
18
+
19
+ ---
20
+
21
+ ## Two modes
22
+
23
+ **Conversational (default).** A thinking partner. Ask the user which thread to pull when several look interesting. Iterate.
24
+
25
+ **Autonomous.** Activate on `--autonomous`, "no questions", "don't ask me", "pursue all threads", or when `/interlock:spec` invokes this skill. Then:
26
+
27
+ 1. **No user Q&A.** No AskUserQuestion, no "which thread interests you?" menus. Open threads are work items, not questions.
28
+ 2. **Fan out on every thread.** Each interesting thread or option gets a read-only investigator. Cap 2–5, merge in synthesis.
29
+ 3. **Prefer defaults.** Ambiguous product intent → pick a reasonable default, record it under Assumptions Made and Pending Clarifications. Non-blocking. Every one of them is also a ledger row — see below.
30
+ 4. **Write the explore brief.** Required exit criterion. Follow `${CLAUDE_PLUGIN_ROOT}/shared/EXPLORE-BRIEF.md`. Path: `.claude/handoff/explore-<slug>-<YYYYMMDD-HHMMSS>.md`.
31
+ 5. **Close short.** Brief path plus a 3–5 line summary. Do not re-dump findings into chat — they are in the brief.
32
+
33
+ ---
34
+
35
+ ## Graph-first reconnaissance, before any fan-out
36
+
37
+ Follow Rule 0 of `${CLAUDE_PLUGIN_ROOT}/shared/TOOL-ECONOMY.md`. If the graph exists, seed scopes then fan out:
38
+
39
+ ```bash
40
+ interlock-graph query "<subsystem tokens>" --budget 1500
41
+ interlock-graph consumers <symbol-or-field>
42
+ interlock-graph path <A> <B>
43
+ ```
44
+
45
+ Scope investigators by real module nodes and consumer lists. Each investigator: graph, then grep for string-keyed and dynamic readers, then Read spans. If the graph is missing and the question spans 3+ subsystems, build it once (`interlock-graph build .`). Never rebuild when it already exists.
46
+
47
+ ---
48
+
49
+ ## When to fan out
50
+
51
+ **Fan out** when the question shows any of these:
52
+
53
+ | Signal | Example |
54
+ |--------|---------|
55
+ | Multiple subsystems touched | "How does deployment work end to end?" |
56
+ | Comparative analysis needed | "Redis or SQLite for this?" |
57
+ | Cross-file tracing required | "What validates the uploaded archive?" |
58
+ | Several options to research | "What are our options for real-time?" |
59
+ | Architecture mapping | "Map how auth flows through the system" |
60
+
61
+ **Mandatory fan-out — shared or derived value changes.** If the change alters what a value *means* or who reads it, run Pattern 4 per `${CLAUDE_PLUGIN_ROOT}/shared/INVARIANT-SWEEP.md`. This overrides the single-threaded default.
62
+
63
+ **Stay single-threaded** for conversational back-and-forth, single-file questions, quick clarifications, sequential discovery where A informs B, or when the user is thinking out loud.
64
+
65
+ When genuinely unsure, start single-threaded and escalate once you realize the question is broader than it looked.
66
+
67
+ ---
68
+
69
+ ## The four patterns
70
+
71
+ **Pattern 1 — Subsystem mapping.** The question spans several parts of the system. One investigator per stage of the flow. Synthesize into a diagram plus narrative.
72
+
73
+ **Pattern 2 — Option comparison.** Several viable approaches. One investigator per option, each reporting fit against *this* codebase, not generic pros and cons. Synthesize into a comparison table plus a recommendation.
74
+
75
+ **Pattern 3 — Layer sweep.** "Where does X happen?" when X could happen at several layers. One investigator per layer (client, API, business logic, persistence, post-processing). Synthesize into what each layer catches and where the gaps are.
76
+
77
+ **Pattern 4 — Impact analysis.** A change would alter what a value means or who reads it. One investigator per **disjoint** layer of the blast radius. Synthesize into an impact map with severity per area, and explicitly flag readers still consuming the raw, untransformed form.
78
+
79
+ ---
80
+
81
+ ## Investigator rules
82
+
83
+ 1. **Focused prompts.** Not "explore everything about X" — "trace the exact flow of Y through files A, B, C".
84
+ 2. **Read-only.** Investigators search, read, and report. They never write.
85
+ 3. **Structured returns.** JSON only (schema below). No narrative dump.
86
+ 4. **2–5 agents.** Fewer than 2 is not a fan-out; more than 5 means the decomposition is too granular.
87
+ 5. **Disjoint scopes.** Non-overlapping assignments so findings merge cleanly. For Pattern 4 this is load-bearing: partition the search space so no reader is missed or double-counted.
88
+ 6. **Label clearly** so progress is visible.
89
+ 7. **Use `subagent_type: "Explore"`** for read-only investigation.
90
+ 8. **Context hygiene.** Wrap user-supplied values per `${CLAUDE_PLUGIN_ROOT}/shared/CONTEXT-HYGIENE.md` before echoing them into a subagent prompt.
91
+ 9. **Locate before Read** — graph, then grep, then Read spans.
92
+
93
+ ### Prompt template
94
+
95
+ ```
96
+ Investigate: [[SPECIFIC_QUESTION]]
97
+
98
+ Context: [[BROADER_TOPIC]]
99
+
100
+ Look for:
101
+ - [specific thing 1]
102
+ - [specific thing 2]
103
+
104
+ Return JSON only:
105
+ { "files": [{ "path": "", "lines": "" }], "how": "", "connections": [], "gaps": [] }
106
+ ```
107
+
108
+ Resolve `[[SPECIFIC_QUESTION]]` and `[[BROADER_TOPIC]]` from session state at emit time. **An unresolved placeholder halts the fan-out — never gap-fill one.**
109
+
110
+ ---
111
+
112
+ ## Synthesis
113
+
114
+ After investigators return, you **must** synthesize. Never dump raw agent output.
115
+
116
+ 1. **Merge overlapping findings** — agents often reach the same file from different angles.
117
+ 2. **Build one unified picture** — a diagram where structure matters.
118
+ 3. **Name the gaps** — what did no agent find? What is still unclear?
119
+ 4. **Surface contradictions** — when two agents disagree, say so; do not silently pick one.
120
+ 5. **Run a completeness check.** Before closing, ask what is missing: a layer not swept, a reader not checked, an option not costed. **For Pattern 4 this is mandatory** — if any layer of the blast radius went uncovered, say so outright. A silent gap reads as "we covered everything" when you didn't.
121
+ 6. **Offer direction.** What is worth exploring next? In autonomous mode, pursue it or record it in the brief rather than asking.
122
+
123
+ The result should read like a senior engineer who just debriefed four specialists — not like four reports stapled together.
124
+
125
+ ---
126
+
127
+ ## OpenSpec awareness
128
+
129
+ ```bash
130
+ openspec list --json # is there an active change?
131
+ ```
132
+
133
+ When a change exists, read its artifacts via `openspec status --change "<name>" --json` for context, and offer to capture decisions as they crystallize:
134
+
135
+ | Insight | Captured in |
136
+ |---|---|
137
+ | New or changed requirement | `specs/<capability>/spec.md` |
138
+ | Design decision | `design.md` |
139
+ | Scope change | `proposal.md` |
140
+
141
+ ---
142
+
143
+ ## Every ambiguity becomes a ledger row
144
+
145
+ Follow `${CLAUDE_PLUGIN_ROOT}/shared/DECISION-LEDGER.md`. Do not restate its format.
146
+
147
+ - `## Pending Clarifications` → `needs_human` rows.
148
+ - `## Assumptions Made` → `agent_resolved` rows with followable evidence (`obvious` is not evidence).
149
+ - Never only chat — a conversational answer still gets a row citing the human.
150
+
151
+ Active change → write `openspec/changes/<change>/decisions.md` now and run `interlock ledger <change>`. No change yet → number them `D1`, `D2`, … in the brief (class stated) so `/interlock:spec` transcribes them. Never renumber; never reuse an id.
152
+
153
+ ---
154
+
155
+ ## Exit
156
+
157
+ Conversational mode ends when the user has what they need.
158
+
159
+ Autonomous mode ends by writing the explore brief and reporting its path — the brief is what `/interlock:spec` reads instead of re-deriving discovery. Writing it is how explore pays forward.
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: fix-tests
3
+ description: Discover and persist how a project's tests run, then run the suite and repair failures by root cause — clustering failures by error signature, fixing the shared cause once, and asserting no test was weakened. Use when tests are failing, the suite is red, CI pasted a failure log, or someone asks to make the tests pass.
4
+ license: MIT
5
+ compatibility: Requires the project's own test tooling. Node.js >= 18 for the bundled interlock CLI.
6
+ argument-hint: "[--e2e] [--dry-run] [--baseline <ref>] [--from-log <path>] [--reconfigure]"
7
+ allowed-tools: Bash Read Write Edit Glob Grep
8
+ metadata:
9
+ type: execution
10
+ outputs:
11
+ - .claude/testing/profile.json
12
+ - .claude/metrics/fix-tests-<timestamp>.json
13
+ ---
14
+
15
+ Make the suite green **without making it weaker**.
16
+
17
+ The failure mode this skill exists to prevent is the one where a suite goes green by shrinking — a deleted assertion, a `.skip`, a narrowed glob, a loosened matcher. That is not a fix; it is the bug plus a lie. Every step below is built around detecting it.
18
+
19
+ | Flag | Effect |
20
+ |------|--------|
21
+ | `--e2e` | Also run the e2e suite (only when `e2e.command` is configured) |
22
+ | `--dry-run` | Diagnose and propose; edit nothing |
23
+ | `--baseline <ref>` | Treat failures already present at `<ref>` as pre-existing |
24
+ | `--from-log <path>` | Parse a pasted CI log instead of running the suite first |
25
+ | `--reconfigure` | Re-run test discovery from scratch |
26
+
27
+ ---
28
+
29
+ ## 1. Resolve the test profile
30
+
31
+ Read `.claude/testing/profile.json`. If it is missing or `--reconfigure` was passed, run the discovery ladder in `${CLAUDE_PLUGIN_ROOT}/shared/TEST-PROFILE.md`: manifests, then runner configs, then CI workflows, then **at most four questions** for gaps that genuinely block a correct run.
32
+
33
+ Resolve `typecheck.command` and `lint.command` on the same pass, the way `coverage.command` is resolved: the profile's own value first, then `package.json` `scripts.typecheck` / `scripts["type-check"]` and `scripts.lint`, then the step in the CI job that runs it. Never ask for either and never invent one — nothing found is `command: null`, which a ship run skips with a stated reason. A ship run's inter-wave checkpoint has no typecheck to run unless this field is filled, and a typecheck failure is a halting kind there.
34
+
35
+ This is the only skill permitted to interview the user about tests. Every later run is zero-question.
36
+
37
+ Persist the profile and mirror it into the `## Testing` block in `CLAUDE.md` (or `AGENTS.md`), between the managed markers, idempotently.
38
+
39
+ ---
40
+
41
+ ## 2. Run and baseline
42
+
43
+ Run the unit command. Record `total`, `passed`, `failed`, `skipped` — this is the baseline that step 6 asserts against.
44
+
45
+ Split failures into:
46
+
47
+ - **Newly broken** — not failing at `--baseline <ref>` (or all failures when no baseline was given)
48
+ - **Pre-existing** — already failing at the baseline. **Report only.** Do not absorb them into this fix unless the user explicitly widens scope.
49
+
50
+ ---
51
+
52
+ ## 3. Flake check
53
+
54
+ Re-run **only** the failing files or test names, once, using `single_file` / `filter_syntax`.
55
+
56
+ - Still failing → a real failure; continue.
57
+ - Passed on re-run → record it in `profile.known_flaky` and **do not "fix" it**. Mention it in the report. A test that passes on retry is a flaky test, and "fixing" it usually means deleting the thing that caught a race.
58
+
59
+ ---
60
+
61
+ ## 4. Cluster by error signature
62
+
63
+ | Signature | Example | Likely shared root cause |
64
+ |---|---|---|
65
+ | Same missing import | `Cannot find module './utils'` | Missing export or wrong path |
66
+ | Same type error | `Type 'X' not assignable to 'Y'` | Interface shape mismatch |
67
+ | Same fixture or setup | `ReferenceError: db is not defined` | Missing shared setup |
68
+ | Same env or config | `ECONNREFUSED`, `env.X is undefined` | Missing env or config |
69
+ | Same assertion pattern | Many `expected X received Y` | Logic bug in shared code |
70
+
71
+ With `--from-log`, parse the log into the same clusters without an initial run — but still record a live baseline before applying any fix.
72
+
73
+ ---
74
+
75
+ ## 5. Fix by root cause
76
+
77
+ ```
78
+ iterations = 0, MAX = 5
79
+
80
+ WHILE failures remain AND iterations < MAX AND NOT --dry-run:
81
+ 1. Pick the cluster covering the MOST failures
82
+ 2. Choose ONE fix — source first
83
+ 3. Apply it
84
+ 4. Re-run ONLY the affected tests
85
+ 5. Drop resolved failures; iterations++
86
+ ```
87
+
88
+ **Source first.** When a test and the code disagree, the default assumption is that the code is wrong. Change the test only when you can state why the test's expectation was incorrect — and say that out loud in the report.
89
+
90
+ **Never**: delete a test, add `.skip`/`.only`, loosen an assertion to match observed output, narrow a glob, raise a timeout to hide a race, or catch-and-ignore an error to make a run pass.
91
+
92
+ `--dry-run` stops after clustering: print the diagnoses and proposed fixes, edit nothing.
93
+
94
+ After 5 iterations, stop and report the unresolved clusters and what was tried. Do not thrash.
95
+
96
+ ---
97
+
98
+ ## 6. Full re-run and no-regression assert
99
+
100
+ Run the full suite again, then assert against the baseline:
101
+
102
+ - `total >= baseline.total`
103
+ - `skipped <= baseline.skipped`
104
+
105
+ **If either assert fails, revert the weakening change** — something deleted tests or added skips. A green suite that runs fewer tests than it did before is a regression wearing a green badge.
106
+
107
+ ---
108
+
109
+ ## 7. Persist
110
+
111
+ - Refresh `.claude/testing/profile.json` (`updated`, `known_flaky`, `notes`) with anything learned about running tests. Preserve every key you did not resolve yourself.
112
+ - Confirm the `## Testing` block is still a single idempotent pair of markers.
113
+ - Write at most **3** `.claude/memory/failure-modes/<slug>.md` entries for genuinely recurring gotchas, with index lines in `.claude/memory/MEMORY.md`. One-off typos do not belong in memory.
114
+ - Write `.claude/metrics/fix-tests-<YYYYMMDD-HHMMSS>.json` with the baseline, final counts, clusters, fixes applied, and anything unresolved.
115
+
116
+ ---
117
+
118
+ ## 8. Report
119
+
120
+ Baseline → final counts. Root causes fixed, one line each. Failures reverted as pre-existing. Flaky tests recorded. Anything still red, and what was tried. If you changed a test rather than source, say which and why.