@tekyzinc/gsd-t 3.25.10 → 3.26.10
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 +58 -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 +30 -5
- package/commands/gsd-t-debug.md +22 -0
- package/commands/gsd-t-impact.md +12 -0
- package/commands/gsd-t-milestone.md +12 -0
- package/commands/gsd-t-partition.md +12 -0
- package/commands/gsd-t-plan.md +12 -0
- package/commands/gsd-t-quick.md +22 -0
- package/docs/requirements.md +16 -0
- package/package.json +1 -1
- package/scripts/hooks/pre-commit-capture-lint +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [3.26.10] - 2026-05-09
|
|
6
|
+
|
|
7
|
+
### Added — M56: Verify-Gate CLI Fan-Out + Upper-Stage Briefs
|
|
8
|
+
|
|
9
|
+
Five file-disjoint domains, 18 atomic tasks, executed serially in-session per the user's "complete in session headless" directive.
|
|
10
|
+
|
|
11
|
+
**D1 — verify-gate native CLI workers**: `bin/gsd-t-verify-gate.cjs::_detectDefaultTrack2` extended with two new native workers:
|
|
12
|
+
- `playwright` — runs `npx playwright test` when `playwright.config.{ts,js,cjs}` present
|
|
13
|
+
- `journey-coverage` — runs `gsd-t check-coverage` via local `bin/gsd-t.js` when `.gsd-t/journey-manifest.json` present
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
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).
|
|
18
|
+
|
|
19
|
+
**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:
|
|
20
|
+
- partition: 3185 bytes (current milestone row + existing domain table + disjointness rules excerpt)
|
|
21
|
+
- plan: 3626 bytes (milestone row + partitioned-domain summaries with files-owned bullets)
|
|
22
|
+
- discuss: 5345 bytes (progress.md header + CLAUDE.md trimmed slice)
|
|
23
|
+
- impact: 3262 bytes (milestone row + integration-points excerpt + git diff summary + changed files)
|
|
24
|
+
- milestone: 2555 bytes (last completed milestone row + current version + last 3 decision-log entries)
|
|
25
|
+
|
|
26
|
+
All under MAX_BRIEF_BYTES (10 KB). 7-13× smaller than the 30-60k full-source read each kind replaces.
|
|
27
|
+
|
|
28
|
+
**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.
|
|
29
|
+
|
|
30
|
+
**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.
|
|
31
|
+
|
|
32
|
+
**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).
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
### SCs
|
|
37
|
+
|
|
38
|
+
- SC1 ✅ verify wall-clock 33975ms < 34000ms M55 baseline (margin 25ms / 0.07%)
|
|
39
|
+
- SC2 ✅ all 5 new briefs under 10 KB cap; 7-13× smaller than 30-60k full reads
|
|
40
|
+
- SC3 ✅ `commands/gsd-t-{quick,debug}.md` carry preflight + brief + verify-gate marker blocks
|
|
41
|
+
- SC4 ✅ deliberately-broken-commit fixture asserts lint exit 4 + violation with file:line (passes in suite)
|
|
42
|
+
- 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.
|
|
43
|
+
- SC6 ✅ 2547/2547 tests pass (baseline 2487 + 60 new M56 tests across D1: 7, D2: 24, D3: 8, D4: 6, D5: 15)
|
|
44
|
+
- 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.
|
|
45
|
+
|
|
46
|
+
### Process notes
|
|
47
|
+
|
|
48
|
+
- 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.
|
|
49
|
+
- Two commits: `a2ec62e` (D5 + plan files) and `bd34a08` (D1+D2+D3+D4 batched).
|
|
50
|
+
|
|
51
|
+
## [3.25.11] - 2026-05-09
|
|
52
|
+
|
|
53
|
+
### Fixed — M55 propagation gaps + misleading update-all status
|
|
54
|
+
|
|
55
|
+
Three small fixes to `bin/gsd-t.js`, all caught immediately post-v3.25.10 ship by the user noticing "18 already current" couldn't possibly be right one minute after publish.
|
|
56
|
+
|
|
57
|
+
- **Patch 1**: Added `parallel-cli.cjs` to `GLOBAL_BIN_TOOLS`. M55 D5 wired 4 of 5 new substrate binaries into the global propagation list (`cli-preflight`, `gsd-t-context-brief`, `gsd-t-verify-gate`, `gsd-t-verify-gate-judge`) but missed `parallel-cli.cjs` — the substrate engine itself. Result: `~/.claude/bin/parallel-cli.cjs` was missing on every install. Now propagates.
|
|
58
|
+
- **Patch 2**: Added 6 M55 binaries to `PROJECT_BIN_TOOLS` (`cli-preflight.cjs`, `parallel-cli.cjs`, `parallel-cli-tee.cjs`, `gsd-t-context-brief.cjs`, `gsd-t-verify-gate.cjs`, `gsd-t-verify-gate-judge.cjs`). M55 D5 only added them to the global tier — projects had to reach for the global CLI for every M55 dispatch. Now they're available locally per-project too, enabling `node bin/cli-preflight.cjs` style invocations from project workflows. Each registered project gets these copied on the next `gsd-t update-all`.
|
|
59
|
+
- **Patch 3**: Replaced `"X — already up to date"` with `"X — no migrations needed (CLAUDE.md guard, CHANGELOG, bin tools, unattended config all current)"` in `updateSingleProject`. The previous string falsely implied the project was at the latest GSD-T version, but the function only checks 7 specific migration ops — a project showing "already up to date" was just one whose 7 migrations had already run, not one running v3.25.10. Honest message now explains what was actually checked.
|
|
60
|
+
- **Tests**: 2487 / 2487 pass clean (zero regressions).
|
|
61
|
+
- **Why a same-day patch and not a defer**: M55's whole point is making the new substrate available for M56 to wire into Quick + Debug + upper-stage commands. Shipping with the binaries half-propagated would force M56 to wait on a propagation fix anyway. Better to land the fix now and have a clean foundation.
|
|
62
|
+
|
|
5
63
|
## [3.25.10] - 2026-05-09
|
|
6
64
|
|
|
7
65
|
### Added — M55 CLI-Preflight Pattern + Parallel-CLI Substrate + Rate-Limit Map + Context Briefs + Verify-Gate (minor: new feature milestone)
|
|
@@ -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
|
@@ -1183,6 +1183,8 @@ const GLOBAL_BIN_TOOLS = [
|
|
|
1183
1183
|
"gsd-t-context-brief.cjs",
|
|
1184
1184
|
"gsd-t-verify-gate.cjs",
|
|
1185
1185
|
"gsd-t-verify-gate-judge.cjs",
|
|
1186
|
+
// M55 D2 substrate — parallel-cli engine (added v3.25.11 patch — missed in initial M55 D5 wire-in).
|
|
1187
|
+
"parallel-cli.cjs",
|
|
1186
1188
|
];
|
|
1187
1189
|
|
|
1188
1190
|
function installGlobalBinTools() {
|
|
@@ -2456,7 +2458,11 @@ function updateSingleProject(projectDir, counts) {
|
|
|
2456
2458
|
if (guardAdded || changelogCreated || binToolsCopied || archiveRan || taskCounterRetired || unattendedConfigCreated || gitignoreUpdated) {
|
|
2457
2459
|
counts.updated++;
|
|
2458
2460
|
} else {
|
|
2459
|
-
|
|
2461
|
+
// The "no mutator ran" branch — distinct from "running latest GSD-T version".
|
|
2462
|
+
// Be explicit: this only means the 7 migration ops were no-ops. The slash
|
|
2463
|
+
// commands and global CLI come from the npm-global install, not from any
|
|
2464
|
+
// per-project artifact, so this status doesn't claim or imply a version.
|
|
2465
|
+
info(`${projectName} — no migrations needed (CLAUDE.md guard, CHANGELOG, bin tools, unattended config all current)`);
|
|
2460
2466
|
counts.skipped++;
|
|
2461
2467
|
}
|
|
2462
2468
|
}
|
|
@@ -2471,6 +2477,12 @@ const PROJECT_BIN_TOOLS = [
|
|
|
2471
2477
|
"gsd-t-unattended.cjs", "gsd-t-unattended-platform.cjs", "gsd-t-unattended-safety.cjs",
|
|
2472
2478
|
"handoff-lock.cjs", "headless-auto-spawn.cjs",
|
|
2473
2479
|
"headless-exit-codes.cjs",
|
|
2480
|
+
// M55 — preflight + parallel-cli + brief + verify-gate libraries copied to project bin/
|
|
2481
|
+
// so per-project workflows can `require('./bin/cli-preflight.cjs')` etc. without
|
|
2482
|
+
// depending on the globally-installed gsd-t CLI being on PATH.
|
|
2483
|
+
"cli-preflight.cjs", "parallel-cli.cjs", "parallel-cli-tee.cjs",
|
|
2484
|
+
"gsd-t-context-brief.cjs",
|
|
2485
|
+
"gsd-t-verify-gate.cjs", "gsd-t-verify-gate-judge.cjs",
|
|
2474
2486
|
];
|
|
2475
2487
|
|
|
2476
2488
|
// Files that older versions of this installer copied into project bin/ but
|
|
@@ -3570,6 +3582,7 @@ function doHeadlessExec(command, cmdArgs, flags) {
|
|
|
3570
3582
|
if (process.env.GSD_T_MODEL) workerEnv.GSD_T_MODEL = process.env.GSD_T_MODEL;
|
|
3571
3583
|
|
|
3572
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)
|
|
3573
3586
|
const result = execFileSync("claude", ["-p", "--dangerously-skip-permissions", prompt], {
|
|
3574
3587
|
encoding: "utf8",
|
|
3575
3588
|
timeout: timeoutMs,
|
|
@@ -3864,6 +3877,7 @@ function spawnClaudeSession(prompt, model) {
|
|
|
3864
3877
|
});
|
|
3865
3878
|
if (env.GSD_T_MODEL === null) delete env.GSD_T_MODEL;
|
|
3866
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)
|
|
3867
3881
|
return execFileSync("claude", ["-p", prompt, "--model", model], {
|
|
3868
3882
|
encoding: "utf8", timeout: 300000,
|
|
3869
3883
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -3912,6 +3926,7 @@ function runLedgerCompaction(projectDir, jsonMode) {
|
|
|
3912
3926
|
GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || projectDir,
|
|
3913
3927
|
});
|
|
3914
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)
|
|
3915
3930
|
const out = execFileSync("claude", ["-p", compactPrompt, "--model", "haiku"], {
|
|
3916
3931
|
encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"],
|
|
3917
3932
|
env,
|
|
@@ -4578,14 +4593,23 @@ if (require.main === module) {
|
|
|
4578
4593
|
}
|
|
4579
4594
|
case "capture-lint": {
|
|
4580
4595
|
const clOpts = { projectDir: process.cwd(), mode: 'staged' };
|
|
4596
|
+
let checkStreamJson = false;
|
|
4581
4597
|
for (let i = 1; i < args.length; i++) {
|
|
4582
4598
|
const a = args[i];
|
|
4583
4599
|
if (a === '--staged') { clOpts.mode = 'staged'; }
|
|
4584
4600
|
else if (a === '--all') { clOpts.mode = 'all'; }
|
|
4601
|
+
else if (a === '--check-stream-json') { checkStreamJson = true; }
|
|
4585
4602
|
else if (a === '--project-dir' && args[i+1]) { clOpts.projectDir = args[++i]; }
|
|
4586
4603
|
else if (a.startsWith('--project-dir=')) { clOpts.projectDir = a.slice(14); }
|
|
4587
4604
|
else if (a === '--help' || a === '-h') {
|
|
4588
|
-
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: …)');
|
|
4589
4613
|
process.exit(0);
|
|
4590
4614
|
}
|
|
4591
4615
|
else {
|
|
@@ -4595,7 +4619,7 @@ if (require.main === module) {
|
|
|
4595
4619
|
}
|
|
4596
4620
|
try {
|
|
4597
4621
|
const linter = require(path.join(__dirname, 'gsd-t-capture-lint.cjs'));
|
|
4598
|
-
const res = linter.main(clOpts);
|
|
4622
|
+
const res = checkStreamJson ? linter.mainStreamJson(clOpts) : linter.main(clOpts);
|
|
4599
4623
|
if (res.error) {
|
|
4600
4624
|
error(`capture-lint: ${res.error}`);
|
|
4601
4625
|
process.exit(2);
|
|
@@ -4603,10 +4627,11 @@ if (require.main === module) {
|
|
|
4603
4627
|
for (const v of res.violations) {
|
|
4604
4628
|
log(`${v.file}:${v.line}: ${v.message}`);
|
|
4605
4629
|
}
|
|
4630
|
+
const modeLabel = checkStreamJson ? 'stream-json' : 'capture';
|
|
4606
4631
|
if (res.violations.length === 0) {
|
|
4607
|
-
log(
|
|
4632
|
+
log(`${modeLabel}-lint: ${res.files.length} file(s) checked — clean`);
|
|
4608
4633
|
} else {
|
|
4609
|
-
log(
|
|
4634
|
+
log(`${modeLabel}-lint: ${res.violations.length} violation(s) across ${res.files.length} file(s)`);
|
|
4610
4635
|
}
|
|
4611
4636
|
process.exit(res.exitCode);
|
|
4612
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
|
|
@@ -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
|
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/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.10",
|
|
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
|