cleargate 0.1.0-alpha.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -2
- package/dist/MANIFEST.json +160 -0
- package/dist/cli.cjs +4383 -16
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4367 -4
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +57 -0
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +145 -0
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +256 -0
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-query.md +143 -0
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +58 -0
- package/dist/templates/cleargate-planning/.claude/agents/qa.md +57 -0
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +129 -0
- package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +4 -0
- package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +18 -0
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +174 -0
- package/dist/templates/cleargate-planning/.claude/settings.json +35 -0
- package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +92 -0
- package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +7 -0
- package/dist/templates/cleargate-planning/.cleargate/delivery/archive/.gitkeep +0 -0
- package/dist/templates/cleargate-planning/.cleargate/delivery/pending-sync/.gitkeep +0 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +508 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +133 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +82 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +77 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +63 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +120 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +53 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/proposal.md +52 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +135 -0
- package/dist/templates/cleargate-planning/CLAUDE.md +42 -0
- package/dist/templates/cleargate-planning/MANIFEST.json +160 -0
- package/dist/templates/synthesis/active-sprint.md +30 -0
- package/dist/templates/synthesis/open-gates.md +38 -0
- package/dist/templates/synthesis/product-state.md +32 -0
- package/dist/templates/synthesis/roadmap.md +63 -0
- package/package.json +9 -2
- package/templates/cleargate-planning/.claude/agents/architect.md +57 -0
- package/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +145 -0
- package/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +256 -0
- package/templates/cleargate-planning/.claude/agents/cleargate-wiki-query.md +143 -0
- package/templates/cleargate-planning/.claude/agents/developer.md +58 -0
- package/templates/cleargate-planning/.claude/agents/qa.md +57 -0
- package/templates/cleargate-planning/.claude/agents/reporter.md +129 -0
- package/templates/cleargate-planning/.claude/hooks/session-start.sh +4 -0
- package/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +18 -0
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +174 -0
- package/templates/cleargate-planning/.claude/settings.json +35 -0
- package/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +92 -0
- package/templates/cleargate-planning/.cleargate/FLASHCARD.md +7 -0
- package/templates/cleargate-planning/.cleargate/delivery/archive/.gitkeep +0 -0
- package/templates/cleargate-planning/.cleargate/delivery/pending-sync/.gitkeep +0 -0
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +508 -0
- package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +133 -0
- package/templates/cleargate-planning/.cleargate/templates/Bug.md +82 -0
- package/templates/cleargate-planning/.cleargate/templates/CR.md +77 -0
- package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +63 -0
- package/templates/cleargate-planning/.cleargate/templates/epic.md +120 -0
- package/templates/cleargate-planning/.cleargate/templates/initiative.md +53 -0
- package/templates/cleargate-planning/.cleargate/templates/proposal.md +52 -0
- package/templates/cleargate-planning/.cleargate/templates/story.md +135 -0
- package/templates/cleargate-planning/CLAUDE.md +42 -0
- package/templates/cleargate-planning/MANIFEST.json +160 -0
- package/templates/synthesis/active-sprint.md +30 -0
- package/templates/synthesis/open-gates.md +38 -0
- package/templates/synthesis/product-state.md +32 -0
- package/templates/synthesis/roadmap.md +63 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qa
|
|
3
|
+
description: Use AFTER a Developer agent reports STATUS=done on a Story. Independent verification gate. Re-runs typecheck + tests in a fresh shell, diffs the commit against the Story's acceptance Gherkin, flags missing scenarios, checks DoD items. Approves or kicks back. Never commits. Never edits code.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the **QA** agent for ClearGate sprint execution. Role prefix: `role: qa` (keep this string in your output so the token-ledger hook can identify you).
|
|
9
|
+
|
|
10
|
+
## Your one job
|
|
11
|
+
Verify that a Developer's claim of "done" is real. Approve with `QA: PASS` or reject with `QA: FAIL <reason>`. Do not commit. Do not edit.
|
|
12
|
+
|
|
13
|
+
## Inputs
|
|
14
|
+
- `STORY=NNN-NN` — **include verbatim in your first line**.
|
|
15
|
+
- Worktree path + commit SHA from the Developer.
|
|
16
|
+
- Path to the Story file (acceptance criteria).
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
1. **Read flashcards.** `Skill(flashcard, "check")`. Flashcards tagged `#qa` or `#test-harness` especially relevant.
|
|
21
|
+
2. **Inspect the commit** — `git show <sha>` in the worktree. Read the diff in full before trusting it.
|
|
22
|
+
3. **Re-run the checks from scratch:**
|
|
23
|
+
- `npm run typecheck` in the package the commit touches
|
|
24
|
+
- `npm test` for that package
|
|
25
|
+
- Capture exit codes, not vibes. A passing summary line that skipped tests is a fail.
|
|
26
|
+
4. **Map commit to acceptance criteria.** For each Gherkin scenario in the Story:
|
|
27
|
+
- Find the corresponding test in the diff
|
|
28
|
+
- If no test matches, that's a FAIL with reason `missing test for "<scenario name>"`
|
|
29
|
+
5. **Check for regressions** — run the full package test suite, not just new tests. If anything else broke, FAIL.
|
|
30
|
+
6. **Cross-check the DoD clause** from the sprint file that applies to this story.
|
|
31
|
+
7. **Record flashcards on recurring QA failure patterns.** `Skill(flashcard, "record: #qa <lesson>")`. Examples:
|
|
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."
|
|
34
|
+
|
|
35
|
+
## Output shape
|
|
36
|
+
```
|
|
37
|
+
STORY: STORY-NNN-NN
|
|
38
|
+
QA: PASS | FAIL
|
|
39
|
+
TYPECHECK: pass | fail
|
|
40
|
+
TESTS: X passed, Y failed, Z skipped (full suite)
|
|
41
|
+
ACCEPTANCE_COVERAGE: N of M Gherkin scenarios have matching tests
|
|
42
|
+
MISSING: <list of scenarios with no test, or "none">
|
|
43
|
+
REGRESSIONS: <list, or "none">
|
|
44
|
+
VERDICT: <one paragraph — what specifically to fix, or "ship it">
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Guardrails
|
|
48
|
+
- **Never approve on Developer's word.** Re-run everything yourself.
|
|
49
|
+
- **Never edit code to "help the Developer pass."** If a test is broken, FAIL and return — don't fix it for them.
|
|
50
|
+
- **Skipped tests count against coverage.** A scenario covered by `test.skip(...)` is MISSING.
|
|
51
|
+
- **Flaky tests count as FAIL.** Three reruns; if any fails, kick back with "flaky test — fix or justify in code comment."
|
|
52
|
+
- **Max kickback round is round 2.** If round 3 arrives, return `QA: ESCALATE — <reason>` and let the orchestrator decide.
|
|
53
|
+
|
|
54
|
+
## What you are NOT
|
|
55
|
+
- Not the Developer — do not propose fixes in detail, just identify gaps.
|
|
56
|
+
- Not the Architect — do not question the story's design, only whether the code meets it.
|
|
57
|
+
- Not the Reporter — terse output, no narrative.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
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.
|
|
4
|
+
tools: Read, Grep, Glob, Bash, Write
|
|
5
|
+
model: opus
|
|
6
|
+
---
|
|
7
|
+
|
|
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
|
+
|
|
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.
|
|
12
|
+
|
|
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`)
|
|
17
|
+
- Path to flashcards file (`.cleargate/FLASHCARD.md`)
|
|
18
|
+
- Worktree / branch list (for `git log` aggregation)
|
|
19
|
+
|
|
20
|
+
## Workflow
|
|
21
|
+
|
|
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
|
+
5b. **Flashcard audit (cleanup 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:
|
|
33
|
+
- file paths (regex: `\S+\.(ts|md|sh|py|sql|json|yaml|toml)`)
|
|
34
|
+
- identifier candidates (CamelCase ≥4 chars OR `snake_case_with_≥2_underscores`)
|
|
35
|
+
- CLI flags (regex: `--[a-z][a-z0-9-]+`)
|
|
36
|
+
- env-var candidates (regex: `[A-Z][A-Z0-9_]{3,}`)
|
|
37
|
+
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 (unflaggable — leave active). Do NOT modify FLASHCARD.md. Output belongs in the report under "Flashcard audit"; a human approves the batch and applies markers separately.
|
|
38
|
+
6. **Synthesize** the report in this structure:
|
|
39
|
+
|
|
40
|
+
```markdown
|
|
41
|
+
# SPRINT-<NN> Report: <Sprint Title>
|
|
42
|
+
|
|
43
|
+
**Status:** ✅ Shipped | ⚠ Partial | ❌ Blocked
|
|
44
|
+
**Window:** YYYY-MM-DD → YYYY-MM-DD (N calendar days, M active dev hours)
|
|
45
|
+
**Stories:** N planned / M shipped / K carried over
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## For Product Management
|
|
50
|
+
|
|
51
|
+
### Sprint goal — did we hit it?
|
|
52
|
+
One paragraph. The goal verbatim from the sprint file, followed by the plain-English answer.
|
|
53
|
+
|
|
54
|
+
### Headline deliverables
|
|
55
|
+
- One bullet per user-facing capability (not per story). Group stories under their business outcome.
|
|
56
|
+
|
|
57
|
+
### Risks that materialized
|
|
58
|
+
From the sprint's risk table — which mitigations fired, which were unused, what surprised us.
|
|
59
|
+
|
|
60
|
+
### Cost envelope
|
|
61
|
+
One line: "~$X across N agent invocations (M tokens cached, saving ~$Y vs. cold)."
|
|
62
|
+
|
|
63
|
+
### What's unblocked for next sprint
|
|
64
|
+
Bullet list tying this sprint's outputs to downstream dependencies.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## For Developers
|
|
69
|
+
|
|
70
|
+
### Per-story walkthrough
|
|
71
|
+
For each shipped story, one compact block:
|
|
72
|
+
|
|
73
|
+
**STORY-NNN-NN: Title** · L<complexity> · <cost_usd> · <wall_time>
|
|
74
|
+
- Files: `path/a.ts`, `path/b.ts`
|
|
75
|
+
- Tests added: N (covering M Gherkin scenarios)
|
|
76
|
+
- Kickbacks: 0 (one-shot) | 1 (reason: …) | 2 (reasons: …)
|
|
77
|
+
- Deviations from plan: none | <describe>
|
|
78
|
+
- Flashcards recorded: [#tag] brief
|
|
79
|
+
- Commit: <sha>
|
|
80
|
+
|
|
81
|
+
### Agent efficiency breakdown
|
|
82
|
+
| Role | Invocations | Tokens | Cost | Tokens/story | Notes |
|
|
83
|
+
|---|---|---|---|---|---|
|
|
84
|
+
| Architect | | | | | |
|
|
85
|
+
| Developer | | | | | |
|
|
86
|
+
| QA | | | | | |
|
|
87
|
+
| Reporter | | | | | — this report |
|
|
88
|
+
|
|
89
|
+
### What the loop got right
|
|
90
|
+
3-5 bullets — based on flashcards + kickback rate + plan-adherence rate.
|
|
91
|
+
|
|
92
|
+
### What the loop got wrong
|
|
93
|
+
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).
|
|
94
|
+
|
|
95
|
+
### Flashcard audit
|
|
96
|
+
Candidates for `[S]` (stale) marker — referenced symbols no longer present in the repo. Human approves the batch before markers are applied.
|
|
97
|
+
|
|
98
|
+
| Card (date · lead-tag · lesson head) | Evidence (symbols not found) | Proposed marker |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| 2026-02-15 · #tsup · tsup single-bundle... | `tsup.config.ts`, `--bundle` | `[S]` |
|
|
101
|
+
|
|
102
|
+
If zero candidates: state "No stale flashcards detected." If there are candidate supersede pairs (a newer card whose lesson directly contradicts an older card's advice), list them under a "Supersede candidates" sub-table with the proposed `[R] → superseded-by <short-ref>` marker for the older card.
|
|
103
|
+
|
|
104
|
+
### Open follow-ups
|
|
105
|
+
Things deliberately deferred, with target sprint.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Meta
|
|
110
|
+
|
|
111
|
+
**Token ledger:** `.cleargate/sprint-runs/<sprint-id>/token-ledger.jsonl` (N rows)
|
|
112
|
+
**Flashcards added:** N (see `.cleargate/FLASHCARD.md`)
|
|
113
|
+
**Model rates used:** <date>
|
|
114
|
+
**Report generated:** <timestamp> by Reporter agent
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
7. **Record a flashcard** on any reporting-specific friction. `Skill(flashcard, "record: #reporting <lesson>")`.
|
|
118
|
+
|
|
119
|
+
## Guardrails
|
|
120
|
+
- **Numbers before narrative.** Every claim in the PM section must be backed by a ledger row, a commit, or a flashcard entry — cite them.
|
|
121
|
+
- **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>)`.
|
|
122
|
+
- **Do not summarize the sprint file.** Assume the reader already read it. Add information; don't restate.
|
|
123
|
+
- **One report. One file. Do not create drafts.** If you're uncertain, emit what you have and flag the uncertainty inline.
|
|
124
|
+
- **Length ceiling: 600 lines.** A longer report won't be read.
|
|
125
|
+
|
|
126
|
+
## What you are NOT
|
|
127
|
+
- Not a PM — you inform decisions, you don't make them.
|
|
128
|
+
- Not a Developer — you don't prescribe fixes.
|
|
129
|
+
- Not a Cheerleader — if the sprint went badly, say so plainly. The loop improves from honesty.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
REPO_ROOT="${CLAUDE_PROJECT_DIR}"
|
|
4
|
+
LOG="${REPO_ROOT}/.cleargate/hook-log/gate-check.log"
|
|
5
|
+
mkdir -p "$(dirname "$LOG")"
|
|
6
|
+
FILE=$(jq -r '.tool_input.file_path' 2>/dev/null || echo "")
|
|
7
|
+
[ -z "$FILE" ] && exit 0
|
|
8
|
+
case "$FILE" in *.cleargate/delivery/*) : ;; *) exit 0 ;; esac
|
|
9
|
+
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
10
|
+
# Ordered chain — stamp MUST precede gate (gate may read draft_tokens)
|
|
11
|
+
node "${REPO_ROOT}/cleargate-cli/dist/cli.js" stamp-tokens "$FILE" >>"$LOG" 2>&1
|
|
12
|
+
SR1=$?
|
|
13
|
+
node "${REPO_ROOT}/cleargate-cli/dist/cli.js" gate check "$FILE" >>"$LOG" 2>&1
|
|
14
|
+
SR2=$?
|
|
15
|
+
node "${REPO_ROOT}/cleargate-cli/dist/cli.js" wiki ingest "$FILE" >>"$LOG" 2>&1
|
|
16
|
+
SR3=$?
|
|
17
|
+
echo "[$TS] stamp=$SR1 gate=$SR2 ingest=$SR3 file=$FILE" >>"$LOG"
|
|
18
|
+
exit 0 # ALWAYS 0 — severity enforcement is at wiki lint, not hook
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SubagentStop hook: append one JSONL row per subagent completion to the active sprint's token ledger.
|
|
3
|
+
#
|
|
4
|
+
# Input: JSON on stdin from Claude Code with fields session_id, transcript_path, cwd, hook_event_name.
|
|
5
|
+
# Output: appends to .cleargate/sprint-runs/<sprint-id>/token-ledger.jsonl
|
|
6
|
+
# Cost computation is deferred to the Reporter agent (prices change; keep raw).
|
|
7
|
+
#
|
|
8
|
+
# Active sprint detection (FIXED 2026-04-19):
|
|
9
|
+
# Primary : .cleargate/sprint-runs/.active sentinel file (one line: "SPRINT-NN")
|
|
10
|
+
# Orchestrator writes this at sprint kickoff, removes/updates at close.
|
|
11
|
+
# Fallback : .cleargate/sprint-runs/_off-sprint/token-ledger.jsonl
|
|
12
|
+
# When no .active sentinel exists, writes still get captured but
|
|
13
|
+
# tagged off-sprint instead of misrouting to a stale sprint dir.
|
|
14
|
+
# (Removed): the old `ls -td sprint-runs/*/ | head -1` mtime heuristic — it
|
|
15
|
+
# misrouted SPRINT-04 firings to SPRINT-03 because ledger appends
|
|
16
|
+
# themselves bumped SPRINT-03's mtime. See REPORT.md SPRINT-04.
|
|
17
|
+
#
|
|
18
|
+
# Work-item ID detection (GENERALIZED 2026-04-19 STORY-008-04):
|
|
19
|
+
# Primary : first user message in the transcript — that's the orchestrator's
|
|
20
|
+
# dispatch prompt, which by agent convention starts with
|
|
21
|
+
# `STORY=NNN-NN`, `PROPOSAL-NNN`, `EPIC-NNN`, `CR-NNN`, or `BUG-NNN`.
|
|
22
|
+
# Pattern : (STORY|PROPOSAL|EPIC|CR|BUG)[-=]?[0-9]+(-[0-9]+)?
|
|
23
|
+
# Fallback : grep first match anywhere in the transcript.
|
|
24
|
+
# story_id : populated only when the match is a STORY-* (backward compat).
|
|
25
|
+
# work_item_id: always populated when detection succeeds; equals story_id for STORY items.
|
|
26
|
+
# (Removed): grep-first-anywhere as PRIMARY — it picked up SPRINT-05 mentions
|
|
27
|
+
# from architect plans being read by the subagent and mistagged
|
|
28
|
+
# every SPRINT-04 firing as STORY-006-01.
|
|
29
|
+
#
|
|
30
|
+
# Robustness: never exits non-zero on parse failure (never block a subagent stop). Errors go to a
|
|
31
|
+
# sibling hook.log so you can diagnose without fighting the runtime.
|
|
32
|
+
|
|
33
|
+
set -u
|
|
34
|
+
|
|
35
|
+
REPO_ROOT="/Users/ssuladze/Documents/Dev/ClearGate"
|
|
36
|
+
LOG_DIR="${REPO_ROOT}/.cleargate/hook-log"
|
|
37
|
+
mkdir -p "${LOG_DIR}"
|
|
38
|
+
HOOK_LOG="${LOG_DIR}/token-ledger.log"
|
|
39
|
+
ACTIVE_SENTINEL="${REPO_ROOT}/.cleargate/sprint-runs/.active"
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
INPUT="$(cat)"
|
|
43
|
+
|
|
44
|
+
# --- parse hook payload ---
|
|
45
|
+
TRANSCRIPT_PATH="$(printf '%s' "${INPUT}" | jq -r '.transcript_path // empty')"
|
|
46
|
+
SESSION_ID="$(printf '%s' "${INPUT}" | jq -r '.session_id // empty')"
|
|
47
|
+
|
|
48
|
+
if [[ -z "${TRANSCRIPT_PATH}" || ! -f "${TRANSCRIPT_PATH}" ]]; then
|
|
49
|
+
printf '[%s] no transcript_path (session=%s)\n' "$(date -u +%FT%TZ)" "${SESSION_ID}" >> "${HOOK_LOG}"
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# --- determine active sprint via sentinel ---
|
|
54
|
+
SPRINT_ID=""
|
|
55
|
+
if [[ -f "${ACTIVE_SENTINEL}" ]]; then
|
|
56
|
+
SPRINT_ID="$(tr -d '[:space:]' < "${ACTIVE_SENTINEL}")"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [[ -n "${SPRINT_ID}" ]]; then
|
|
60
|
+
SPRINT_DIR="${REPO_ROOT}/.cleargate/sprint-runs/${SPRINT_ID}"
|
|
61
|
+
mkdir -p "${SPRINT_DIR}"
|
|
62
|
+
printf '[%s] routing to sprint=%s (sentinel)\n' "$(date -u +%FT%TZ)" "${SPRINT_ID}" >> "${HOOK_LOG}"
|
|
63
|
+
else
|
|
64
|
+
# No active sprint: capture the row in an off-sprint ledger so we don't lose data.
|
|
65
|
+
SPRINT_ID="_off-sprint"
|
|
66
|
+
SPRINT_DIR="${REPO_ROOT}/.cleargate/sprint-runs/_off-sprint"
|
|
67
|
+
mkdir -p "${SPRINT_DIR}"
|
|
68
|
+
printf '[%s] no .active sentinel — bucketing as _off-sprint\n' "$(date -u +%FT%TZ)" >> "${HOOK_LOG}"
|
|
69
|
+
fi
|
|
70
|
+
LEDGER="${SPRINT_DIR}/token-ledger.jsonl"
|
|
71
|
+
|
|
72
|
+
# --- walk transcript, sum usage across all assistant turns in this subagent run ---
|
|
73
|
+
USAGE_JSON="$(jq -cs '
|
|
74
|
+
map(select(.type == "assistant" and .message.usage))
|
|
75
|
+
| (map(.message.usage.input_tokens // 0) | add) as $in
|
|
76
|
+
| (map(.message.usage.output_tokens // 0) | add) as $out
|
|
77
|
+
| (map(.message.usage.cache_creation_input_tokens // 0) | add) as $cc
|
|
78
|
+
| (map(.message.usage.cache_read_input_tokens // 0) | add) as $cr
|
|
79
|
+
| (map(.message.model) | unique | map(select(. != null)) | join(",")) as $models
|
|
80
|
+
| (length) as $turns
|
|
81
|
+
| {input: $in, output: $out, cache_creation: $cc, cache_read: $cr, model: $models, turns: $turns}
|
|
82
|
+
' "${TRANSCRIPT_PATH}" 2>/dev/null)"
|
|
83
|
+
|
|
84
|
+
if [[ -z "${USAGE_JSON}" || "${USAGE_JSON}" == "null" ]]; then
|
|
85
|
+
printf '[%s] could not parse usage from %s\n' "$(date -u +%FT%TZ)" "${TRANSCRIPT_PATH}" >> "${HOOK_LOG}"
|
|
86
|
+
exit 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# --- detect agent_type ---
|
|
90
|
+
# Subagent transcripts contain `subagent_type` in the parent's tool invocation.
|
|
91
|
+
# Best-effort extraction; falls back to grepping role markers in the transcript body.
|
|
92
|
+
AGENT_TYPE="$(jq -rs '
|
|
93
|
+
[.[] | select(.type == "user") | .message.content]
|
|
94
|
+
| tostring
|
|
95
|
+
| capture("subagent_type[\"\\s:=]+(?<t>[a-zA-Z0-9_-]+)"; "g")?.t
|
|
96
|
+
// "unknown"
|
|
97
|
+
' "${TRANSCRIPT_PATH}" 2>/dev/null)"
|
|
98
|
+
[[ -z "${AGENT_TYPE}" || "${AGENT_TYPE}" == "null" ]] && AGENT_TYPE="unknown"
|
|
99
|
+
|
|
100
|
+
if [[ "${AGENT_TYPE}" == "unknown" ]]; then
|
|
101
|
+
for role in architect developer qa reporter; do
|
|
102
|
+
if grep -qiE "\\b${role}\\b agent|role: ${role}|you are the ${role}" "${TRANSCRIPT_PATH}" 2>/dev/null; then
|
|
103
|
+
AGENT_TYPE="${role}"
|
|
104
|
+
break
|
|
105
|
+
fi
|
|
106
|
+
done
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# --- detect work_item_id (PRIMARY: first user message; FALLBACK: anywhere-grep) ---
|
|
110
|
+
# Orchestrator convention (.claude/agents/developer.md): the first line of the
|
|
111
|
+
# dispatch prompt is `STORY=NNN-NN`. The first user message in the transcript
|
|
112
|
+
# is that prompt. Look there first to avoid picking up work-item mentions inside
|
|
113
|
+
# the subagent's later reads of plans, sprint files, etc.
|
|
114
|
+
#
|
|
115
|
+
# Pattern covers: STORY-NNN-NN, PROPOSAL-NNN, EPIC-NNN, CR-NNN, BUG-NNN
|
|
116
|
+
# (and = separator variants like STORY=008-04)
|
|
117
|
+
WORK_ITEM_RAW="$(jq -rs '
|
|
118
|
+
[.[] | select(.type == "user")] | .[0].message.content
|
|
119
|
+
| if type == "array" then map(.text? // "") | join(" ") else (. // "") end
|
|
120
|
+
| tostring
|
|
121
|
+
| scan("(STORY|PROPOSAL|EPIC|CR|BUG)[-=]?([0-9]+(-[0-9]+)?)") | .[0:2] | join("-")
|
|
122
|
+
' "${TRANSCRIPT_PATH}" 2>/dev/null | head -1)"
|
|
123
|
+
|
|
124
|
+
if [[ -n "${WORK_ITEM_RAW}" && "${WORK_ITEM_RAW}" != "null" && "${WORK_ITEM_RAW}" != "-" ]]; then
|
|
125
|
+
# Normalize: replace any = separator with - in the result
|
|
126
|
+
WORK_ITEM_ID="$(printf '%s' "${WORK_ITEM_RAW}" | sed 's/=/-/g')"
|
|
127
|
+
else
|
|
128
|
+
# Fallback: grep anywhere in the transcript
|
|
129
|
+
WORK_ITEM_ID="$(grep -oE '(STORY|PROPOSAL|EPIC|CR|BUG)[-=]?[0-9]+(-[0-9]+)?' "${TRANSCRIPT_PATH}" 2>/dev/null \
|
|
130
|
+
| head -1 \
|
|
131
|
+
| sed 's/=/-/g')"
|
|
132
|
+
if [[ -n "${WORK_ITEM_ID}" ]]; then
|
|
133
|
+
printf '[%s] work_item_id fallback grep: %s\n' "$(date -u +%FT%TZ)" "${WORK_ITEM_ID}" >> "${HOOK_LOG}"
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
[[ -z "${WORK_ITEM_ID}" ]] && WORK_ITEM_ID=""
|
|
137
|
+
|
|
138
|
+
# story_id is populated only when the work item is a STORY-* (backward compat)
|
|
139
|
+
STORY_ID=""
|
|
140
|
+
if [[ "${WORK_ITEM_ID}" == STORY-* ]]; then
|
|
141
|
+
STORY_ID="${WORK_ITEM_ID}"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Legacy fallback: if no work_item_id found at all, fall back to old grep for story_id only
|
|
145
|
+
if [[ -z "${WORK_ITEM_ID}" ]]; then
|
|
146
|
+
STORY_ID="$(grep -oE 'STORY[-=]?[0-9]{3}-[0-9]{2}' "${TRANSCRIPT_PATH}" 2>/dev/null \
|
|
147
|
+
| head -1 \
|
|
148
|
+
| sed -E 's/STORY[-=]?([0-9]{3}-[0-9]{2})/STORY-\1/')"
|
|
149
|
+
[[ -z "${STORY_ID}" ]] && STORY_ID="none"
|
|
150
|
+
WORK_ITEM_ID="${STORY_ID}"
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# --- assemble ledger row ---
|
|
154
|
+
TS="$(date -u +%FT%TZ)"
|
|
155
|
+
ROW="$(jq -cn \
|
|
156
|
+
--arg ts "${TS}" \
|
|
157
|
+
--arg agent "${AGENT_TYPE}" \
|
|
158
|
+
--arg story "${STORY_ID}" \
|
|
159
|
+
--arg work_item "${WORK_ITEM_ID}" \
|
|
160
|
+
--arg session "${SESSION_ID}" \
|
|
161
|
+
--arg transcript "${TRANSCRIPT_PATH}" \
|
|
162
|
+
--arg sprint "${SPRINT_ID}" \
|
|
163
|
+
--argjson usage "${USAGE_JSON}" \
|
|
164
|
+
'{ts: $ts, sprint_id: $sprint, agent_type: $agent, story_id: $story, work_item_id: $work_item, session_id: $session, transcript: $transcript} + $usage')"
|
|
165
|
+
|
|
166
|
+
printf '%s\n' "${ROW}" >> "${LEDGER}"
|
|
167
|
+
printf '[%s] wrote row: sprint=%s agent=%s work_item=%s story=%s tokens=in:%s/out:%s\n' \
|
|
168
|
+
"${TS}" "${SPRINT_ID}" "${AGENT_TYPE}" "${WORK_ITEM_ID}" "${STORY_ID}" \
|
|
169
|
+
"$(printf '%s' "${USAGE_JSON}" | jq -r '.input')" \
|
|
170
|
+
"$(printf '%s' "${USAGE_JSON}" | jq -r '.output')" \
|
|
171
|
+
>> "${HOOK_LOG}"
|
|
172
|
+
} 2>> "${HOOK_LOG}"
|
|
173
|
+
|
|
174
|
+
exit 0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SubagentStop": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/token-ledger.sh"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"PostToolUse": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Edit|Write",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/stamp-and-gate.sh"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"SessionStart": [
|
|
25
|
+
{
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-start.sh"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flashcard
|
|
3
|
+
description: Append-only project lesson log at .cleargate/FLASHCARD.md. Use BEFORE starting non-trivial work to read past gotchas ("check" mode). Use WHEN you (a) hit a surprise, (b) found the winning path after a non-trivial task succeeded, or (c) were corrected by the user — record a one-liner for future agents ("record: <text>" mode). One-liners only; tag with #schema/#auth/#test-harness/etc. Also triggers on phrases: "turns out", "unexpected", "gotcha", "wasted time on", "starting work on", "before implementing", "user pushed back", "prefer X over Y", "winning path", "after several attempts", "the recipe that worked".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Flashcard — Project Lesson Log
|
|
7
|
+
|
|
8
|
+
Append-only one-liner log of non-obvious gotchas that future agents in this project should know. Lives at `.cleargate/FLASHCARD.md` in the project root. Not a general wiki — only things that surprised us and would surprise someone else.
|
|
9
|
+
|
|
10
|
+
## Modes
|
|
11
|
+
|
|
12
|
+
### `check` — read before work
|
|
13
|
+
Read `.cleargate/FLASHCARD.md`. Apply the Rule 8 filter: skip cards marked `[S]` (stale) or `[R]` (resolved) unless your current task directly matches a tag in a superseded card. Scan active cards for tags relevant to your task (grep by `#schema`, `#auth`, etc.). If unsure whether a card applies, err on applying it — reading 20 one-liners is cheap.
|
|
14
|
+
|
|
15
|
+
Use `check-all` instead when investigating history or debugging a recurring issue — it includes `[S]` and `[R]` cards.
|
|
16
|
+
|
|
17
|
+
### `record: <text>` — three trigger classes, same format
|
|
18
|
+
Append a single line to `.cleargate/FLASHCARD.md`. Same 120-char cap regardless of trigger:
|
|
19
|
+
|
|
20
|
+
1. **Surprise** — something bit us. Lead with the surprise, not the context.
|
|
21
|
+
2. **Recipe** — the winning path after a non-trivial task succeeded (roughly 5+ tool calls, or one clear dead-end recovery). Lead with the *action that worked*, not the failures traversed. Tag `#recipe` alongside the domain tag.
|
|
22
|
+
3. **Correction** — the user pushed back on an approach. Capture the rule, not the exchange. Prefer the shape "prefer X over Y because Z". Tag `#correction` alongside the domain tag.
|
|
23
|
+
|
|
24
|
+
Format (unchanged):
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
YYYY-MM-DD · #tag1 #tag2 · <lesson ≤ 120 chars>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
```
|
|
32
|
+
2026-04-18 · #redis #auth · Invite tokens in Redis-only vanish on eviction — use Postgres invites table as source of truth.
|
|
33
|
+
2026-04-19 · #recipe #wiki · For wiki drift detection, git SHA beats content hash — drops the metadata-lifecycle dependency.
|
|
34
|
+
2026-04-19 · #correction #planning · Prefer two L1/L2 stories over one L3 at epic-decomposition (user: splits are free pre-push).
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Tag vocabulary (append new tags freely, but prefer these)
|
|
38
|
+
- `#schema` — Drizzle / migration / Postgres table shape gotcha
|
|
39
|
+
- `#auth` — JWT / refresh / bcrypt / token handling
|
|
40
|
+
- `#keychain` — macOS Keychain / libsecret / `@napi-rs/keyring` / `keytar`
|
|
41
|
+
- `#redis` — Redis key shape, TTL, persistence
|
|
42
|
+
- `#test-harness` — local docker compose, Postgres 18, Redis 8, flaky tests
|
|
43
|
+
- `#ci` — pipeline / GitHub Actions / pre-commit hooks
|
|
44
|
+
- `#mcp` — MCP SDK / Streamable HTTP / session protocol
|
|
45
|
+
- `#cli` — Commander / tsup / bin entry / npm publish
|
|
46
|
+
- `#admin-api` — Admin API contract / OpenAPI snapshot / zod
|
|
47
|
+
- `#ui` — SvelteKit / Tailwind / DaisyUI
|
|
48
|
+
- `#reporting` — sprint report generation
|
|
49
|
+
- `#qa` — recurring QA kickback patterns
|
|
50
|
+
- `#ambiguity` — story-spec ambiguities that bit us
|
|
51
|
+
|
|
52
|
+
## Rules
|
|
53
|
+
|
|
54
|
+
1. **Grep before append.** `grep -iF "<key phrase>" .cleargate/FLASHCARD.md` — if a matching card exists, skip or edit the existing line with a date suffix (e.g. `… (reconfirmed 2026-05-10)`). Never duplicate.
|
|
55
|
+
2. **One line per card.** Hard cap 120 characters for the lesson body. If it needs more, you are writing docs, not a flashcard — put docs elsewhere.
|
|
56
|
+
3. **Lead with the surprise, not the context.** Good: "Drizzle 0.45 silently drops `DEFAULT gen_random_uuid()` — use `sql` template." Bad: "When working on schema migrations yesterday we found that sometimes…"
|
|
57
|
+
4. **Lessons, not events.** "Shipped STORY-004-07 today" is NOT a flashcard. "Postgres 18 needs `pgcrypto` extension for gen_random_uuid — not enabled by default in official docker image" IS.
|
|
58
|
+
5. **Ordered newest-first after the header** — new entries go at the TOP of the log section, not bottom. Readers scan the top; old stuff drifts down.
|
|
59
|
+
6. **Never delete.** Edit to add reconfirmations, supersede pointers, or status markers (Rule 7); keep the original text. History is the point.
|
|
60
|
+
7. **Status markers for cleanup.** A card may carry a status marker placed *immediately after the second `·`* and before the lesson body:
|
|
61
|
+
- no marker → active (default; the vast majority of cards).
|
|
62
|
+
- `[S]` → stale. The symbol the card references no longer exists in the repo.
|
|
63
|
+
- `[R] → superseded-by <short-ref>` → resolved or replaced by a later card / shipped fix. `<short-ref>` is a date+tag (e.g. `2026-04-19/#hooks-sentinel`) or a STORY/CR ID.
|
|
64
|
+
Markers are additive — the original lesson text is preserved. The reporter agent flags candidates at sprint end; a human approves the batch before markers are applied (see `.claude/agents/reporter.md` → "Flashcard audit").
|
|
65
|
+
8. **Check-mode filter.** `check` reads active cards only (no marker). Include `[S]` / `[R]` cards only when: (a) their tags directly match the current task area, or (b) the invocation is `check-all` (explicit history read). This keeps `check` signal-dense without losing the historical record.
|
|
66
|
+
|
|
67
|
+
## Invocation contract
|
|
68
|
+
|
|
69
|
+
When an agent invokes this skill:
|
|
70
|
+
|
|
71
|
+
- **`Skill(flashcard, "check")`** — open `.cleargate/FLASHCARD.md`, apply the Rule 8 filter, summarize matching active cards in one line per card. If none apply, respond "no relevant flashcards" and proceed.
|
|
72
|
+
- **`Skill(flashcard, "check-all")`** — same as `check` but includes `[S]` / `[R]` cards. Use when investigating history, debugging a recurring issue, or tracing a supersede chain.
|
|
73
|
+
- **`Skill(flashcard, "record: <text>")`** — parse the text for date + tags + body. If date missing, insert today's UTC date. If tags missing, refuse with "add at least one tag." For recipe-class entries include `#recipe`; for correction-class entries include `#correction`. Grep for duplicates; if dup, reconfirm the existing line instead of appending. Append to the top of the log section in the file.
|
|
74
|
+
|
|
75
|
+
## File shape
|
|
76
|
+
|
|
77
|
+
`.cleargate/FLASHCARD.md` layout:
|
|
78
|
+
|
|
79
|
+
```markdown
|
|
80
|
+
# ClearGate Flashcards
|
|
81
|
+
|
|
82
|
+
One-liner gotcha log. Newest first. Grep by tag (e.g. `grep '#schema'`).
|
|
83
|
+
Active cards have no marker; `[S]` = stale, `[R]` = resolved (see SKILL.md Rule 7).
|
|
84
|
+
Format: `YYYY-MM-DD · #tags · [marker]? lesson`
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
2026-04-19 · #redis #auth · <newest active lesson>
|
|
89
|
+
2026-04-17 · #schema · [S] drizzle-kit v0.42 silently drops indexes — fixed in v0.45 upgrade.
|
|
90
|
+
2026-04-15 · #hooks · [R] → superseded-by 2026-04-19/#hooks-sentinel · SubagentStop fires on orchestrator not subagents.
|
|
91
|
+
...
|
|
92
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# ClearGate Flashcards
|
|
2
|
+
|
|
3
|
+
One-liner gotcha log. Newest first. Grep by tag (e.g. `grep '#schema'`).
|
|
4
|
+
Active cards have no marker; `[S]` = stale, `[R]` = resolved (see `.claude/skills/flashcard/SKILL.md` Rules 7–8).
|
|
5
|
+
Format: `YYYY-MM-DD · #tags · [marker]? lesson`
|
|
6
|
+
|
|
7
|
+
---
|
|
File without changes
|
|
File without changes
|