@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.
Files changed (45) hide show
  1. package/README.md +17 -9
  2. package/bin/desktop.ini +2 -0
  3. package/bin/global-sync-manager.js +350 -0
  4. package/bin/gsd-t.js +592 -2
  5. package/bin/metrics-collector.js +167 -0
  6. package/bin/metrics-rollup.js +200 -0
  7. package/bin/patch-lifecycle.js +195 -0
  8. package/bin/rule-engine.js +160 -0
  9. package/commands/desktop.ini +2 -0
  10. package/commands/gsd-t-complete-milestone.md +192 -5
  11. package/commands/gsd-t-debug.md +16 -2
  12. package/commands/gsd-t-execute.md +257 -52
  13. package/commands/gsd-t-help.md +25 -10
  14. package/commands/gsd-t-integrate.md +35 -7
  15. package/commands/gsd-t-metrics.md +143 -0
  16. package/commands/gsd-t-plan.md +49 -2
  17. package/commands/gsd-t-quick.md +15 -3
  18. package/commands/gsd-t-status.md +78 -0
  19. package/commands/gsd-t-test-sync.md +2 -2
  20. package/commands/gsd-t-verify.md +140 -9
  21. package/commands/gsd-t-visualize.md +11 -1
  22. package/commands/gsd-t-wave.md +34 -19
  23. package/docs/GSD-T-README.md +9 -6
  24. package/docs/architecture.md +84 -2
  25. package/docs/ci-examples/desktop.ini +2 -0
  26. package/docs/ci-examples/github-actions.yml +104 -0
  27. package/docs/ci-examples/gitlab-ci.yml +116 -0
  28. package/docs/desktop.ini +2 -0
  29. package/docs/infrastructure.md +87 -1
  30. package/docs/prd-graph-engine.md +2 -2
  31. package/docs/prd-gsd2-hybrid.md +258 -135
  32. package/docs/requirements.md +63 -2
  33. package/examples/.gsd-t/contracts/desktop.ini +2 -0
  34. package/examples/.gsd-t/desktop.ini +2 -0
  35. package/examples/.gsd-t/domains/desktop.ini +2 -0
  36. package/examples/.gsd-t/domains/example-domain/desktop.ini +2 -0
  37. package/examples/desktop.ini +2 -0
  38. package/examples/rules/.gitkeep +0 -0
  39. package/package.json +40 -40
  40. package/scripts/desktop.ini +2 -0
  41. package/scripts/gsd-t-dashboard-server.js +19 -2
  42. package/scripts/gsd-t-dashboard.html +63 -0
  43. package/scripts/gsd-t-event-writer.js +1 -0
  44. package/templates/CLAUDE-global.md +30 -9
  45. 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
+ }
@@ -0,0 +1,2 @@
1
+ [.ShellClassInfo]
2
+ IconResource=C:\Program Files\Google\Drive File Stream\122.0.1.0\GoogleDriveFS.exe,27
@@ -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** at the end of `/user:gsd-t-wave` after verify passes
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", continue to Step 3
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 the full test suite**: Execute ALL tests unit, integration, and E2E
259
- 2. **Run Playwright E2E** (if configured): Detect `playwright.config.*` or Playwright in dependencies. If present, run the full Playwright suite. If specs are missing or stale, invoke `gsd-t-test-sync` first.
260
- 3. **Verify all pass**: Every test must pass. If any fail, fix before tagging (up to 2 attempts)
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
 
@@ -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 affected tests**: Execute all tests related to the changed files and domain
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. **Run E2E tests**: If the fix changed UI, routes, or user flows and an E2E framework exists, run affected specs
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