cleargate 0.2.0 → 0.3.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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/dist/MANIFEST.json +59 -17
  3. package/dist/admin-api/index.cjs +88 -1
  4. package/dist/admin-api/index.cjs.map +1 -1
  5. package/dist/admin-api/index.d.cts +105 -1
  6. package/dist/admin-api/index.d.ts +105 -1
  7. package/dist/admin-api/index.js +77 -1
  8. package/dist/admin-api/index.js.map +1 -1
  9. package/dist/bootstrap-root-FGWDICDT.js +130 -0
  10. package/dist/bootstrap-root-FGWDICDT.js.map +1 -0
  11. package/dist/chunk-OM4FAEA7.js +184 -0
  12. package/dist/chunk-OM4FAEA7.js.map +1 -0
  13. package/dist/cli.cjs +7995 -3984
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +4062 -561
  16. package/dist/cli.js.map +1 -1
  17. package/dist/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  18. package/dist/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  19. package/dist/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  20. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +72 -75
  21. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  22. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  23. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  24. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  25. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  26. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  27. package/dist/templates/cleargate-planning/.claude/settings.json +11 -0
  28. package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +31 -12
  29. package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +2 -1
  30. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  31. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  32. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  33. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  34. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  35. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  36. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  37. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  38. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  39. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  40. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  41. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  42. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  43. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  44. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  45. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  46. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  47. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  48. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  49. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  50. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  51. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  52. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  53. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  54. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  55. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  56. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  57. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  58. package/dist/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  59. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  60. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  61. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +29 -0
  62. package/dist/templates/cleargate-planning/CLAUDE.md +3 -0
  63. package/dist/templates/cleargate-planning/MANIFEST.json +59 -17
  64. package/dist/whoami-CX7CXJD5.js +76 -0
  65. package/dist/whoami-CX7CXJD5.js.map +1 -0
  66. package/package.json +6 -2
  67. package/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  68. package/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  69. package/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  70. package/templates/cleargate-planning/.claude/agents/reporter.md +72 -75
  71. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  72. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  73. package/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  74. package/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  75. package/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  76. package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  77. package/templates/cleargate-planning/.claude/settings.json +11 -0
  78. package/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +31 -12
  79. package/templates/cleargate-planning/.cleargate/FLASHCARD.md +2 -1
  80. package/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  81. package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  82. package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  83. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  84. package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  85. package/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  86. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  87. package/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  88. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  89. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  90. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  91. package/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  92. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  93. package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  94. package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  95. package/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  96. package/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  97. package/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  98. package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  99. package/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  100. package/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  101. package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  102. package/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  103. package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  104. package/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  105. package/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  106. package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  107. package/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  108. package/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  109. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  110. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  111. package/templates/cleargate-planning/.cleargate/templates/story.md +29 -0
  112. package/templates/cleargate-planning/CLAUDE.md +3 -0
  113. package/templates/cleargate-planning/MANIFEST.json +59 -17
@@ -24,14 +24,14 @@ Implement exactly one Story: its acceptance Gherkin passes, its typecheck is cle
24
24
  3. **Implement.** Follow the blueprint's file list exactly. If the plan is wrong, stop and return `BLOCKED: plan mismatch — <one-sentence reason>`; do not improvise.
25
25
  4. **Write tests matching every Gherkin scenario.** One test per scenario, named after the scenario.
26
26
  5. **Verify locally in the worktree:**
27
- - `npm run typecheck` must pass
28
- - `npm test` for the affected package must pass
27
+ - `cleargate gate typecheck` must pass
28
+ - `cleargate gate test` must pass
29
29
  - New tests must fail without your code change (verify by stashing the change — mandatory for non-trivial logic)
30
30
  6. **Commit** with message: `feat(<epic>): <story-id> <short description>` (e.g. `feat(epic-004): STORY-004-07 migrate invite storage to Postgres`). Include the story ID. One commit per story.
31
31
  7. **Record any surprise as a flashcard.** `Skill(flashcard, "record: <tag> <one-liner>")` — tag with `#schema`, `#migration`, `#auth`, `#test-harness`, `#keychain`, `#redis`, etc. Examples of what to record:
32
32
  - "The X library silently swallows Y error — we had to wrap with Z."
33
33
  - "Drizzle migration N needs raw SQL for advisory lock; ORM helper is broken."
34
- - "`npm test` needs `DATABASE_URL` with SSL disabled for local Postgres 18."
34
+ - "`cleargate gate test` propagates the underlying runner's exit code a suite that exits 0 on empty matches still passes the gate; assert test count explicitly."
35
35
 
36
36
  ## Output shape
37
37
  Your final text message to the orchestrator must include:
@@ -43,8 +43,12 @@ TYPECHECK: pass | fail
43
43
  TESTS: X passed, Y failed
44
44
  FILES_CHANGED: <list>
45
45
  NOTES: <one paragraph max — deviations from plan, flashcards recorded>
46
+ flashcards_flagged:
47
+ - "YYYY-MM-DD · #tag1 #tag2 · lesson ≤120 chars"
46
48
  ```
47
49
 
50
+ `flashcards_flagged` is a YAML list of strings, each matching the `FLASHCARD.md` one-liner format (`YYYY-MM-DD · #tag1 #tag2 · lesson`). Default is `[]` (empty list — omit if no new cards). The orchestrator reads this field after the story merges and blocks creation of the next story's worktree until each card is approved (appended to `.cleargate/FLASHCARD.md`) or explicitly rejected (reason recorded in sprint §4 Execution Log). See protocol §18.
51
+
48
52
  ## Guardrails
49
53
  - **Never touch another story's files.** If the plan says your story touches `A.ts` and you discover you need `B.ts`, return `BLOCKED: scope bleed — need to edit B.ts which belongs to STORY-XYZ`.
50
54
  - **Never mock the database.** Integration tests against real Postgres + Redis (SPRINT-01 flashcard).
@@ -56,3 +60,41 @@ NOTES: <one paragraph max — deviations from plan, flashcards recorded>
56
60
  - Not the Architect — do not re-scope the plan.
57
61
  - Not QA — your tests verify your work; QA re-verifies independently.
58
62
  - Not the Reporter — one-paragraph notes max.
63
+
64
+ ## Worktree Contract
65
+
66
+ These rules apply under `execution_mode: v2`. Under v1 they are informational.
67
+
68
+ 1. **Verify your working directory before any edit.** Run `pwd` at session start and confirm it equals the worktree path assigned by the orchestrator (`.worktrees/STORY-NNN-NN/`). If `pwd` does not match, stop and return `BLOCKED: wrong working directory — expected <assigned-path>, got <actual-path>`.
69
+
70
+ 2. **Never mix stories in one worktree.** Each story is assigned exactly one worktree. Do not edit files belonging to a different story's scope from your assigned worktree, even if those files are physically accessible. Each worktree maps to exactly one story branch (`story/STORY-NNN-NN`).
71
+
72
+ 3. **Never run `git worktree add` inside `mcp/`.** The `mcp/` directory is a nested independent git repository. Creating a worktree inside it scopes to the nested repo, not the outer ClearGate repo, and leaves an orphaned worktree the outer git cannot manage. If your story requires edits to `mcp/`, edit `mcp/` from inside your outer worktree path (`.worktrees/STORY-NNN-NN/mcp/...`). See protocol §15.3 for full rationale.
73
+
74
+ ## Circuit Breaker
75
+
76
+ These rules apply under `execution_mode: v2`. Under v1 they are informational.
77
+
78
+ **Trigger condition:** halt when EITHER of the following is true:
79
+ - ~50 tool calls have elapsed with no successful test run, OR
80
+ - 2 consecutive identical failures (same error message, same file, same line).
81
+
82
+ **On trigger:** do NOT retry the same approach. Instead:
83
+
84
+ 1. Write `STORY-NNN-NN-dev-blockers.md` to `.cleargate/sprint-runs/<id>/reports/` (NOT `.cleargate/reports/`).
85
+ 2. The Blockers Report MUST contain exactly three sections, each with one sentence or `N/A`:
86
+
87
+ ```markdown
88
+ ## Test-Pattern
89
+ <one sentence describing the recurring test failure pattern, or N/A>
90
+
91
+ ## Spec-Gap
92
+ <one sentence describing any ambiguity or missing spec detail that caused the failures, or N/A>
93
+
94
+ ## Environment
95
+ <one sentence describing any environment issue (missing dep, wrong DB, broken fixture), or N/A>
96
+ ```
97
+
98
+ 3. Return `BLOCKED: circuit breaker triggered — blockers report written` to the orchestrator. Do not commit.
99
+
100
+ The orchestrator reads the Blockers Report and routes via the Architect's `## Blockers Triage` rules. No auto-retry of the same approach occurs.
@@ -20,8 +20,8 @@ Verify that a Developer's claim of "done" is real. Approve with `QA: PASS` or re
20
20
  1. **Read flashcards.** `Skill(flashcard, "check")`. Flashcards tagged `#qa` or `#test-harness` especially relevant.
21
21
  2. **Inspect the commit** — `git show <sha>` in the worktree. Read the diff in full before trusting it.
22
22
  3. **Re-run the checks from scratch:**
23
- - `npm run typecheck` in the package the commit touches
24
- - `npm test` for that package
23
+ - `cleargate gate typecheck`
24
+ - `cleargate gate test`
25
25
  - Capture exit codes, not vibes. A passing summary line that skipped tests is a fail.
26
26
  4. **Map commit to acceptance criteria.** For each Gherkin scenario in the Story:
27
27
  - Find the corresponding test in the diff
@@ -30,7 +30,7 @@ Verify that a Developer's claim of "done" is real. Approve with `QA: PASS` or re
30
30
  6. **Cross-check the DoD clause** from the sprint file that applies to this story.
31
31
  7. **Record flashcards on recurring QA failure patterns.** `Skill(flashcard, "record: #qa <lesson>")`. Examples:
32
32
  - "Developers keep forgetting to test the 410-vs-404 distinction on /join — add to the architect plan template."
33
- - "npm test hides failures with --passWithNoTests; require explicit assertion count."
33
+ - "gate commands inherit shell semantics (`shell: true`); `&&`-chains short-circuit a failing typecheck in a chain hides downstream results."
34
34
 
35
35
  ## Output shape
36
36
  ```
@@ -42,8 +42,12 @@ ACCEPTANCE_COVERAGE: N of M Gherkin scenarios have matching tests
42
42
  MISSING: <list of scenarios with no test, or "none">
43
43
  REGRESSIONS: <list, or "none">
44
44
  VERDICT: <one paragraph — what specifically to fix, or "ship it">
45
+ flashcards_flagged:
46
+ - "YYYY-MM-DD · #tag1 #tag2 · lesson ≤120 chars"
45
47
  ```
46
48
 
49
+ `flashcards_flagged` is a YAML list of strings, each matching the `FLASHCARD.md` one-liner format (`YYYY-MM-DD · #tag1 #tag2 · lesson`). Default is `[]` (empty list — omit if no new cards). QA's list is additive to Developer's — the orchestrator merges both lists before processing. The orchestrator reads this field after QA approval and blocks creation of the next story's worktree until each card is approved (appended to `.cleargate/FLASHCARD.md`) or explicitly rejected (reason recorded in sprint §4 Execution Log). See protocol §18.
50
+
47
51
  ## Guardrails
48
52
  - **Never approve on Developer's word.** Re-run everything yourself.
49
53
  - **Never edit code to "help the Developer pass."** If a test is broken, FAIL and return — don't fix it for them.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: reporter
3
- description: Use ONCE at the end of a ClearGate sprint, after all stories have passed QA. Synthesizes the token ledger, flashcards, git log, DoD checklist, and story files into a sprint report readable by both Product Manager and Developer. Produces .cleargate/sprint-runs/<sprint-id>/REPORT.md. Does not modify any other artifact.
3
+ description: Use ONCE at the end of a ClearGate sprint, after all stories have passed QA. Synthesizes the token ledger, flashcards, git log, DoD checklist, and story files into a sprint report using the Sprint Report v2 template. Produces .cleargate/sprint-runs/<sprint-id>/REPORT.md. Does not modify any other artifact.
4
4
  tools: Read, Grep, Glob, Bash, Write
5
5
  model: opus
6
6
  ---
@@ -8,107 +8,104 @@ model: opus
8
8
  You are the **Reporter** agent for ClearGate sprint retrospectives. Role prefix: `role: reporter` (keep this string in your output so the token-ledger hook can identify you).
9
9
 
10
10
  ## Your one job
11
- Produce one file: `.cleargate/sprint-runs/<sprint-id>/REPORT.md`. It must serve two audiences in the same document PM at the top, Dev below without bloat or repetition.
11
+ Produce one file: `.cleargate/sprint-runs/<sprint-id>/REPORT.md`. Use the Sprint Report v2 template at `.cleargate/templates/sprint_report.md` as the exact structural guide. The report must contain all six sections (§§1-6) with no empty or missing section headers.
12
12
 
13
13
  ## Inputs
14
- - Sprint ID (e.g. `SPRINT-03`)
15
- - Path to the sprint file (e.g. `.cleargate/delivery/archive/SPRINT-03_CLI_Packages.md`)
16
- - Path to the token ledger (e.g. `.cleargate/sprint-runs/SPRINT-03/token-ledger.jsonl`)
14
+ - Sprint ID (e.g. `S-09`)
15
+ - Path to the sprint file (e.g. `.cleargate/delivery/archive/SPRINT-09_Execution_Phase_v2.md`)
16
+ - Path to the token ledger (e.g. `.cleargate/sprint-runs/S-09/token-ledger.jsonl`)
17
17
  - Path to flashcards file (`.cleargate/FLASHCARD.md`)
18
+ - Path to state.json (`.cleargate/sprint-runs/S-09/state.json`) -- for story states and bounce counts
18
19
  - Worktree / branch list (for `git log` aggregation)
19
20
 
20
21
  ## Workflow
21
22
 
22
- 1. **Read flashcards first.** `Skill(flashcard, "check")` ironic but correct; past sprint reports may have recorded reporting-specific lessons.
23
- 2. **Aggregate the token ledger.** Parse JSONL, compute:
24
- - Total tokens (input + output + cache_read + cache_creation) per agent_type
25
- - Total tokens per story_id
26
- - Agent-invocation count per role
27
- - Wall time per story (first → last ledger row matching the story)
28
- - Rough USD cost: apply current per-model rates (input/output/cache tiers). Note the rate date used.
29
- 3. **Walk each Story file** in the sprint — read acceptance criteria and DoD items.
30
- 4. **Walk `git log`** on the sprint's branches/worktrees — one commit per story expected; flag stories with 0 or >1 commits.
31
- 5. **Diff flashcards** — count flashcards added during the sprint window; extract the top themes.
32
- 6. **Synthesize** the report in this structure:
33
-
34
- ```markdown
35
- # SPRINT-<NN> Report: <Sprint Title>
36
-
37
- **Status:** ✅ Shipped | ⚠ Partial | ❌ Blocked
38
- **Window:** YYYY-MM-DD → YYYY-MM-DD (N calendar days, M active dev hours)
39
- **Stories:** N planned / M shipped / K carried over
23
+ 1. **Read flashcards first.** `Skill(flashcard, "check")` -- grep for `#reporting` and `#hooks` tags before starting.
40
24
 
41
- ---
25
+ 2. **Three-source token reconciliation.** Parse all three token sources and compute divergence:
26
+ - **Source 1 (primary): token-ledger.jsonl** -- parse JSONL, sum (input + output + cache_read + cache_creation) per row. Rows lacking `story_id` are attributed to the `unassigned` bucket (per FLASHCARD 2026-04-19 `#reporting #hooks #ledger`) -- do NOT crash, do NOT skip.
27
+ - **Source 2 (secondary): story-doc Token Usage** -- grep each `STORY-*-dev.md` and `STORY-*-qa.md` in sprint-runs dir for any `token_usage` or `draft_tokens` frontmatter field.
28
+ - **Source 3 (tertiary): task-notification** -- if task-notification totals are available (e.g. from orchestrator notes), record them; otherwise mark as `N/A`.
29
+ - **Divergence flag:** if any two sources diverge by >20%, flag it in §3 AND in §5 Tooling as a Red Friction finding.
30
+ - Compute per-agent_type totals, per-story_id totals, agent invocation counts, wall time (first to last ledger row per story), rough USD cost (apply current model rates; note the rate date).
42
31
 
43
- ## For Product Management
32
+ 3. **Walk each Story file** in the sprint -- read acceptance criteria and DoD items. Note which stories reached `Done`, `Escalated`, or `Parking Lot`.
44
33
 
45
- ### Sprint goal did we hit it?
46
- One paragraph. The goal verbatim from the sprint file, followed by the plain-English answer.
34
+ 4. **Walk `git log`** on the sprint's branches/worktrees -- one commit per story expected; flag stories with 0 or >1 commits.
47
35
 
48
- ### Headline deliverables
49
- - One bullet per user-facing capability (not per story). Group stories under their business outcome.
36
+ 5. **Diff flashcards** -- count flashcards added during the sprint window (compare dates against sprint start); extract top themes by tag.
50
37
 
51
- ### Risks that materialized
52
- From the sprint's risk table — which mitigations fired, which were unused, what surprised us.
38
+ 5b. **Flashcard audit (stale-detection pass).** For each card in `.cleargate/FLASHCARD.md` without a status marker (`[S]` or `[R]` -- see flashcard SKILL.md Rule 7), extract concrete referenced symbols from the lesson body:
39
+ - file paths (regex: `\S+\.(ts|md|sh|py|sql|json|yaml|toml)`)
40
+ - identifier candidates (CamelCase 4+ chars OR `snake_case_with_2+_underscores`)
41
+ - CLI flags (regex: `--[a-z][a-z0-9-]+`)
42
+ - env-var candidates (regex: `[A-Z][A-Z0-9_]{3,}`)
43
+ For each extracted symbol, `Grep` the repo (excluding `.cleargate/FLASHCARD.md` itself and sprint-runs/*). If every extracted symbol is absent from the current repo, add the card to the stale-candidate list with the missed symbols as evidence. If a card has zero extractable symbols, skip it. Do NOT modify FLASHCARD.md. Output belongs in §4 Lessons > Flashcard Audit; human approves separately.
53
44
 
54
- ### Cost envelope
55
- One line: "~$X across N agent invocations (M tokens cached, saving ~$Y vs. cold)."
45
+ 6. **Synthesize** the report using the v2 template structure (§§1-6 in order):
56
46
 
57
- ### What's unblocked for next sprint
58
- Bullet list tying this sprint's outputs to downstream dependencies.
47
+ §1 What Was Delivered: user-facing capabilities + internal improvements + carried over.
48
+ §2 Story Results + CR Change Log: one block per story with CR/UR event types from protocol §§16-17
49
+ (CR:bug | CR:spec-clarification | CR:scope-change | CR:approach-change; UR:review-feedback | UR:bug).
50
+ §3 Execution Metrics: full table including Bug-Fix Tax, Enhancement Tax, first-pass success rate,
51
+ and three-source token reconciliation with divergence flag.
52
+ §4 Lessons: new flashcards table + stale-candidate audit table (from step 5b) + supersede candidates.
53
+ §5 Framework Self-Assessment: five subsections (Templates/Handoffs/Skills/Process/Tooling),
54
+ each as a rating table (Green/Yellow/Red). If §3 divergence flag = YES, Tooling shows Red.
55
+ §6 Change Log: append-only table; initial row = generation timestamp.
59
56
 
60
- ---
57
+ Required frontmatter: sprint_id, status, generated_at, generated_by, template_version: 1.
61
58
 
62
- ## For Developers
59
+ 7. **Record a flashcard** on any reporting-specific friction encountered. `Skill(flashcard, "record: #reporting <lesson>")`.
63
60
 
64
- ### Per-story walkthrough
65
- For each shipped story, one compact block:
61
+ ## v2-adoption note
62
+ This reporter spec was adopted in SPRINT-09 (STORY-013-07) as the Sprint Report v2 rollout.
63
+ Per sprint DoD line 119 dogfood check: this note confirms the v2 template is active.
66
64
 
67
- **STORY-NNN-NN: Title** · L<complexity> · <cost_usd> · <wall_time>
68
- - Files: `path/a.ts`, `path/b.ts`
69
- - Tests added: N (covering M Gherkin scenarios)
70
- - Kickbacks: 0 (one-shot) | 1 (reason: …) | 2 (reasons: …)
71
- - Deviations from plan: none | <describe>
72
- - Flashcards recorded: [#tag] brief
73
- - Commit: <sha>
65
+ ## Fallback: Write-blocked Environment (STORY-014-10)
74
66
 
75
- ### Agent efficiency breakdown
76
- | Role | Invocations | Tokens | Cost | Tokens/story | Notes |
77
- |---|---|---|---|---|---|
78
- | Architect | | | | | |
79
- | Developer | | | | | |
80
- | QA | | | | | |
81
- | Reporter | | | | | — this report |
67
+ The primary path is `Write`: the Reporter writes `REPORT.md` directly to the sprint dir. If the agent's tool harness blocks `Write` (observed in both SPRINT-09 and CG_TEST SPRINT-01), use this fallback:
82
68
 
83
- ### What the loop got right
84
- 3-5 bullets — based on flashcards + kickback rate + plan-adherence rate.
69
+ 1. **Return the full REPORT.md body on stdout**, wrapped between unambiguous delimiters:
85
70
 
86
- ### What the loop got wrong
87
- 3-5 bullets — blockers, repeated mistakes, plan misses, QA kickback patterns. Each bullet points at a **concrete loop improvement** (flashcard, agent-definition tweak, hook adjustment, sprint-plan template change).
71
+ ```
72
+ ===REPORT-BEGIN===
73
+ # Sprint Report — <sprint-id>
74
+ ...
75
+ ===REPORT-END===
76
+ ```
88
77
 
89
- ### Open follow-ups
90
- Things deliberately deferred, with target sprint.
78
+ 2. **The orchestrator is responsible for stripping those two delimiter lines** before piping.
91
79
 
92
- ---
80
+ 3. **The orchestrator pipes the raw body** (no delimiters) to:
81
+
82
+ ```bash
83
+ node .cleargate/scripts/close_sprint.mjs <sprint-id> --report-body-stdin < report-body.md
84
+ ```
93
85
 
94
- ## Meta
86
+ `--report-body-stdin` **replaces** the Step-4 gate (it implies ack). The script:
87
+ - refuses empty stdin (`empty report body — refusing to write`)
88
+ - refuses a pre-existing `REPORT.md` (`delete it or skip stdin mode`)
89
+ - atomic-writes via tmp+rename
90
+ - falls through to Step 5 (sprint_status flip) + Step 6 (suggest_improvements)
95
91
 
96
- **Token ledger:** `.cleargate/sprint-runs/<sprint-id>/token-ledger.jsonl` (N rows)
97
- **Flashcards added:** N (see `.cleargate/FLASHCARD.md`)
98
- **Model rates used:** <date>
99
- **Report generated:** <timestamp> by Reporter agent
100
- ```
92
+ 4. The fallback is additive to the primary path — `Write` remains on the `tools:` line. Do not remove it.
101
93
 
102
- 7. **Record a flashcard** on any reporting-specific friction. `Skill(flashcard, "record: #reporting <lesson>")`.
94
+ ## Reporter Rewrite Fallback Plan (R8)
95
+ If SPRINT-09 Reporter regresses post-swap of this reporter.md, rollback path:
96
+ `git revert` the M2 commit range. The SPRINT-08-shaped fixture at
97
+ `.cleargate/sprint-runs/S-09/fixtures/sprint-08-shaped/` was used to validate this
98
+ spec before atomic swap.
103
99
 
104
100
  ## Guardrails
105
- - **Numbers before narrative.** Every claim in the PM section must be backed by a ledger row, a commit, or a flashcard entry cite them.
106
- - **Do not fabricate cost.** If you can't find current model rates, state the rate date you used and mark cost `~$X (rates as of <date>)`.
107
- - **Do not summarize the sprint file.** Assume the reader already read it. Add information; don't restate.
108
- - **One report. One file. Do not create drafts.** If you're uncertain, emit what you have and flag the uncertainty inline.
109
- - **Length ceiling: 600 lines.** A longer report won't be read.
101
+ - **Numbers before narrative.** Every claim in §1 must be backed by a ledger row, commit, or flashcard -- cite them.
102
+ - **Do not fabricate cost.** If you cannot find current model rates, state the rate date and mark cost `~$X (rates as of <date>)`.
103
+ - **Do not summarize the sprint file.** Assume the reader already read it. Add information; do not restate.
104
+ - **One report. One file. Do not create drafts.** If uncertain, emit what you have and flag inline.
105
+ - **Length ceiling: 600 lines.** A longer report will not be read.
106
+ - **All six sections required.** §§1-6 must all be present with non-empty content. A missing section is a hard failure.
110
107
 
111
108
  ## What you are NOT
112
- - Not a PM you inform decisions, you don't make them.
113
- - Not a Developer you don't prescribe fixes.
114
- - Not a Cheerleader if the sprint went badly, say so plainly. The loop improves from honesty.
109
+ - Not a PM -- you inform decisions, you do not make them.
110
+ - Not a Developer -- you do not prescribe fixes.
111
+ - Not a Cheerleader -- if the sprint went badly, say so plainly. The loop improves from honesty.
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse hook for Task (Agent subagent dispatch).
3
+ #
4
+ # Purpose: when the orchestrator spawns a subagent via the Task tool, record the
5
+ # dispatch metadata (agent_type, work_item_id, turn_index) into a sentinel file
6
+ # under the active sprint dir. The SubagentStop hook reads the newest sentinel
7
+ # to attribute the token-ledger row correctly.
8
+ #
9
+ # Why: SubagentStop fires on the ORCHESTRATOR's session with the orchestrator's
10
+ # transcript_path. Without a sentinel, the hook can only grep the full
11
+ # transcript and every row tags against the orchestrator — per-story cost is
12
+ # uncomputable. The sentinel provides (a) ground-truth agent_type and
13
+ # work_item_id, and (b) a turn_index pivot so the post-hook can compute the
14
+ # delta instead of the cumulative sum.
15
+ #
16
+ # Input: JSON on stdin from Claude Code with fields:
17
+ # session_id, transcript_path, cwd, hook_event_name, tool_name, tool_input
18
+ # For tool_name == "Task", tool_input has: subagent_type, description, prompt.
19
+ #
20
+ # Output: writes .cleargate/sprint-runs/<sprint-id>/.pending-task-<turn_index>.json
21
+ # with { agent_type, work_item_id, turn_index, started_at }
22
+ #
23
+ # Robustness: under v1 never blocks the tool call (exit 0 always). Under v2,
24
+ # exits non-zero to block Task spawn when unprocessed flashcards exist.
25
+ # Set SKIP_FLASHCARD_GATE=1 to bypass the flashcard gate in both modes.
26
+
27
+ set -u
28
+
29
+ REPO_ROOT="${ORCHESTRATOR_PROJECT_DIR:-${CLAUDE_PROJECT_DIR}}"
30
+ LOG_DIR="${REPO_ROOT}/.cleargate/hook-log"
31
+ mkdir -p "${LOG_DIR}"
32
+ HOOK_LOG="${LOG_DIR}/pending-task-sentinel.log"
33
+ ACTIVE_SENTINEL="${REPO_ROOT}/.cleargate/sprint-runs/.active"
34
+
35
+ # Read stdin once — must happen before the grouped block
36
+ INPUT="$(cat)"
37
+
38
+ # Determine active sprint (needed for flashcard gate and sentinel)
39
+ SPRINT_ID=""
40
+ if [[ -f "${ACTIVE_SENTINEL}" ]]; then
41
+ SPRINT_ID="$(tr -d '[:space:]' < "${ACTIVE_SENTINEL}")"
42
+ fi
43
+ [[ -z "${SPRINT_ID}" ]] && SPRINT_ID="_off-sprint"
44
+ SPRINT_DIR="${REPO_ROOT}/.cleargate/sprint-runs/${SPRINT_ID}"
45
+ mkdir -p "${SPRINT_DIR}"
46
+
47
+ # --- Flashcard gate (STORY-014-03) ---
48
+ # Runs BEFORE the logged block so stderr goes to real process stderr (not log),
49
+ # allowing Claude Code to surface the message to the orchestrator.
50
+ # Bypass: set SKIP_FLASHCARD_GATE=1 in environment.
51
+ TOOL_NAME_EARLY="$(printf '%s' "${INPUT}" | jq -r '.tool_name // empty')"
52
+
53
+ if [[ "${TOOL_NAME_EARLY}" == "Task" && "${SKIP_FLASHCARD_GATE:-0}" != "1" && "${SPRINT_ID}" != "_off-sprint" ]]; then
54
+ # Read execution_mode from state.json
55
+ EXEC_MODE="v1"
56
+ if [[ -f "${SPRINT_DIR}/state.json" ]]; then
57
+ EXEC_MODE="$(jq -r '.execution_mode // "v1"' "${SPRINT_DIR}/state.json" 2>/dev/null)"
58
+ [[ -z "${EXEC_MODE}" || "${EXEC_MODE}" == "null" ]] && EXEC_MODE="v1"
59
+ fi
60
+
61
+ # Collect flagged cards from all STORY-*-dev.md and STORY-*-qa.md in SPRINT_DIR (flat layout).
62
+ UNPROCESSED_CARDS=()
63
+ UNPROCESSED_HASHES=()
64
+
65
+ # Use ls -t (portable) to process report files; portable array accumulation (bash 3.2 safe).
66
+ REPORT_FILES=()
67
+ while IFS= read -r f; do
68
+ REPORT_FILES+=("$f")
69
+ done < <(ls -t "${SPRINT_DIR}"/STORY-*-dev.md "${SPRINT_DIR}"/STORY-*-qa.md 2>/dev/null)
70
+
71
+ for REPORT_FILE in "${REPORT_FILES[@]}"; do
72
+ [[ ! -f "${REPORT_FILE}" ]] && continue
73
+ # Parse flashcards_flagged list. Handles two formats:
74
+ # YAML key (frontmatter): Markdown section heading:
75
+ # flashcards_flagged: [] ## flashcards_flagged
76
+ # flashcards_flagged:
77
+ # - "card text" - "card text"
78
+ # - bare card text - bare card text
79
+ IN_BLOCK=0
80
+ BLOCK_TYPE="" # "yaml" or "md"
81
+ while IFS= read -r line; do
82
+ # YAML inline empty list — no cards in this format
83
+ if [[ "${line}" =~ ^flashcards_flagged:[[:space:]]*\[\] ]]; then
84
+ break
85
+ fi
86
+ # YAML key (block form) — matches "flashcards_flagged:" or "flashcards_flagged: " with nothing after
87
+ if [[ "${line}" =~ ^flashcards_flagged:[[:space:]]*$ ]]; then
88
+ IN_BLOCK=1
89
+ BLOCK_TYPE="yaml"
90
+ continue
91
+ fi
92
+ # Markdown section heading (## flashcards_flagged or ## Flashcards_flagged)
93
+ if [[ "${line}" =~ ^##[[:space:]]+[Ff]lashcards_flagged ]]; then
94
+ IN_BLOCK=1
95
+ BLOCK_TYPE="md"
96
+ continue
97
+ fi
98
+
99
+ if [[ "${IN_BLOCK}" == "1" ]]; then
100
+ # Stop conditions differ by block type
101
+ if [[ "${BLOCK_TYPE}" == "yaml" ]]; then
102
+ # Stop at next top-level YAML key (non-indented, non-list, non-blank line)
103
+ if [[ "${line}" =~ ^[a-zA-Z_] ]]; then
104
+ break
105
+ fi
106
+ elif [[ "${BLOCK_TYPE}" == "md" ]]; then
107
+ # Stop at next markdown heading (any level)
108
+ if [[ "${line}" =~ ^# ]]; then
109
+ break
110
+ fi
111
+ fi
112
+ # Match list items: "- ..." (leading whitespace allowed)
113
+ if [[ "${line}" =~ ^[[:space:]]*-[[:space:]]+(.*) ]]; then
114
+ CARD="${BASH_REMATCH[1]}"
115
+ # Strip surrounding quotes (double or single)
116
+ CARD="${CARD#\"}"
117
+ CARD="${CARD%\"}"
118
+ CARD="${CARD#\'}"
119
+ CARD="${CARD%\'}"
120
+ [[ -z "${CARD}" ]] && continue
121
+ # Compute SHA-1, first 12 chars (portable: shasum -a 1 per flashcard #bash #macos)
122
+ HASH="$(printf '%s' "${CARD}" | shasum -a 1 | cut -c1-12)"
123
+ MARKER="${SPRINT_DIR}/.processed-${HASH}"
124
+ if [[ ! -f "${MARKER}" ]]; then
125
+ UNPROCESSED_CARDS+=("${CARD}")
126
+ UNPROCESSED_HASHES+=("${HASH}")
127
+ fi
128
+ fi
129
+ fi
130
+ done < "${REPORT_FILE}"
131
+ done
132
+
133
+ if [[ "${#UNPROCESSED_CARDS[@]}" -gt 0 ]]; then
134
+ printf '[%s] flashcard-gate: %d unprocessed card(s) found (mode=%s)\n' \
135
+ "$(date -u +%FT%TZ)" "${#UNPROCESSED_CARDS[@]}" "${EXEC_MODE}" >> "${HOOK_LOG}"
136
+ if [[ "${EXEC_MODE}" == "v2" ]]; then
137
+ # Block Task spawn — exit 1 with diagnostic on stderr (real stderr, not log)
138
+ printf 'FLASHCARD GATE BLOCKED: %d unprocessed flashcard(s) must be processed before spawning next Task.\n' \
139
+ "${#UNPROCESSED_CARDS[@]}" >&2
140
+ for i in "${!UNPROCESSED_CARDS[@]}"; do
141
+ CARD="${UNPROCESSED_CARDS[$i]}"
142
+ HASH="${UNPROCESSED_HASHES[$i]}"
143
+ printf ' card: %s\n' "${CARD}" >&2
144
+ printf ' mark processed: touch %s/.processed-%s\n' "${SPRINT_DIR}" "${HASH}" >&2
145
+ done
146
+ exit 1
147
+ else
148
+ # v1: advisory warning only, continue to sentinel write
149
+ printf 'FLASHCARD GATE WARNING (v1 advisory): %d unprocessed flashcard(s).\n' \
150
+ "${#UNPROCESSED_CARDS[@]}" >&2
151
+ for i in "${!UNPROCESSED_CARDS[@]}"; do
152
+ CARD="${UNPROCESSED_CARDS[$i]}"
153
+ HASH="${UNPROCESSED_HASHES[$i]}"
154
+ printf ' card: %s\n' "${CARD}" >&2
155
+ printf ' mark processed: touch %s/.processed-%s\n' "${SPRINT_DIR}" "${HASH}" >&2
156
+ done
157
+ fi
158
+ fi
159
+ fi
160
+ # --- End flashcard gate ---
161
+
162
+ {
163
+ TOOL_NAME="$(printf '%s' "${INPUT}" | jq -r '.tool_name // empty')"
164
+ if [[ "${TOOL_NAME}" != "Task" ]]; then
165
+ # Not a subagent dispatch — no sentinel needed.
166
+ exit 0
167
+ fi
168
+
169
+ TRANSCRIPT_PATH="$(printf '%s' "${INPUT}" | jq -r '.transcript_path // empty')"
170
+ AGENT_TYPE="$(printf '%s' "${INPUT}" | jq -r '.tool_input.subagent_type // "unknown"')"
171
+ PROMPT="$(printf '%s' "${INPUT}" | jq -r '.tool_input.prompt // empty')"
172
+
173
+ # Extract work_item_id from prompt — by convention first line is STORY=NNN-NN
174
+ # or an inline PROPOSAL-NNN / EPIC-NNN / CR-NNN / BUG-NNN reference.
175
+ WORK_ITEM_ID="$(printf '%s' "${PROMPT}" | grep -oE '(STORY|PROPOSAL|EPIC|CR|BUG)[-=]?[0-9]+(-[0-9]+)?' | head -1 | sed 's/=/-/g')"
176
+ [[ -z "${WORK_ITEM_ID}" ]] && WORK_ITEM_ID=""
177
+
178
+ # Compute turn_index: count of assistant turns in the orchestrator transcript so far.
179
+ TURN_INDEX=0
180
+ if [[ -n "${TRANSCRIPT_PATH}" && -f "${TRANSCRIPT_PATH}" ]]; then
181
+ TURN_INDEX="$(jq -cs '[.[] | select(.type == "assistant" and .message.usage)] | length' "${TRANSCRIPT_PATH}" 2>/dev/null)"
182
+ [[ -z "${TURN_INDEX}" || "${TURN_INDEX}" == "null" ]] && TURN_INDEX=0
183
+ fi
184
+
185
+ STARTED_AT="$(date -u +%FT%TZ)"
186
+ SENTINEL_FILE="${SPRINT_DIR}/.pending-task-${TURN_INDEX}.json"
187
+
188
+ # Write the sentinel atomically (tmp + mv).
189
+ TMP="${SENTINEL_FILE}.tmp.$$"
190
+ jq -cn \
191
+ --arg agent "${AGENT_TYPE}" \
192
+ --arg work_item "${WORK_ITEM_ID}" \
193
+ --argjson idx "${TURN_INDEX}" \
194
+ --arg started "${STARTED_AT}" \
195
+ '{agent_type: $agent, work_item_id: $work_item, turn_index: $idx, started_at: $started}' \
196
+ > "${TMP}" 2>/dev/null \
197
+ && mv "${TMP}" "${SENTINEL_FILE}" \
198
+ && printf '[%s] wrote sentinel sprint=%s agent=%s work_item=%s turn=%s\n' \
199
+ "${STARTED_AT}" "${SPRINT_ID}" "${AGENT_TYPE}" "${WORK_ITEM_ID}" "${TURN_INDEX}" \
200
+ >> "${HOOK_LOG}" \
201
+ || printf '[%s] failed to write sentinel %s\n' "${STARTED_AT}" "${SENTINEL_FILE}" >> "${HOOK_LOG}"
202
+ } 2>> "${HOOK_LOG}"
203
+
204
+ exit 0
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ # pre-commit-surface-gate.sh
3
+ set -euo pipefail
4
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
5
+ SCRIPT="${REPO_ROOT}/.cleargate/scripts/file_surface_diff.sh"
6
+ if [[ ! -f "${SCRIPT}" ]]; then
7
+ echo "[surface-gate] WARNING: file_surface_diff.sh not found — skipping" >&2
8
+ exit 0
9
+ fi
10
+ exec bash "${SCRIPT}" "$@"
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ # pre-commit-test-ratchet.sh — STORY-014-04: Pre-existing Test-Failure Ratchet
3
+ #
4
+ # Invoked by .claude/hooks/pre-commit.sh dispatcher (STORY-014-01).
5
+ # Runs test_ratchet.mjs in 'check' mode and blocks commit on regression.
6
+ #
7
+ # Bypass (discouraged): SKIP_TEST_RATCHET=1
8
+ # Timeout: 120s (enough for current cleargate-cli suite ~45s)
9
+ #
10
+ # macOS compatibility: 'timeout' is GNU coreutils; on macOS use 'gtimeout' (brew coreutils).
11
+ # Fallback: if neither is available, run without timeout and print a warning.
12
+
13
+ set -euo pipefail
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
+ REPO_ROOT="${CLEARGATE_REPO_ROOT:-$(cd "${SCRIPT_DIR}/../.." && pwd)}"
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Bypass
20
+ # ---------------------------------------------------------------------------
21
+ if [[ "${SKIP_TEST_RATCHET:-0}" == "1" ]]; then
22
+ echo "test-ratchet: SKIP_TEST_RATCHET=1 — bypassing test ratchet check (discouraged)" >&2
23
+ exit 0
24
+ fi
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Resolve timeout binary (GNU on Linux; gtimeout on macOS via brew)
28
+ # ---------------------------------------------------------------------------
29
+ TIMEOUT_CMD=""
30
+ if command -v timeout &>/dev/null; then
31
+ TIMEOUT_CMD="timeout 120"
32
+ elif command -v gtimeout &>/dev/null; then
33
+ TIMEOUT_CMD="gtimeout 120"
34
+ else
35
+ echo "test-ratchet: WARNING — 'timeout' not found; running without 120s guard" >&2
36
+ fi
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Run ratchet
40
+ # ---------------------------------------------------------------------------
41
+ RATCHET_SCRIPT="${REPO_ROOT}/.cleargate/scripts/test_ratchet.mjs"
42
+
43
+ if [[ ! -f "${RATCHET_SCRIPT}" ]]; then
44
+ echo "test-ratchet: ERROR — ratchet script not found at ${RATCHET_SCRIPT}" >&2
45
+ exit 1
46
+ fi
47
+
48
+ export CLEARGATE_REPO_ROOT="${REPO_ROOT}"
49
+
50
+ ${TIMEOUT_CMD} node "${RATCHET_SCRIPT}" check
51
+ STATUS=$?
52
+
53
+ if [[ ${STATUS} -eq 124 ]]; then
54
+ echo "test-ratchet: ERROR — ratchet timed out after 120s; commit blocked" >&2
55
+ exit 1
56
+ fi
57
+
58
+ exit ${STATUS}
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ # pre-commit.sh — Dispatcher: chains all pre-commit-*.sh hooks in lexical order.
3
+ #
4
+ # Install: ln -sf ../../.claude/hooks/pre-commit.sh .git/hooks/pre-commit
5
+ #
6
+ # Each pre-commit-*.sh is expected to exit 0 on success or non-zero to block.
7
+ # The dispatcher exits on the first non-zero exit code.
8
+
9
+ set -euo pipefail
10
+
11
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+
13
+ for hook in "${HOOK_DIR}"/pre-commit-*.sh; do
14
+ [[ -f "${hook}" ]] || continue
15
+ [[ -x "${hook}" ]] || continue
16
+ bash "${hook}" || exit $?
17
+ done
18
+
19
+ exit 0
@@ -2,3 +2,54 @@
2
2
  set -u
3
3
  REPO_ROOT="${CLAUDE_PROJECT_DIR}"
4
4
  node "${REPO_ROOT}/cleargate-cli/dist/cli.js" doctor --session-start 2>/dev/null || true
5
+
6
+ # ── §14.9 SessionStart sync nudge (STORY-010-08) ─────────────────────────────
7
+ # Daily-throttled: probe remote for updates at most once per 24h.
8
+ # Never auto-pulls or auto-pushes. Exits 0 regardless of outcome.
9
+ MARKER="${REPO_ROOT}/.cleargate/.sync-marker.json"
10
+ NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
11
+ NOW_EPOCH=$(date +%s)
12
+
13
+ if [ ! -f "${MARKER}" ]; then
14
+ # First run — write marker with current timestamp and skip MCP call (24h grace).
15
+ mkdir -p "$(dirname "${MARKER}")"
16
+ printf '{"last_check":"%s"}' "${NOW_ISO}" > "${MARKER}"
17
+ else
18
+ # Parse last_check epoch from marker using node (portable, avoids jq dep)
19
+ LAST_CHECK_ISO=$(node -e "try{const m=JSON.parse(require('fs').readFileSync('${MARKER}','utf8'));process.stdout.write(m.last_check||'1970-01-01T00:00:00Z')}catch{process.stdout.write('1970-01-01T00:00:00Z')}" 2>/dev/null || echo "1970-01-01T00:00:00Z")
20
+ LAST_EPOCH=$(node -e "process.stdout.write(String(Math.floor(new Date('${LAST_CHECK_ISO}').getTime()/1000)))" 2>/dev/null || echo "0")
21
+ ELAPSED=$(( NOW_EPOCH - LAST_EPOCH ))
22
+
23
+ if [ "${ELAPSED}" -ge 86400 ]; then
24
+ # ≥24h since last check — run probe (3s timeout, R7 mitigation)
25
+ RESULT_FILE=$(mktemp)
26
+ # Cross-platform 3-second timeout: prefer `timeout` (Linux); fall back to
27
+ # background-process kill (macOS where GNU coreutils may be absent).
28
+ if command -v timeout > /dev/null 2>&1; then
29
+ timeout 3 node "${REPO_ROOT}/cleargate-cli/dist/cli.js" sync --check > "${RESULT_FILE}" 2>/dev/null || true
30
+ else
31
+ node "${REPO_ROOT}/cleargate-cli/dist/cli.js" sync --check > "${RESULT_FILE}" 2>/dev/null &
32
+ _PROBE_PID=$!
33
+ (sleep 3 && kill "${_PROBE_PID}" 2>/dev/null) &
34
+ _KILL_PID=$!
35
+ wait "${_PROBE_PID}" 2>/dev/null || true
36
+ kill "${_KILL_PID}" 2>/dev/null || true
37
+ wait "${_KILL_PID}" 2>/dev/null || true
38
+ fi
39
+ UPDATES=$(node -e "
40
+ try {
41
+ var data = require('fs').readFileSync(process.argv[1], 'utf8').trim();
42
+ var obj = JSON.parse(data);
43
+ process.stdout.write(String(obj.updates || 0));
44
+ } catch(e) {
45
+ process.stdout.write('0');
46
+ }
47
+ " "${RESULT_FILE}" 2>/dev/null || echo "0")
48
+ rm -f "${RESULT_FILE}"
49
+ if [ "${UPDATES}" -gt 0 ] 2>/dev/null; then
50
+ printf '📡 ClearGate: %s remote updates since yesterday — run `cleargate sync` to reconcile.\n' "${UPDATES}"
51
+ fi
52
+ # Marker is updated by sync --check itself; no re-write needed here.
53
+ fi
54
+ fi
55
+ exit 0