@tekyzinc/gsd-t 2.39.13 → 2.45.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/README.md +17 -9
- 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 +192 -5
- package/commands/gsd-t-debug.md +16 -2
- package/commands/gsd-t-execute.md +257 -52
- package/commands/gsd-t-help.md +25 -10
- package/commands/gsd-t-integrate.md +35 -7
- package/commands/gsd-t-metrics.md +143 -0
- package/commands/gsd-t-plan.md +49 -2
- package/commands/gsd-t-quick.md +15 -3
- package/commands/gsd-t-status.md +78 -0
- package/commands/gsd-t-test-sync.md +2 -2
- package/commands/gsd-t-verify.md +140 -9
- package/commands/gsd-t-visualize.md +11 -1
- package/commands/gsd-t-wave.md +34 -19
- package/docs/GSD-T-README.md +9 -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/infrastructure.md +87 -1
- package/docs/prd-graph-engine.md +2 -2
- package/docs/prd-gsd2-hybrid.md +258 -135
- package/docs/requirements.md +63 -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/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 +30 -9
- 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,9 +439,12 @@ 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)
|
|
261
448
|
4. **Compare to baseline**: If a test baseline was recorded at milestone start, verify coverage has improved or at minimum not regressed
|
|
262
449
|
5. **Log test results**: Include test pass/fail counts in the milestone summary (Step 4)
|
|
263
450
|
|
package/commands/gsd-t-debug.md
CHANGED
|
@@ -281,13 +281,27 @@ 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. **
|
|
290
|
+
4. **If the project has a UI but no E2E specs cover the fixed area**: WRITE THEM.
|
|
287
291
|
5. **Regression check**: Confirm the fix doesn't break any adjacent functionality
|
|
288
292
|
|
|
289
293
|
Commit: `[debug] Fix {description} — root cause: {explanation}`
|
|
290
294
|
|
|
295
|
+
## Step 5.5: Emit Task Metrics
|
|
296
|
+
|
|
297
|
+
After committing, emit a task-metrics record for this debug session — run via Bash:
|
|
298
|
+
`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`
|
|
299
|
+
|
|
300
|
+
Signal type is always `debug-invoked` for debug sessions.
|
|
301
|
+
|
|
302
|
+
Emit task_complete event — run via Bash:
|
|
303
|
+
`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`
|
|
304
|
+
|
|
291
305
|
$ARGUMENTS
|
|
292
306
|
|
|
293
307
|
## Auto-Clear
|