cleargate 0.5.0 → 0.6.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/MANIFEST.json +30 -16
- package/dist/cli.cjs +486 -51
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +481 -47
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +24 -0
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +24 -0
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +74 -0
- package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +162 -0
- package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +10 -7
- package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +9 -8
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +36 -13
- package/dist/templates/cleargate-planning/.claude/settings.json +9 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +55 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +7 -7
- package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +137 -40
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +93 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +8 -4
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +9 -1
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +74 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +65 -1
- package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +31 -8
- package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +93 -8
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +19 -4
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +58 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +32 -2
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +3 -1
- package/dist/templates/cleargate-planning/CLAUDE.md +1 -1
- package/dist/templates/cleargate-planning/MANIFEST.json +30 -16
- package/package.json +1 -1
- package/templates/cleargate-planning/.claude/agents/architect.md +24 -0
- package/templates/cleargate-planning/.claude/agents/developer.md +24 -0
- package/templates/cleargate-planning/.claude/agents/reporter.md +74 -0
- package/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +162 -0
- package/templates/cleargate-planning/.claude/hooks/session-start.sh +10 -7
- package/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +9 -8
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +36 -13
- package/templates/cleargate-planning/.claude/settings.json +9 -0
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +55 -0
- package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +7 -7
- package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +137 -40
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +93 -0
- package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +8 -4
- package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +9 -1
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +74 -0
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +65 -1
- package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +31 -8
- package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +93 -8
- package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +19 -4
- package/templates/cleargate-planning/.cleargate/templates/hotfix.md +58 -0
- package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +32 -2
- package/templates/cleargate-planning/.cleargate/templates/story.md +3 -1
- package/templates/cleargate-planning/CLAUDE.md +1 -1
- package/templates/cleargate-planning/MANIFEST.json +30 -16
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* update_state.mjs — Atomic state/counter update for a story in state.json
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* node update_state.mjs <STORY-ID> <new-state>
|
|
7
|
-
* node update_state.mjs <STORY-ID> --qa-bounce
|
|
8
|
-
* node update_state.mjs <STORY-ID> --arch-bounce
|
|
6
|
+
* node update_state.mjs <STORY-ID> <new-state> — transition to a new state
|
|
7
|
+
* node update_state.mjs <STORY-ID> --qa-bounce — increment qa_bounces counter
|
|
8
|
+
* node update_state.mjs <STORY-ID> --arch-bounce — increment arch_bounces counter
|
|
9
|
+
* node update_state.mjs <STORY-ID> --lane <standard|fast> — set lane for a story
|
|
10
|
+
* node update_state.mjs <STORY-ID> --lane-demote <reason> — demote story from fast lane
|
|
9
11
|
*
|
|
10
12
|
* Atomic write: write to .tmp.<pid> file, then rename to final path.
|
|
11
13
|
* Idempotent: if new state equals current (for state transitions) and
|
|
@@ -13,13 +15,16 @@
|
|
|
13
15
|
*
|
|
14
16
|
* Auto-escalation: when qa_bounces or arch_bounces reaches BOUNCE_CAP (3),
|
|
15
17
|
* state is automatically set to "Escalated".
|
|
18
|
+
*
|
|
19
|
+
* Migration: reads v1 state.json transparently; upgrades to v2 on first touch
|
|
20
|
+
* (injects lane fields with defaults; emits one stderr log line).
|
|
16
21
|
*/
|
|
17
22
|
|
|
18
23
|
import fs from 'node:fs';
|
|
19
24
|
import path from 'node:path';
|
|
20
25
|
import { fileURLToPath } from 'node:url';
|
|
21
26
|
import { SCHEMA_VERSION, VALID_STATES, TERMINAL_STATES, BOUNCE_CAP } from './constants.mjs';
|
|
22
|
-
import { validateState } from './validate_state.mjs';
|
|
27
|
+
import { validateState, validateShapeIgnoringVersion } from './validate_state.mjs';
|
|
23
28
|
|
|
24
29
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
30
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
@@ -29,11 +34,36 @@ function usage() {
|
|
|
29
34
|
'Usage:\n' +
|
|
30
35
|
' node update_state.mjs <STORY-ID> <new-state>\n' +
|
|
31
36
|
' node update_state.mjs <STORY-ID> --qa-bounce\n' +
|
|
32
|
-
' node update_state.mjs <STORY-ID> --arch-bounce\n'
|
|
37
|
+
' node update_state.mjs <STORY-ID> --arch-bounce\n' +
|
|
38
|
+
' node update_state.mjs <STORY-ID> --lane <standard|fast>\n' +
|
|
39
|
+
' node update_state.mjs <STORY-ID> --lane-demote <reason>\n'
|
|
33
40
|
);
|
|
34
41
|
process.exit(2);
|
|
35
42
|
}
|
|
36
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Migrate a v1 state.json to v2 by injecting lane fields with defaults.
|
|
46
|
+
* Mutates the state object in-place and returns it.
|
|
47
|
+
* Emits a single stderr log line describing the migration.
|
|
48
|
+
* @param {object} state - Parsed v1 state object
|
|
49
|
+
* @returns {object} - The mutated (now v2) state object
|
|
50
|
+
*/
|
|
51
|
+
export function migrateV1ToV2(state) {
|
|
52
|
+
state.schema_version = 2;
|
|
53
|
+
const storyIds = Object.keys(state.stories || {});
|
|
54
|
+
for (const id of storyIds) {
|
|
55
|
+
const story = state.stories[id];
|
|
56
|
+
if (story.lane == null) story.lane = 'standard';
|
|
57
|
+
if (story.lane_assigned_by == null) story.lane_assigned_by = 'migration-default';
|
|
58
|
+
if (story.lane_demoted_at === undefined) story.lane_demoted_at = null;
|
|
59
|
+
if (story.lane_demotion_reason === undefined) story.lane_demotion_reason = null;
|
|
60
|
+
}
|
|
61
|
+
process.stderr.write(
|
|
62
|
+
`migration: schema_version 1 → 2 for sprint ${state.sprint_id} (${storyIds.length} stories defaulted to lane: standard)\n`
|
|
63
|
+
);
|
|
64
|
+
return state;
|
|
65
|
+
}
|
|
66
|
+
|
|
37
67
|
function resolveStateFile() {
|
|
38
68
|
const envFile = process.env.CLEARGATE_STATE_FILE;
|
|
39
69
|
if (envFile) return path.resolve(envFile);
|
|
@@ -71,10 +101,24 @@ function main() {
|
|
|
71
101
|
process.exit(1);
|
|
72
102
|
}
|
|
73
103
|
|
|
74
|
-
//
|
|
104
|
+
// Pre-migration: validate shape (ignoring version) before potentially migrating
|
|
105
|
+
const preCheck = validateShapeIgnoringVersion(state);
|
|
106
|
+
if (!preCheck.valid) {
|
|
107
|
+
process.stderr.write(`Error: state.json is invalid:\n`);
|
|
108
|
+
for (const e of preCheck.errors) process.stderr.write(` - ${e}\n`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Migrate v1 → v2 if needed; write atomically so subsequent reads see v2
|
|
113
|
+
if (state.schema_version === 1) {
|
|
114
|
+
state = migrateV1ToV2(state);
|
|
115
|
+
atomicWrite(stateFile, state);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Post-migration strict validation
|
|
75
119
|
const { valid, errors } = validateState(state);
|
|
76
120
|
if (!valid) {
|
|
77
|
-
process.stderr.write(`Error: state.json is invalid:\n`);
|
|
121
|
+
process.stderr.write(`Error: state.json is invalid after migration:\n`);
|
|
78
122
|
for (const e of errors) process.stderr.write(` - ${e}\n`);
|
|
79
123
|
process.exit(1);
|
|
80
124
|
}
|
|
@@ -86,7 +130,48 @@ function main() {
|
|
|
86
130
|
|
|
87
131
|
const story = state.stories[storyId];
|
|
88
132
|
|
|
89
|
-
if (action === '--
|
|
133
|
+
if (action === '--lane') {
|
|
134
|
+
const laneValue = args[2];
|
|
135
|
+
if (!laneValue || !['standard', 'fast'].includes(laneValue)) {
|
|
136
|
+
process.stderr.write(
|
|
137
|
+
`Error: --lane requires a value of "standard" or "fast"\n`
|
|
138
|
+
);
|
|
139
|
+
process.exit(2);
|
|
140
|
+
}
|
|
141
|
+
// TODO(STORY-022-04): cross-read sprint plan to enforce rubric §6 contradiction check
|
|
142
|
+
// (expected_bounce_exposure: med|high + lane: fast is a contradiction per PROPOSAL-013 §2.3 #6)
|
|
143
|
+
story.lane = laneValue;
|
|
144
|
+
story.lane_assigned_by = 'human-override';
|
|
145
|
+
story.updated_at = new Date().toISOString();
|
|
146
|
+
state.last_action = `lane-set ${storyId}: lane=${laneValue} (human-override)`;
|
|
147
|
+
state.updated_at = story.updated_at;
|
|
148
|
+
atomicWrite(stateFile, state);
|
|
149
|
+
process.stdout.write(
|
|
150
|
+
`Updated ${storyId}: lane="${laneValue}", lane_assigned_by="human-override"\n`
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
} else if (action === '--lane-demote') {
|
|
154
|
+
const reason = args[2];
|
|
155
|
+
if (!reason) {
|
|
156
|
+
process.stderr.write(
|
|
157
|
+
`Error: --lane-demote requires a reason string\n`
|
|
158
|
+
);
|
|
159
|
+
process.exit(2);
|
|
160
|
+
}
|
|
161
|
+
story.lane = 'standard';
|
|
162
|
+
story.lane_demoted_at = new Date().toISOString();
|
|
163
|
+
story.lane_demotion_reason = reason;
|
|
164
|
+
story.qa_bounces = 0;
|
|
165
|
+
story.arch_bounces = 0;
|
|
166
|
+
story.updated_at = story.lane_demoted_at;
|
|
167
|
+
state.last_action = `lane-demote ${storyId}: "${reason}"`;
|
|
168
|
+
state.updated_at = story.updated_at;
|
|
169
|
+
atomicWrite(stateFile, state);
|
|
170
|
+
process.stdout.write(
|
|
171
|
+
`Updated ${storyId}: lane="standard", lane_demoted_at="${story.lane_demoted_at}", qa_bounces=0, arch_bounces=0\n`
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
} else if (action === '--qa-bounce') {
|
|
90
175
|
if (story.state === 'Escalated') {
|
|
91
176
|
process.stderr.write(`Error: story ${storyId} is already Escalated\n`);
|
|
92
177
|
process.exit(1);
|
|
@@ -4,6 +4,14 @@ Do NOT draft this file manually. Do NOT invoke cleargate_push_item on this file.
|
|
|
4
4
|
The Vibe Coder may annotate the "Execution Guidelines" section locally — this section is never pushed.
|
|
5
5
|
Output location: .cleargate/plans/SPRINT-{ID}.md
|
|
6
6
|
Do NOT output these instructions.
|
|
7
|
+
|
|
8
|
+
§1 Lane column placement: "Lane" is inserted between "Title" and "Milestone" in the Consolidated Deliverables
|
|
9
|
+
table (§1). This positions lane as a planning signal adjacent to the story title, which is where the Architect
|
|
10
|
+
most naturally reads it during Sprint Design Review. Values are "standard" (default) or "fast" (see protocol §24).
|
|
11
|
+
|
|
12
|
+
§2.4 Lane Audit: The Architect populates one row per fast-lane story during Sprint Design Review. Empty by default.
|
|
13
|
+
Rows are added only for non-`standard` lanes. The subsection is numbered 2.4; the former §2.4 ADR-Conflict Flags
|
|
14
|
+
is renumbered to §2.5.
|
|
7
15
|
</instructions>
|
|
8
16
|
|
|
9
17
|
---
|
|
@@ -40,9 +48,9 @@ cached_gate_result:
|
|
|
40
48
|
## 1. Consolidated Deliverables
|
|
41
49
|
*(Pulled from PM tool. IDs are the remote PM entity IDs.)*
|
|
42
50
|
|
|
43
|
-
| Story ID | Title | Milestone | Parallel? | Bounce Exposure |
|
|
44
|
-
|
|
45
|
-
| `{STORY-NNN-NN}` | {Title} | M{N} | y / n | low / med / high |
|
|
51
|
+
| Story ID | Title | Lane | Milestone | Parallel? | Bounce Exposure |
|
|
52
|
+
|---|---|---|---|---|---|
|
|
53
|
+
| `{STORY-NNN-NN}` | {Title} | standard / fast | M{N} | y / n | low / med / high |
|
|
46
54
|
|
|
47
55
|
## 2. Execution Strategy
|
|
48
56
|
*(Written by Architect during Sprint Design Review. Required before `execution_mode: v2` sprint start. Under v1, this section may be omitted or left as a stub.)*
|
|
@@ -64,7 +72,14 @@ Example:
|
|
|
64
72
|
{Explicit conflict risks. One bullet per risk. Cite file + story pair.}
|
|
65
73
|
- None identified. (Replace with actual warnings if applicable.)
|
|
66
74
|
|
|
67
|
-
### 2.4
|
|
75
|
+
### 2.4 Lane Audit
|
|
76
|
+
{Architect populates one row per fast-lane story during Sprint Design Review. Empty by default — rows added only for non-`standard` lanes.}
|
|
77
|
+
|
|
78
|
+
| Story | Lane | Rationale (≤80 chars) |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `STORY-NNN-NN` | fast | <one-line rationale> |
|
|
81
|
+
|
|
82
|
+
### 2.5 ADR-Conflict Flags
|
|
68
83
|
{Any story whose implementation conflicts with an Architectural Decision Record in `.cleargate/knowledge/` or prior sprint decisions. One bullet per flag.}
|
|
69
84
|
- None identified. (Replace with actual flags if applicable.)
|
|
70
85
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
hotfix_id: "{ID}"
|
|
3
|
+
status: "Draft"
|
|
4
|
+
severity: "P2"
|
|
5
|
+
originating_signal: "user-report"
|
|
6
|
+
created_at: "{ISO}"
|
|
7
|
+
created_at_version: "cleargate@0.5.0"
|
|
8
|
+
merged_at: null
|
|
9
|
+
commit_sha: null
|
|
10
|
+
verified_by: null
|
|
11
|
+
lane: "hotfix"
|
|
12
|
+
draft_tokens:
|
|
13
|
+
input: null
|
|
14
|
+
output: null
|
|
15
|
+
cache_read: null
|
|
16
|
+
cache_creation: null
|
|
17
|
+
model: null
|
|
18
|
+
sessions: []
|
|
19
|
+
cached_gate_result:
|
|
20
|
+
pass: null
|
|
21
|
+
failing_criteria: []
|
|
22
|
+
last_gate_check: null
|
|
23
|
+
# Sync attribution (EPIC-010). Optional; stamped by `cleargate push` / `cleargate pull`.
|
|
24
|
+
pushed_by: null
|
|
25
|
+
pushed_at: null
|
|
26
|
+
last_pulled_by: null
|
|
27
|
+
last_pulled_at: null
|
|
28
|
+
last_remote_update: null
|
|
29
|
+
source: "local-authored"
|
|
30
|
+
last_synced_status: null
|
|
31
|
+
last_synced_body_sha: null
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# {ID}: {SLUG}
|
|
35
|
+
|
|
36
|
+
## 1. Anomaly
|
|
37
|
+
|
|
38
|
+
**Expected Behavior:** {What the system should do under normal conditions.}
|
|
39
|
+
|
|
40
|
+
**Actual Behavior:** {What it is doing now — the observed deviation.}
|
|
41
|
+
|
|
42
|
+
## 2. Files Touched
|
|
43
|
+
|
|
44
|
+
Hotfix discipline: ≤2 files, ≤30 LOC net (EPIC-022 §3).
|
|
45
|
+
|
|
46
|
+
- `{path/to/file.ts}` — {brief description of change}
|
|
47
|
+
|
|
48
|
+
## 3. Verification Steps
|
|
49
|
+
|
|
50
|
+
> Rule: §3 must be non-empty before merging. An empty §3 blocks merge at review time.
|
|
51
|
+
|
|
52
|
+
1. - [ ] {Step 1: describe what to run or observe}
|
|
53
|
+
2. - [ ] {Step 2: confirm the anomaly is resolved}
|
|
54
|
+
3. - [ ] {Step 3: confirm no regression in adjacent behavior}
|
|
55
|
+
|
|
56
|
+
## 4. Rollback
|
|
57
|
+
|
|
58
|
+
If the hotfix introduces a regression, revert by running `git revert <commit-sha>` on the sprint or main branch. The original anomaly will reappear; escalate to a sprint story for a permanent fix. No data migrations are involved unless noted in §2 above.
|
|
@@ -3,14 +3,15 @@ sprint_id: "SPRINT-NN"
|
|
|
3
3
|
status: "Draft"
|
|
4
4
|
generated_at: "<ISO-8601>"
|
|
5
5
|
generated_by: "Reporter agent"
|
|
6
|
-
template_version:
|
|
6
|
+
template_version: 2
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
<!-- Sprint Report v2 Template — template_version:
|
|
9
|
+
<!-- Sprint Report v2 Template — template_version: 2 -->
|
|
10
10
|
<!-- Event-type vocabulary (STORY-013-05 / protocol §§16–17):
|
|
11
11
|
User-Review: UR:review-feedback | UR:bug
|
|
12
12
|
Change-Request: CR:bug | CR:spec-clarification | CR:scope-change | CR:approach-change
|
|
13
13
|
Circuit-breaker: test-pattern | spec-gap | environment
|
|
14
|
+
Lane-Demotion: LD
|
|
14
15
|
These tokens appear verbatim in §2 CR Change Log and §3 Execution Metrics tallies. -->
|
|
15
16
|
|
|
16
17
|
# SPRINT-<NN> Report: <Sprint Title>
|
|
@@ -74,6 +75,12 @@ template_version: 1
|
|
|
74
75
|
| Stories shipped (Done) | N |
|
|
75
76
|
| Stories escalated | N |
|
|
76
77
|
| Stories carried over | N |
|
|
78
|
+
| Fast-Track Ratio | N% |
|
|
79
|
+
| Fast-Track Demotion Rate | N% |
|
|
80
|
+
| Hotfix Count (sprint window) | N |
|
|
81
|
+
| Hotfix-to-Story Ratio | N |
|
|
82
|
+
| Hotfix Cap Breaches | N |
|
|
83
|
+
| LD events | N |
|
|
77
84
|
| Total QA bounces | N |
|
|
78
85
|
| Total Arch bounces | N |
|
|
79
86
|
| CR:bug events | N |
|
|
@@ -157,6 +164,29 @@ If zero candidates: No stale flashcards detected.
|
|
|
157
164
|
| Three-surface landing compliance | Green/Yellow/Red | |
|
|
158
165
|
| Circuit-breaker fires (if any) | Green/Yellow/Red | |
|
|
159
166
|
|
|
167
|
+
### Lane Audit
|
|
168
|
+
<!-- Filled by Reporter at sprint close. One row per fast-lane story. -->
|
|
169
|
+
|
|
170
|
+
| Story | Files touched | LOC | Demoted? | In retrospect, was fast correct? (y/n) | Notes |
|
|
171
|
+
|---|---|---|---|---|---|
|
|
172
|
+
| `STORY-NNN-NN` | N | N | y / n | y / n | |
|
|
173
|
+
|
|
174
|
+
### Hotfix Audit
|
|
175
|
+
<!-- Filled by Reporter at sprint close. One row per hotfix merged during the sprint window. -->
|
|
176
|
+
|
|
177
|
+
| Hotfix ID | Originating signal | Files touched | LOC | Resolved-by SHA | Could this have been a sprint story? (y/n) | If y — why was it missed at planning? |
|
|
178
|
+
|---|---|---|---|---|---|---|
|
|
179
|
+
| `HOTFIX-NN` | <signal> | N | N | `<sha>` | y / n | |
|
|
180
|
+
|
|
181
|
+
### Hotfix Trend
|
|
182
|
+
<!-- Filled by Reporter at sprint close. -->
|
|
183
|
+
|
|
184
|
+
<!-- TBD: Reporter fills at sprint close -->
|
|
185
|
+
|
|
186
|
+
<Reporter writes a one-paragraph narrative summarising the rolling 4-sprint hotfix count
|
|
187
|
+
and a monotonic-increase flag (yes/no). Empty by default — leave the placeholder text
|
|
188
|
+
intact for sprints with no hotfixes.>
|
|
189
|
+
|
|
160
190
|
### Tooling
|
|
161
191
|
| Item | Rating | Notes |
|
|
162
192
|
|---|---|---|
|
|
@@ -29,7 +29,8 @@ When the rubric is ambiguous, surface the decision to the human as a one-liner (
|
|
|
29
29
|
§0.1 v2 Decomposition Signals:
|
|
30
30
|
`parallel_eligible`: "y" if this story can run concurrently with other stories in the same milestone; "n" if it has a strict predecessor dependency. Default "y". Set by Architect during Sprint Design Review.
|
|
31
31
|
`expected_bounce_exposure`: "low" | "med" | "high" — predicted re-work risk derived from §2.1 scenario count + §3 file-count + ambiguity level. Default "low". Set by Architect. Used by orchestrator to sequence high-exposure stories before low-exposure ones in a v2 sprint to surface risk early.
|
|
32
|
-
|
|
32
|
+
`lane`: "standard" | "fast" — Architect-set during Sprint Design Review per the seven-check rubric in protocol §24. Default "standard". Absent in pre-EPIC-022 stories means standard per the migration default in update_state.mjs.
|
|
33
|
+
All three fields are v2-only signals. Under v1 sprints they are informational; defaults apply for stories authored before SPRINT-09.
|
|
33
34
|
|
|
34
35
|
Do NOT output these instructions.
|
|
35
36
|
</instructions>
|
|
@@ -44,6 +45,7 @@ actor: "{Persona Name}"
|
|
|
44
45
|
complexity_label: "L2"
|
|
45
46
|
parallel_eligible: "y"
|
|
46
47
|
expected_bounce_exposure: "low"
|
|
48
|
+
lane: "standard"
|
|
47
49
|
created_at: "2026-04-17T00:00:00Z"
|
|
48
50
|
updated_at: "2026-04-17T00:00:00Z"
|
|
49
51
|
created_at_version: "strategy-phase-pre-init"
|
|
@@ -18,7 +18,7 @@ This repository uses **ClearGate** — a standalone planning framework for AI co
|
|
|
18
18
|
|
|
19
19
|
**Duplicate check before drafting.** Before drafting a Proposal or work item, grep `.cleargate/delivery/archive/` + `.cleargate/FLASHCARD.md` for similar past work. If you find overlap, surface it as a one-liner (*"This is very close to STORY-003-05 shipped in SPRINT-01 — are you extending it or redoing it?"*) instead of drafting a duplicate.
|
|
20
20
|
|
|
21
|
-
**Halt at gates.** You halt at Gate 1 (Proposal approval) and Gate 2 (Ambiguity resolution) and wait for explicit human sign-off. You never call `cleargate_push_item` without `approved: true` and explicit human confirmation (
|
|
21
|
+
**Halt at gates.** You halt at Gate 1 (Proposal approval) and Gate 2 (Ambiguity resolution) and wait for explicit human sign-off. You never call `cleargate_push_item` without `approved: true` (hard reject) and explicit human confirmation. Readiness gates (`cached_gate_result.pass`) are advisory by default — the push proceeds and the item body receives an `[advisory: gate_failed — <criteria>]` prefix; opt into hard-reject via `STRICT_PUSH_GATES=true` on the MCP server.
|
|
22
22
|
|
|
23
23
|
**Drafting work items:**
|
|
24
24
|
- Use the templates in `.cleargate/templates/` (`proposal.md`, `epic.md`, `story.md`, `CR.md`, `Bug.md`, `Sprint Plan Template.md`, `initiative.md`).
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cleargate_version": "0.
|
|
3
|
-
"generated_at": "2026-04-
|
|
2
|
+
"cleargate_version": "0.6.0",
|
|
3
|
+
"generated_at": "2026-04-27T06:34:21.351Z",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": ".claude/agents/architect.md",
|
|
7
|
-
"sha256": "
|
|
7
|
+
"sha256": "297020d3344d9122c0ff95e00b436cb83640acbc78377684c04f6af8fb6fbcc4",
|
|
8
8
|
"tier": "agent",
|
|
9
9
|
"overwrite_policy": "always",
|
|
10
10
|
"preserve_on_uninstall": false
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": ".claude/agents/developer.md",
|
|
35
|
-
"sha256": "
|
|
35
|
+
"sha256": "602b8ef4ca0f8bc11472a612744535d1a6794533ceaf97587d5d421c4fc49b77",
|
|
36
36
|
"tier": "agent",
|
|
37
37
|
"overwrite_policy": "always",
|
|
38
38
|
"preserve_on_uninstall": false
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
"path": ".claude/agents/reporter.md",
|
|
49
|
-
"sha256": "
|
|
49
|
+
"sha256": "c2f6e9db07c737ada4c2f76497d08e0f881bc8f0fceb073b9479753d33c4ecf4",
|
|
50
50
|
"tier": "agent",
|
|
51
51
|
"overwrite_policy": "always",
|
|
52
52
|
"preserve_on_uninstall": false
|
|
@@ -80,29 +80,36 @@
|
|
|
80
80
|
"preserve_on_uninstall": false
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
|
-
"path": ".claude/hooks/
|
|
84
|
-
"sha256": "
|
|
83
|
+
"path": ".claude/hooks/pre-edit-gate.sh",
|
|
84
|
+
"sha256": "a9fae2eae43cf493a5bf79e9d15b909a27750515817a2ed0b45d77892b49ab2c",
|
|
85
85
|
"tier": "hook",
|
|
86
86
|
"overwrite_policy": "always",
|
|
87
87
|
"preserve_on_uninstall": false
|
|
88
88
|
},
|
|
89
|
+
{
|
|
90
|
+
"path": ".claude/hooks/session-start.sh",
|
|
91
|
+
"sha256": "77c206eeaa5d460f9e751244f25ff09089578ad4d044487cbcff8b3e1d78582f",
|
|
92
|
+
"tier": "hook",
|
|
93
|
+
"overwrite_policy": "pin-aware",
|
|
94
|
+
"preserve_on_uninstall": false
|
|
95
|
+
},
|
|
89
96
|
{
|
|
90
97
|
"path": ".claude/hooks/stamp-and-gate.sh",
|
|
91
|
-
"sha256": "
|
|
98
|
+
"sha256": "3488dfea2874e8069453a2fdf40164d2c3fefe9ad7e2453190501391707aee7b",
|
|
92
99
|
"tier": "hook",
|
|
93
|
-
"overwrite_policy": "
|
|
100
|
+
"overwrite_policy": "pin-aware",
|
|
94
101
|
"preserve_on_uninstall": false
|
|
95
102
|
},
|
|
96
103
|
{
|
|
97
104
|
"path": ".claude/hooks/token-ledger.sh",
|
|
98
|
-
"sha256": "
|
|
105
|
+
"sha256": "45113b3c3f3a78e350e112665c1aadf74a246bb4ebfe514ea7230d6bd3fa01c1",
|
|
99
106
|
"tier": "hook",
|
|
100
107
|
"overwrite_policy": "always",
|
|
101
108
|
"preserve_on_uninstall": false
|
|
102
109
|
},
|
|
103
110
|
{
|
|
104
111
|
"path": ".claude/settings.json",
|
|
105
|
-
"sha256": "
|
|
112
|
+
"sha256": "539345bf59249e645a2b5faeb9cc4f44deb657ac77e9833ef74f7e4a1f4c6e3b",
|
|
106
113
|
"tier": "cli-config",
|
|
107
114
|
"overwrite_policy": "merge-3way",
|
|
108
115
|
"preserve_on_uninstall": false
|
|
@@ -123,14 +130,14 @@
|
|
|
123
130
|
},
|
|
124
131
|
{
|
|
125
132
|
"path": ".cleargate/knowledge/cleargate-protocol.md",
|
|
126
|
-
"sha256": "
|
|
133
|
+
"sha256": "a3d02a7e6660403370568f3e412990a6e44394e65040ce21adc5f8e4269946ca",
|
|
127
134
|
"tier": "protocol",
|
|
128
135
|
"overwrite_policy": "merge-3way",
|
|
129
136
|
"preserve_on_uninstall": false
|
|
130
137
|
},
|
|
131
138
|
{
|
|
132
139
|
"path": ".cleargate/knowledge/readiness-gates.md",
|
|
133
|
-
"sha256": "
|
|
140
|
+
"sha256": "d7a79a50e3cb5fd42735b017d3d88dff0fa443444951e717013e423770bad87b",
|
|
134
141
|
"tier": "protocol",
|
|
135
142
|
"overwrite_policy": "merge-3way",
|
|
136
143
|
"preserve_on_uninstall": false
|
|
@@ -156,6 +163,13 @@
|
|
|
156
163
|
"overwrite_policy": "merge-3way",
|
|
157
164
|
"preserve_on_uninstall": false
|
|
158
165
|
},
|
|
166
|
+
{
|
|
167
|
+
"path": ".cleargate/templates/hotfix.md",
|
|
168
|
+
"sha256": "2a7c745b0d46f4a092c507d6b427c15d64540e332f9544bc572451a313f4e5cc",
|
|
169
|
+
"tier": "template",
|
|
170
|
+
"overwrite_policy": "merge-3way",
|
|
171
|
+
"preserve_on_uninstall": false
|
|
172
|
+
},
|
|
159
173
|
{
|
|
160
174
|
"path": ".cleargate/templates/initiative.md",
|
|
161
175
|
"sha256": "9bf4a5a5ff7af0938ff48d5eaa182f227195d101ba9de4d56123b4fc211b8cac",
|
|
@@ -172,7 +186,7 @@
|
|
|
172
186
|
},
|
|
173
187
|
{
|
|
174
188
|
"path": ".cleargate/templates/Sprint Plan Template.md",
|
|
175
|
-
"sha256": "
|
|
189
|
+
"sha256": "583f71c76c0b3c8cb219183f5881250219cec2719c909abf4d3069beed8cad51",
|
|
176
190
|
"tier": "template",
|
|
177
191
|
"overwrite_policy": "merge-3way",
|
|
178
192
|
"preserve_on_uninstall": false
|
|
@@ -186,14 +200,14 @@
|
|
|
186
200
|
},
|
|
187
201
|
{
|
|
188
202
|
"path": ".cleargate/templates/sprint_report.md",
|
|
189
|
-
"sha256": "
|
|
203
|
+
"sha256": "e73126842e219d856d32c4cb72e0a98066ea9ad4993e3f3919f9c8296d73afdf",
|
|
190
204
|
"tier": "template",
|
|
191
205
|
"overwrite_policy": "merge-3way",
|
|
192
206
|
"preserve_on_uninstall": false
|
|
193
207
|
},
|
|
194
208
|
{
|
|
195
209
|
"path": ".cleargate/templates/story.md",
|
|
196
|
-
"sha256": "
|
|
210
|
+
"sha256": "f46364178f09d2514405723e53d5e388ab41bce5e2b318dc1aed8743500f042f",
|
|
197
211
|
"tier": "template",
|
|
198
212
|
"overwrite_policy": "merge-3way",
|
|
199
213
|
"preserve_on_uninstall": false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cleargate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Planning framework for Claude Code agents — sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
|
|
@@ -115,6 +115,30 @@ After SPRINT-10 ships, `max = 20`. A story drafted before SPRINT-10 might cite `
|
|
|
115
115
|
|
|
116
116
|
**Rule:** Never let a Developer emit a protocol section number that conflicts with an existing one. Audit first, emit second.
|
|
117
117
|
|
|
118
|
+
## Lane Classification
|
|
119
|
+
|
|
120
|
+
Before emitting a `lane` recommendation per story during Sprint Design Review, run the seven-check rubric. A story is eligible for `lane: fast` **only if all seven checks pass**. Any single false flips it to `standard`.
|
|
121
|
+
|
|
122
|
+
1. **Size cap.** Implementation diff projected at ≤2 files AND ≤50 LOC net (additions + deletions). Tests count toward the cap; generated files do not.
|
|
123
|
+
2. **No forbidden surfaces.** Story does not modify any of the following file-path prefixes:
|
|
124
|
+
| Prefix | Category |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `mcp/src/db/`, `**/migrations/` | Database schema / migration |
|
|
127
|
+
| `mcp/src/auth/`, `mcp/src/admin-api/auth-*` | Auth / identity flow |
|
|
128
|
+
| `cleargate.config.json`, `mcp/src/config.ts` | Runtime config schema |
|
|
129
|
+
| `mcp/src/adapters/` | MCP adapter API surface |
|
|
130
|
+
| `cleargate-planning/MANIFEST.json` | Scaffold manifest |
|
|
131
|
+
| token handling, invite verification, gate enforcement | Security-relevant code |
|
|
132
|
+
3. **No new dependency.** Story does not add a package to any `package.json`. Removals and version pins within an existing major are allowed.
|
|
133
|
+
4. **Single acceptance scenario or doc-only.** Story Gherkin has exactly one `Scenario:` block (or zero, for pure doc/comment changes). Stories with `Scenario Outline:` or multiple scenarios are not fast-eligible.
|
|
134
|
+
5. **Existing tests cover the runtime change.** Either (a) story description names an existing test file the change exercises, or (b) story is doc-only / comment-only / non-runtime config. The pre-gate scanner verifies (a) by checking that at least one referenced test file exists and includes the affected module name as a string match.
|
|
135
|
+
6. **`expected_bounce_exposure: low`.** A story can only be fast if its decomposition signal is already `low`. `med` or `high` is auto-`standard`.
|
|
136
|
+
7. **No epic-spanning subsystem touches.** Story's affected files all live under one of the epic's declared scope directories. A story that touches files outside its parent epic's declared scope is auto-`standard`.
|
|
137
|
+
|
|
138
|
+
**Sprint Design Review tail step:** After running the rubric on each story, emit `lane: standard|fast` per story in the §1 story table. For every non-`standard` lane, emit a one-line rationale (≤80 chars). Architect MUST write a `## §2.4 Lane Audit` subsection in the Sprint Plan listing every fast-lane story with a ≤80-char rationale. Empty by default — rows added only for non-`standard` lanes.
|
|
139
|
+
|
|
140
|
+
Full rubric, demotion mechanics, and forbidden-surface table are in protocol §24 "Lane Routing". These rules apply under `execution_mode: v2`.
|
|
141
|
+
|
|
118
142
|
## Guardrails
|
|
119
143
|
- **No production code.** You write one markdown plan file. Nothing else.
|
|
120
144
|
- **No speculation.** Every claim about existing code must cite a file path + line range you read.
|
|
@@ -71,6 +71,30 @@ These rules apply under `execution_mode: v2`. Under v1 they are informational.
|
|
|
71
71
|
|
|
72
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
73
|
|
|
74
|
+
## Lane-Aware Execution
|
|
75
|
+
|
|
76
|
+
These rules apply under `execution_mode: v2`. Under v1 they are informational.
|
|
77
|
+
|
|
78
|
+
**On spawn:** read `.cleargate/sprint-runs/<sprint-id>/state.json` for the current sprint (locate the active sprint via `.cleargate/sprint-runs/.active`). Look up the story's `lane` field under `state.json.stories[<story-id>].lane`. Default to `"standard"` if the field is absent, the story key is missing, or `state.json` does not exist.
|
|
79
|
+
|
|
80
|
+
**lane=fast behavior:**
|
|
81
|
+
|
|
82
|
+
- Skip writing the architect-plan-citation block. No plan exists for fast-lane stories; the orchestrator dispatched without one.
|
|
83
|
+
- The pre-gate scanner (`pre_gate_runner.sh`) is **never skipped** on lane=fast — that routing contract belongs to `pre_gate_runner.sh` (STORY-022-04). The Developer's commit MUST still pass typecheck + tests, and the single-commit rule is fully preserved.
|
|
84
|
+
- All other guardrails (no `--no-verify`, no scope bleed, no mocked DB) remain in force regardless of lane.
|
|
85
|
+
|
|
86
|
+
**lane=standard behavior (or lane absent / state.json missing):**
|
|
87
|
+
|
|
88
|
+
- Follow the existing four-agent contract verbatim: read the Architect's plan, cite it in your blueprint section, implement against it.
|
|
89
|
+
|
|
90
|
+
**Demotion handler:**
|
|
91
|
+
|
|
92
|
+
If `state.json` lane flips from `"fast"` to `"standard"` mid-sprint (`lane_demoted_at` populated by `pre_gate_runner.sh` after a fast-lane scanner failure), the orchestrator re-dispatches the story with the architect plan attached. The Developer treats the new dispatch as a fresh spawn and follows the lane=standard contract — there is no state machine or continuation logic on the Developer side.
|
|
93
|
+
|
|
94
|
+
**First-line marker contract preserved:**
|
|
95
|
+
|
|
96
|
+
The Developer's first response line still emits `STORY=NNN-NN` (or `CR=NNN`, `BUG=NNN`, `EPIC=NNN`, `PROPOSAL=NNN` / `PROP=NNN`) per BUG-010's detector contract. Lane is **NOT** part of the first-line marker.
|
|
97
|
+
|
|
74
98
|
## Circuit Breaker
|
|
75
99
|
|
|
76
100
|
These rules apply under `execution_mode: v2`. Under v1 they are informational.
|
|
@@ -97,6 +97,80 @@ If SPRINT-09 Reporter regresses post-swap of this reporter.md, rollback path:
|
|
|
97
97
|
`.cleargate/sprint-runs/S-09/fixtures/sprint-08-shaped/` was used to validate this
|
|
98
98
|
spec before atomic swap.
|
|
99
99
|
|
|
100
|
+
## Sprint Report v2.1 — Lane + Hotfix Metrics
|
|
101
|
+
|
|
102
|
+
When `state.json` has `schema_version >= 2` AND at least one story shipped with `lane: fast`,
|
|
103
|
+
the Reporter MUST populate the following additional rows and sections. When the activation
|
|
104
|
+
conditions are not met (v1 state, or all stories `lane: standard`), these rows and sections
|
|
105
|
+
may be omitted or left with placeholder values.
|
|
106
|
+
|
|
107
|
+
### §3 Execution Metrics — Six New Rows
|
|
108
|
+
|
|
109
|
+
The Reporter computes and writes these six rows in §3 (after the existing rows):
|
|
110
|
+
|
|
111
|
+
| Row label | Computation | Source |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `Fast-Track Ratio` | `count(stories where lane=fast at sprint close) / total stories × 100` | `state.json` `.stories[*].lane` |
|
|
114
|
+
| `Fast-Track Demotion Rate` | `count(stories with LD event) / count(stories where lane=fast was ever assigned) × 100` | `state.json` `.stories[*].lane_demoted_at` + sprint markdown §4 LD rows |
|
|
115
|
+
| `Hotfix Count (sprint window)` | Count of rows in `wiki/topics/hotfix-ledger.md` where `merged_at` is between sprint `started_at` and `closed_at` | `wiki/topics/hotfix-ledger.md` filtered by sprint window |
|
|
116
|
+
| `Hotfix-to-Story Ratio` | `Hotfix Count / total in-sprint stories` | Derived from above |
|
|
117
|
+
| `Hotfix Cap Breaches` | Count of rolling-7-day windows during the sprint window that had ≥ 3 hotfixes | `wiki/topics/hotfix-ledger.md` `merged_at` column |
|
|
118
|
+
| `LD events` | Count of LD event rows in sprint markdown §4 events list | Sprint plan file `## §4 Events Log` or equivalent |
|
|
119
|
+
|
|
120
|
+
**Sources detail:**
|
|
121
|
+
|
|
122
|
+
- `state.json` lane fields per `.cleargate/scripts/state.schema.json` StoryEntry: `lane`, `lane_assigned_by`, `lane_demoted_at`, `lane_demotion_reason`.
|
|
123
|
+
- Sprint markdown §4 LD events written by `pre_gate_runner.sh` `append_ld_event` (STORY-022-04). Each LD row records the story, timestamp, and demotion reason.
|
|
124
|
+
- `wiki/topics/hotfix-ledger.md` — filter rows by `merged_at` between sprint `started_at` and `closed_at`. If the ledger is absent, record `Hotfix Count = 0` and a note explaining the fallback.
|
|
125
|
+
- For historical sprints with `schema_version: 1` (no lane fields), default all lane metrics to `0` or `N/A` and note the fallback in §5 Tooling.
|
|
126
|
+
|
|
127
|
+
### §5 Process — Lane Audit table
|
|
128
|
+
|
|
129
|
+
One row per story that was ever assigned `lane: fast` during the sprint (whether it shipped fast
|
|
130
|
+
or was auto-demoted). The Reporter computes the first four columns from `git log` + `state.json`;
|
|
131
|
+
the last two columns are left blank for human fill-in at sprint close.
|
|
132
|
+
|
|
133
|
+
Template row format (per `sprint_report.md` lines 167-172):
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
| Story | Files touched | LOC | Demoted? | In retrospect, was fast correct? (y/n) | Notes |
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- **Story**: story ID (e.g. `STORY-022-08`).
|
|
140
|
+
- **Files touched**: count via `git diff --name-only <base>..<story-sha>`.
|
|
141
|
+
- **LOC**: `git diff --stat <base>..<story-sha>` insertions+deletions total.
|
|
142
|
+
- **Demoted?**: `y` if `lane_demoted_at` is non-null in `state.json`; `n` otherwise.
|
|
143
|
+
- **In retrospect, was fast correct?**: blank — human fills at close.
|
|
144
|
+
- **Notes**: blank — human fills at close.
|
|
145
|
+
|
|
146
|
+
### §5 Process — Hotfix Audit table
|
|
147
|
+
|
|
148
|
+
One row per hotfix merged within the sprint window. Read from `wiki/topics/hotfix-ledger.md`
|
|
149
|
+
filtered by `merged_at` between sprint `started_at` and `closed_at`. Last two columns blank.
|
|
150
|
+
|
|
151
|
+
Template row format (per `sprint_report.md` lines 174-179):
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
| Hotfix ID | Originating signal | Files touched | LOC | Resolved-by SHA | Could this have been a sprint story? (y/n) | If y — why was it missed at planning? |
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
If zero hotfixes in window, write a single row: `| (none) | — | — | — | — | — | — |`
|
|
158
|
+
|
|
159
|
+
### §5 Process — Hotfix Trend narrative
|
|
160
|
+
|
|
161
|
+
A one-paragraph narrative summarising the rolling 4-sprint hotfix count and a
|
|
162
|
+
monotonic-increase flag. The Reporter reads the last 4 sprint `REPORT.md` files
|
|
163
|
+
(at `.cleargate/sprint-runs/<id>/REPORT.md`) OR walks `wiki/topics/hotfix-ledger.md`
|
|
164
|
+
by `sprint_id` field to gather per-sprint counts.
|
|
165
|
+
|
|
166
|
+
Monotonic-increase flag: if the count increased (or stayed ≥ 1) for 3+ consecutive sprints,
|
|
167
|
+
flag it as `trend: INCREASING` and recommend a retrospective action in §5 Tooling.
|
|
168
|
+
|
|
169
|
+
For historical v1-schema sprints with no lane data, record `0 hotfixes (v1 — no ledger data)`.
|
|
170
|
+
|
|
171
|
+
Template location: `sprint_report.md` lines 181-188. Leave the placeholder text intact for
|
|
172
|
+
sprints with no hotfixes in the window.
|
|
173
|
+
|
|
100
174
|
## Guardrails
|
|
101
175
|
- **Numbers before narrative.** Every claim in §1 must be backed by a ledger row, commit, or flashcard -- cite them.
|
|
102
176
|
- **Do not fabricate cost.** If you cannot find current model rates, state the rate date and mark cost `~$X (rates as of <date>)`.
|