@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.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/base/claude/agents/debug-sidecar.md +133 -0
- package/dist/templates/base/claude/agents/doc-sidecar.md +60 -0
- package/dist/templates/base/claude/agents/implementer.md +162 -31
- package/dist/templates/base/claude/agents/test-sidecar.md +106 -0
- package/dist/templates/base/claude/hooks/mailbox-listener.ts +246 -0
- package/dist/templates/base/claude/hooks/sprint-registry.ts +85 -0
- package/dist/templates/base/claude/settings.json +19 -0
- package/dist/templates/base/claude/skills/sprint-launch/SKILL.md +176 -0
- package/dist/templates/base/claude/skills/sprint-teammate/sprint-teammate.md +79 -0
- package/dist/templates/base/scripts/lib/SprintLauncher.ts +325 -0
- package/dist/templates/base/scripts/lib/TmuxManager.ts +296 -0
- package/dist/templates/base/scripts/lib/WorktreeIsolator.ts +165 -0
- package/dist/templates/base/scripts/lib/WorktreeManager.ts +106 -0
- package/dist/templates/base/scripts/lib/mailbox/types.ts +175 -0
- package/dist/templates/base/scripts/lib/sidecar/SidecarWatcher.ts +249 -0
- package/dist/templates/base/scripts/lib/sidecar/run.ts +90 -0
- package/dist/templates/base/scripts/sprint-launch.ts +285 -0
- package/package.json +1 -1
- package/templates/base/claude/agents/debug-sidecar.md +133 -0
- package/templates/base/claude/agents/doc-sidecar.md +60 -0
- package/templates/base/claude/agents/implementer.md +162 -31
- package/templates/base/claude/agents/test-sidecar.md +106 -0
- package/templates/base/claude/hooks/mailbox-listener.ts +246 -0
- package/templates/base/claude/hooks/sprint-registry.ts +85 -0
- package/templates/base/claude/settings.json +19 -0
- package/templates/base/claude/skills/sprint-launch/SKILL.md +176 -0
- package/templates/base/claude/skills/sprint-teammate/sprint-teammate.md +79 -0
- package/templates/base/scripts/lib/SprintLauncher.ts +325 -0
- package/templates/base/scripts/lib/TmuxManager.ts +296 -0
- package/templates/base/scripts/lib/WorktreeIsolator.ts +165 -0
- package/templates/base/scripts/lib/WorktreeManager.ts +106 -0
- package/templates/base/scripts/lib/mailbox/types.ts +175 -0
- package/templates/base/scripts/lib/sidecar/SidecarWatcher.ts +249 -0
- package/templates/base/scripts/lib/sidecar/run.ts +90 -0
- 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
|
-
|
|
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
|
-
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Token Efficiency: 3-File Rule
|
|
299
|
+
|
|
300
|
+
Before reading/editing files directly:
|
|
291
301
|
|
|
292
|
-
1.
|
|
293
|
-
2.
|
|
294
|
-
3.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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.
|
|
386
|
+
### 1. Retornar Output Estruturado
|
|
307
387
|
|
|
308
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
404
|
+
Voce tem acesso direto ao MCP tool `artifactStore`. Armazene o report diretamente no MinIO:
|
|
324
405
|
|
|
325
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
492
|
+
### 6. Atualizar Status Final
|
|
372
493
|
|
|
373
|
-
|
|
494
|
+
Ao completar TODAS as tarefas:
|
|
374
495
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
|
511
|
+
- [ ] Structured output returned to CLI
|
|
386
512
|
- [ ] Checkpoints criados a cada grupo de tarefas
|
|
387
|
-
- [ ] implementation-report.md
|
|
388
|
-
- [ ] Artefato
|
|
389
|
-
- [ ] Gate implement.passed = true
|
|
390
|
-
- [ ] Status do workflow = "completed"
|
|
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));
|