@tekyzinc/gsd-t 4.8.10 → 4.9.12
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 +35 -0
- package/README.md +1 -1
- package/bin/gsd-t-architectural-trigger.cjs +80 -5
- package/bin/gsd-t-jargon-lint.cjs +363 -0
- package/bin/gsd-t-shrink-metric.cjs +255 -0
- package/bin/gsd-t.js +53 -0
- package/commands/gsd-t-milestone.md +6 -0
- package/commands/gsd-t-quick.md +13 -6
- package/package.json +1 -1
- package/scripts/gsd-t-brevity-guard.js +380 -0
- package/templates/CLAUDE-global.md +16 -0
- package/templates/prompts/blind-adversary-subagent.md +4 -0
- package/templates/prompts/pre-mortem-subagent.md +4 -0
- package/templates/prompts/qa-subagent.md +4 -0
- package/templates/prompts/red-team-subagent.md +4 -0
- package/templates/workflows/gsd-t-execute.workflow.js +7 -1
- package/templates/workflows/gsd-t-quick.workflow.js +16 -2
- package/templates/workflows/gsd-t-verify.workflow.js +82 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* gsd-t-shrink-metric — M92 D2 (shrink-verdict, the keystone move)
|
|
5
|
+
*
|
|
6
|
+
* A deterministic leanness readout for a change. Today verify's `overallVerdict`
|
|
7
|
+
* is a pure AND of additive gates — the schema literally CANNOT say "we made it
|
|
8
|
+
* smaller." This module measures the change's net size from a `git diff --numstat`
|
|
9
|
+
* envelope so a NET-NEGATIVE (leaner) change becomes a first-class, ORTHOGONAL
|
|
10
|
+
* success dimension surfaced ALONGSIDE the pass/fail enum (never folded into it).
|
|
11
|
+
*
|
|
12
|
+
* MEASURED, not attested ([[feedback_measure_dont_claim]]): leanness comes from
|
|
13
|
+
* `git diff --numstat`, never an LLM judgment.
|
|
14
|
+
*
|
|
15
|
+
* Output: { filesAdded, filesRemoved, filesModified, insertions, deletions, netLoc, leaner }
|
|
16
|
+
* netLoc = insertions - deletions
|
|
17
|
+
* leaner = netLoc <= 0 (removed at least as much as it added)
|
|
18
|
+
*
|
|
19
|
+
* Input — EITHER:
|
|
20
|
+
* --numstat <path|-> parse a PRE-CAPTURED `git diff --numstat` string (testable
|
|
21
|
+
* with no repo; `-` reads stdin).
|
|
22
|
+
* --range <base>..<head> --project-dir <p> compute LIVE (runs git itself).
|
|
23
|
+
*
|
|
24
|
+
* numstat grammar (porcelain `git diff --numstat`): one line per path,
|
|
25
|
+
* <insertions>\t<deletions>\t<path>
|
|
26
|
+
* where a BINARY file emits `-\t-\t<path>` (no textual LOC — counted as a modified
|
|
27
|
+
* file with 0 loc contribution). File add/remove/modify is inferred from the
|
|
28
|
+
* `--numstat` line alone WITHOUT a name-status pass:
|
|
29
|
+
* - deletions>0 AND insertions==0 → a file with only removals → filesRemoved
|
|
30
|
+
* - insertions>0 AND deletions==0 → a file with only additions → filesAdded
|
|
31
|
+
* - both>0, OR a binary line → filesModified
|
|
32
|
+
* (This is a heuristic over numstat: a wholly-new file shows insertions/0, a wholly-
|
|
33
|
+
* deleted file shows 0/deletions. It is deterministic and sufficient for the leanness
|
|
34
|
+
* readout; netLoc/leaner — the load-bearing signal — are EXACT regardless of the
|
|
35
|
+
* add/remove/modify split.)
|
|
36
|
+
*
|
|
37
|
+
* Hard engineering bar (mirror gsd-t-guard-map.cjs): zero deps (Node built-ins
|
|
38
|
+
* only), never throws (bad input → exitCode 64, never an uncaught throw),
|
|
39
|
+
* pure parsing. Deterministic — ZERO LLM judgment.
|
|
40
|
+
*
|
|
41
|
+
* Exit: 0 metric computed · 64 bad input.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const fs = require("node:fs");
|
|
45
|
+
const { execFileSync } = require("node:child_process");
|
|
46
|
+
|
|
47
|
+
// ─── numstat parsing (pure) ────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse a `git diff --numstat` string into the shrink metric. Pure; never throws.
|
|
51
|
+
* @param {string} numstat - raw numstat text
|
|
52
|
+
* @returns {{ filesAdded, filesRemoved, filesModified, insertions, deletions, netLoc, leaner, files }}
|
|
53
|
+
*/
|
|
54
|
+
function parseNumstat(numstat) {
|
|
55
|
+
const text = String(numstat == null ? "" : numstat);
|
|
56
|
+
const lines = text.split(/\r?\n/);
|
|
57
|
+
|
|
58
|
+
let filesAdded = 0;
|
|
59
|
+
let filesRemoved = 0;
|
|
60
|
+
let filesModified = 0;
|
|
61
|
+
let insertions = 0;
|
|
62
|
+
let deletions = 0;
|
|
63
|
+
let files = 0;
|
|
64
|
+
|
|
65
|
+
for (const raw of lines) {
|
|
66
|
+
const line = raw.replace(/\r$/, "");
|
|
67
|
+
if (line.trim() === "") continue;
|
|
68
|
+
// numstat columns are TAB-separated: <ins>\t<del>\t<path>. Be tolerant of
|
|
69
|
+
// runs of whitespace (git uses a single tab, but stay robust).
|
|
70
|
+
const m = line.match(/^\s*(-|\d+)\s+(-|\d+)\s+(.+)$/);
|
|
71
|
+
if (!m) {
|
|
72
|
+
// A non-empty line that is not a numstat row → malformed input.
|
|
73
|
+
return { _malformed: true, badLine: line.slice(0, 200) };
|
|
74
|
+
}
|
|
75
|
+
files += 1;
|
|
76
|
+
const insTok = m[1];
|
|
77
|
+
const delTok = m[2];
|
|
78
|
+
const binary = insTok === "-" || delTok === "-";
|
|
79
|
+
|
|
80
|
+
if (binary) {
|
|
81
|
+
// A binary change: counted as a modified file, 0 textual loc contribution.
|
|
82
|
+
filesModified += 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ins = Number(insTok);
|
|
87
|
+
const del = Number(delTok);
|
|
88
|
+
// Number() of a \d+ token is always a finite non-negative integer here.
|
|
89
|
+
insertions += ins;
|
|
90
|
+
deletions += del;
|
|
91
|
+
|
|
92
|
+
if (del > 0 && ins === 0) filesRemoved += 1;
|
|
93
|
+
else if (ins > 0 && del === 0) filesAdded += 1;
|
|
94
|
+
else filesModified += 1; // both>0, or 0/0 (an empty/mode-only change)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const netLoc = insertions - deletions;
|
|
98
|
+
return {
|
|
99
|
+
filesAdded,
|
|
100
|
+
filesRemoved,
|
|
101
|
+
filesModified,
|
|
102
|
+
insertions,
|
|
103
|
+
deletions,
|
|
104
|
+
netLoc,
|
|
105
|
+
leaner: netLoc <= 0,
|
|
106
|
+
files,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── live git (only on the --range path) ───────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Capture `git diff --numstat <range>` for a project. Returns { ok, numstat } or
|
|
114
|
+
* { ok:false, reason }. Never throws.
|
|
115
|
+
* @param {string} range - e.g. "<base>..HEAD"
|
|
116
|
+
* @param {string} projectDir
|
|
117
|
+
*/
|
|
118
|
+
function captureNumstat(range, projectDir) {
|
|
119
|
+
// SECURITY (M92 Red Team BUG-1, [[feedback_defense_in_depth_at_adapters]]):
|
|
120
|
+
// `execFileSync` blocks SHELL injection but NOT git-ARGUMENT injection — a range
|
|
121
|
+
// beginning with `-` is parsed by git as an OPTION (e.g. `--output=<path>` makes
|
|
122
|
+
// git OVERWRITE an arbitrary file). The range is LLM-derived upstream (verify
|
|
123
|
+
// computes the base via a haiku agent), so it is untrusted. Reject any
|
|
124
|
+
// dash-leading / non-string / empty range BEFORE the git call. The trailing `--`
|
|
125
|
+
// is defense-in-depth (NOT sufficient alone — `--output` is honored before `--`).
|
|
126
|
+
if (typeof range !== "string" || range === "" || range.startsWith("-")) {
|
|
127
|
+
return { ok: false, reason: `invalid range (refused: must be a non-empty, non-option string): ${JSON.stringify(range)}` };
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const out = execFileSync("git", ["diff", "--numstat", range, "--"], {
|
|
131
|
+
cwd: projectDir || ".",
|
|
132
|
+
encoding: "utf8",
|
|
133
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
134
|
+
});
|
|
135
|
+
return { ok: true, numstat: out };
|
|
136
|
+
} catch (e) {
|
|
137
|
+
return { ok: false, reason: `git diff failed: ${(e && e.message) || "unknown"}` };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readNumstatFile(p) {
|
|
142
|
+
// `-` → stdin (fd 0). Otherwise a path.
|
|
143
|
+
if (p === "-") return fs.readFileSync(0, "utf8");
|
|
144
|
+
return fs.readFileSync(p, "utf8");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── driver ─────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Compute the shrink metric from options. Never throws — bad input → exitCode 64.
|
|
151
|
+
* @param {{ numstat?:string, range?:string, projectDir?:string }} o
|
|
152
|
+
* @returns {{ ok, exitCode, ... }}
|
|
153
|
+
*/
|
|
154
|
+
function runMetric(o) {
|
|
155
|
+
const opt = (o && typeof o === "object") ? o : {};
|
|
156
|
+
const hasNumstat = typeof opt.numstat === "string" && opt.numstat.length > 0;
|
|
157
|
+
const hasRange = typeof opt.range === "string" && opt.range.length > 0;
|
|
158
|
+
|
|
159
|
+
if (!hasNumstat && !hasRange) {
|
|
160
|
+
return { ok: false, exitCode: 64, reason: "need --numstat <path|-> OR --range <base>..<head>" };
|
|
161
|
+
}
|
|
162
|
+
if (hasNumstat && hasRange) {
|
|
163
|
+
return { ok: false, exitCode: 64, reason: "give EITHER --numstat OR --range, not both" };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let numstatText;
|
|
167
|
+
let source;
|
|
168
|
+
if (hasNumstat) {
|
|
169
|
+
source = `numstat:${opt.numstat}`;
|
|
170
|
+
try {
|
|
171
|
+
numstatText = readNumstatFile(opt.numstat);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return { ok: false, exitCode: 64, reason: `cannot read --numstat input: ${(e && e.message) || "unknown"}`, source };
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
source = `range:${opt.range}`;
|
|
177
|
+
const cap = captureNumstat(opt.range, opt.projectDir);
|
|
178
|
+
if (!cap.ok) {
|
|
179
|
+
// A git failure on the live path is BAD INPUT (unresolvable range / not a repo)
|
|
180
|
+
// → exit 64. The verify workflow turns this into a logged skip-with-reason,
|
|
181
|
+
// never a fabricated metric.
|
|
182
|
+
return { ok: false, exitCode: 64, reason: cap.reason, source };
|
|
183
|
+
}
|
|
184
|
+
numstatText = cap.numstat;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const parsed = parseNumstat(numstatText);
|
|
188
|
+
if (parsed._malformed) {
|
|
189
|
+
return { ok: false, exitCode: 64, reason: `malformed numstat line: "${parsed.badLine}"`, source };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
ok: true,
|
|
194
|
+
exitCode: 0,
|
|
195
|
+
source,
|
|
196
|
+
filesAdded: parsed.filesAdded,
|
|
197
|
+
filesRemoved: parsed.filesRemoved,
|
|
198
|
+
filesModified: parsed.filesModified,
|
|
199
|
+
insertions: parsed.insertions,
|
|
200
|
+
deletions: parsed.deletions,
|
|
201
|
+
netLoc: parsed.netLoc,
|
|
202
|
+
leaner: parsed.leaner,
|
|
203
|
+
files: parsed.files,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ─── CLI ─────────────────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
function parseArgs(argv) {
|
|
210
|
+
const o = { numstat: null, range: null, projectDir: ".", help: false };
|
|
211
|
+
for (let i = 0; i < argv.length; i++) {
|
|
212
|
+
const a = argv[i];
|
|
213
|
+
if (a === "--help" || a === "-h") o.help = true;
|
|
214
|
+
else if (a === "--numstat") o.numstat = argv[++i];
|
|
215
|
+
else if (a === "--range") o.range = argv[++i];
|
|
216
|
+
else if (a === "--project-dir" || a === "--projectDir") o.projectDir = argv[++i];
|
|
217
|
+
else if (a === "--json") {/* default output is JSON */}
|
|
218
|
+
}
|
|
219
|
+
return o;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const HELP = `Usage:
|
|
223
|
+
gsd-t shrink-metric --numstat <path|-> [--json]
|
|
224
|
+
gsd-t shrink-metric --range <base>..<head> --project-dir <p> [--json]
|
|
225
|
+
|
|
226
|
+
The M92 shrink-metric (D2). Computes a deterministic leanness readout from a
|
|
227
|
+
\`git diff --numstat\` envelope: { filesAdded, filesRemoved, filesModified,
|
|
228
|
+
insertions, deletions, netLoc, leaner } where netLoc = insertions - deletions and
|
|
229
|
+
leaner = netLoc <= 0. MEASURED, zero LLM judgment.
|
|
230
|
+
|
|
231
|
+
--numstat PATH parse a pre-captured \`git diff --numstat\` string (\`-\` = stdin).
|
|
232
|
+
--range R compute live: runs \`git diff --numstat <R>\` in --project-dir.
|
|
233
|
+
--project-dir P cwd for the live --range git call (default ".").
|
|
234
|
+
|
|
235
|
+
Exit: 0 metric computed · 64 bad input (no source, both sources, unreadable, or
|
|
236
|
+
malformed numstat / unresolvable range).`;
|
|
237
|
+
|
|
238
|
+
function main() {
|
|
239
|
+
const o = parseArgs(process.argv.slice(2));
|
|
240
|
+
if (o.help) { process.stdout.write(HELP + "\n"); process.exit(0); }
|
|
241
|
+
let res;
|
|
242
|
+
try {
|
|
243
|
+
res = runMetric(o);
|
|
244
|
+
} catch (e) {
|
|
245
|
+
// Defense in depth — runMetric is written never to throw, but the contract
|
|
246
|
+
// mandates the module never throws, so any escape maps to 64.
|
|
247
|
+
res = { ok: false, exitCode: 64, reason: `metric-error: ${e && e.message}` };
|
|
248
|
+
}
|
|
249
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
250
|
+
process.exit(res.exitCode);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (require.main === module) main();
|
|
254
|
+
|
|
255
|
+
module.exports = { runMetric, parseNumstat, captureNumstat };
|
package/bin/gsd-t.js
CHANGED
|
@@ -395,6 +395,53 @@ function addHeartbeatHook(hooks, event, cmd) {
|
|
|
395
395
|
return true;
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
+
// ─── Brevity Guard (M93) ────────────────────────────────────────────────────
|
|
399
|
+
// A BLOCKING Stop hook (not async — it must run synchronously to block a verbose
|
|
400
|
+
// answer-mode reply). Enforces the Reader Contract: answer-first, no preamble,
|
|
401
|
+
// jargon glossed. Fail-open by design (the script never blocks on error).
|
|
402
|
+
|
|
403
|
+
const BREVITY_GUARD_SCRIPT = "gsd-t-brevity-guard.js";
|
|
404
|
+
|
|
405
|
+
function installBrevityGuard() {
|
|
406
|
+
ensureDir(SCRIPTS_DIR);
|
|
407
|
+
const src = path.join(PKG_SCRIPTS, BREVITY_GUARD_SCRIPT);
|
|
408
|
+
const dest = path.join(SCRIPTS_DIR, BREVITY_GUARD_SCRIPT);
|
|
409
|
+
if (!fs.existsSync(src)) { warn("Brevity-guard script not found in package — skipping"); return; }
|
|
410
|
+
|
|
411
|
+
const srcContent = fs.readFileSync(src, "utf8");
|
|
412
|
+
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : "";
|
|
413
|
+
if (normalizeEol(srcContent) !== normalizeEol(destContent)) {
|
|
414
|
+
copyFile(src, dest, BREVITY_GUARD_SCRIPT);
|
|
415
|
+
} else {
|
|
416
|
+
info("Brevity-guard script unchanged");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const parsed = readSettingsJson();
|
|
420
|
+
if (parsed === null && fs.existsSync(SETTINGS_JSON)) {
|
|
421
|
+
warn("settings.json has invalid JSON — cannot configure brevity-guard hook");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const settings = parsed || {};
|
|
425
|
+
if (!settings.hooks) settings.hooks = {};
|
|
426
|
+
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
427
|
+
|
|
428
|
+
const already = settings.hooks.Stop.some((entry) =>
|
|
429
|
+
entry.hooks && entry.hooks.some((h) => h.command && h.command.includes(BREVITY_GUARD_SCRIPT))
|
|
430
|
+
);
|
|
431
|
+
if (already) { info("Brevity-guard hook already configured"); return; }
|
|
432
|
+
|
|
433
|
+
// Blocking (no async) so the Stop-hook block decision is honored.
|
|
434
|
+
const cmd = `node "${dest.replace(/\\/g, "\\\\")}"`;
|
|
435
|
+
settings.hooks.Stop.push({ matcher: "", hooks: [{ type: "command", command: cmd }] });
|
|
436
|
+
|
|
437
|
+
if (!isSymlink(SETTINGS_JSON)) {
|
|
438
|
+
fs.writeFileSync(SETTINGS_JSON, JSON.stringify(settings, null, 2));
|
|
439
|
+
success("Brevity-guard Stop hook configured in settings.json");
|
|
440
|
+
} else {
|
|
441
|
+
warn("Skipping settings.json write — target is a symlink");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
398
445
|
// ─── Context Meter ──────────────────────────────────────────────────────────
|
|
399
446
|
|
|
400
447
|
const CONTEXT_METER_SCRIPT = "gsd-t-context-meter.js";
|
|
@@ -1613,6 +1660,9 @@ async function doInstall(opts = {}) {
|
|
|
1613
1660
|
heading("Heartbeat (Real-time Events)");
|
|
1614
1661
|
installHeartbeat();
|
|
1615
1662
|
|
|
1663
|
+
heading("Brevity Guard (M93 — concise replies)");
|
|
1664
|
+
installBrevityGuard();
|
|
1665
|
+
|
|
1616
1666
|
heading("Update Check (Session Start)");
|
|
1617
1667
|
installUpdateCheck();
|
|
1618
1668
|
|
|
@@ -2592,6 +2642,9 @@ const PROJECT_BIN_TOOLS = [
|
|
|
2592
2642
|
// divergence-grammar = §4 parse/format round-trip (G4).
|
|
2593
2643
|
"gsd-t-guard-map.cjs", "gsd-t-guard-map-derive.cjs", "gsd-t-milestone-state.cjs",
|
|
2594
2644
|
"gsd-t-rule-consume.cjs", "gsd-t-divergence-grammar.cjs",
|
|
2645
|
+
// M93 — jargon-gloss lint for written docs (the file surface the brevity-guard
|
|
2646
|
+
// Stop hook can't reach). Propagated so a project's doc checks can invoke it.
|
|
2647
|
+
"gsd-t-jargon-lint.cjs",
|
|
2595
2648
|
];
|
|
2596
2649
|
|
|
2597
2650
|
// Files that older versions of this installer copied into project bin/ but
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
You are the lead agent. Define a new milestone by invoking the generic upper-stage Workflow at `templates/workflows/gsd-t-phase.workflow.js` with `phase: "milestone"`. A milestone is a significant deliverable (e.g., "User Authentication", "Payment Integration").
|
|
4
4
|
|
|
5
|
+
## Default altitude: smallest change that hits the crux (M92)
|
|
6
|
+
|
|
7
|
+
**Before reaching for milestone ceremony, ask whether the ask even needs a milestone.** The default recommendation is the SMALLEST change that hits the crux — if the work is a one-file change or a focused fix, `/gsd-t-quick` (do it directly) is the answer, not a milestone with partition + plan→execute + competition.
|
|
8
|
+
|
|
9
|
+
Milestone ceremony — definition, partition into domains, plan→execute waves, Competition Mode — is the **opt-in escalation**, justified when the crux genuinely needs cross-domain coordination or carries real uncertainty (multiple coupled deliverables, several domains, a decomposition decision worth competing). It is never the implied-default "bigger is more rigorous." If you cannot name why the crux needs a milestone, the smaller path IS the recommendation.
|
|
10
|
+
|
|
5
11
|
## What this command does
|
|
6
12
|
|
|
7
13
|
```
|
package/commands/gsd-t-quick.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
You are executing a small, focused task that doesn't need full phase planning. This is for bug fixes, config changes, small features, and ad-hoc work.
|
|
4
4
|
|
|
5
|
+
## Default altitude: smallest change that hits the crux (M92)
|
|
6
|
+
|
|
7
|
+
**The default recommendation is the SMALLEST change that hits the crux — do it directly.** Before choosing scope: state the crux in one line, grep/read what already exists, then make the smallest one-file change that hits it — editing inward at the source, not outward at the N consumers.
|
|
8
|
+
|
|
9
|
+
Ceremony — the full execute workflow, partition, plan→execute, competition — is the **opt-in escalation**, reached for ONLY when the crux genuinely needs cross-domain coordination or real uncertainty (see Step 2's boundary check). It is never the implied-default "Recommended." If you cannot name why the crux needs ceremony, the smallest change IS the answer.
|
|
10
|
+
|
|
5
11
|
## Argument Parsing
|
|
6
12
|
|
|
7
13
|
Parse `$ARGUMENTS`. The first positional arg is the quick task description (`$TASK`). M43 D4 removed the `--watch` opt-out; `--in-session`/`--headless` were never shipped. Under `.gsd-t/contracts/headless-default-contract.md` **v2.0.0** the inner subagent spawn (Step 0.1 fresh-dispatch) and all validation spawns (Design Verification Step 5.25, Red Team Step 5.5, doc-ripple Step 6) go headless unconditionally. A legacy `--watch` token is accepted but ignored (stderr deprecation line).
|
|
@@ -192,13 +198,14 @@ Based on $ARGUMENTS, determine:
|
|
|
192
198
|
- Does it cross a domain boundary?
|
|
193
199
|
- Does it affect any existing contract?
|
|
194
200
|
|
|
195
|
-
###
|
|
196
|
-
|
|
197
|
-
"This change touches {domain-1} and {domain-2} and may affect {contract}.
|
|
198
|
-
Should I proceed with quick mode or use the full execute workflow?"
|
|
201
|
+
### Default — within a single domain or pre-partition:
|
|
202
|
+
The smallest change that hits the crux is the recommendation. Proceed directly.
|
|
199
203
|
|
|
200
|
-
###
|
|
201
|
-
|
|
204
|
+
### Escalate to ceremony ONLY when the crux needs it:
|
|
205
|
+
If — and only if — the change genuinely crosses domain boundaries or affects a contract (real cross-domain coordination / real uncertainty), warn the user:
|
|
206
|
+
"This change touches {domain-1} and {domain-2} and may affect {contract}.
|
|
207
|
+
The smallest direct change does not contain the crux — escalate to the full execute workflow?"
|
|
208
|
+
This is the opt-in escalation, justified by the crux — not a default.
|
|
202
209
|
|
|
203
210
|
## Step 3: Execute
|
|
204
211
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.12",
|
|
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",
|