@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 +63 -0
- package/bin/gsd-t-capture-lint.cjs +164 -0
- package/bin/gsd-t-context-brief-kinds/discuss.cjs +63 -0
- package/bin/gsd-t-context-brief-kinds/impact.cjs +100 -0
- package/bin/gsd-t-context-brief-kinds/milestone.cjs +78 -0
- package/bin/gsd-t-context-brief-kinds/partition.cjs +81 -0
- package/bin/gsd-t-context-brief-kinds/plan.cjs +95 -0
- package/bin/gsd-t-context-brief.cjs +1 -1
- package/bin/gsd-t-parallel.cjs +1 -0
- package/bin/gsd-t-ratelimit-probe-worker.cjs +1 -0
- package/bin/gsd-t-verify-gate.cjs +20 -0
- package/bin/gsd-t.js +17 -4
- package/commands/gsd-t-debug.md +22 -0
- package/commands/gsd-t-impact.md +12 -0
- package/commands/gsd-t-milestone.md +33 -5
- package/commands/gsd-t-partition.md +12 -0
- package/commands/gsd-t-plan.md +12 -0
- package/commands/gsd-t-promote-debt.md +1 -1
- package/commands/gsd-t-quick.md +22 -0
- package/commands/gsd-t-scan.md +6 -6
- package/docs/requirements.md +16 -0
- package/package.json +1 -1
- package/scripts/hooks/pre-commit-capture-lint +13 -0
- package/templates/CLAUDE-global.md +24 -0
- package/templates/stacks/design-to-code.md +1 -1
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,
|
package/bin/gsd-t-parallel.cjs
CHANGED
|
@@ -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(
|
|
4632
|
+
log(`${modeLabel}-lint: ${res.files.length} file(s) checked — clean`);
|
|
4620
4633
|
} else {
|
|
4621
|
-
log(
|
|
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) {
|
package/commands/gsd-t-debug.md
CHANGED
|
@@ -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
|
package/commands/gsd-t-impact.md
CHANGED
|
@@ -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
|
-
- **
|
|
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
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
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
|
package/commands/gsd-t-plan.md
CHANGED
|
@@ -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
|
|
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
|
|
package/commands/gsd-t-quick.md
CHANGED
|
@@ -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
|
package/commands/gsd-t-scan.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
472
|
+
### Suggested: Dependency Update (Medium)
|
|
473
473
|
Combines: TD-020, dependency table items with breaking=yes
|
|
474
|
-
Estimated
|
|
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
|
|
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
|
```
|
package/docs/requirements.md
CHANGED
|
@@ -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.
|
|
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,
|
|
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:
|