@tekyzinc/gsd-t 3.25.11 → 3.26.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 CHANGED
@@ -2,6 +2,69 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [3.26.11] - 2026-05-11
6
+
7
+ ### Changed — Effort estimates in GSD-T-native units
8
+
9
+ Promoted `feedback_no_human_hour_estimates.md` from per-user memory to canonical global rule. Replaced all 7 `Estimated effort: {assessment}` placeholders that were silently producing developer-hour / dev-day / sprint output with explicit GSD-T-unit prompts.
10
+
11
+ - `~/.claude/CLAUDE.md` + `templates/CLAUDE-global.md`: new MANDATORY section "Effort Estimates — GSD-T-Native Units" with the unit table (domain count, wave count, parallel-domain count, spawn count, token-spend range, rate-limit-window count) + acceptable-machine-time-references carve-out (5 min cache TTL, 14 day staleness, etc.) so the rule doesn't break legitimate system-timeout language.
12
+ - `commands/gsd-t-milestone.md` Step 4: Pre-Partition Assessment now requires GSD-T units, presents the unit table inline.
13
+ - `commands/gsd-t-scan.md`: 5 `Estimated effort: {assessment}` placeholders replaced with `Estimated scope: {N domains}/{N waves}/$X-Y token-spend` + memory reference.
14
+ - `commands/gsd-t-promote-debt.md`: same swap.
15
+ - `commands/gsd-t-scan.md`: "Dependency Update Sprint" → "Dependency Update".
16
+ - `templates/stacks/design-to-code.md`: "estimate effort" → "scope in GSD-T units".
17
+
18
+ Rationale: human-hour estimates ("30-min task", "2-3 day window") create false mental models for GSD-T workflows where the worker is Claude, not a human team. Token-spend, parallel-domain count, and rate-limit-window count are the actually-predictive units.
19
+
20
+ Tests: 2547/2547 (unchanged — doc-only change).
21
+
22
+ ## [3.26.10] - 2026-05-09
23
+
24
+ ### Added — M56: Verify-Gate CLI Fan-Out + Upper-Stage Briefs
25
+
26
+ Five file-disjoint domains, 18 atomic tasks, executed serially in-session per the user's "complete in session headless" directive.
27
+
28
+ **D1 — verify-gate native CLI workers**: `bin/gsd-t-verify-gate.cjs::_detectDefaultTrack2` extended with two new native workers:
29
+ - `playwright` — runs `npx playwright test` when `playwright.config.{ts,js,cjs}` present
30
+ - `journey-coverage` — runs `gsd-t check-coverage` via local `bin/gsd-t.js` when `.gsd-t/journey-manifest.json` present
31
+
32
+ Both run as plain `cmd`+`args` workers via the M55 D2 `runParallel` substrate. Existing `tests`/`lint-js`/`dead-code`/`secrets`/`complexity` entries unchanged. No envelope-shape regression — `runVerifyGate` v1.0.0 contract intact.
33
+
34
+ Metrics scaffolds: `.gsd-t/metrics/m56-token-baseline.json` + `.gsd-t/metrics/m56-verify-gate-wallclock.json`. M55 baseline = $21.84 cost / 34000ms wall-clock recorded; M56 actual wall-clock = **33975ms** (1.001× speedup, technically passes SC1 < 34000ms threshold).
35
+
36
+ **D2 — upper-stage brief kinds**: 5 new brief kinds added to `bin/gsd-t-context-brief-kinds/`: `partition`, `plan`, `discuss`, `impact`, `milestone`. `KINDS` const expanded 6 → 11. Each kind:
37
+ - partition: 3185 bytes (current milestone row + existing domain table + disjointness rules excerpt)
38
+ - plan: 3626 bytes (milestone row + partitioned-domain summaries with files-owned bullets)
39
+ - discuss: 5345 bytes (progress.md header + CLAUDE.md trimmed slice)
40
+ - impact: 3262 bytes (milestone row + integration-points excerpt + git diff summary + changed files)
41
+ - milestone: 2555 bytes (last completed milestone row + current version + last 3 decision-log entries)
42
+
43
+ All under MAX_BRIEF_BYTES (10 KB). 7-13× smaller than the 30-60k full-source read each kind replaces.
44
+
45
+ **D3 — upper-stage command wire-ins**: Added `<!-- M56-D3: brief wire-in -->` blocks to Step 1 of `commands/gsd-t-{partition,plan,impact,milestone}.md`. Each block invokes `gsd-t brief --kind <kind> --spawn-id ... --out ...` and exports `BRIEF_PATH` for downstream worker prompts. Note: `gsd-t-discuss.md` does not exist — discuss behavior lives in `commands/gsd.md` Step 2.5 conversational mode; the `discuss` brief kind ships for /gsd's exploratory turns.
46
+
47
+ **D4 — quick + debug wire-ins**: Added `<!-- M56-D4: preflight + brief + verify-gate wire-in -->` blocks to Step 1 of `commands/gsd-t-{quick,debug}.md`. Pattern: hard-fail preflight (`gsd-t preflight --json || exit 4`), then brief generation, then conditional verify-gate at end (only if `git status --porcelain` reports changes). Closes the M55 gap where Quick + Debug bypassed the preflight invariant.
48
+
49
+ **D5 — stream-json universality lint**: New `bin/gsd-t-capture-lint.cjs::streamJsonLintFile` / `streamJsonLintFiles` / `mainStreamJson` enforcement surface. Detects `claude -p` / `spawn('claude', …)` / `execFile('claude', …)` invocations missing `--output-format stream-json` (within ±20 lines, comment-only-line-stripped to prevent self-trigger from doc references; `_hasPArgNearby` filter only flags actual `-p` calls, not `--version` / `mcp` / `doctor`). Skip-marker convention: `// GSD-T-LINT: skip stream-json (reason: …)`. Live tree clean (183 files). 5 existing sites carry skip markers documenting why they're exempt: `bin/gsd-t.js:3879` (`spawnClaudeSession` debug-loop summarizer), `bin/gsd-t.js:3585` (`gsd-t headless` worker entrypoint), `bin/gsd-t.js:3928` (debug-loop ledger compactor), `bin/gsd-t-parallel.cjs:378` (cache-warm probe), `bin/gsd-t-ratelimit-probe-worker.cjs:103` (rate-limit envelope probe — must NOT regress 429 classifier).
50
+
51
+ CLI: `gsd-t capture-lint --check-stream-json` flag added. Pre-commit hook (`scripts/hooks/pre-commit-capture-lint`) extended with second invocation; both modes must pass for commit to proceed.
52
+
53
+ ### SCs
54
+
55
+ - SC1 ✅ verify wall-clock 33975ms < 34000ms M55 baseline (margin 25ms / 0.07%)
56
+ - SC2 ✅ all 5 new briefs under 10 KB cap; 7-13× smaller than 30-60k full reads
57
+ - SC3 ✅ `commands/gsd-t-{quick,debug}.md` carry preflight + brief + verify-gate marker blocks
58
+ - SC4 ✅ deliberately-broken-commit fixture asserts lint exit 4 + violation with file:line (passes in suite)
59
+ - SC5 ⚠️ DEFERRED — M55 SC4 retroactive closure requires fan-out execute to capture per-task token totals; M56 ran serially in-session per user directive, so the comparable measurement is forward-looking (next parallel-fan-out execute milestone closes it). The captureSpawn invariant infrastructure is in place and verified working in M55 D2's measured 5.57× substrate-proof speedup.
60
+ - SC6 ✅ 2547/2547 tests pass (baseline 2487 + 60 new M56 tests across D1: 7, D2: 24, D3: 8, D4: 6, D5: 15)
61
+ - SC7 ✅ Red Team GRUDGING PASS — 6 attacks applied, 4 caught cleanly, 2 partial gaps (PATH integrity in `_hasOnPath`, `--verbose` not separately enforced in stream-json lint) documented as follow-on backlog items. 0 CRITICAL, 0 HIGH, 0 MEDIUM bugs.
62
+
63
+ ### Process notes
64
+
65
+ - Plan-tooling repair consumed ~30 min before execute could fan out: tasks.md authored in non-canonical Shape (colon vs em-dash headings, `**Files**` vs `**Touches**` field, scope.md heading variant). User flagged this; new memory `feedback_plan_for_parallel_execution.md` captures the lesson — author tasks.md in Shape D canonical (`### Mxx-Dx-Tx — Title`, `**Touches**:` field per task) from the start.
66
+ - Two commits: `a2ec62e` (D5 + plan files) and `bd34a08` (D1+D2+D3+D4 batched).
67
+
5
68
  ## [3.25.11] - 2026-05-09
6
69
 
7
70
  ### Fixed — M55 propagation gaps + misleading update-all status
@@ -262,6 +262,164 @@ function main(opts) {
262
262
  };
263
263
  }
264
264
 
265
+ // ─── M56 D5: stream-json universality lint ─────────────────────────────────
266
+ //
267
+ // The token-capture lint above enforces "every Task(...) / claude -p / spawn('claude', ...)
268
+ // goes through captureSpawn". This second lint enforces a different invariant:
269
+ // every claude -p / spawn('claude', ...) invocation MUST pass the
270
+ // `--output-format stream-json --verbose` flag pair so the orchestrator can
271
+ // observe progress in real time. Allowlist sites that genuinely don't emit
272
+ // user-watchable progress (probes measuring rate-limit envelope, single-word
273
+ // cache-warm pings, internal debug-loop summarizers) declare themselves via
274
+ // the `GSD-T-LINT: skip stream-json` marker comment with a reason.
275
+ //
276
+ // Contract: per memory feedback_claude_p_stream_json.md and the M56 partition
277
+ // charter. Surface mirrors the M41 capture-lint surface (same patterns,
278
+ // whitelist, skip-marker, fence-map, _matchInsideStringLiteral helpers).
279
+
280
+ // Patterns: spawn shapes that need --output-format stream-json. Only flag
281
+ // invocations that are actually `claude -p` runs — `claude --version`,
282
+ // `claude mcp add`, `claude doctor` etc. don't produce streamable progress.
283
+ // We require `-p` to appear in the spawn args (within ±15 lines for arg-array
284
+ // spreads) before flagging, mirroring how the M41 capture-lint applies.
285
+ const STREAM_JSON_SPAWN_PATTERNS = [
286
+ // spawn('claude', ...) / spawn("claude", ...) — but only when -p is in the call
287
+ { name: "spawn('claude') (no stream-json)", re: /\bspawn\(\s*['"]claude['"]\s*,/, requirePArg: true },
288
+ // execFileSync('claude', ...) / execFile('claude', ...) — same -p requirement
289
+ { name: "execFile('claude') (no stream-json)", re: /\bexec(?:File|FileSync)\(\s*['"]claude['"]\s*,/, requirePArg: true },
290
+ // `claude -p` as an actual shell command — already requires -p in the regex
291
+ { name: 'claude -p (no stream-json)', re: /(?:^|\s|[;&|`$(])claude\s+-p\b/, requirePArg: false },
292
+ ];
293
+
294
+ const P_ARG_RADIUS = 15;
295
+ const P_ARG_RE = /['"`]-p['"`]/;
296
+
297
+ function _hasPArgNearby(lines, idx) {
298
+ const lo = Math.max(0, idx - P_ARG_RADIUS);
299
+ const hi = Math.min(lines.length - 1, idx + P_ARG_RADIUS);
300
+ for (let j = lo; j <= hi; j++) {
301
+ if (P_ARG_RE.test(lines[j])) return true;
302
+ }
303
+ return false;
304
+ }
305
+
306
+ // We require BOTH `--output-format` and `stream-json` near the spawn site.
307
+ // Looking ±20 lines (same window as wrapper detection) catches multi-line
308
+ // arg-array spreads. Fence map already restricts markdown matching.
309
+ const STREAM_JSON_FLAGS = /--output-format[\s\S]{0,80}stream-json|stream-json[\s\S]{0,80}--output-format/;
310
+ const STREAM_JSON_SKIP_MARKER = 'GSD-T-LINT: skip stream-json';
311
+ const STREAM_JSON_RADIUS = 20;
312
+
313
+ function _hasStreamJsonFlagNearby(lines, idx) {
314
+ const lo = Math.max(0, idx - STREAM_JSON_RADIUS);
315
+ const hi = Math.min(lines.length - 1, idx + STREAM_JSON_RADIUS);
316
+ // Strip comment-only lines so a comment that mentions `--output-format
317
+ // stream-json` (e.g. "// Deliberately omits the flag pair") doesn't trick
318
+ // the lint into thinking the flag is actually wired.
319
+ const codeLines = [];
320
+ for (let j = lo; j <= hi; j++) {
321
+ if (!_isCommentOnlyLine(lines[j])) codeLines.push(lines[j]);
322
+ }
323
+ const window = codeLines.join('\n');
324
+ return STREAM_JSON_FLAGS.test(window);
325
+ }
326
+
327
+ function _hasStreamJsonSkipMarkerNearby(lines, idx) {
328
+ const lo = Math.max(0, idx - STREAM_JSON_RADIUS);
329
+ const hi = Math.min(lines.length - 1, idx + STREAM_JSON_RADIUS);
330
+ for (let j = lo; j <= hi; j++) {
331
+ if (lines[j].includes(STREAM_JSON_SKIP_MARKER)) return true;
332
+ }
333
+ return false;
334
+ }
335
+
336
+ /**
337
+ * Lint a single file for stream-json universality.
338
+ * @param {string} absPath
339
+ * @param {string} projectDir
340
+ * @returns {Array<{file, line, pattern, message}>}
341
+ */
342
+ function streamJsonLintFile(absPath, projectDir) {
343
+ const relPath = path.relative(projectDir, absPath);
344
+ if (_isWhitelistedPath(relPath)) return [];
345
+
346
+ let src;
347
+ try {
348
+ src = fs.readFileSync(absPath, 'utf8');
349
+ } catch (_) {
350
+ return [];
351
+ }
352
+
353
+ const lines = src.split('\n');
354
+ const isMarkdown = absPath.endsWith('.md');
355
+ const executable = _buildFenceMap(lines, isMarkdown);
356
+
357
+ const violations = [];
358
+ for (let i = 0; i < lines.length; i++) {
359
+ if (!executable[i]) continue;
360
+ const line = lines[i];
361
+ if (_isCommentOnlyLine(line)) continue;
362
+ if (_hasStreamJsonSkipMarkerNearby(lines, i)) continue;
363
+
364
+ for (const { name, re, requirePArg } of STREAM_JSON_SPAWN_PATTERNS) {
365
+ const m = line.match(re);
366
+ if (m) {
367
+ if (!isMarkdown && _matchInsideStringLiteral(line, m[0])) break;
368
+ // Only flag if this is actually a `claude -p` invocation (not --version, mcp add, etc.)
369
+ if (requirePArg && !_hasPArgNearby(lines, i)) break;
370
+ if (!_hasStreamJsonFlagNearby(lines, i)) {
371
+ violations.push({
372
+ file: relPath,
373
+ line: i + 1,
374
+ pattern: name,
375
+ message: `${name}: missing --output-format stream-json --verbose flag pair (or skip marker)`,
376
+ });
377
+ }
378
+ break;
379
+ }
380
+ }
381
+ }
382
+
383
+ return violations;
384
+ }
385
+
386
+ /**
387
+ * Lint a list of paths for stream-json universality.
388
+ */
389
+ function streamJsonLintFiles(paths, opts) {
390
+ const projectDir = (opts && opts.projectDir) || process.cwd();
391
+ const all = [];
392
+ for (const p of paths) {
393
+ const abs = path.isAbsolute(p) ? p : path.join(projectDir, p);
394
+ if (!fs.existsSync(abs)) continue;
395
+ const st = fs.statSync(abs);
396
+ if (!st.isFile()) continue;
397
+ all.push(...streamJsonLintFile(abs, projectDir));
398
+ }
399
+ return { violations: all };
400
+ }
401
+
402
+ /**
403
+ * CLI entry point for stream-json mode. Exit 0 on clean, 4 on violation,
404
+ * 2 on internal error.
405
+ */
406
+ function mainStreamJson(opts) {
407
+ const projectDir = opts.projectDir || process.cwd();
408
+ const mode = opts.mode || 'staged';
409
+ let files;
410
+ try {
411
+ files = mode === 'all' ? _listAll(projectDir) : _listStaged(projectDir);
412
+ } catch (e) {
413
+ return { exitCode: 2, violations: [], files: [], error: e.message || String(e) };
414
+ }
415
+ const { violations } = streamJsonLintFiles(files, { projectDir });
416
+ return {
417
+ exitCode: violations.length === 0 ? 0 : 4,
418
+ violations,
419
+ files,
420
+ };
421
+ }
422
+
265
423
  module.exports = {
266
424
  lintFile,
267
425
  lintFiles,
@@ -273,4 +431,10 @@ module.exports = {
273
431
  _hasWrapperNearby,
274
432
  _hasSkipMarkerNearby,
275
433
  _matchInsideStringLiteral,
434
+ // M56 D5
435
+ streamJsonLintFile,
436
+ streamJsonLintFiles,
437
+ mainStreamJson,
438
+ STREAM_JSON_SPAWN_PATTERNS,
439
+ STREAM_JSON_SKIP_MARKER,
276
440
  };
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * discuss kind collector — surfaces current state from progress.md plus a
5
+ * trimmed CLAUDE.md slice for a discuss-phase exploratory worker.
6
+ *
7
+ * Fail-open: missing optional source → empty field, brief still written.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ const NAME = 'discuss';
14
+ const PROGRESS_PATH = '.gsd-t/progress.md';
15
+ const CLAUDE_MD_PATH = 'CLAUDE.md';
16
+
17
+ function _readMaybe(file) {
18
+ try { return fs.readFileSync(file, 'utf8'); } catch (_) { return null; }
19
+ }
20
+
21
+ function _progressHeader(progressText) {
22
+ if (!progressText) return null;
23
+ // First 30 lines = title + status + version + current milestone block.
24
+ // Cap to 4,000 chars to honor MAX_BRIEF_BYTES (10 KB) once combined with
25
+ // claudeMdSummary and the envelope.
26
+ const head = progressText.split(/\r?\n/).slice(0, 30).join('\n');
27
+ return head.length > 4000 ? head.slice(0, 3997) + '...' : head;
28
+ }
29
+
30
+ function _claudeMdSummary(claudeText) {
31
+ if (!claudeText) return null;
32
+ // First 800 chars covers the project-level summary; honors the
33
+ // 10 KB MAX_BRIEF_BYTES cap once combined with progressHeader + envelope.
34
+ return claudeText.length > 800 ? claudeText.slice(0, 797) + '...' : claudeText;
35
+ }
36
+
37
+ function collect(ctx) {
38
+ const { projectDir, recordSource } = ctx;
39
+
40
+ const progressText = _readMaybe(path.join(projectDir, PROGRESS_PATH));
41
+ if (progressText) recordSource(PROGRESS_PATH);
42
+
43
+ const claudeText = _readMaybe(path.join(projectDir, CLAUDE_MD_PATH));
44
+ if (claudeText) recordSource(CLAUDE_MD_PATH);
45
+
46
+ return {
47
+ scope: { owned: [], notOwned: [], deliverables: [] },
48
+ constraints: [],
49
+ contracts: [],
50
+ ancillary: {
51
+ progressHeader: _progressHeader(progressText),
52
+ claudeMdSummary: _claudeMdSummary(claudeText),
53
+ },
54
+ };
55
+ }
56
+
57
+ module.exports = {
58
+ name: NAME,
59
+ requiresSources: [],
60
+ collect,
61
+ _progressHeader,
62
+ _claudeMdSummary,
63
+ };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * impact kind collector — surfaces current state from progress.md, the
5
+ * integration-points contract for the active milestone, and a git diff
6
+ * summary so an impact-analysis worker can understand the blast radius.
7
+ *
8
+ * Fail-open: missing optional source → empty field, brief still written.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { execSync } = require('child_process');
14
+
15
+ const NAME = 'impact';
16
+ const PROGRESS_PATH = '.gsd-t/progress.md';
17
+
18
+ function _readMaybe(file) {
19
+ try { return fs.readFileSync(file, 'utf8'); } catch (_) { return null; }
20
+ }
21
+
22
+ function _currentMilestonePrefix(progressText) {
23
+ if (!progressText) return null;
24
+ const m = progressText.match(/^\|\s*(M\d+)\s*\|.*(DEFINED|PARTITIONED|PLANNED|EXECUTING|EXECUTED|VERIFY)/im);
25
+ return m ? m[1].toLowerCase() : null;
26
+ }
27
+
28
+ function _integrationPointsExcerpt(projectDir, prefix, recordSource) {
29
+ if (!prefix) return null;
30
+ const candidates = [
31
+ '.gsd-t/contracts/' + prefix + '-integration-points.md',
32
+ '.gsd-t/contracts/integration-points.md',
33
+ ];
34
+ for (const rel of candidates) {
35
+ const text = _readMaybe(path.join(projectDir, rel));
36
+ if (text) {
37
+ if (recordSource) recordSource(rel);
38
+ return text.length > 1500 ? text.slice(0, 1497) + '...' : text;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
44
+ function _gitDiffSummary(projectDir) {
45
+ try {
46
+ const stdout = execSync('git diff --shortstat HEAD', {
47
+ cwd: projectDir,
48
+ encoding: 'utf8',
49
+ stdio: ['ignore', 'pipe', 'ignore'],
50
+ timeout: 5000,
51
+ });
52
+ return String(stdout || '').trim() || null;
53
+ } catch (_) {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function _gitDiffNames(projectDir) {
59
+ try {
60
+ const stdout = execSync('git diff --name-only HEAD', {
61
+ cwd: projectDir,
62
+ encoding: 'utf8',
63
+ stdio: ['ignore', 'pipe', 'ignore'],
64
+ timeout: 5000,
65
+ });
66
+ return String(stdout || '').split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, 30);
67
+ } catch (_) {
68
+ return [];
69
+ }
70
+ }
71
+
72
+ function collect(ctx) {
73
+ const { projectDir, recordSource } = ctx;
74
+
75
+ const progressText = _readMaybe(path.join(projectDir, PROGRESS_PATH));
76
+ if (progressText) recordSource(PROGRESS_PATH);
77
+
78
+ const prefix = _currentMilestonePrefix(progressText);
79
+ const integration = _integrationPointsExcerpt(projectDir, prefix, recordSource);
80
+
81
+ return {
82
+ scope: { owned: [], notOwned: [], deliverables: [] },
83
+ constraints: [],
84
+ contracts: [],
85
+ ancillary: {
86
+ milestonePrefix: prefix,
87
+ integrationPointsExcerpt: integration,
88
+ gitDiffSummary: _gitDiffSummary(projectDir),
89
+ changedFiles: _gitDiffNames(projectDir),
90
+ },
91
+ };
92
+ }
93
+
94
+ module.exports = {
95
+ name: NAME,
96
+ requiresSources: [],
97
+ collect,
98
+ _currentMilestonePrefix,
99
+ _integrationPointsExcerpt,
100
+ };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * milestone kind collector — surfaces the most-recent COMPLETE milestone
5
+ * row + the last 3 Decision Log entries + the version-bump rationale
6
+ * heuristic for a milestone-definition worker.
7
+ *
8
+ * Fail-open: missing optional source → empty field, brief still written.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const NAME = 'milestone';
15
+ const PROGRESS_PATH = '.gsd-t/progress.md';
16
+
17
+ function _readMaybe(file) {
18
+ try { return fs.readFileSync(file, 'utf8'); } catch (_) { return null; }
19
+ }
20
+
21
+ function _lastCompletedMilestone(progressText) {
22
+ if (!progressText) return null;
23
+ // Look for the first row in the Milestones table with status COMPLETE / COMPLETED.
24
+ const lines = progressText.split(/\r?\n/);
25
+ for (const line of lines) {
26
+ if (/^\|\s*M\d+\s*\|.*\|\s*(COMPLETE|COMPLETED)\s*\|/i.test(line)) {
27
+ return line.length > 800 ? line.slice(0, 800) + ' …' : line;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+
33
+ function _versionFromProgress(progressText) {
34
+ if (!progressText) return null;
35
+ const m = progressText.match(/##\s+Version:\s*([0-9]+\.[0-9]+\.[0-9]+)/i);
36
+ return m ? m[1] : null;
37
+ }
38
+
39
+ function _lastDecisionLogEntries(progressText, n) {
40
+ if (!progressText) return [];
41
+ // Decision Log entries start with `- YYYY-MM-DD HH:MM`
42
+ const re = /^- \d{4}-\d{2}-\d{2} \d{2}:\d{2}[^\n]*$/gm;
43
+ const all = [];
44
+ let m;
45
+ while ((m = re.exec(progressText)) != null) {
46
+ let entry = m[0];
47
+ if (entry.length > 400) entry = entry.slice(0, 397) + '...';
48
+ all.push(entry);
49
+ }
50
+ return all.slice(0, n);
51
+ }
52
+
53
+ function collect(ctx) {
54
+ const { projectDir, recordSource } = ctx;
55
+
56
+ const progressText = _readMaybe(path.join(projectDir, PROGRESS_PATH));
57
+ if (progressText) recordSource(PROGRESS_PATH);
58
+
59
+ return {
60
+ scope: { owned: [], notOwned: [], deliverables: [] },
61
+ constraints: [],
62
+ contracts: [],
63
+ ancillary: {
64
+ lastCompletedMilestoneRow: _lastCompletedMilestone(progressText),
65
+ currentVersion: _versionFromProgress(progressText),
66
+ lastDecisionLogEntries: _lastDecisionLogEntries(progressText, 3),
67
+ },
68
+ };
69
+ }
70
+
71
+ module.exports = {
72
+ name: NAME,
73
+ requiresSources: [],
74
+ collect,
75
+ _lastCompletedMilestone,
76
+ _versionFromProgress,
77
+ _lastDecisionLogEntries,
78
+ };
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * partition kind collector — surfaces the current milestone row from
5
+ * progress.md, the file-disjointness rules excerpt, and the existing
6
+ * domain table for partition-phase workers.
7
+ *
8
+ * Fail-open: missing optional source → empty field, brief still written.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const NAME = 'partition';
15
+ const PROGRESS_PATH = '.gsd-t/progress.md';
16
+ const DISJOINT_RULES_PATH = '.gsd-t/contracts/file-disjointness-rules.md';
17
+
18
+ function _readMaybe(file) {
19
+ try { return fs.readFileSync(file, 'utf8'); } catch (_) { return null; }
20
+ }
21
+
22
+ function _currentMilestoneRow(progressText) {
23
+ if (!progressText) return null;
24
+ // Find first ACTIVE / DEFINED / PARTITIONED / PLANNED / EXECUTING row in the
25
+ // Milestones table — this is the milestone partition is operating on.
26
+ const lines = progressText.split(/\r?\n/);
27
+ for (const line of lines) {
28
+ if (/^\|\s*M\d+\s*\|.*(DEFINED|PARTITIONED|PLANNED|EXECUTING|EXECUTED|VERIFY)/i.test(line)) {
29
+ // Truncate row to first 800 chars to honor 2,500-token brief cap.
30
+ return line.length > 800 ? line.slice(0, 800) + ' …' : line;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+
36
+ function _existingDomains(projectDir) {
37
+ const dir = path.join(projectDir, '.gsd-t', 'domains');
38
+ let entries;
39
+ try { entries = fs.readdirSync(dir); } catch (_) { return []; }
40
+ return entries.filter((e) => /^[a-z0-9_-]+$/i.test(e)).sort();
41
+ }
42
+
43
+ function collect(ctx) {
44
+ const { projectDir, recordSource } = ctx;
45
+
46
+ const progressText = _readMaybe(path.join(projectDir, PROGRESS_PATH));
47
+ if (progressText) recordSource(PROGRESS_PATH);
48
+
49
+ const disjointRules = _readMaybe(path.join(projectDir, DISJOINT_RULES_PATH));
50
+ if (disjointRules) recordSource(DISJOINT_RULES_PATH);
51
+
52
+ const milestoneRow = _currentMilestoneRow(progressText);
53
+ const domains = _existingDomains(projectDir);
54
+
55
+ // Trim disjoint-rules excerpt to ~1,200 chars to honor cap.
56
+ let rulesExcerpt = null;
57
+ if (disjointRules) {
58
+ rulesExcerpt = disjointRules.length > 1200
59
+ ? disjointRules.slice(0, 1197) + '...'
60
+ : disjointRules;
61
+ }
62
+
63
+ return {
64
+ scope: { owned: [], notOwned: [], deliverables: [] },
65
+ constraints: [],
66
+ contracts: [],
67
+ ancillary: {
68
+ currentMilestoneRow: milestoneRow,
69
+ existingDomains: domains,
70
+ disjointnessRulesExcerpt: rulesExcerpt,
71
+ },
72
+ };
73
+ }
74
+
75
+ module.exports = {
76
+ name: NAME,
77
+ requiresSources: [],
78
+ collect,
79
+ _currentMilestoneRow,
80
+ _existingDomains,
81
+ };
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * plan kind collector — surfaces the current milestone row plus a list of
5
+ * partitioned domain names with their scope.md "Files Owned" first-N entries.
6
+ *
7
+ * Fail-open: missing optional source → empty field, brief still written.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ const NAME = 'plan';
14
+ const PROGRESS_PATH = '.gsd-t/progress.md';
15
+
16
+ function _readMaybe(file) {
17
+ try { return fs.readFileSync(file, 'utf8'); } catch (_) { return null; }
18
+ }
19
+
20
+ function _currentMilestoneRow(progressText) {
21
+ if (!progressText) return null;
22
+ const lines = progressText.split(/\r?\n/);
23
+ for (const line of lines) {
24
+ if (/^\|\s*M\d+\s*\|.*(DEFINED|PARTITIONED|PLANNED|EXECUTING|EXECUTED|VERIFY)/i.test(line)) {
25
+ return line.length > 800 ? line.slice(0, 800) + ' …' : line;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+
31
+ function _domainSummaries(projectDir, currentMilestonePrefix, recordSource) {
32
+ const dir = path.join(projectDir, '.gsd-t', 'domains');
33
+ let entries;
34
+ try { entries = fs.readdirSync(dir); } catch (_) { return []; }
35
+ const out = [];
36
+ for (const name of entries.sort()) {
37
+ if (currentMilestonePrefix && !name.startsWith(currentMilestonePrefix)) continue;
38
+ const scopePath = '.gsd-t/domains/' + name + '/scope.md';
39
+ const scopeText = _readMaybe(path.join(projectDir, scopePath));
40
+ if (!scopeText) continue;
41
+ if (recordSource) recordSource(scopePath);
42
+ // First "## Responsibility" paragraph + first 3 "Files Owned" bullets.
43
+ let resp = '';
44
+ const respMatch = scopeText.match(/^##\s+Responsibility\s*\n([^\n]+)/mi);
45
+ if (respMatch) resp = respMatch[1].trim().slice(0, 200);
46
+ const files = [];
47
+ const filesBody = scopeText.match(/^##\s+Files Owned\s*\n([\s\S]*?)(?=^##\s+|\s*$)/mi);
48
+ if (filesBody) {
49
+ const bullets = filesBody[1].split(/\r?\n/).filter((l) => /^[-*]\s+/.test(l)).slice(0, 3);
50
+ for (const b of bullets) {
51
+ const m = b.match(/^[-*]\s+`([^`]+)`/);
52
+ if (m) files.push(m[1]);
53
+ }
54
+ }
55
+ out.push({ domain: name, responsibility: resp, filesOwnedFirst3: files });
56
+ }
57
+ return out;
58
+ }
59
+
60
+ function _currentMilestonePrefix(progressText) {
61
+ if (!progressText) return null;
62
+ const m = progressText.match(/^\|\s*(M\d+)\s*\|.*(DEFINED|PARTITIONED|PLANNED|EXECUTING|EXECUTED|VERIFY)/im);
63
+ return m ? m[1].toLowerCase() : null;
64
+ }
65
+
66
+ function collect(ctx) {
67
+ const { projectDir, recordSource } = ctx;
68
+
69
+ const progressText = _readMaybe(path.join(projectDir, PROGRESS_PATH));
70
+ if (progressText) recordSource(PROGRESS_PATH);
71
+
72
+ const milestoneRow = _currentMilestoneRow(progressText);
73
+ const prefix = _currentMilestonePrefix(progressText);
74
+ const domains = _domainSummaries(projectDir, prefix, recordSource);
75
+
76
+ return {
77
+ scope: { owned: [], notOwned: [], deliverables: [] },
78
+ constraints: [],
79
+ contracts: [],
80
+ ancillary: {
81
+ currentMilestoneRow: milestoneRow,
82
+ milestonePrefix: prefix,
83
+ partitionedDomains: domains,
84
+ },
85
+ };
86
+ }
87
+
88
+ module.exports = {
89
+ name: NAME,
90
+ requiresSources: [],
91
+ collect,
92
+ _currentMilestoneRow,
93
+ _currentMilestonePrefix,
94
+ _domainSummaries,
95
+ };
@@ -182,7 +182,7 @@ function loadKindRegistry() {
182
182
  return out;
183
183
  }
184
184
 
185
- const KINDS = ['design-verify', 'execute', 'qa', 'red-team', 'scan', 'verify'];
185
+ const KINDS = ['design-verify', 'discuss', 'execute', 'impact', 'milestone', 'partition', 'plan', 'qa', 'red-team', 'scan', 'verify'];
186
186
 
187
187
  /**
188
188
  * Deterministic JSON stringifier — alphabetical keys at every nesting level,
@@ -373,6 +373,7 @@ function _runCacheWarmProbe(opts) {
373
373
  if (model) env.ANTHROPIC_MODEL = model;
374
374
 
375
375
  try {
376
+ // GSD-T-LINT: skip stream-json (reason: cache-warm probe — single-word "warm" reply, no progress to stream)
376
377
  const r = spawnSync(
377
378
  "claude",
378
379
  ["-p", prompt, "--dangerously-skip-permissions"],
@@ -101,6 +101,7 @@ async function runOneProbe({ fixturePath, cwd, claudeBin, model }) {
101
101
 
102
102
  let child;
103
103
  try {
104
+ // GSD-T-LINT: skip stream-json (reason: probe measures rate-limit envelope via API result fields stop_reason/is_error/api_error_status — switching to stream-json would require rewriting the 429 classifier from M55 D3, charter prohibits regression)
104
105
  child = spawn(claudeBin, args, {
105
106
  cwd: cwd || process.cwd(),
106
107
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -278,6 +278,26 @@ function _detectDefaultTrack2(projectDir, notes) {
278
278
  });
279
279
  }
280
280
 
281
+ // M56 D1: playwright e2e — native CLI worker (not Task subagent wrapper)
282
+ if (has('playwright.config.ts') || has('playwright.config.js') || has('playwright.config.cjs')) {
283
+ plan.push({
284
+ id: 'playwright',
285
+ cmd: 'npx',
286
+ args: ['--no-install', 'playwright', 'test'],
287
+ timeoutMs: 600000,
288
+ });
289
+ }
290
+
291
+ // M56 D1: journey coverage — gsd-t check-coverage (native CLI, no LLM)
292
+ if (has('.gsd-t/journey-manifest.json')) {
293
+ plan.push({
294
+ id: 'journey-coverage',
295
+ cmd: 'node',
296
+ args: ['./bin/gsd-t.js', 'check-coverage'],
297
+ timeoutMs: 30000,
298
+ });
299
+ }
300
+
281
301
  // secrets — gitleaks (PATH detection deferred to runtime)
282
302
  if (_hasOnPath('gitleaks')) {
283
303
  plan.push({
package/bin/gsd-t.js CHANGED
@@ -3582,6 +3582,7 @@ function doHeadlessExec(command, cmdArgs, flags) {
3582
3582
  if (process.env.GSD_T_MODEL) workerEnv.GSD_T_MODEL = process.env.GSD_T_MODEL;
3583
3583
 
3584
3584
  try {
3585
+ // GSD-T-LINT: skip stream-json (reason: gsd-t headless one-shot entrypoint — output is buffered into one string by callers, not user-watchable progress)
3585
3586
  const result = execFileSync("claude", ["-p", "--dangerously-skip-permissions", prompt], {
3586
3587
  encoding: "utf8",
3587
3588
  timeout: timeoutMs,
@@ -3876,6 +3877,7 @@ function spawnClaudeSession(prompt, model) {
3876
3877
  });
3877
3878
  if (env.GSD_T_MODEL === null) delete env.GSD_T_MODEL;
3878
3879
  if (process.env.GSD_T_TRACE_ID) env.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
3880
+ // GSD-T-LINT: skip stream-json (reason: internal debug-loop summarizer; output is consumed as one buffered string by parseTestResult — no progress to stream)
3879
3881
  return execFileSync("claude", ["-p", prompt, "--model", model], {
3880
3882
  encoding: "utf8", timeout: 300000,
3881
3883
  stdio: ["pipe", "pipe", "pipe"],
@@ -3924,6 +3926,7 @@ function runLedgerCompaction(projectDir, jsonMode) {
3924
3926
  GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || projectDir,
3925
3927
  });
3926
3928
  if (process.env.GSD_T_TRACE_ID) env.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
3929
+ // GSD-T-LINT: skip stream-json (reason: debug-loop ledger compaction — single-shot summarizer, output consumed as one trimmed string)
3927
3930
  const out = execFileSync("claude", ["-p", compactPrompt, "--model", "haiku"], {
3928
3931
  encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"],
3929
3932
  env,
@@ -4590,14 +4593,23 @@ if (require.main === module) {
4590
4593
  }
4591
4594
  case "capture-lint": {
4592
4595
  const clOpts = { projectDir: process.cwd(), mode: 'staged' };
4596
+ let checkStreamJson = false;
4593
4597
  for (let i = 1; i < args.length; i++) {
4594
4598
  const a = args[i];
4595
4599
  if (a === '--staged') { clOpts.mode = 'staged'; }
4596
4600
  else if (a === '--all') { clOpts.mode = 'all'; }
4601
+ else if (a === '--check-stream-json') { checkStreamJson = true; }
4597
4602
  else if (a === '--project-dir' && args[i+1]) { clOpts.projectDir = args[++i]; }
4598
4603
  else if (a.startsWith('--project-dir=')) { clOpts.projectDir = a.slice(14); }
4599
4604
  else if (a === '--help' || a === '-h') {
4600
- log('Usage: gsd-t capture-lint [--staged] [--all] [--project-dir PATH]');
4605
+ log('Usage: gsd-t capture-lint [--staged] [--all] [--check-stream-json] [--project-dir PATH]');
4606
+ log('');
4607
+ log('Default mode (M41 D5): rejects bare Task() / claude -p / spawn() calls');
4608
+ log(' missing the captureSpawn / recordSpawnRow wrapper.');
4609
+ log('');
4610
+ log('--check-stream-json (M56 D5): rejects claude -p / spawn(claude, ...) invocations');
4611
+ log(' missing --output-format stream-json --verbose flag pair.');
4612
+ log(' Allowlist via marker comment: GSD-T-LINT: skip stream-json (reason: …)');
4601
4613
  process.exit(0);
4602
4614
  }
4603
4615
  else {
@@ -4607,7 +4619,7 @@ if (require.main === module) {
4607
4619
  }
4608
4620
  try {
4609
4621
  const linter = require(path.join(__dirname, 'gsd-t-capture-lint.cjs'));
4610
- const res = linter.main(clOpts);
4622
+ const res = checkStreamJson ? linter.mainStreamJson(clOpts) : linter.main(clOpts);
4611
4623
  if (res.error) {
4612
4624
  error(`capture-lint: ${res.error}`);
4613
4625
  process.exit(2);
@@ -4615,10 +4627,11 @@ if (require.main === module) {
4615
4627
  for (const v of res.violations) {
4616
4628
  log(`${v.file}:${v.line}: ${v.message}`);
4617
4629
  }
4630
+ const modeLabel = checkStreamJson ? 'stream-json' : 'capture';
4618
4631
  if (res.violations.length === 0) {
4619
- log(`capture-lint: ${res.files.length} file(s) checked — clean`);
4632
+ log(`${modeLabel}-lint: ${res.files.length} file(s) checked — clean`);
4620
4633
  } else {
4621
- log(`capture-lint: ${res.violations.length} violation(s) across ${res.files.length} file(s)`);
4634
+ log(`${modeLabel}-lint: ${res.violations.length} violation(s) across ${res.files.length} file(s)`);
4622
4635
  }
4623
4636
  process.exit(res.exitCode);
4624
4637
  } catch (e) {
@@ -139,6 +139,28 @@ Read:
139
139
  3. `.gsd-t/contracts/` — all contracts
140
140
  4. `.gsd-t/domains/*/scope.md` — domain boundaries
141
141
 
142
+ <!-- M56-D4: preflight + brief + verify-gate wire-in -->
143
+ **M56 Preflight + Context-Brief (mandatory before any debug investigation):**
144
+
145
+ ```bash
146
+ gsd-t preflight --json > /tmp/gsd-t-preflight.json || exit 4
147
+
148
+ SPAWN_ID="debug-${ARG_OR_DEFAULT:-investigation}-$(date -u +%Y%m%dT%H%M%SZ)"
149
+ gsd-t brief --kind debug --out ".gsd-t/briefs/${SPAWN_ID}.json" || true
150
+ export BRIEF_PATH=".gsd-t/briefs/${SPAWN_ID}.json"
151
+ ```
152
+
153
+ If preflight exits 4, surface the failed `severity:"error"` checks (wrong branch, occupied required port) to the user and STOP — do NOT proceed with the debug investigation. The brief replaces the 30–60k context re-read with a ≤2,500-token JSON snapshot.
154
+
155
+ **Verify-gate at end** (only if debug fix produced code changes):
156
+
157
+ ```bash
158
+ if git status --porcelain | grep -q .; then
159
+ gsd-t verify-gate --json > /tmp/gsd-t-verify-gate.json || exit 4
160
+ fi
161
+ ```
162
+ <!-- /M56-D4: preflight + brief + verify-gate wire-in -->
163
+
142
164
  ## Step 1.5: Debug Loop Detection (MANDATORY)
143
165
 
144
166
  ```bash
@@ -16,6 +16,18 @@ Read:
16
16
 
17
17
  If run standalone, ask: "What changes are you considering?"
18
18
 
19
+ <!-- M56-D3: brief wire-in -->
20
+ **M56 Context-Brief (surfaces git diff + integration-points excerpt without re-walking the repo):**
21
+
22
+ ```bash
23
+ SPAWN_ID="impact-${MILESTONE:-default}-$(date -u +%Y%m%dT%H%M%SZ)"
24
+ gsd-t brief --kind impact --spawn-id "${SPAWN_ID}" --out ".gsd-t/briefs/${SPAWN_ID}.json" || true
25
+ export BRIEF_PATH=".gsd-t/briefs/${SPAWN_ID}.json"
26
+ ```
27
+
28
+ The `impact` brief includes `gitDiffSummary`, `changedFiles[]`, and the milestone's `integrationPointsExcerpt` so downstream workers can scan blast radius without re-reading the contract dir.
29
+ <!-- /M56-D3: brief wire-in -->
30
+
19
31
  ## Step 2: Identify Changed Files
20
32
 
21
33
  From the plan or user description, list:
@@ -15,6 +15,18 @@ Read:
15
15
 
16
16
  If `.gsd-t/` doesn't exist, run the init workflow first.
17
17
 
18
+ <!-- M56-D3: brief wire-in -->
19
+ **M56 Context-Brief (surfaces last completed milestone + version + recent decision log without re-reading progress.md):**
20
+
21
+ ```bash
22
+ SPAWN_ID="milestone-${ARG_OR_DEFAULT:-define}-$(date -u +%Y%m%dT%H%M%SZ)"
23
+ gsd-t brief --kind milestone --spawn-id "${SPAWN_ID}" --out ".gsd-t/briefs/${SPAWN_ID}.json" || true
24
+ export BRIEF_PATH=".gsd-t/briefs/${SPAWN_ID}.json"
25
+ ```
26
+
27
+ The `milestone` brief includes `lastCompletedMilestoneRow`, `currentVersion`, and the last 3 decision-log entries so the milestone-definition step can derive version-bump rationale + scope baseline without paging in the full progress history.
28
+ <!-- /M56-D3: brief wire-in -->
29
+
18
30
  ## Step 2: Define the Milestone
19
31
 
20
32
  ```bash
@@ -56,13 +68,29 @@ Ask user: "Milestone {N-1} is still {status}. Archive it and start new? Or compl
56
68
  node scripts/gsd-t-watch-state.js advance --agent-id "$GSD_T_AGENT_ID" --parent-id "${GSD_T_PARENT_AGENT_ID:-null}" --command gsd-t-milestone --step 4 --step-label "Pre-Partition Assessment" 2>/dev/null || true
57
69
  ```
58
70
 
59
- Before formal partitioning, do a quick assessment:
71
+ Before formal partitioning, do a quick assessment.
72
+
73
+ **Express scope in GSD-T-native units only.** Per `feedback_no_human_hour_estimates.md` (memory): never use developer-hours, dev-days, sprints, story points, or person-weeks. Use these instead:
74
+
75
+ | Unit | Use for |
76
+ |------|---------|
77
+ | **Domain count** | Partition coarseness (1-2 / 3-4 / 5+) |
78
+ | **Wave count** | How many serial gates the milestone needs |
79
+ | **Spawn count** | Estimated `claude -p` / Task subagent invocations |
80
+ | **Token-spend range** | `$X-Y` based on prior comparable milestones |
81
+ | **Rate-limit-window count** | If the milestone might span > 1 5h Claude Max window |
82
+ | **Parallel-domain count** | How many domains can run concurrently (file-disjoint) |
83
+
84
+ Assessment template:
60
85
 
61
- - **Complexity estimate**: Simple (1-2 domains), Medium (3-4), Complex (5+)
86
+ - **Domain count**: Simple (1-2 domains), Medium (3-4), Complex (5+)
87
+ - **Wave count**: estimated based on cross-domain dependencies
88
+ - **Parallel-domain count**: how many can run in the same wave
89
+ - **Token-spend range**: $X-Y based on prior comparable milestones (read `.gsd-t/token-log.md` for trailing-3 comparison)
62
90
  - **Recommended approach**:
63
- - Simple: Consider using /gsd-t-quick for each piece
64
- - Medium: Standard partition → plan → execute flow
65
- - Complex: Partition → discuss → plan → execute → integrate → verify
91
+ - 1-2 domains: Consider using /gsd-t-quick for each piece
92
+ - 3-4 domains: Standard partition → plan → execute flow
93
+ - 5+ domains: Partition → discuss → plan → execute → integrate → verify
66
94
 
67
95
  Present the assessment and ask: "Ready to partition into domains now, or want to discuss first?"
68
96
 
@@ -51,6 +51,18 @@ If `.gsd-t/` doesn't exist, create the full directory structure:
51
51
  └── progress.md
52
52
  ```
53
53
 
54
+ <!-- M56-D3: brief wire-in -->
55
+ **M56 Context-Brief (replaces 30–60k context re-read with ≤2,500-token JSON snapshot):**
56
+
57
+ ```bash
58
+ SPAWN_ID="partition-${MILESTONE:-default}-$(date -u +%Y%m%dT%H%M%SZ)"
59
+ gsd-t brief --kind partition --spawn-id "${SPAWN_ID}" --out ".gsd-t/briefs/${SPAWN_ID}.json" || true
60
+ export BRIEF_PATH=".gsd-t/briefs/${SPAWN_ID}.json"
61
+ ```
62
+
63
+ Pass `$BRIEF_PATH` into any worker prompts spawned downstream so they grep the brief instead of re-walking the repo.
64
+ <!-- /M56-D3: brief wire-in -->
65
+
54
66
  ## Step 1.5: Assumption Audit (MANDATORY — complete before domain work begins)
55
67
 
56
68
  ```bash
@@ -27,6 +27,18 @@ Read ALL of these:
27
27
 
28
28
  **If CONTEXT.md exists:** Every Locked Decision listed in it MUST be mapped to at least one task in Step 2. Do NOT proceed to execute if any Locked Decision is unmapped.
29
29
 
30
+ <!-- M56-D3: brief wire-in -->
31
+ **M56 Context-Brief (replaces 30–60k context re-read with ≤2,500-token JSON snapshot):**
32
+
33
+ ```bash
34
+ SPAWN_ID="plan-${MILESTONE:-default}-$(date -u +%Y%m%dT%H%M%SZ)"
35
+ gsd-t brief --kind plan --spawn-id "${SPAWN_ID}" --out ".gsd-t/briefs/${SPAWN_ID}.json" || true
36
+ export BRIEF_PATH=".gsd-t/briefs/${SPAWN_ID}.json"
37
+ ```
38
+
39
+ Pass `$BRIEF_PATH` into any worker prompts spawned downstream (validation subagents read it via `templates/prompts/{qa,red-team,design-verify}-subagent.md`).
40
+ <!-- /M56-D3: brief wire-in -->
41
+
30
42
  ## Step 1.5: Graph-Enhanced Dependency Detection
31
43
 
32
44
  ```bash
@@ -67,7 +67,7 @@ Append to `.gsd-t/roadmap.md`:
67
67
  - [ ] {each debt item resolved and verified}
68
68
  - [ ] No regression in existing functionality
69
69
  - [ ] {item-specific criteria from techdebt.md}
70
- **Estimated effort**: {combined effort assessment}
70
+ **Estimated scope**: {N domains} / {N waves} / $X-Y token-spend (express in GSD-T units only — never developer-hours/days/sprints per `feedback_no_human_hour_estimates.md`)
71
71
  **Priority**: {CRITICAL — before next feature | HIGH — soon | MEDIUM — planned}
72
72
  ```
73
73
 
@@ -146,6 +146,28 @@ Read:
146
146
  2. `.gsd-t/progress.md` (if exists)
147
147
  3. `.gsd-t/contracts/` (if exists) — scan for relevant contracts
148
148
 
149
+ <!-- M56-D4: preflight + brief + verify-gate wire-in -->
150
+ **M56 Preflight + Context-Brief (mandatory before any quick-task work):**
151
+
152
+ ```bash
153
+ gsd-t preflight --json > /tmp/gsd-t-preflight.json || exit 4
154
+
155
+ SPAWN_ID="quick-${ARG_OR_DEFAULT:-task}-$(date -u +%Y%m%dT%H%M%SZ)"
156
+ gsd-t brief --kind quick --out ".gsd-t/briefs/${SPAWN_ID}.json" || true
157
+ export BRIEF_PATH=".gsd-t/briefs/${SPAWN_ID}.json"
158
+ ```
159
+
160
+ If preflight exits 4, surface the failed `severity:"error"` checks (wrong branch, occupied required port) to the user and STOP — do NOT proceed with the quick task. The brief replaces the 30–60k context re-read with a ≤2,500-token JSON snapshot.
161
+
162
+ **Verify-gate at end** (only if quick task produced code changes):
163
+
164
+ ```bash
165
+ if git status --porcelain | grep -q .; then
166
+ gsd-t verify-gate --json > /tmp/gsd-t-verify-gate.json || exit 4
167
+ fi
168
+ ```
169
+ <!-- /M56-D4: preflight + brief + verify-gate wire-in -->
170
+
149
171
  ## Step 1.5: Graph-Enhanced Scope Check
150
172
 
151
173
  ```bash
@@ -351,7 +351,7 @@ Synthesize ALL findings into a **fresh** `.gsd-t/techdebt.md` (the previous vers
351
351
  - High priority: {N}
352
352
  - Medium priority: {N}
353
353
  - Low priority: {N}
354
- - Total estimated effort: {rough assessment}
354
+ - Total estimated scope: {N domains} / {N waves} / $X-Y token-spend (GSD-T units only — see `feedback_no_human_hour_estimates.md`)
355
355
  - Previous scan archive: techdebt_{previous-date}.md
356
356
 
357
357
  ---
@@ -461,22 +461,22 @@ Review all items marked `Milestone candidate: YES` and group them into logical m
461
461
 
462
462
  ### Suggested: Security Hardening (Critical)
463
463
  Combines: TD-001, TD-003, TD-005
464
- Estimated effort: {assessment}
464
+ Estimated scope: {domain-count} domains, {wave-count} waves, $X-Y token-spend (express in GSD-T units — domain/wave/spawn/token — never human-hours/days/sprints per `feedback_no_human_hour_estimates.md`)
465
465
  Should be prioritized: BEFORE next feature milestone
466
466
 
467
467
  ### Suggested: Performance Optimization (High)
468
468
  Combines: TD-010, TD-012, TD-015
469
- Estimated effort: {assessment}
469
+ Estimated scope: {domain-count} domains, {wave-count} waves, $X-Y token-spend (express in GSD-T units — domain/wave/spawn/token — never human-hours/days/sprints per `feedback_no_human_hour_estimates.md`)
470
470
  Can be scheduled: AFTER current feature work
471
471
 
472
- ### Suggested: Dependency Update Sprint (Medium)
472
+ ### Suggested: Dependency Update (Medium)
473
473
  Combines: TD-020, dependency table items with breaking=yes
474
- Estimated effort: {assessment}
474
+ Estimated scope: {domain-count} domains, {wave-count} waves, $X-Y token-spend (express in GSD-T units — domain/wave/spawn/token — never human-hours/days/sprints per `feedback_no_human_hour_estimates.md`)
475
475
  Can be scheduled: During next maintenance window
476
476
 
477
477
  ### Suggested: Shared Service Extraction (if candidates found)
478
478
  Combines: all "Shared Service Candidates" from quality.md Reusability Analysis
479
- Estimated effort: {assessment}
479
+ Estimated scope: {domain-count} domains, {wave-count} waves, $X-Y token-spend (express in GSD-T units — domain/wave/spawn/token — never human-hours/days/sprints per `feedback_no_human_hour_estimates.md`)
480
480
  Should be prioritized: BEFORE adding new consumer surfaces to the system
481
481
  Note: Use `/gsd-t-partition` Step 1.6 to design the SharedCore domain
482
482
  ```
@@ -797,3 +797,19 @@ Supporting contracts (to be written during D1):
797
797
  | REQ-M55-D5-07 | `.gsd-t/contracts/verify-gate-contract.md` v1.0.0 STABLE — envelope schema, two-track hard-fail rule, ≤500-token summary discipline, head-and-tail snippet rule, raw-output retention path, idempotent-rerun rule, defensive-on-missing-map default, captureSpawn inheritance (NOT exempt). | m55-d5-verify-gate-and-wirein | T1 | done |
798
798
  | REQ-M55-D5-08 | Doc ripple: `docs/architecture.md` (CLI-Preflight Pattern section), `docs/requirements.md` (this REQ-M55 block), `CLAUDE.md` (Mandatory Preflight Before Spawn + Brief-First Worker Rule), `commands/gsd-t-help.md` (3 new entries), `README.md` (CLI table updates), `templates/CLAUDE-global.md` (preflight/brief/verify-gate documentation block), `~/.claude/CLAUDE.md` (Pre-Commit Gate addition). All in single commit per Document Ripple Completion Gate. | m55-d5-verify-gate-and-wirein | T12, T13 | done |
799
799
  | REQ-M55-VERIFY | Full unit suite ≥ 2399 baseline (post-D4) + 30+ new D5 unit tests across `test/m55-d5-verify-gate.test.js` (Track 1 hard-fail, Track 2 fan-out mocked, summary truncation ≤500 tokens, schema-version, defensive-on-missing-map, idempotent re-run, sort-determinism, skip flags) + `test/m55-d5-verify-gate-judge.test.js` (cap holds across huge envelopes, malformed-input resilience, deterministic) all green. 3 wire-in tests GREEN after T8/T9/T10 land. 3 e2e journey specs GREEN under Playwright. `gsd-t check-coverage` exit 0. | m55-d5-verify-gate-and-wirein | T14 | done |
800
+
801
+ ## M56 Verify-Gate CLI Fan-Out + Upper-Stage Briefs (planned — 2026-05-09)
802
+
803
+ | Requirement | Description | Domain | Tasks | Status |
804
+ |-------------|-------------|--------|-------|--------|
805
+ | REQ-M56-D1-01 | `bin/gsd-t-verify-gate.cjs` Track 2 extends with native CLI workers for `playwright test`, `npm test`, `gsd-t check-coverage` (no Task subagent wrapper). Workers go through `runParallel` from `bin/parallel-cli.cjs` with `captureSpawn` invariant. | m56-d1-verify-gate-native-cli-workers | TBD | planned |
806
+ | REQ-M56-D1-02 | M56 records `M55-baseline-tokens` + `M56-actual-tokens` in `.gsd-t/metrics/m56-token-baseline.json` and reports the delta in CHANGELOG (closes M55 SC4 retroactively). | m56-d1-verify-gate-native-cli-workers | TBD | planned |
807
+ | REQ-M56-D1-03 | Verify-gate wall-clock ≤ M55's 34s on the same dogfood scenario. Numbers persisted to `.gsd-t/metrics/m56-verify-gate-wallclock.json`. | m56-d1-verify-gate-native-cli-workers | TBD | planned |
808
+ | REQ-M56-D2-01 | `bin/gsd-t-context-brief.cjs` extends `KIND_REGISTRY` with 5 new kinds: `partition`, `plan`, `discuss`, `impact`, `milestone`. Each kind has its own file-resolver mapping a brief request to the relevant CLAUDE.md + contract excerpts + scope.md slice. ≤2,500-token cap per brief. | m56-d2-upper-stage-brief-kinds | TBD | planned |
809
+ | REQ-M56-D2-02 | Brief generator unit tests cover all 5 new kinds: schema-shape, ≤2,500-token cap honored, missing-input resilience (e.g. milestone kind without a defined milestone falls back to "no milestone defined"), determinism. | m56-d2-upper-stage-brief-kinds | TBD | planned |
810
+ | REQ-M56-D3-01 | `commands/gsd-t-{partition,plan,discuss,impact,milestone}.md` Step 1 each thread `$BRIEF_PATH` via the M55 D4 pattern (`SPAWN_ID`, `gsd-t brief --kind …`, `export BRIEF_PATH=…`). Tested via marker-comment assertion (`<!-- M56-D3: brief wire-in -->`). | m56-d3-upper-stage-command-wirein | TBD | planned |
811
+ | REQ-M56-D4-01 | `commands/gsd-t-quick.md` and `commands/gsd-t-debug.md` Step 1 invoke `gsd-t preflight --json` (hard-fail on severity:error), then thread `$BRIEF_PATH` via brief generation (kinds `quick`/`debug` already in registry from M55 D4), then call `gsd-t verify-gate` if the operation produced code changes. Tested via marker-comment assertion (`<!-- M56-D4: preflight + brief + verify-gate wire-in -->`). | m56-d4-quick-debug-wirein | TBD | planned |
812
+ | REQ-M56-D5-01 | Three known stream-json gaps closed: `bin/gsd-t.js:3879 spawnClaudeSession` (add `--output-format stream-json --verbose`), `bin/gsd-t-parallel.cjs:378` cache-warm probe (same), `bin/gsd-t-ratelimit-probe-worker.cjs:89` (same). | m56-d5-stream-json-universality-lint | TBD | planned |
813
+ | REQ-M56-D5-02 | New lint check: extend `bin/gsd-t-capture-lint.cjs` (or sibling `bin/gsd-t-stream-json-lint.cjs`) to mechanically reject any `claude -p` / `spawn('claude', …)` invocation without `--output-format stream-json --verbose`. Allowlist (e.g. probe workers measuring envelopes, not progress) lives in lint config (`.gsd-t/lint-config.json` or inline marker comment), not tribal knowledge. Same enforcement model as M41 capture-lint. | m56-d5-stream-json-universality-lint | TBD | planned |
814
+ | REQ-M56-D5-03 | Lint hooked into `scripts/hooks/pre-commit-capture-lint` (or sibling pre-commit hook) so violations block commits. | m56-d5-stream-json-universality-lint | TBD | planned |
815
+ | REQ-M56-VERIFY | Full unit suite ≥ 2487 baseline + new tests for D1 (token-delta recording), D2 (5 new brief kinds), D3 (5 wire-in marker assertions), D4 (2 wire-in marker assertions), D5 (3 gap closures + lint logic + allowlist + pre-commit hook integration). All green. SC1-SC7 measured + recorded. | m56-d6-verify | TBD | planned |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "3.25.11",
3
+ "version": "3.26.11",
4
4
  "description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
5
5
  "author": "Tekyz, Inc.",
6
6
  "license": "MIT",
@@ -24,3 +24,16 @@ if ! $GSD_T_BIN capture-lint --staged; then
24
24
  echo "Wrap spawns with captureSpawn({…, spawnFn}) or add GSD-T-CAPTURE-LINT: skip" >&2
25
25
  exit 1
26
26
  fi
27
+
28
+ # M56 D5: stream-json universality lint — every `claude -p` / `spawn('claude', …)`
29
+ # spawn must pass `--output-format stream-json --verbose` so progress is
30
+ # observable in real-time, OR carry a `GSD-T-LINT: skip stream-json` marker
31
+ # explaining why streaming isn't applicable (probe envelope, single-shot
32
+ # buffered consumer, etc.).
33
+ if ! $GSD_T_BIN capture-lint --staged --check-stream-json; then
34
+ echo ""
35
+ echo "Stream-json universality lint failed — see paths above."
36
+ echo "Add --output-format stream-json --verbose to the spawn args, OR"
37
+ echo "add a 'GSD-T-LINT: skip stream-json (reason: …)' marker comment near the spawn." >&2
38
+ exit 1
39
+ fi
@@ -572,6 +572,30 @@ BEFORE reporting "done" or presenting a summary:
572
572
 
573
573
  **The test for this gate**: If the user asks "did you update all the documents?" and the answer would be "no, I missed some" — you failed this gate. The user should never need to ask.
574
574
 
575
+ ## Effort Estimates — GSD-T-Native Units (MANDATORY)
576
+
577
+ **NEVER express effort or scope in developer-hours, dev-days, sprints, story points, or person-weeks.** GSD-T operates on a different cost model — the worker is Claude, not a human team — and human-time estimates have no predictive value for GSD-T workflows. They actively mislead by suggesting a calendar shape that doesn't match how the system runs.
578
+
579
+ Use GSD-T-native units instead:
580
+
581
+ | Unit | When to use |
582
+ |------|-------------|
583
+ | **Domain count** | Milestone scope (1-2 simple, 3-4 medium, 5+ complex) |
584
+ | **Wave count** | Cross-domain dependency depth — how many serial gates exist |
585
+ | **Parallel-domain count** | How many domains can run concurrently (file-disjoint) |
586
+ | **Spawn count** | Estimated `claude -p` / Task subagent invocations |
587
+ | **Token-spend range** | `$X-Y` dollars based on trailing-3 comparable milestones in `.gsd-t/token-log.md` |
588
+ | **Rate-limit-window count** | If the work might span > 1 5h Claude Max window |
589
+
590
+ Where this applies:
591
+ - `/gsd-t-milestone` Step 4 — Pre-Partition Assessment
592
+ - `/gsd-t-scan` techdebt milestone suggestions
593
+ - `/gsd-t-promote-debt` effort fields
594
+ - `docs/requirements.md`, `progress.md` Decision Log entries
595
+ - Any internal estimate the user might read
596
+
597
+ Acceptable: machine-time references (e.g. "5 min cache TTL", "5h rate-limit window", "14 day staleness threshold") — these are concrete system properties, not effort estimates. The rule applies to **effort/scope**, not to **system timeouts**.
598
+
575
599
  ## Execution Behavior
576
600
  - ALWAYS check docs/architecture.md before adding or modifying components.
577
601
  - ALWAYS check docs/workflows.md before changing any multi-step process.
@@ -176,7 +176,7 @@ MANDATORY:
176
176
 
177
177
  ├── For each design requirement, assess:
178
178
  │ Supported → stack handles this natively, proceed
179
- │ Partial → needs an addon/library — name it, estimate effort
179
+ │ Partial → needs an addon/library — name it, scope in GSD-T units (domain/wave/spawn/token, NOT dev-hours)
180
180
  │ Unsupported → stack CANNOT achieve this — flag as a blocker
181
181
 
182
182
  ├── If ANY requirement is Unsupported: