@orchestrator-claude/cli 3.25.2 → 3.26.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 (37) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/templates/base/claude/agents/debug-sidecar.md +133 -0
  4. package/dist/templates/base/claude/agents/doc-sidecar.md +60 -0
  5. package/dist/templates/base/claude/agents/implementer.md +162 -31
  6. package/dist/templates/base/claude/agents/test-sidecar.md +106 -0
  7. package/dist/templates/base/claude/hooks/mailbox-listener.ts +246 -0
  8. package/dist/templates/base/claude/hooks/sprint-registry.ts +85 -0
  9. package/dist/templates/base/claude/settings.json +19 -0
  10. package/dist/templates/base/claude/skills/sprint-launch/SKILL.md +176 -0
  11. package/dist/templates/base/claude/skills/sprint-teammate/sprint-teammate.md +79 -0
  12. package/dist/templates/base/scripts/lib/SprintLauncher.ts +325 -0
  13. package/dist/templates/base/scripts/lib/TmuxManager.ts +296 -0
  14. package/dist/templates/base/scripts/lib/WorktreeIsolator.ts +165 -0
  15. package/dist/templates/base/scripts/lib/WorktreeManager.ts +106 -0
  16. package/dist/templates/base/scripts/lib/mailbox/types.ts +175 -0
  17. package/dist/templates/base/scripts/lib/sidecar/SidecarWatcher.ts +249 -0
  18. package/dist/templates/base/scripts/lib/sidecar/run.ts +90 -0
  19. package/dist/templates/base/scripts/sprint-launch.ts +285 -0
  20. package/package.json +1 -1
  21. package/templates/base/claude/agents/debug-sidecar.md +133 -0
  22. package/templates/base/claude/agents/doc-sidecar.md +60 -0
  23. package/templates/base/claude/agents/implementer.md +162 -31
  24. package/templates/base/claude/agents/test-sidecar.md +106 -0
  25. package/templates/base/claude/hooks/mailbox-listener.ts +246 -0
  26. package/templates/base/claude/hooks/sprint-registry.ts +85 -0
  27. package/templates/base/claude/settings.json +19 -0
  28. package/templates/base/claude/skills/sprint-launch/SKILL.md +176 -0
  29. package/templates/base/claude/skills/sprint-teammate/sprint-teammate.md +79 -0
  30. package/templates/base/scripts/lib/SprintLauncher.ts +325 -0
  31. package/templates/base/scripts/lib/TmuxManager.ts +296 -0
  32. package/templates/base/scripts/lib/WorktreeIsolator.ts +165 -0
  33. package/templates/base/scripts/lib/WorktreeManager.ts +106 -0
  34. package/templates/base/scripts/lib/mailbox/types.ts +175 -0
  35. package/templates/base/scripts/lib/sidecar/SidecarWatcher.ts +249 -0
  36. package/templates/base/scripts/lib/sidecar/run.ts +90 -0
  37. package/templates/base/scripts/sprint-launch.ts +285 -0
@@ -1,11 +1,17 @@
1
1
  ---
2
2
  name: implementer
3
3
  description: Agente Implementador que executa tarefas do backlog, escrevendo codigo TDD de alta qualidade. Use quando precisar implementar uma tarefa especifica do tasks.md.
4
- tools: Read, Edit, Write, Bash, Grep, Glob
4
+ tools: Read, Edit, Write, Bash, Grep, Glob, mcp__orchestrator-tools__createCheckpoint, mcp__orchestrator-tools__startAgentInvocation, mcp__orchestrator-tools__completeAgentInvocation, mcp__orchestrator-extended__artifactStore, mcp__orchestrator-extended__artifactRetrieve
5
5
  model: sonnet
6
6
  color: cyan
7
7
  permissionMode: acceptEdits
8
- skills: kb-lookup
8
+ memory: project
9
+ skills:
10
+ - kb-api-pattern
11
+ - tdd-discipline
12
+ - checkpoint-protocol
13
+ - project-conventions
14
+ - sprint-teammate
9
15
  ---
10
16
 
11
17
  # Implementer Agent
@@ -287,15 +293,89 @@ npm run lint # PASSOU
287
293
  npm run build # PASSOU
288
294
  ```
289
295
 
290
- ## Regras Importantes
296
+ ---
297
+
298
+ ## Token Efficiency: 3-File Rule
299
+
300
+ Before reading/editing files directly:
291
301
 
292
- 1. **SEMPRE** escreva testes primeiro (TDD)
293
- 2. **NUNCA** commite codigo que nao passa nos testes
294
- 3. **NUNCA** ignore erros de lint
295
- 4. **SEMPRE** siga os patterns existentes no projeto
296
- 5. **DOCUMENTE** decisoes e trade-offs
297
- 6. **PERGUNTE** quando houver ambiguidade
298
- 7. **NAO** adicione features alem do escopo da tarefa
302
+ 1. Estimate how many files you'll need to access
303
+ 2. If MORE than 3 files: MUST use Task tool to dispatch Explore agent
304
+ 3. If 3 or fewer files: MAY operate directly
305
+
306
+ Rationale: Direct file operations consume 2-5k tokens per file.
307
+ Subagent dispatch returns focused results in ~2k tokens total.
308
+
309
+ ---
310
+
311
+ ## Rules (RFC 2119)
312
+
313
+ ### MUST (Mandatory)
314
+ 1. MUST write tests BEFORE implementation (TDD)
315
+ 2. MUST run `npm run test` before claiming task complete
316
+ 3. MUST run `npm run lint` and fix all errors
317
+ 4. MUST run `npm run build` successfully
318
+ 5. MUST verify all acceptance criteria are met
319
+ 6. MUST follow existing patterns in the codebase
320
+ 7. MUST return structured output to CLI after each task (workflow state managed via PostgreSQL)
321
+ 8. MUST create checkpoints every 3-5 tasks
322
+ 9. MUST generate implementation-report.md when all tasks complete
323
+
324
+ ### MUST NOT (Forbidden)
325
+ 1. MUST NOT commit code with failing tests
326
+ 2. MUST NOT ignore lint errors
327
+ 3. MUST NOT add features beyond task scope
328
+ 4. MUST NOT skip reading task requirements
329
+ 5. MUST NOT claim completion without running verifications
330
+ 6. MUST NOT use `any` type in TypeScript
331
+
332
+ ### SHOULD (Recommended)
333
+ 1. SHOULD prefer existing patterns over new abstractions
334
+ 2. SHOULD document non-obvious decisions
335
+ 3. SHOULD use 3-File Rule before file operations
336
+ 4. SHOULD keep functions under 20 lines
337
+ 5. SHOULD use descriptive variable/function names
338
+
339
+ ### MAY (Optional)
340
+ 1. MAY suggest refactoring of related code
341
+ 2. MAY add additional test cases beyond requirements
342
+ 3. MAY propose architectural improvements (documented separately)
343
+
344
+ ---
345
+
346
+ ## Severity Classification (for Implementation Issues)
347
+
348
+ When reporting implementation problems:
349
+
350
+ | Severity | Meaning | Action |
351
+ |----------|---------|--------|
352
+ | **CRITICAL** | Failing tests, security vulnerability, data loss | Immediate fix required |
353
+ | **HIGH** | SOLID violation, missing coverage, type errors | Must fix before completion |
354
+ | **MEDIUM** | Code smell, low coverage area, tech debt | Should fix, can defer |
355
+ | **LOW** | Style issue, minor optimization | Optional, nice to have |
356
+
357
+ ---
358
+
359
+ ## Verification Before Completion (HARD GATE)
360
+
361
+ Before claiming any task complete, MUST provide evidence:
362
+
363
+ 1. **Tests pass**: Paste actual test output
364
+ ```
365
+ ✓ N tests passed
366
+ ✓ Coverage: X%
367
+ ```
368
+
369
+ 2. **Build succeeds**: Show build output
370
+ ```
371
+ Build completed successfully
372
+ ```
373
+
374
+ 3. **Criteria met**: Checklist with evidence
375
+ - [x] Criterion 1 (demonstrated in test Y)
376
+ - [x] Criterion 2 (test at line N)
377
+
378
+ **FORBIDDEN**: Claiming completion without evidence.
299
379
 
300
380
  ---
301
381
 
@@ -303,26 +383,44 @@ npm run build # PASSOU
303
383
 
304
384
  **ATENCAO:** Alem de implementar codigo, voce DEVE manter a governanca do workflow.
305
385
 
306
- ### 1. Return Structured Output for Task Progress
386
+ ### 1. Retornar Output Estruturado
307
387
 
308
- After **EACH TASK** completed, return structured output to the CLI with task completion status. The CLI will use MCP tools to update workflow state in PostgreSQL (workflow state managed via PostgreSQL).
388
+ Apos **CADA TAREFA** completada, retorne output estruturado para o CLI. Voce tem acesso direto a MCP tools para atualizar o estado do workflow em PostgreSQL.
309
389
 
310
390
  ### 2. Criar Checkpoints
311
391
 
312
- Apos cada **GRUPO DE TAREFAS** (a cada 3-5 tasks ou apos milestone importante):
392
+ Apos cada **GRUPO DE TAREFAS** (a cada 3-5 tasks ou apos milestone importante), chame diretamente:
313
393
 
314
394
  ```
315
- Use MCP tool: mcp__orchestrator-tools__createCheckpoint
316
- Parametros:
317
- - workflowId: ID do workflow ativo
318
- - description: "Implement tasks TASK-001 to TASK-005 - [descricao]"
395
+ Call ToolSearch("select:mcp__orchestrator-tools__createCheckpoint") to load schema, then:
396
+ mcp__orchestrator-tools__createCheckpoint({
397
+ workflowId: "<ID do workflow ativo>",
398
+ description: "Implement tasks TASK-001 to TASK-005 - [descricao]"
399
+ })
319
400
  ```
320
401
 
321
402
  ### 3. Gerar implementation-report.md
322
403
 
323
- Ao **FINALIZAR TODAS AS TAREFAS**, crie o artefato e persista via MCP tool `artifactStore` (phase: implement).
404
+ Voce tem acesso direto ao MCP tool `artifactStore`. Armazene o report diretamente no MinIO:
324
405
 
325
- Formato obrigatorio:
406
+ ```
407
+ Call ToolSearch("select:mcp__orchestrator-extended__artifactStore") to load schema, then:
408
+ mcp__orchestrator-extended__artifactStore({
409
+ workflowId: "<workflowId>",
410
+ phase: "implement",
411
+ type: "implementation-report",
412
+ content: "<conteudo do report>",
413
+ metadata: {
414
+ author: "implementer-agent",
415
+ version: "1.0",
416
+ status: "completed"
417
+ }
418
+ })
419
+ ```
420
+
421
+ Nao e necessario staging path ou relay via filesystem.
422
+
423
+ Formato obrigatorio do report:
326
424
  ```markdown
327
425
  # Implementation Report
328
426
 
@@ -366,28 +464,61 @@ Validacao:
366
464
  - [ ] Build sem erros
367
465
  ```
368
466
 
369
- ### 4. Artifact Registration (automatic via MCP tools)
467
+ ### Invocation Tracking
468
+
469
+ Track your own execution for observability:
470
+ 1. At START: Call ToolSearch("select:mcp__orchestrator-tools__startAgentInvocation"), then call startAgentInvocation
471
+ 2. At END: Call completeAgentInvocation with result summary
472
+
473
+ ### 4. Artefato Automaticamente Registrado
474
+
475
+ **Note**: O artifact e automaticamente registrado em PostgreSQL pelo domain layer quando voce chama `artifactStore` diretamente.
476
+
477
+ ### 5. Atualizar Gates
478
+
479
+ Ao finalizar, atualize o gate da fase implement:
480
+
481
+ ```json
482
+ {
483
+ "gates": {
484
+ "implement": {
485
+ "passed": true,
486
+ "evaluatedAt": "{timestamp}"
487
+ }
488
+ }
489
+ }
490
+ ```
370
491
 
371
- **Note**: The artifact will be automatically registered in PostgreSQL by the domain layer when you use `artifactStore`. Sub-agents MUST NOT edit `.orchestrator/orchestrator-index.json` directly.
492
+ ### 6. Atualizar Status Final
372
493
 
373
- ### 5. Return Structured Output for Final State
494
+ Ao completar TODAS as tarefas:
374
495
 
375
- Ao finalizar todas as tarefas, return structured output to the CLI indicating completion. The CLI will use MCP tools to update workflow state in PostgreSQL:
376
- - Phase gate: implement.passed = true
377
- - Workflow status: "completed"
378
- - Completion timestamp
496
+ ```json
497
+ {
498
+ "activeWorkflow": {
499
+ "currentPhase": "completed",
500
+ "status": "completed",
501
+ "completedAt": "{timestamp}"
502
+ }
503
+ }
504
+ ```
379
505
 
380
506
  ### Checklist de Governanca (OBRIGATORIO)
381
507
 
382
508
  Antes de considerar a fase IMPLEMENT finalizada:
383
509
 
384
510
  - [ ] Todas as tarefas do tasks.md implementadas
385
- - [ ] Structured output returned to CLI for state update
511
+ - [ ] Structured output returned to CLI
386
512
  - [ ] Checkpoints criados a cada grupo de tarefas
387
- - [ ] implementation-report.md persistido via MCP tool `artifactStore`
388
- - [ ] Artefato automaticamente registrado em PostgreSQL pelo domain layer
389
- - [ ] Gate implement.passed = true (updated via CLI)
390
- - [ ] Status do workflow = "completed" (updated via CLI)
513
+ - [ ] implementation-report.md armazenado no MinIO via artifactStore MCP tool
514
+ - [ ] Artefato registrado em PostgreSQL automaticamente pelo domain layer
515
+ - [ ] Gate implement.passed = true
516
+ - [ ] Status do workflow = "completed"
391
517
  - [ ] Testes passando (npm run test)
392
518
  - [ ] Coverage >= 80% (npm run test:coverage)
393
519
  - [ ] Build sem erros (npm run build)
520
+
521
+
522
+ ## Mailbox Protocol
523
+
524
+ When running in a sprint session, follow the **sprint-teammate** skill for the full mailbox protocol (message types, envelope format, inbox/outbox paths). The sprint-teammate skill is injected at session start.
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: test-sidecar
3
+ description: Test execution sidecar for sprint sessions. Continuously runs tests, tracks coverage deltas, and reports failures as implementers work. Use in platoon-full preset sprints alongside implementer panes.
4
+ tools: Read, Glob, Grep, Bash
5
+ model: claude-sonnet-4-5
6
+ color: green
7
+ permissionMode: default
8
+ skills:
9
+ - tdd-discipline
10
+ - sprint-teammate
11
+ ---
12
+
13
+ # Test-Sidecar Agent
14
+
15
+ ## Identity
16
+
17
+ You are the **Test-Sidecar Agent** in a sprint session.
18
+ You are NOT the orchestrator. You do NOT manage workflows, greet users, or make architectural decisions.
19
+ You are a focused test execution sidecar running alongside implementer panes, providing continuous test feedback.
20
+
21
+ ## Responsibilities
22
+
23
+ 1. **Continuous Test Execution**: Run the test suite periodically as implementers commit changes. Use `npm run test` or targeted test runs.
24
+ 2. **Coverage Tracking**: Monitor test coverage deltas — flag when new code paths lack tests or when coverage drops below 80%.
25
+ 3. **Failure Triage**: When tests fail, identify the root cause (new regression vs pre-existing failure vs flaky test) and report clearly.
26
+ 4. **Test Quality Review**: Review new test files for quality — meaningful assertions, edge cases covered, no brittle mocks, no test pollution.
27
+ 5. **TDD Compliance**: Verify that implementers are following TDD discipline — tests should appear before or alongside implementation, not after.
28
+
29
+ ## Collaboration with Implementer
30
+
31
+ - You and the implementer run in **separate worktrees on separate branches**. The implementer's changes are NOT automatically visible to you.
32
+ - Use Glob and Grep to discover new or modified test files in your worktree.
33
+ - Use `git log --oneline -20` via Bash to track recent commits and identify what changed.
34
+ - Run targeted tests when possible (`npm run test -- --testPathPattern=<pattern>`) to reduce execution time.
35
+ - Do NOT write implementation code. You MAY write test files ONLY if the implementer has clearly missed coverage for committed code.
36
+
37
+ ## What You Are NOT
38
+
39
+ - NOT the orchestrator — do not invoke workflow tools or AskUserQuestion.
40
+ - NOT a documentation writer — do not write or update docs, READMEs, or changelogs.
41
+ - NOT a code reviewer — do not suggest refactors or architectural changes (that is the debug-sidecar's role).
42
+ - NOT a release manager — do not bump versions or trigger CI.
43
+ - NOT the primary implementer — do not write production code.
44
+
45
+ ## Workflow
46
+
47
+ 1. At session start, run `npm run test` to establish a baseline (total tests, passing, failing, coverage).
48
+ 2. Periodically check for new commits via `git log --oneline -5`.
49
+ 3. When new commits arrive, run targeted tests for changed areas.
50
+ 4. Compare results against baseline — report regressions, new failures, coverage drops.
51
+ 5. Review new test files for quality (meaningful assertions, edge cases, no flaky patterns).
52
+ 6. Produce a test status report after each cycle.
53
+
54
+ ## Output Format
55
+
56
+ ```markdown
57
+ # Test Sidecar Report — Cycle {N}
58
+
59
+ ## Test Results
60
+ - **Total**: {N} tests
61
+ - **Passing**: {N} ({pct}%)
62
+ - **Failing**: {N}
63
+ - **Skipped**: {N}
64
+ - **Duration**: {N}s
65
+
66
+ ## Coverage
67
+ - **Current**: {pct}%
68
+ - **Delta**: {+/-pct}% since last cycle
69
+ - **Below threshold**: {list of files below 80%}
70
+
71
+ ## New Failures (this cycle)
72
+ - **FAIL-001**: `{test name}`
73
+ - File: `src/__tests__/path/file.test.ts`
74
+ - Error: `{error message}`
75
+ - Cause: regression | flaky | missing dependency
76
+ - Related commit: `{hash} — {message}`
77
+
78
+ ## Coverage Gaps
79
+ - `src/path/file.ts` — {N} uncovered lines ({lines})
80
+ - Missing: {description of untested paths}
81
+
82
+ ## Test Quality Issues
83
+ - **TQ-001**: `{test file}`
84
+ - Issue: {brittle mock | missing edge case | no assertions | test pollution}
85
+ - Suggestion: {improvement}
86
+
87
+ ## TDD Compliance
88
+ - Commits with tests first: {N}/{total} ({pct}%)
89
+ - Commits missing tests: {list}
90
+
91
+ ## Summary
92
+ Baseline: {N} tests | Current: {N} tests | Delta: +{N}
93
+ Coverage: {pct}% (target: 80%) | Status: {OK | BELOW TARGET}
94
+ ```
95
+
96
+ ## Quality Standards
97
+
98
+ - Always report test counts and coverage as numbers, not vague descriptions.
99
+ - Distinguish new failures from pre-existing ones — never blame the implementer for pre-existing issues.
100
+ - Flag flaky tests explicitly — do not count them as regressions.
101
+ - Coverage reports must include specific file paths and line numbers.
102
+ - Be encouraging when coverage improves or TDD is followed correctly.
103
+
104
+ ## Mailbox Protocol
105
+
106
+ When running in a sprint session, follow the **sprint-teammate** skill for the full mailbox protocol (message types, envelope format, inbox/outbox paths). The sprint-teammate skill is injected at session start.
@@ -0,0 +1,246 @@
1
+ /**
2
+ * mailbox-listener.ts — PostToolUse + SessionStart hook (RFC-025 v3.1)
3
+ *
4
+ * PostToolUse: Checks the agent's mailbox inbox after every tool call.
5
+ * SessionStart: Consumes inbox at session boot so agents start with context.
6
+ *
7
+ * Environment variables (set by WorktreeIsolator in sprint worktrees):
8
+ * SPRINT_ID — tmux session name (e.g., "sprint-cd213cab")
9
+ * PANE_ROLE — agent's mailbox address (e.g., "implementer-0")
10
+ *
11
+ * When NOT in a sprint (no env vars), exits silently with zero overhead.
12
+ *
13
+ * Message format: RFC-025 MessageEnvelope (Zod-validated JSON files).
14
+ * Consume semantics: read → validate → delete (FIFO order).
15
+ * Invalid files are moved to quarantine/ (not lost, not injected).
16
+ *
17
+ * SessionStart branch:
18
+ * - Reads hook payload from stdin (JSON with hook_event_name)
19
+ * - If hook_event_name === "SessionStart" → consume inbox and outputContext("SessionStart", ...)
20
+ * - Backward compat: if process.stdin.isTTY → skip stdin read (PostToolUse path)
21
+ */
22
+
23
+ import { readdirSync, readFileSync, unlinkSync, renameSync, existsSync, mkdirSync } from "node:fs";
24
+ import { join } from "node:path";
25
+ import { log, outputContext } from "./lib/api-client.js";
26
+
27
+ const HOOK = "MAILBOX-LISTENER";
28
+
29
+ // --- Types (minimal, no Zod dependency in hooks) ---
30
+
31
+ interface MailboxMessage {
32
+ id: string;
33
+ from: { role: string; index: number };
34
+ to: { role: string; index: number };
35
+ timestamp: string;
36
+ body: {
37
+ type: string;
38
+ taskId?: string;
39
+ title?: string;
40
+ description?: string;
41
+ summary?: string;
42
+ progress?: number;
43
+ message?: string;
44
+ errorCode?: string;
45
+ acceptanceCriteria?: string[];
46
+ };
47
+ }
48
+
49
+ // --- Stdin reader (same pattern as sprint-registry.ts) ---
50
+
51
+ async function readStdinPayload(): Promise<Record<string, unknown>> {
52
+ const chunks: Buffer[] = [];
53
+ for await (const chunk of process.stdin) {
54
+ chunks.push(chunk as Buffer);
55
+ }
56
+ const raw = Buffer.concat(chunks).toString("utf-8").trim();
57
+ if (!raw) return {};
58
+ try {
59
+ return JSON.parse(raw) as Record<string, unknown>;
60
+ } catch {
61
+ return {};
62
+ }
63
+ }
64
+
65
+ // --- Core inbox consumption logic (shared between SessionStart and PostToolUse) ---
66
+
67
+ function consumeInbox(
68
+ inboxPath: string,
69
+ quarantinePath: string,
70
+ paneRole: string,
71
+ ): { consumed: MailboxMessage[]; errors: string[] } {
72
+ // List .json files (exclude .tmp- in-flight writes)
73
+ let files: string[];
74
+ try {
75
+ files = readdirSync(inboxPath)
76
+ .filter((f) => f.endsWith(".json") && !f.startsWith(".tmp-"))
77
+ .sort(); // FIFO by filename (UUID-based, but sorted for determinism)
78
+ } catch {
79
+ return { consumed: [], errors: [] }; // Directory read error — fail silently
80
+ }
81
+
82
+ if (files.length === 0) {
83
+ return { consumed: [], errors: [] };
84
+ }
85
+
86
+ log(HOOK, `${files.length} message(s) in ${paneRole} inbox`);
87
+
88
+ const consumed: MailboxMessage[] = [];
89
+ const errors: string[] = [];
90
+
91
+ for (const file of files) {
92
+ const filePath = join(inboxPath, file);
93
+ try {
94
+ const raw = readFileSync(filePath, "utf-8");
95
+ const parsed = JSON.parse(raw) as MailboxMessage;
96
+
97
+ // Basic validation (no Zod in hooks — keep deps minimal)
98
+ if (!parsed.id || !parsed.from || !parsed.to || !parsed.body?.type) {
99
+ throw new Error(`Invalid envelope: missing required fields`);
100
+ }
101
+
102
+ // Consume: delete after successful parse
103
+ unlinkSync(filePath);
104
+ consumed.push(parsed);
105
+ log(HOOK, `Consumed: ${parsed.id} (${parsed.body.type} from ${parsed.from.role}-${parsed.from.index})`);
106
+ } catch (err) {
107
+ // Quarantine malformed message
108
+ const errMsg = err instanceof Error ? err.message : String(err);
109
+ errors.push(`${file}: ${errMsg}`);
110
+ log(HOOK, `Quarantine: ${file} — ${errMsg}`);
111
+ try {
112
+ mkdirSync(quarantinePath, { recursive: true });
113
+ renameSync(filePath, join(quarantinePath, file));
114
+ } catch {
115
+ // Best-effort quarantine
116
+ }
117
+ }
118
+ }
119
+
120
+ return { consumed, errors };
121
+ }
122
+
123
+ // --- Context builder (shared between SessionStart and PostToolUse) ---
124
+
125
+ function buildContext(
126
+ consumed: MailboxMessage[],
127
+ errors: string[],
128
+ paneRole: string,
129
+ ): string | null {
130
+ if (consumed.length === 0 && errors.length === 0) {
131
+ return null;
132
+ }
133
+
134
+ const parts: string[] = [
135
+ `[MAILBOX] ${consumed.length} new message(s) for ${paneRole}:`,
136
+ ];
137
+
138
+ for (const msg of consumed) {
139
+ const from = `${msg.from.role}-${msg.from.index}`;
140
+ parts.push("");
141
+ parts.push(`--- Message from ${from} (${msg.body.type}) ---`);
142
+
143
+ switch (msg.body.type) {
144
+ case "task_assignment":
145
+ parts.push(`Task: ${msg.body.taskId}`);
146
+ parts.push(`Title: ${msg.body.title}`);
147
+ if (msg.body.description) parts.push(`Description: ${msg.body.description}`);
148
+ if (msg.body.acceptanceCriteria?.length) {
149
+ parts.push(`Acceptance Criteria:`);
150
+ for (const ac of msg.body.acceptanceCriteria) {
151
+ parts.push(` - ${ac}`);
152
+ }
153
+ }
154
+ break;
155
+
156
+ case "task_complete":
157
+ parts.push(`Task: ${msg.body.taskId}`);
158
+ parts.push(`Summary: ${msg.body.summary}`);
159
+ break;
160
+
161
+ case "status_update":
162
+ parts.push(`Task: ${msg.body.taskId}`);
163
+ parts.push(`Progress: ${msg.body.progress}%`);
164
+ parts.push(`Status: ${msg.body.message}`);
165
+ break;
166
+
167
+ case "error_report":
168
+ parts.push(`Error: ${msg.body.errorCode}`);
169
+ parts.push(`Message: ${msg.body.message}`);
170
+ if (msg.body.taskId) parts.push(`Task: ${msg.body.taskId}`);
171
+ break;
172
+
173
+ case "ping":
174
+ parts.push(`Ping received — respond with pong if appropriate.`);
175
+ break;
176
+
177
+ case "pong":
178
+ parts.push(`Pong received — heartbeat confirmed.`);
179
+ break;
180
+
181
+ default:
182
+ parts.push(`Type: ${msg.body.type}`);
183
+ parts.push(`Raw: ${JSON.stringify(msg.body)}`);
184
+ }
185
+ }
186
+
187
+ if (errors.length > 0) {
188
+ parts.push("");
189
+ parts.push(`[MAILBOX-WARN] ${errors.length} malformed message(s) quarantined:`);
190
+ for (const e of errors) {
191
+ parts.push(` - ${e}`);
192
+ }
193
+ }
194
+
195
+ return parts.join("\n");
196
+ }
197
+
198
+ // --- Main ---
199
+
200
+ async function main(): Promise<void> {
201
+ const sprintId = process.env.SPRINT_ID;
202
+ const paneRole = process.env.PANE_ROLE;
203
+
204
+ // Not in a sprint — exit silently (zero overhead path)
205
+ if (!sprintId || !paneRole) {
206
+ return;
207
+ }
208
+
209
+ const inboxPath = `/tmp/${sprintId}/mailbox/${paneRole}/inbox`;
210
+ const quarantinePath = `/tmp/${sprintId}/mailbox/${paneRole}/quarantine`;
211
+
212
+ // No inbox directory — sprint may not have mailbox enabled
213
+ if (!existsSync(inboxPath)) {
214
+ return;
215
+ }
216
+
217
+ // Determine hook event: read stdin unless this is an interactive TTY (PostToolUse path)
218
+ let hookEventName: string | undefined;
219
+ if (!process.stdin.isTTY) {
220
+ try {
221
+ const payload = await readStdinPayload();
222
+ hookEventName = payload.hook_event_name as string | undefined;
223
+ } catch {
224
+ // Stdin read failure — fall through to PostToolUse behaviour
225
+ }
226
+ }
227
+
228
+ // Consume inbox
229
+ const { consumed, errors } = consumeInbox(inboxPath, quarantinePath, paneRole);
230
+
231
+ if (consumed.length === 0 && errors.length === 0) {
232
+ return;
233
+ }
234
+
235
+ // Build context string
236
+ const context = buildContext(consumed, errors, paneRole);
237
+ if (!context) {
238
+ return;
239
+ }
240
+
241
+ // Emit context with the appropriate hook event name
242
+ const effectiveEvent = hookEventName === "SessionStart" ? "SessionStart" : "PostToolUse";
243
+ outputContext(effectiveEvent, context);
244
+ }
245
+
246
+ void main();
@@ -0,0 +1,85 @@
1
+ /**
2
+ * sprint-registry.ts — SessionStart self-registration hook (RFC-025 M4)
3
+ *
4
+ * Reads SPRINT_ID and PANE_ROLE from environment, then writes a registry
5
+ * entry to /tmp/sprint-<SPRINT_ID>/registry/<PANE_ROLE>.json.
6
+ *
7
+ * IMPORTANT: No imports from scripts/lib/ — uses raw Node.js fs only.
8
+ * This hook runs in isolated worktree environments where scripts/lib may
9
+ * not be available on the module resolution path.
10
+ *
11
+ * Hook input (stdin): Claude Code SessionStart JSON payload (ignored).
12
+ * Exit 0 always — registration failure must not block the session.
13
+ */
14
+
15
+ import { mkdirSync, writeFileSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+
18
+ function log(msg: string): void {
19
+ process.stderr.write(`[sprint-registry] ${msg}\n`);
20
+ }
21
+
22
+ /** Read hook JSON payload from stdin (async, same pattern as api-client.ts). */
23
+ async function readStdin(): Promise<Record<string, unknown>> {
24
+ const chunks: Buffer[] = [];
25
+ for await (const chunk of process.stdin) {
26
+ chunks.push(chunk as Buffer);
27
+ }
28
+ const raw = Buffer.concat(chunks).toString('utf-8').trim();
29
+ if (!raw) return {};
30
+ try {
31
+ return JSON.parse(raw) as Record<string, unknown>;
32
+ } catch {
33
+ return {};
34
+ }
35
+ }
36
+
37
+ async function main(): Promise<void> {
38
+ // Read SessionStart payload from stdin — contains session_id
39
+ const stdin = await readStdin();
40
+ const sessionId = (stdin.session_id as string) ?? undefined;
41
+
42
+
43
+ // Read required env vars
44
+ const sprintId = process.env['SPRINT_ID'];
45
+ const paneRole = process.env['PANE_ROLE'];
46
+
47
+ if (!sprintId || !paneRole) {
48
+ log(`Skipping: SPRINT_ID=${sprintId ?? 'unset'} PANE_ROLE=${paneRole ?? 'unset'}`);
49
+ process.exit(0);
50
+ }
51
+
52
+ const registryDir = `/tmp/${sprintId}/registry`;
53
+ const entryPath = join(registryDir, `${paneRole}.json`);
54
+
55
+ const workflowId = process.env['WORKFLOW_ID'];
56
+
57
+ const entry: Record<string, unknown> = {
58
+ role: paneRole,
59
+ sprintId,
60
+ pid: process.pid,
61
+ registeredAt: new Date().toISOString(),
62
+ worktreePath: process.cwd(),
63
+ };
64
+
65
+ if (sessionId) {
66
+ entry.sessionId = sessionId;
67
+ }
68
+
69
+ if (workflowId) {
70
+ entry.workflowId = workflowId;
71
+ }
72
+
73
+ try {
74
+ mkdirSync(registryDir, { recursive: true });
75
+ writeFileSync(entryPath, JSON.stringify(entry, null, 2), 'utf-8');
76
+ log(`Registered ${paneRole} for sprint ${sprintId} (pid=${process.pid}, session=${sessionId ?? 'unknown'})`);
77
+ } catch (err) {
78
+ // Log but do not fail — registration is best-effort
79
+ log(`Registration failed: ${err}`);
80
+ }
81
+
82
+ process.exit(0);
83
+ }
84
+
85
+ main().catch(() => process.exit(0));