@tekyzinc/gsd-t 2.39.13 → 2.46.11
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/CHANGELOG.md +12 -0
- package/README.md +19 -10
- package/bin/desktop.ini +2 -0
- package/bin/global-sync-manager.js +350 -0
- package/bin/gsd-t.js +592 -2
- package/bin/metrics-collector.js +167 -0
- package/bin/metrics-rollup.js +200 -0
- package/bin/patch-lifecycle.js +195 -0
- package/bin/rule-engine.js +160 -0
- package/commands/desktop.ini +2 -0
- package/commands/gsd-t-complete-milestone.md +194 -6
- package/commands/gsd-t-debug.md +38 -3
- package/commands/gsd-t-doc-ripple.md +148 -0
- package/commands/gsd-t-execute.md +328 -54
- package/commands/gsd-t-help.md +32 -10
- package/commands/gsd-t-integrate.md +59 -7
- package/commands/gsd-t-metrics.md +143 -0
- package/commands/gsd-t-plan.md +49 -2
- package/commands/gsd-t-qa.md +26 -5
- package/commands/gsd-t-quick.md +36 -3
- package/commands/gsd-t-status.md +78 -0
- package/commands/gsd-t-test-sync.md +23 -2
- package/commands/gsd-t-verify.md +142 -10
- package/commands/gsd-t-visualize.md +11 -1
- package/commands/gsd-t-wave.md +64 -18
- package/docs/GSD-T-README.md +10 -6
- package/docs/architecture.md +84 -2
- package/docs/ci-examples/desktop.ini +2 -0
- package/docs/ci-examples/github-actions.yml +104 -0
- package/docs/ci-examples/gitlab-ci.yml +116 -0
- package/docs/desktop.ini +2 -0
- package/docs/framework-comparison-scorecard.md +160 -0
- package/docs/infrastructure.md +87 -1
- package/docs/prd-graph-engine.md +2 -2
- package/docs/prd-gsd2-hybrid.md +258 -135
- package/docs/requirements.md +66 -2
- package/examples/.gsd-t/contracts/desktop.ini +2 -0
- package/examples/.gsd-t/desktop.ini +2 -0
- package/examples/.gsd-t/domains/desktop.ini +2 -0
- package/examples/.gsd-t/domains/example-domain/desktop.ini +2 -0
- package/examples/desktop.ini +2 -0
- package/examples/rules/.gitkeep +0 -0
- package/examples/rules/desktop.ini +2 -0
- package/package.json +40 -40
- package/scripts/desktop.ini +2 -0
- package/scripts/gsd-t-dashboard-server.js +19 -2
- package/scripts/gsd-t-dashboard.html +63 -0
- package/scripts/gsd-t-event-writer.js +1 -0
- package/templates/CLAUDE-global.md +92 -10
- package/templates/desktop.ini +2 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD-T Declarative Rule Engine — Pattern detection via JSONL rules
|
|
5
|
+
*
|
|
6
|
+
* Loads rules from .gsd-t/metrics/rules.jsonl, evaluates them against
|
|
7
|
+
* task-metrics data, manages activation tracking and rule lifecycle.
|
|
8
|
+
*
|
|
9
|
+
* Zero external dependencies (Node.js built-ins only).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
getActiveRules, evaluateRules, getPreMortemRules, getPatchTemplate,
|
|
17
|
+
recordActivation, flagInactiveRules, consolidateRules,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// ── getActiveRules ───────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** @param {string} [projectDir] @returns {object[]} Active rules */
|
|
23
|
+
function getActiveRules(projectDir) {
|
|
24
|
+
return loadJsonl(rulesPath(projectDir)).filter((r) => r.status === "active");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── evaluateRules ────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
/** @param {string} domain @param {object} [opts] @returns {object[]} Matches */
|
|
30
|
+
function evaluateRules(domain, opts) {
|
|
31
|
+
const o = opts || {};
|
|
32
|
+
const dir = o.projectDir || process.cwd();
|
|
33
|
+
const rules = getActiveRules(dir);
|
|
34
|
+
const { readTaskMetrics } = require("./metrics-collector.js");
|
|
35
|
+
const filters = o.milestone ? { milestone: o.milestone } : {};
|
|
36
|
+
const allRecs = readTaskMetrics(filters, dir);
|
|
37
|
+
const domRecs = allRecs.filter((r) => r.domain === domain);
|
|
38
|
+
const matches = [];
|
|
39
|
+
for (const rule of rules) {
|
|
40
|
+
const win = rule.trigger.window || 0;
|
|
41
|
+
const scope = rule.trigger.scope || "domain";
|
|
42
|
+
let recs;
|
|
43
|
+
if (scope === "global") recs = win > 0 ? allRecs.slice(-win) : allRecs;
|
|
44
|
+
else if (scope === "milestone") {
|
|
45
|
+
recs = allRecs.filter((r) => r.milestone === o.milestone);
|
|
46
|
+
if (win > 0) recs = recs.slice(-win);
|
|
47
|
+
} else recs = win > 0 ? domRecs.slice(-win) : domRecs;
|
|
48
|
+
if (recs.length === 0) continue;
|
|
49
|
+
const matched = evalTrigger(rule.trigger, recs);
|
|
50
|
+
if (matched.length > 0) matches.push({ rule, matchedRecords: matched, severity: rule.severity });
|
|
51
|
+
}
|
|
52
|
+
return matches;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── getPreMortemRules ────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/** @param {string} domainType @param {string} [projectDir] @returns {object[]} */
|
|
58
|
+
function getPreMortemRules(domainType, projectDir) {
|
|
59
|
+
return getActiveRules(projectDir || process.cwd()).filter((r) => r.activation_count > 0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── getPatchTemplate ─────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/** @param {string} templateId @param {string} [projectDir] @returns {object|null} */
|
|
65
|
+
function getPatchTemplate(templateId, projectDir) {
|
|
66
|
+
return loadJsonl(templatesPath(projectDir)).find((t) => t.id === templateId) || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── recordActivation ─────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
/** @param {string} ruleId @param {string} [projectDir] */
|
|
72
|
+
function recordActivation(ruleId, projectDir) {
|
|
73
|
+
const fp = rulesPath(projectDir);
|
|
74
|
+
const rules = loadJsonl(fp);
|
|
75
|
+
const r = rules.find((x) => x.id === ruleId);
|
|
76
|
+
if (!r) return;
|
|
77
|
+
r.activation_count = (r.activation_count || 0) + 1;
|
|
78
|
+
r.last_activated = new Date().toISOString();
|
|
79
|
+
atomicWriteJsonl(fp, rules);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── flagInactiveRules ────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/** @param {number} threshold @param {string} [projectDir] @returns {object[]} */
|
|
85
|
+
function flagInactiveRules(threshold, projectDir) {
|
|
86
|
+
const rules = loadJsonl(rulesPath(projectDir));
|
|
87
|
+
return rules.filter((r) => {
|
|
88
|
+
if (r.status !== "active" || r.activation_count > 0) return false;
|
|
89
|
+
const num = parseMilestoneNum(r.milestone_created);
|
|
90
|
+
if (num === null) return false;
|
|
91
|
+
// Compare against highest milestone number among all rules as proxy for "current"
|
|
92
|
+
const maxM = rules.reduce((m, x) => Math.max(m, parseMilestoneNum(x.milestone_created) || 0), 0);
|
|
93
|
+
return (maxM - num) >= threshold;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── consolidateRules ─────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/** @param {string[]} ruleIds @param {object} consolidated @param {string} [projectDir] */
|
|
100
|
+
function consolidateRules(ruleIds, consolidated, projectDir) {
|
|
101
|
+
const fp = rulesPath(projectDir);
|
|
102
|
+
const rules = loadJsonl(fp);
|
|
103
|
+
for (const rule of rules) { if (ruleIds.includes(rule.id)) rule.status = "consolidated"; }
|
|
104
|
+
rules.push(consolidated);
|
|
105
|
+
atomicWriteJsonl(fp, rules);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Trigger evaluation ───────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
function evalTrigger(trigger, records) {
|
|
111
|
+
const { metric, operator, threshold } = trigger;
|
|
112
|
+
if (operator === "pattern_count") {
|
|
113
|
+
const m = records.filter((r) => fieldVal(r, metric) != null);
|
|
114
|
+
return m.length >= threshold ? m : [];
|
|
115
|
+
}
|
|
116
|
+
return records.filter((r) => {
|
|
117
|
+
const v = fieldVal(r, metric);
|
|
118
|
+
return v != null && cmp(v, operator, threshold);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function cmp(v, op, t) {
|
|
123
|
+
switch (op) {
|
|
124
|
+
case "gt": return v > t; case "gte": return v >= t;
|
|
125
|
+
case "lt": return v < t; case "lte": return v <= t;
|
|
126
|
+
case "eq": return v === t; case "neq": return v !== t;
|
|
127
|
+
case "in": return Array.isArray(t) && t.includes(v);
|
|
128
|
+
default: return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function fieldVal(rec, metric) {
|
|
133
|
+
return metric === "first_pass_rate" ? (rec.pass ? 1 : 0) : rec[metric];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
function rulesPath(d) { return path.join(d || process.cwd(), ".gsd-t", "metrics", "rules.jsonl"); }
|
|
139
|
+
function templatesPath(d) { return path.join(d || process.cwd(), ".gsd-t", "metrics", "patch-templates.jsonl"); }
|
|
140
|
+
|
|
141
|
+
function loadJsonl(fp) {
|
|
142
|
+
if (!fs.existsSync(fp)) return [];
|
|
143
|
+
const c = fs.readFileSync(fp, "utf8").trim();
|
|
144
|
+
return c ? c.split("\n").map(safeParse).filter(Boolean) : [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function safeParse(l) { try { return JSON.parse(l); } catch { return null; } }
|
|
148
|
+
|
|
149
|
+
function atomicWriteJsonl(fp, records) {
|
|
150
|
+
const dir = path.dirname(fp);
|
|
151
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
152
|
+
const tmp = fp + ".tmp." + process.pid;
|
|
153
|
+
fs.writeFileSync(tmp, records.map((r) => JSON.stringify(r)).join("\n") + "\n");
|
|
154
|
+
fs.renameSync(tmp, fp);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseMilestoneNum(s) {
|
|
158
|
+
const m = (s || "").match(/M(\d+)/);
|
|
159
|
+
return m ? parseInt(m[1], 10) : null;
|
|
160
|
+
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
You are finalizing a completed milestone. Your job is to archive the milestone documentation, create a git tag, and prepare for the next milestone.
|
|
4
4
|
|
|
5
5
|
This command is:
|
|
6
|
-
- **Auto-invoked**
|
|
6
|
+
- **Auto-invoked** by `/user:gsd-t-verify` (Step 8) after all quality gates pass — at ALL autonomy levels
|
|
7
|
+
- **Auto-invoked** by `/user:gsd-t-wave` as part of the VERIFY+COMPLETE phase
|
|
7
8
|
- **Standalone** when user wants to manually close a milestone
|
|
8
9
|
|
|
9
10
|
## Step 1: Verify Completion
|
|
@@ -50,6 +51,58 @@ Do not proceed to archiving. Create the smoke test now, run it, confirm it passe
|
|
|
50
51
|
|
|
51
52
|
> This gate exists because complete-milestone is the last opportunity to catch "shipped blind" features before they become user-facing bugs requiring 15 debug sessions to resolve.
|
|
52
53
|
|
|
54
|
+
## Step 1.75: Goal-Backward Verification Gate (MANDATORY)
|
|
55
|
+
|
|
56
|
+
Before archiving, verify that milestone goals are actually achieved end-to-end — not just structurally present. This catches placeholder implementations that passed all quality gates.
|
|
57
|
+
|
|
58
|
+
Refer to `.gsd-t/contracts/goal-backward-contract.md` for the full verification flow, placeholder patterns, and findings report format.
|
|
59
|
+
|
|
60
|
+
### 1.75.1 Check for Existing Goal-Backward Results
|
|
61
|
+
|
|
62
|
+
1. Read `.gsd-t/verify-report.md` — check if it contains a `Goal-Backward:` line
|
|
63
|
+
2. If the verify-report already shows `Goal-Backward: PASS` or `Goal-Backward: WARN` (no CRITICAL/HIGH), skip to Step 2
|
|
64
|
+
3. If no goal-backward results exist, or verify was run without this step, execute the full goal-backward check now (follow the same logic as `gsd-t-verify.md` Step 5.5)
|
|
65
|
+
|
|
66
|
+
### 1.75.2 Evaluate Goal-Backward Status
|
|
67
|
+
|
|
68
|
+
**If CRITICAL or HIGH findings exist:**
|
|
69
|
+
- Display findings report to user in the contract format
|
|
70
|
+
- **BLOCK milestone completion** — do not proceed to archiving
|
|
71
|
+
- Prompt:
|
|
72
|
+
```
|
|
73
|
+
⛔ Goal-Backward Verification FAILED — milestone completion blocked.
|
|
74
|
+
|
|
75
|
+
Findings:
|
|
76
|
+
{findings table}
|
|
77
|
+
|
|
78
|
+
Options:
|
|
79
|
+
1. Fix the findings and re-run /user:gsd-t-verify
|
|
80
|
+
2. Override with explicit acknowledgment: re-run this command with --force-goal-backward
|
|
81
|
+
|
|
82
|
+
Proceed with option 1 (recommended) or acknowledge to force completion?
|
|
83
|
+
```
|
|
84
|
+
- If user provides `--force-goal-backward` flag or explicit acknowledgment: log the override and proceed with warning
|
|
85
|
+
- Log to `.gsd-t/progress.md` Decision Log:
|
|
86
|
+
```
|
|
87
|
+
- {date}: [goal-backward-override] Milestone "{name}" completed with unresolved goal-backward findings — user acknowledged. Findings: {summary}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**If only MEDIUM findings (warnings):**
|
|
91
|
+
- Log findings to `.gsd-t/progress.md` Decision Log:
|
|
92
|
+
```
|
|
93
|
+
- {date}: [goal-backward-warn] Goal-backward check found {N} medium findings before milestone archive — {summary}. Not blocking.
|
|
94
|
+
```
|
|
95
|
+
- Proceed to Step 2
|
|
96
|
+
|
|
97
|
+
**If PASS (no findings):**
|
|
98
|
+
- Log to `.gsd-t/progress.md` Decision Log:
|
|
99
|
+
```
|
|
100
|
+
- {date}: [goal-backward-pass] Goal-backward verification passed — {N} requirements checked, no placeholder patterns found
|
|
101
|
+
```
|
|
102
|
+
- Proceed to Step 2
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
53
106
|
## Step 2: Gap Analysis Gate
|
|
54
107
|
|
|
55
108
|
After verification passes, run a gap analysis against `docs/requirements.md` scoped to this milestone's deliverables:
|
|
@@ -85,7 +138,112 @@ Before archiving, extract learning from the event stream to improve future runs.
|
|
|
85
138
|
- If approved: append the rule to CLAUDE.md under the relevant section
|
|
86
139
|
- Write event: `node ~/.claude/scripts/gsd-t-event-writer.js --type distillation --command gsd-t-complete-milestone --reasoning "{rule}" --outcome success || true`
|
|
87
140
|
|
|
88
|
-
5. If no patterns found (fewer than 3 occurrences): log "Distillation complete — no repeating patterns found"
|
|
141
|
+
5. If no patterns found (fewer than 3 occurrences): log "Distillation complete — no repeating patterns found"
|
|
142
|
+
|
|
143
|
+
### Step 2.5b: Rule Engine Distillation
|
|
144
|
+
|
|
145
|
+
After event-stream pattern detection, run rule-based distillation using the declarative rule engine and patch lifecycle:
|
|
146
|
+
|
|
147
|
+
1. **Rule Evaluation**: Run via Bash:
|
|
148
|
+
`node -e "const re = require('./bin/rule-engine.js'); const domains = [/* list milestone domain names */]; domains.forEach(d => { const m = re.evaluateRules(d, { projectDir: '.', milestone: '{milestone-id}' }); m.forEach(x => { console.log('FIRED: ' + x.rule.id + ' — ' + x.rule.name + ' [' + x.severity + ']'); re.recordActivation(x.rule.id, '.'); }); });" 2>/dev/null || true`
|
|
149
|
+
|
|
150
|
+
2. **Patch Candidate Generation**: For each fired rule with `action: 'patch'`, run via Bash:
|
|
151
|
+
`node -e "const re = require('./bin/rule-engine.js'); const pl = require('./bin/patch-lifecycle.js'); const fired = [/* rule IDs from step 1 */]; fired.forEach(ruleId => { const rule = re.getActiveRules('.').find(r => r.id === ruleId); if(rule && rule.action === 'patch' && rule.patch_template_id) { const p = pl.createCandidate(ruleId, rule.patch_template_id, 0, '.'); console.log('CANDIDATE: ' + p.id + ' from ' + ruleId); } });" 2>/dev/null || true`
|
|
152
|
+
|
|
153
|
+
3. **Promotion Gate Check**: For all applied/measured patches, run via Bash:
|
|
154
|
+
`node -e "const pl = require('./bin/patch-lifecycle.js'); ['applied','measured'].forEach(s => { pl.getPatchesByStatus(s, '.').forEach(p => { const g = pl.checkPromotionGate(p.id, '.'); if(g.passes) { pl.promote(p.id, '.'); console.log('PROMOTED: ' + p.id); } else if(p.measured_milestones && p.measured_milestones.length >= 2) { pl.deprecate(p.id, g.reason, '.'); console.log('DEPRECATED: ' + p.id + ' — ' + g.reason); } }); });" 2>/dev/null || true`
|
|
155
|
+
|
|
156
|
+
4. **Graduation**: For promoted patches sustained 3+ milestones, run via Bash:
|
|
157
|
+
`node -e "const pl = require('./bin/patch-lifecycle.js'); pl.getPatchesByStatus('promoted', '.').forEach(p => { const r = pl.graduate(p.id, '.'); if(r.target) console.log('GRADUATED: ' + p.id + ' → ' + r.target); });" 2>/dev/null || true`
|
|
158
|
+
|
|
159
|
+
5. **Consolidation & Deprecation**: Run via Bash:
|
|
160
|
+
`node -e "const re = require('./bin/rule-engine.js'); const f = re.flagInactiveRules(5, '.'); if(f.length) f.forEach(r => console.log('INACTIVE: ' + r.id + ' — no activations in 5+ milestones'));" 2>/dev/null || true`
|
|
161
|
+
|
|
162
|
+
6. **Quality Budget Governance**: Compute rework percentage from task-metrics. Run via Bash:
|
|
163
|
+
`node -e "const mc = require('./bin/metrics-collector.js'); const recs = mc.readTaskMetrics({ milestone: '{milestone-id}' }, '.'); if(recs.length) { const rework = recs.filter(r => r.fix_cycles > 0).length; const pct = (rework/recs.length*100).toFixed(1); console.log('Rework: ' + pct + '% (' + rework + '/' + recs.length + ')'); if(pct > 20) console.log('⚠️ REWORK CEILING EXCEEDED — triggering constraint tightening for next milestone'); }" 2>/dev/null || true`
|
|
164
|
+
|
|
165
|
+
When rework ceiling (20%) exceeded: log warning and note that next milestone should: force discuss phase, require contract review, split large tasks.
|
|
166
|
+
|
|
167
|
+
### Step 2.5c: Global Rule Promotion
|
|
168
|
+
|
|
169
|
+
After local rule promotion completes, propagate newly promoted rules to global metrics:
|
|
170
|
+
|
|
171
|
+
1. **Check for promoted rules**: Run via Bash:
|
|
172
|
+
```bash
|
|
173
|
+
node -e "const pl = require('./bin/patch-lifecycle.js'); const promoted = pl.getPatchesByStatus('promoted', '.'); console.log(JSON.stringify(promoted.map(p => ({ id: p.id, rule_id: p.rule_id, template_id: p.template_id }))));" 2>/dev/null || true
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
2. **Copy promoted rules to global**: For each promoted rule, run via Bash:
|
|
177
|
+
```bash
|
|
178
|
+
node -e "
|
|
179
|
+
const gsm = require('./bin/global-sync-manager.js');
|
|
180
|
+
const re = require('./bin/rule-engine.js');
|
|
181
|
+
const pl = require('./bin/patch-lifecycle.js');
|
|
182
|
+
const promoted = pl.getPatchesByStatus('promoted', '.');
|
|
183
|
+
let count = 0;
|
|
184
|
+
for (const p of promoted) {
|
|
185
|
+
const rule = re.getActiveRules('.').find(r => r.id === p.rule_id);
|
|
186
|
+
if (!rule) continue;
|
|
187
|
+
const written = gsm.writeGlobalRule({
|
|
188
|
+
id: rule.id,
|
|
189
|
+
original_rule: rule,
|
|
190
|
+
source_project: (() => { try { return require('./package.json').name; } catch { return require('path').basename(process.cwd()); } })(),
|
|
191
|
+
source_project_dir: process.cwd(),
|
|
192
|
+
});
|
|
193
|
+
gsm.checkUniversalPromotion(written.global_id);
|
|
194
|
+
count++;
|
|
195
|
+
}
|
|
196
|
+
if (count > 0) console.log('Promoted ' + count + ' rules to global metrics');
|
|
197
|
+
" 2>/dev/null || true
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
3. **Write global rollup entry**: Run via Bash:
|
|
201
|
+
```bash
|
|
202
|
+
node -e "
|
|
203
|
+
const gsm = require('./bin/global-sync-manager.js');
|
|
204
|
+
const mc = require('./bin/metrics-collector.js');
|
|
205
|
+
const mr = require('./bin/metrics-rollup.js');
|
|
206
|
+
const name = (() => { try { return require('./package.json').name; } catch { return require('path').basename(process.cwd()); } })();
|
|
207
|
+
const rollups = mr.readRollups({}, '.');
|
|
208
|
+
const latest = rollups.length > 0 ? rollups[rollups.length - 1] : null;
|
|
209
|
+
if (latest) {
|
|
210
|
+
gsm.writeGlobalRollup({
|
|
211
|
+
source_project: name, source_project_dir: process.cwd(),
|
|
212
|
+
milestone: latest.milestone, version: latest.version,
|
|
213
|
+
total_tasks: latest.total_tasks, first_pass_rate: latest.first_pass_rate,
|
|
214
|
+
avg_duration_s: latest.avg_duration_s, total_fix_cycles: latest.total_fix_cycles,
|
|
215
|
+
total_tokens: latest.total_tokens, elo_after: latest.elo_after,
|
|
216
|
+
signal_distribution: latest.signal_distribution,
|
|
217
|
+
domain_breakdown: latest.domain_breakdown,
|
|
218
|
+
});
|
|
219
|
+
console.log('Updated global rollup for ' + name);
|
|
220
|
+
}
|
|
221
|
+
" 2>/dev/null || true
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
4. **Write global signal distribution**: Run via Bash:
|
|
225
|
+
```bash
|
|
226
|
+
node -e "
|
|
227
|
+
const gsm = require('./bin/global-sync-manager.js');
|
|
228
|
+
const mc = require('./bin/metrics-collector.js');
|
|
229
|
+
const name = (() => { try { return require('./package.json').name; } catch { return require('path').basename(process.cwd()); } })();
|
|
230
|
+
const allRecs = mc.readTaskMetrics({}, '.');
|
|
231
|
+
if (allRecs.length === 0) { process.exit(0); }
|
|
232
|
+
const counts = {};
|
|
233
|
+
allRecs.forEach(r => { counts[r.signal_type] = (counts[r.signal_type] || 0) + 1; });
|
|
234
|
+
const total = allRecs.length;
|
|
235
|
+
const rates = {};
|
|
236
|
+
for (const [k, v] of Object.entries(counts)) { rates[k] = Math.round(v / total * 1000) / 1000; }
|
|
237
|
+
gsm.writeGlobalSignalDistribution({
|
|
238
|
+
source_project: name, source_project_dir: process.cwd(),
|
|
239
|
+
total_tasks: total, signal_counts: counts, signal_rates: rates,
|
|
240
|
+
domain_type_signals: [],
|
|
241
|
+
});
|
|
242
|
+
console.log('Updated global signal distribution for ' + name);
|
|
243
|
+
" 2>/dev/null || true
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
5. If no rules were promoted this milestone: skip silently (steps 3-4 still run for rollup/signal tracking).
|
|
89
247
|
|
|
90
248
|
## Step 3: Gather Milestone Artifacts
|
|
91
249
|
|
|
@@ -233,6 +391,32 @@ If `.gsd-t/scan/` exists (a prior scan has been run):
|
|
|
233
391
|
|
|
234
392
|
If `.gsd-t/scan/` doesn't exist → skip (no scan data to maintain).
|
|
235
393
|
|
|
394
|
+
## Step 8.7: Generate Metrics Rollup
|
|
395
|
+
|
|
396
|
+
Generate milestone-level metrics aggregation and ELO score:
|
|
397
|
+
|
|
398
|
+
1. Run via Bash:
|
|
399
|
+
`node bin/metrics-rollup.js {milestone-id} {version} 2>/dev/null`
|
|
400
|
+
2. If rollup succeeds, display:
|
|
401
|
+
```
|
|
402
|
+
## Process Metrics — {milestone}
|
|
403
|
+
- ELO: {elo_before} → {elo_after} ({elo_delta > 0 ? '+' : ''}{elo_delta})
|
|
404
|
+
- First-pass rate: {first_pass_rate * 100}%
|
|
405
|
+
- Tasks: {total_tasks} | Fix cycles: {total_fix_cycles}
|
|
406
|
+
- Avg duration: {avg_duration_s}s | Avg context: {avg_context_pct}%
|
|
407
|
+
```
|
|
408
|
+
3. If `trend_delta` is present (previous milestone exists), display:
|
|
409
|
+
```
|
|
410
|
+
Trend vs previous: first-pass rate {delta > 0 ? '↑' : '↓'} {delta}%, duration {delta}s
|
|
411
|
+
```
|
|
412
|
+
4. If `heuristic_flags` has entries, display as warnings:
|
|
413
|
+
```
|
|
414
|
+
⚠️ Heuristic: {heuristic} ({severity}) — {description}
|
|
415
|
+
```
|
|
416
|
+
5. If rollup fails (no task-metrics data): log "No task-metrics found — rollup skipped" and continue.
|
|
417
|
+
|
|
418
|
+
Include ELO score in the milestone summary (Step 5) and git tag message (Step 11).
|
|
419
|
+
|
|
236
420
|
## Step 9: Document Ripple
|
|
237
421
|
|
|
238
422
|
Before creating the git tag, verify all documentation is up to date:
|
|
@@ -255,11 +439,15 @@ Before creating the git tag, verify all documentation is up to date:
|
|
|
255
439
|
|
|
256
440
|
Verify the milestone is truly complete:
|
|
257
441
|
|
|
258
|
-
1. **Run
|
|
259
|
-
|
|
260
|
-
|
|
442
|
+
1. **Run ALL configured test suites** — detect and run every one:
|
|
443
|
+
a. Unit/integration tests (vitest/jest/mocha)
|
|
444
|
+
b. If `playwright.config.*` exists → run `npx playwright test` (full suite). Unit tests alone are NEVER sufficient when E2E exists.
|
|
445
|
+
c. If specs are missing or stale, invoke `gsd-t-test-sync` first.
|
|
446
|
+
d. Report: "Unit: X/Y pass | E2E: X/Y pass"
|
|
447
|
+
2. **Verify all pass**: Every test must pass. If any fail, fix before tagging (up to 2 attempts)
|
|
448
|
+
3. **Functional test quality gate**: Read every Playwright spec. Verify assertions check **functional behavior** (state changed after action, data loaded, content updated, widget responded to input) — NOT just element existence (`isVisible`, `toBeAttached`, `toBeEnabled`). Shallow tests that would pass on an empty HTML page with the right element IDs are a milestone completion FAIL. Flag and rewrite before proceeding.
|
|
261
449
|
4. **Compare to baseline**: If a test baseline was recorded at milestone start, verify coverage has improved or at minimum not regressed
|
|
262
|
-
5. **Log test results**: Include test pass/fail counts in the milestone summary (Step 4)
|
|
450
|
+
5. **Log test results**: Include test pass/fail counts and shallow test audit results in the milestone summary (Step 4)
|
|
263
451
|
|
|
264
452
|
## Step 11: Create Git Tag
|
|
265
453
|
|
package/commands/gsd-t-debug.md
CHANGED
|
@@ -281,13 +281,48 @@ Skip scan docs not affected by this fix. Skip analytical sections — those requ
|
|
|
281
281
|
Before committing, ensure the fix is solid:
|
|
282
282
|
|
|
283
283
|
1. **Update tests**: If the bug reveals a missing test case, add one that would have caught it
|
|
284
|
-
2. **Run
|
|
284
|
+
2. **Run ALL configured test suites** — this is NOT optional:
|
|
285
|
+
a. Detect all runners: check for vitest/jest config, playwright.config.*, cypress.config.*
|
|
286
|
+
b. Run EVERY detected suite. Unit tests alone are NEVER sufficient when E2E exists.
|
|
287
|
+
c. If `playwright.config.*` exists → run `npx playwright test` (full suite)
|
|
288
|
+
d. Report ALL results: "Unit: X/Y pass | E2E: X/Y pass"
|
|
285
289
|
3. **Verify passing**: All tests must pass. If any fail, fix before proceeding (up to 2 attempts)
|
|
286
|
-
4. **
|
|
287
|
-
5. **
|
|
290
|
+
4. **If the project has a UI but no E2E specs cover the fixed area**: WRITE THEM.
|
|
291
|
+
5. **Functional test quality**: Every E2E assertion must verify an action produced the correct outcome (state changed, data loaded, content updated) — not just that elements exist. Tests that only check `isVisible`/`toBeEnabled` are shallow layout tests and don't catch real bugs. If a test would pass on an empty HTML page with the right IDs, rewrite it.
|
|
292
|
+
6. **Regression check**: Confirm the fix doesn't break any adjacent functionality
|
|
288
293
|
|
|
289
294
|
Commit: `[debug] Fix {description} — root cause: {explanation}`
|
|
290
295
|
|
|
296
|
+
## Step 5.5: Emit Task Metrics
|
|
297
|
+
|
|
298
|
+
After committing, emit a task-metrics record for this debug session — run via Bash:
|
|
299
|
+
`node bin/metrics-collector.js --milestone {current-milestone-or-none} --domain {domain-or-debug} --task debug-{timestamp} --command debug --duration_s {elapsed} --tokens_used {estimated} --context_pct ${CTX_PCT:-0} --pass {true|false} --fix_cycles {attempts} --signal_type debug-invoked --notes "[debug] {description}" 2>/dev/null || true`
|
|
300
|
+
|
|
301
|
+
Signal type is always `debug-invoked` for debug sessions.
|
|
302
|
+
|
|
303
|
+
Emit task_complete event — run via Bash:
|
|
304
|
+
`node ~/.claude/scripts/gsd-t-event-writer.js --type task_complete --command gsd-t-debug --reasoning "signal_type=debug-invoked, domain={domain}" --outcome {success|failure} || true`
|
|
305
|
+
|
|
306
|
+
## Step 6: Doc-Ripple (Automated)
|
|
307
|
+
|
|
308
|
+
After all work is committed but before reporting completion:
|
|
309
|
+
|
|
310
|
+
1. Run threshold check — read `git diff --name-only HEAD~1` and evaluate against doc-ripple-contract.md trigger conditions
|
|
311
|
+
2. If SKIP: log "Doc-ripple: SKIP — {reason}" and proceed to completion
|
|
312
|
+
3. If FIRE: spawn doc-ripple agent:
|
|
313
|
+
|
|
314
|
+
⚙ [{model}] gsd-t-doc-ripple → blast radius analysis + parallel updates
|
|
315
|
+
|
|
316
|
+
Task subagent (general-purpose, model: sonnet):
|
|
317
|
+
"Execute the doc-ripple workflow per commands/gsd-t-doc-ripple.md.
|
|
318
|
+
Git diff context: {files changed list}
|
|
319
|
+
Command that triggered: debug
|
|
320
|
+
Produce manifest at .gsd-t/doc-ripple-manifest.md.
|
|
321
|
+
Update all affected documents.
|
|
322
|
+
Report: 'Doc-ripple: {N} checked, {N} updated, {N} skipped'"
|
|
323
|
+
|
|
324
|
+
4. After doc-ripple returns, verify manifest exists and report summary inline
|
|
325
|
+
|
|
291
326
|
$ARGUMENTS
|
|
292
327
|
|
|
293
328
|
## Auto-Clear
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# GSD-T: Doc-Ripple — Automated Document Ripple Enforcement
|
|
2
|
+
|
|
3
|
+
You are the doc-ripple agent. You identify and update all downstream documents after code changes. You are spawned by execute, integrate, quick, debug, and wave after primary work is committed.
|
|
4
|
+
|
|
5
|
+
## Step 1: Load Context
|
|
6
|
+
|
|
7
|
+
Read:
|
|
8
|
+
1. `CLAUDE.md` — project conventions and Pre-Commit Gate (project-specific extensions)
|
|
9
|
+
2. `.gsd-t/contracts/doc-ripple-contract.md` — trigger conditions, manifest format, update protocol
|
|
10
|
+
3. `.gsd-t/contracts/pre-commit-gate.md` — the gate checklist you cross-reference
|
|
11
|
+
|
|
12
|
+
Run via Bash:
|
|
13
|
+
`git diff --name-only HEAD~1 2>/dev/null || git diff --cached --name-only`
|
|
14
|
+
|
|
15
|
+
Store the changed file list for Steps 2–3.
|
|
16
|
+
|
|
17
|
+
## Step 2: Threshold Check
|
|
18
|
+
|
|
19
|
+
Evaluate the changed file list against trigger conditions from `doc-ripple-contract.md`.
|
|
20
|
+
|
|
21
|
+
Output exactly:
|
|
22
|
+
```
|
|
23
|
+
DOC-RIPPLE THRESHOLD: {FIRE|SKIP}
|
|
24
|
+
Files changed: {N} across {N} directories
|
|
25
|
+
Cross-cutting signals: {list or "none"}
|
|
26
|
+
Reason: {brief explanation}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**If SKIP**: log the decision, output `Doc-ripple: SKIP — {reason}`, and stop. No manifest. No updates.
|
|
30
|
+
|
|
31
|
+
**If FIRE**: proceed to Step 3.
|
|
32
|
+
|
|
33
|
+
## Step 3: Blast Radius Analysis
|
|
34
|
+
|
|
35
|
+
For each changed file, classify its type: source, test, contract, template, command, doc, config.
|
|
36
|
+
|
|
37
|
+
Cross-reference `.gsd-t/contracts/pre-commit-gate.md` gate checklist:
|
|
38
|
+
- API endpoint/shape changed → api-contract.md, Swagger spec, CLAUDE.md, README.md
|
|
39
|
+
- Database schema changed → schema-contract.md, docs/schema.md
|
|
40
|
+
- UI component interface changed → component-contract.md
|
|
41
|
+
- New files or directories added → owning domain scope.md
|
|
42
|
+
- Requirement implemented or changed → docs/requirements.md
|
|
43
|
+
- Component or data flow changed → docs/architecture.md
|
|
44
|
+
- Any file modified → .gsd-t/progress.md (Decision Log entry)
|
|
45
|
+
- Architectural decision made → .gsd-t/progress.md (with rationale)
|
|
46
|
+
- Tech debt discovered or fixed → .gsd-t/techdebt.md
|
|
47
|
+
- New convention established → CLAUDE.md or domain constraints.md
|
|
48
|
+
- Command file added/changed → GSD-T-README.md, README.md, templates/CLAUDE-global.md, commands/gsd-t-help.md
|
|
49
|
+
- Command added/removed → all 4 above + package.json version + bin/gsd-t.js count
|
|
50
|
+
- Wave flow changed → gsd-t-wave.md, GSD-T-README.md, README.md
|
|
51
|
+
- Template changed → verify gsd-t-init output
|
|
52
|
+
|
|
53
|
+
Build the final list: `{ document, status (UPDATED|SKIPPED), action, reason }`.
|
|
54
|
+
|
|
55
|
+
## Step 4: Generate Manifest
|
|
56
|
+
|
|
57
|
+
Write `.gsd-t/doc-ripple-manifest.md` (overwrite):
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
# Doc-Ripple Manifest — {date}
|
|
61
|
+
|
|
62
|
+
## Trigger
|
|
63
|
+
- Command: {triggering command}
|
|
64
|
+
- Files changed: {N}
|
|
65
|
+
- Threshold: FIRE — {reason}
|
|
66
|
+
|
|
67
|
+
## Blast Radius
|
|
68
|
+
|
|
69
|
+
| Document | Status | Action | Reason |
|
|
70
|
+
|----------|--------|--------|--------|
|
|
71
|
+
| {path} | {UPDATED|SKIPPED} | {action} | {reason} |
|
|
72
|
+
|
|
73
|
+
## Summary
|
|
74
|
+
- Documents checked: {N}
|
|
75
|
+
- Documents updated: {N}
|
|
76
|
+
- Documents skipped (already current): {N}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Step 5: Update Documents
|
|
80
|
+
|
|
81
|
+
Count documents marked UPDATED.
|
|
82
|
+
|
|
83
|
+
**Fewer than 3 updates — inline:**
|
|
84
|
+
For each document: read current content → determine minimal edit → apply via Edit tool (not Write) → verify after edit.
|
|
85
|
+
|
|
86
|
+
**3 or more updates — parallel subagents:**
|
|
87
|
+
|
|
88
|
+
For each document or logical group:
|
|
89
|
+
|
|
90
|
+
⚙ [haiku] gsd-t-doc-ripple → update {document}
|
|
91
|
+
(Use sonnet for docs/architecture.md and docs/requirements.md — these need reasoning.)
|
|
92
|
+
|
|
93
|
+
**OBSERVABILITY LOGGING (MANDATORY) — for each subagent spawn:**
|
|
94
|
+
|
|
95
|
+
Before spawning — run via Bash:
|
|
96
|
+
`T_START=$(date +%s) && DT_START=$(date +"%Y-%m-%d %H:%M") && TOK_START=${CLAUDE_CONTEXT_TOKENS_USED:-0} && TOK_MAX=${CLAUDE_CONTEXT_TOKENS_MAX:-200000}`
|
|
97
|
+
|
|
98
|
+
After subagent returns — run via Bash:
|
|
99
|
+
`T_END=$(date +%s) && DT_END=$(date +"%Y-%m-%d %H:%M") && TOK_END=${CLAUDE_CONTEXT_TOKENS_USED:-0} && DURATION=$((T_END-T_START))`
|
|
100
|
+
|
|
101
|
+
Compute tokens:
|
|
102
|
+
- No compaction (TOK_END >= TOK_START): `TOKENS=$((TOK_END-TOK_START))`, COMPACTED=null
|
|
103
|
+
- Compaction detected (TOK_END < TOK_START): `TOKENS=$(((TOK_MAX-TOK_START)+TOK_END))`, COMPACTED=$DT_END
|
|
104
|
+
|
|
105
|
+
Compute context utilization:
|
|
106
|
+
`if [ "${CLAUDE_CONTEXT_TOKENS_MAX:-0}" -gt 0 ]; then CTX_PCT=$(echo "scale=1; ${CLAUDE_CONTEXT_TOKENS_USED:-0} * 100 / ${CLAUDE_CONTEXT_TOKENS_MAX}" | bc); else CTX_PCT="N/A"; fi`
|
|
107
|
+
|
|
108
|
+
Alert thresholds:
|
|
109
|
+
- CTX_PCT >= 85: `echo "🔴 CRITICAL: Context at ${CTX_PCT}% — compaction likely. Task MUST be split."`
|
|
110
|
+
- CTX_PCT >= 70: `echo "⚠️ WARNING: Context at ${CTX_PCT}% — approaching compaction threshold. Consider splitting."`
|
|
111
|
+
|
|
112
|
+
Append to `.gsd-t/token-log.md` (create with header `| Datetime-start | Datetime-end | Command | Step | Model | Duration(s) | Notes | Tokens | Compacted | Domain | Task | Ctx% |` if missing):
|
|
113
|
+
`| {DT_START} | {DT_END} | gsd-t-doc-ripple | Step 5 | {model} | {DURATION}s | update:{document} | {TOKENS} | {COMPACTED} | doc-ripple | — | {CTX_PCT} |`
|
|
114
|
+
|
|
115
|
+
**Each document-update subagent prompt:**
|
|
116
|
+
```
|
|
117
|
+
Task subagent (general-purpose, model: {haiku|sonnet}):
|
|
118
|
+
"Update a single document as part of doc-ripple enforcement.
|
|
119
|
+
|
|
120
|
+
Document to update: {path}
|
|
121
|
+
Action: {action from manifest}
|
|
122
|
+
Reason: {reason from manifest}
|
|
123
|
+
Git diff context (changed files): {file list}
|
|
124
|
+
|
|
125
|
+
Instructions:
|
|
126
|
+
1. Read the current document
|
|
127
|
+
2. Apply the minimal edit — add/update only the affected section
|
|
128
|
+
3. Use the Edit tool (not Write) — preserve all existing content
|
|
129
|
+
4. Re-read after edit to confirm correctness
|
|
130
|
+
5. Report: 'Updated {document} — {one-line summary of change}'"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Step 6: Report Summary
|
|
134
|
+
|
|
135
|
+
Output:
|
|
136
|
+
```
|
|
137
|
+
Doc-ripple: {N} checked, {N} updated, {N} skipped
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
List each updated document with a one-line summary of what changed.
|
|
141
|
+
|
|
142
|
+
If any update failed, list it under `Failures:` and flag for manual review.
|
|
143
|
+
|
|
144
|
+
$ARGUMENTS
|
|
145
|
+
|
|
146
|
+
## Auto-Clear
|
|
147
|
+
|
|
148
|
+
All work is written to project files. Execute `/clear` to free the context window for the next command.
|