@tekyzinc/gsd-t 4.4.11 → 4.6.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 +17 -6
- package/README.md +56 -2
- package/bin/gsd-t-model-profile.cjs +480 -0
- package/bin/gsd-t-model-tier-policy.cjs +156 -1
- package/bin/gsd-t.js +128 -26
- package/commands/gsd-t-debug.md +36 -3
- package/commands/gsd-t-design-decompose.md +31 -3
- package/commands/gsd-t-doc-ripple.md +31 -3
- package/commands/gsd-t-help.md +15 -1
- package/commands/gsd-t-impact.md +31 -3
- package/commands/gsd-t-milestone.md +31 -3
- package/commands/gsd-t-partition.md +39 -3
- package/commands/gsd-t-plan.md +31 -3
- package/commands/gsd-t-prd.md +31 -3
- package/commands/gsd-t-status.md +14 -0
- package/commands/gsd-t-verify.md +35 -2
- package/commands/gsd-t-wave.md +34 -3
- package/docs/requirements.md +8 -1
- package/package.json +1 -1
- package/scripts/gsd-t-auto-route.js +106 -5
- package/scripts/gsd-t-statusline.js +72 -1
- package/templates/CLAUDE-global.md +83 -284
- package/templates/workflows/gsd-t-debug.workflow.js +7 -1
- package/templates/workflows/gsd-t-phase.workflow.js +11 -4
- package/templates/workflows/gsd-t-verify.workflow.js +7 -1
- package/templates/workflows/gsd-t-wave.workflow.js +7 -2
- package/templates/test-helpers/launch-extension.ts +0 -81
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Zero external runtime deps — installer-package invariant.
|
|
6
6
|
* No top-level side effects.
|
|
7
7
|
*
|
|
8
|
-
* Contract: .gsd-t/contracts/model-tier-policy-contract.md v1.
|
|
8
|
+
* Contract: .gsd-t/contracts/model-tier-policy-contract.md v1.1.0 STABLE
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
'use strict';
|
|
@@ -91,6 +91,158 @@ function resolve(stageKey) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Profile Dimension (M86 — additive over the frozen M85 STAGE_TIERS)
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Frozen profile → stage-key → tier map.
|
|
100
|
+
*
|
|
101
|
+
* Three named profiles:
|
|
102
|
+
* standard — ZERO fable (pre-M85 tiers: probes→opus, judge→sonnet,
|
|
103
|
+
* pre-mortem→opus, red-team→opus, debug both cycles→opus).
|
|
104
|
+
* pro — red-team + pre-mortem + debug-cycle-2 → fable; everything
|
|
105
|
+
* else reverts to standard.
|
|
106
|
+
* premium — all 6 M85 fable stages (the full M85 posture).
|
|
107
|
+
*
|
|
108
|
+
* competition-producers is HELD at opus in ALL profiles (M82 blindness
|
|
109
|
+
* invariant — never fable). It is NOT included here because it is never
|
|
110
|
+
* overridable; the resolver enforces this separately.
|
|
111
|
+
*
|
|
112
|
+
* @type {Readonly<Record<string, Readonly<Record<string, string>>>>}
|
|
113
|
+
*/
|
|
114
|
+
const PROFILE_STAGE_TIERS = Object.freeze({
|
|
115
|
+
standard: Object.freeze({
|
|
116
|
+
'solution-space-probe': 'opus',
|
|
117
|
+
'partition-probe': 'opus',
|
|
118
|
+
'competition-judge': 'sonnet',
|
|
119
|
+
'pre-mortem': 'opus',
|
|
120
|
+
'red-team': 'opus',
|
|
121
|
+
'debug-cycle-2': 'opus',
|
|
122
|
+
}),
|
|
123
|
+
pro: Object.freeze({
|
|
124
|
+
'solution-space-probe': 'opus',
|
|
125
|
+
'partition-probe': 'opus',
|
|
126
|
+
'competition-judge': 'sonnet',
|
|
127
|
+
'pre-mortem': 'fable',
|
|
128
|
+
'red-team': 'fable',
|
|
129
|
+
'debug-cycle-2': 'fable',
|
|
130
|
+
}),
|
|
131
|
+
premium: Object.freeze({
|
|
132
|
+
'solution-space-probe': 'fable',
|
|
133
|
+
'partition-probe': 'fable',
|
|
134
|
+
'competition-judge': 'fable',
|
|
135
|
+
'pre-mortem': 'fable',
|
|
136
|
+
'red-team': 'fable',
|
|
137
|
+
'debug-cycle-2': 'fable',
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/** The 6 injectable designated stages (competition-producers excluded). */
|
|
142
|
+
const INJECTABLE_STAGES = Object.freeze([
|
|
143
|
+
'solution-space-probe',
|
|
144
|
+
'partition-probe',
|
|
145
|
+
'competition-judge',
|
|
146
|
+
'pre-mortem',
|
|
147
|
+
'red-team',
|
|
148
|
+
'debug-cycle-2',
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
/** The HELD producers model id — used by blindness clamps. */
|
|
152
|
+
const PRODUCERS_MODEL_ID = MODEL_IDS.opus; // claude-opus-4-8
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolves the concrete model id for a given stage key under a profile,
|
|
156
|
+
* honoring precedence: stageOverrides[stage] ?? profile-tier ?? global-default.
|
|
157
|
+
*
|
|
158
|
+
* Blindness clamps (M82 / pre-mortem c2 #4 — enforced at RESOLVE, not only at
|
|
159
|
+
* write time because the config file is hand-editable):
|
|
160
|
+
* - competition-producers key in stageOverrides: silently dropped (never in overrides map).
|
|
161
|
+
* - competition-judge resolved to the producers' model id: BLOCKED — drops the override
|
|
162
|
+
* and uses the profile tier for competition-judge instead.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} stageKey
|
|
165
|
+
* @param {{ profile?: string, stageOverrides?: Record<string,string> }} opts
|
|
166
|
+
* @returns {{ model: string, tier: string, requiresThinkingOmitted: boolean,
|
|
167
|
+
* configError?: string }}
|
|
168
|
+
*/
|
|
169
|
+
// Own-property lookup guard. Validation-by-truthiness (`!MODEL_IDS[x]`) is a
|
|
170
|
+
// validation BYPASS for Object.prototype keys ("constructor", "toString", …):
|
|
171
|
+
// the inherited value is truthy, the resolved "model" is a function, and
|
|
172
|
+
// JSON.stringify silently DROPS the key from the envelope — the workflow's
|
|
173
|
+
// `?? "fable"` fallback then bills premium on a cost-control profile
|
|
174
|
+
// (Red Team M86 HIGH). Every tier/profile/stage map lookup goes through this.
|
|
175
|
+
function hasOwn(obj, key) {
|
|
176
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function resolveProfile(stageKey, opts) {
|
|
180
|
+
opts = opts || {};
|
|
181
|
+
const profileValid = typeof opts.profile === 'string' && hasOwn(PROFILE_STAGE_TIERS, opts.profile);
|
|
182
|
+
const profile = profileValid ? opts.profile : 'premium'; // named global default
|
|
183
|
+
|
|
184
|
+
const stageOverrides = (opts.stageOverrides && typeof opts.stageOverrides === 'object' && !Array.isArray(opts.stageOverrides))
|
|
185
|
+
? opts.stageOverrides
|
|
186
|
+
: {};
|
|
187
|
+
|
|
188
|
+
// competition-producers is held at opus — not resolvable via profile dimension.
|
|
189
|
+
if (stageKey === 'competition-producers') {
|
|
190
|
+
return {
|
|
191
|
+
model: PRODUCERS_MODEL_ID,
|
|
192
|
+
tier: 'opus',
|
|
193
|
+
requiresThinkingOmitted: requiresThinkingOmitted(PRODUCERS_MODEL_ID),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Resolve tier from precedence chain:
|
|
198
|
+
// 1. stageOverrides[stage] if it's a valid tier and not a blindness violation
|
|
199
|
+
// 2. profile-tier
|
|
200
|
+
// global-default (premium) is the fallback when profile is unknown — MARKED,
|
|
201
|
+
// never silent (Red Team M86 r2 LOW: library callers bypassing readConfig got
|
|
202
|
+
// silent premium for an invalid profile).
|
|
203
|
+
|
|
204
|
+
const profileTierMap = PROFILE_STAGE_TIERS[profile];
|
|
205
|
+
const stageKnown = hasOwn(profileTierMap, stageKey);
|
|
206
|
+
const errors = [];
|
|
207
|
+
if (!profileValid && opts.profile !== undefined) {
|
|
208
|
+
errors.push(`unknown profile "${opts.profile}" — using the named global default "premium"`);
|
|
209
|
+
}
|
|
210
|
+
if (!stageKnown) {
|
|
211
|
+
// Unknown stage key — defensive sonnet, but NEVER silently (Red Team M86 MEDIUM:
|
|
212
|
+
// a typo'd stage returning ok:true sonnet regressed the M85 explicit unknown-stage error)
|
|
213
|
+
errors.push(`unknown stage "${stageKey}" — not a designated stage; defensive sonnet fallback`);
|
|
214
|
+
}
|
|
215
|
+
let resolvedTier;
|
|
216
|
+
|
|
217
|
+
const rawOverrideTier = hasOwn(stageOverrides, stageKey) ? stageOverrides[stageKey] : undefined;
|
|
218
|
+
if (rawOverrideTier !== undefined) {
|
|
219
|
+
if (typeof rawOverrideTier !== 'string' || !hasOwn(MODEL_IDS, rawOverrideTier)) {
|
|
220
|
+
// Invalid tier in override — fall back to profile tier, record configError.
|
|
221
|
+
// The fallback for an UNKNOWN stage is the cheap defensive tier, never fable
|
|
222
|
+
// (Red Team M86 r2 LOW: unknown stage + invalid override resolved fable on standard).
|
|
223
|
+
errors.push(`stageOverrides["${stageKey}"] has invalid tier "${rawOverrideTier}"; falling back to profile tier`);
|
|
224
|
+
resolvedTier = stageKnown ? profileTierMap[stageKey] : 'sonnet';
|
|
225
|
+
} else if (stageKey === 'competition-judge' && MODEL_IDS[rawOverrideTier] === PRODUCERS_MODEL_ID) {
|
|
226
|
+
// Blindness clamp: competition-judge must not equal producers' model
|
|
227
|
+
errors.push(`stageOverrides["competition-judge"] resolves to "${MODEL_IDS[rawOverrideTier]}" (=producers' model); blindness clamp rejected — falling back to profile tier`);
|
|
228
|
+
resolvedTier = stageKnown ? profileTierMap[stageKey] : 'sonnet';
|
|
229
|
+
} else {
|
|
230
|
+
resolvedTier = rawOverrideTier;
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
resolvedTier = stageKnown ? profileTierMap[stageKey] : 'sonnet';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const modelId = hasOwn(MODEL_IDS, resolvedTier) ? MODEL_IDS[resolvedTier] : MODEL_IDS.sonnet;
|
|
237
|
+
const result = {
|
|
238
|
+
model: modelId,
|
|
239
|
+
tier: resolvedTier,
|
|
240
|
+
requiresThinkingOmitted: requiresThinkingOmitted(modelId),
|
|
241
|
+
};
|
|
242
|
+
if (errors.length) result.configError = errors.join('; ');
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
94
246
|
// ---------------------------------------------------------------------------
|
|
95
247
|
// Exports
|
|
96
248
|
// ---------------------------------------------------------------------------
|
|
@@ -98,8 +250,11 @@ function resolve(stageKey) {
|
|
|
98
250
|
module.exports = {
|
|
99
251
|
MODEL_IDS,
|
|
100
252
|
STAGE_TIERS,
|
|
253
|
+
PROFILE_STAGE_TIERS,
|
|
254
|
+
INJECTABLE_STAGES,
|
|
101
255
|
requiresThinkingOmitted,
|
|
102
256
|
resolve,
|
|
257
|
+
resolveProfile,
|
|
103
258
|
};
|
|
104
259
|
|
|
105
260
|
// ---------------------------------------------------------------------------
|
package/bin/gsd-t.js
CHANGED
|
@@ -1011,12 +1011,17 @@ function configureAutoRouteHook(scriptPath) {
|
|
|
1011
1011
|
const HOOKS_DIR = path.join(SCRIPTS_DIR, "hooks");
|
|
1012
1012
|
const PKG_HOOKS = path.join(PKG_SCRIPTS, "hooks");
|
|
1013
1013
|
|
|
1014
|
-
// Each entry: { script, events, async } — `events` is the array of hook
|
|
1015
|
-
// names this script must be wired into.
|
|
1016
|
-
//
|
|
1017
|
-
//
|
|
1018
|
-
//
|
|
1019
|
-
// `gsd-t-
|
|
1014
|
+
// Each entry: { script, events, async, runner } — `events` is the array of hook
|
|
1015
|
+
// event names this script must be wired into. `runner` is the interpreter
|
|
1016
|
+
// ("node" default, "bash" for *.sh hooks). `async: true` lets the hook run
|
|
1017
|
+
// detached; OMIT it for hooks whose stdout must reach the terminal (the
|
|
1018
|
+
// ctx-cue banner is synchronous by design — see its header).
|
|
1019
|
+
// The `gsd-t-conversation-capture.js` hook runs on SessionStart,
|
|
1020
|
+
// UserPromptSubmit, and Stop (per the global CLAUDE.md M45 D2 install block —
|
|
1021
|
+
// PostToolUse stays opt-in via the GSD_T_CAPTURE_TOOL_USES env flag, so we
|
|
1022
|
+
// don't auto-register it). `gsd-t-in-session-usage-hook.js` runs on Stop (per
|
|
1023
|
+
// M43 D1 contract). `gsd-t-ctx-cue.sh` runs synchronously on Stop (M85 —
|
|
1024
|
+
// low-context red banner; sync so its stdout reaches the terminal).
|
|
1020
1025
|
const IN_SESSION_HOOKS = [
|
|
1021
1026
|
{
|
|
1022
1027
|
script: "gsd-t-conversation-capture.js",
|
|
@@ -1028,6 +1033,11 @@ const IN_SESSION_HOOKS = [
|
|
|
1028
1033
|
events: ["Stop"],
|
|
1029
1034
|
async: true,
|
|
1030
1035
|
},
|
|
1036
|
+
{
|
|
1037
|
+
script: "gsd-t-ctx-cue.sh",
|
|
1038
|
+
events: ["Stop"],
|
|
1039
|
+
runner: "bash",
|
|
1040
|
+
},
|
|
1031
1041
|
];
|
|
1032
1042
|
|
|
1033
1043
|
function installInSessionHooks() {
|
|
@@ -1072,7 +1082,8 @@ function configureInSessionHooks() {
|
|
|
1072
1082
|
let added = 0;
|
|
1073
1083
|
for (const hook of IN_SESSION_HOOKS) {
|
|
1074
1084
|
const scriptPath = path.join(HOOKS_DIR, hook.script);
|
|
1075
|
-
const
|
|
1085
|
+
const runner = hook.runner || "node";
|
|
1086
|
+
const cmd = `${runner} "${scriptPath.replace(/\\/g, "\\\\")}"`;
|
|
1076
1087
|
|
|
1077
1088
|
for (const event of hook.events) {
|
|
1078
1089
|
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
@@ -1106,6 +1117,67 @@ function configureInSessionHooks() {
|
|
|
1106
1117
|
}
|
|
1107
1118
|
}
|
|
1108
1119
|
|
|
1120
|
+
// ─── Status Line ─────────────────────────────────────────────────────────────
|
|
1121
|
+
|
|
1122
|
+
// The GSD-T status bar. Canonical source: scripts/statusline-command.sh. Copy
|
|
1123
|
+
// it to ~/.claude/statusline-command.sh and point settings.statusLine at it so
|
|
1124
|
+
// edits in the package survive install/update/update-all. We only set
|
|
1125
|
+
// statusLine when it is absent or already points at our script — never clobber
|
|
1126
|
+
// a user's custom status line.
|
|
1127
|
+
const STATUSLINE_SCRIPT = "statusline-command.sh";
|
|
1128
|
+
|
|
1129
|
+
function installStatusLine() {
|
|
1130
|
+
ensureDir(CLAUDE_DIR);
|
|
1131
|
+
const src = path.join(PKG_SCRIPTS, STATUSLINE_SCRIPT);
|
|
1132
|
+
if (!fs.existsSync(src)) {
|
|
1133
|
+
info("No statusline-command.sh in package — skipping status line");
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
const dest = path.join(CLAUDE_DIR, STATUSLINE_SCRIPT);
|
|
1137
|
+
const srcContent = fs.readFileSync(src, "utf8");
|
|
1138
|
+
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : "";
|
|
1139
|
+
if (normalizeEol(srcContent) !== normalizeEol(destContent)) {
|
|
1140
|
+
copyFile(src, dest, STATUSLINE_SCRIPT);
|
|
1141
|
+
try { fs.chmodSync(dest, 0o755); } catch {}
|
|
1142
|
+
} else {
|
|
1143
|
+
info("Status line script unchanged");
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const parsed = readSettingsJson();
|
|
1147
|
+
if (parsed === null && fs.existsSync(SETTINGS_JSON)) {
|
|
1148
|
+
warn("settings.json has invalid JSON — cannot configure status line");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const settings = parsed || {};
|
|
1152
|
+
const desiredCmd = `bash ${dest}`;
|
|
1153
|
+
const existing = settings.statusLine;
|
|
1154
|
+
// Set only when absent or already ours (command references our script).
|
|
1155
|
+
const isOurs =
|
|
1156
|
+
existing &&
|
|
1157
|
+
existing.type === "command" &&
|
|
1158
|
+
typeof existing.command === "string" &&
|
|
1159
|
+
existing.command.includes(STATUSLINE_SCRIPT);
|
|
1160
|
+
if (existing && !isOurs) {
|
|
1161
|
+
info("Custom status line present — leaving it untouched");
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (isOurs && existing.command === desiredCmd) {
|
|
1165
|
+
info("Status line already configured");
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (isSymlink(SETTINGS_JSON)) {
|
|
1169
|
+
warn("Skipping settings.json write — target is a symlink");
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
settings.statusLine = { type: "command", command: desiredCmd };
|
|
1173
|
+
try {
|
|
1174
|
+
fs.writeFileSync(SETTINGS_JSON, JSON.stringify(settings, null, 2));
|
|
1175
|
+
success("Status line configured in settings.json");
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
warn(`Failed to write settings.json: ${e.message}`);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1109
1181
|
// ─── Figma MCP ──────────────────────────────────────────────────────────────
|
|
1110
1182
|
|
|
1111
1183
|
const FIGMA_MCP_URL = "https://mcp.figma.com/mcp";
|
|
@@ -1188,6 +1260,8 @@ const GLOBAL_BIN_TOOLS = [
|
|
|
1188
1260
|
"gsd-t-traceability-gate.cjs",
|
|
1189
1261
|
// M85 — Model-tier policy single source of truth (resolver + predicate).
|
|
1190
1262
|
"gsd-t-model-tier-policy.cjs",
|
|
1263
|
+
// M86 — Model-profile config + resolver CLI (standard/pro/premium tier-spend switch).
|
|
1264
|
+
"gsd-t-model-profile.cjs",
|
|
1191
1265
|
];
|
|
1192
1266
|
|
|
1193
1267
|
function installGlobalBinTools() {
|
|
@@ -1537,9 +1611,12 @@ async function doInstall(opts = {}) {
|
|
|
1537
1611
|
heading("Auto-Route (UserPromptSubmit)");
|
|
1538
1612
|
installAutoRoute();
|
|
1539
1613
|
|
|
1540
|
-
heading("In-Session Hooks (Conversation Capture + Token Usage)");
|
|
1614
|
+
heading("In-Session Hooks (Conversation Capture + Token Usage + Ctx Cue)");
|
|
1541
1615
|
installInSessionHooks();
|
|
1542
1616
|
|
|
1617
|
+
heading("Status Line");
|
|
1618
|
+
installStatusLine();
|
|
1619
|
+
|
|
1543
1620
|
heading("Figma MCP (Design-to-Code)");
|
|
1544
1621
|
configureFigmaMcp();
|
|
1545
1622
|
|
|
@@ -2484,6 +2561,8 @@ const PROJECT_BIN_TOOLS = [
|
|
|
2484
2561
|
// M85 — Model-tier policy resolver, so command invokers in consumer projects
|
|
2485
2562
|
// can resolve stage tiers at invoke time (M69 injection pattern).
|
|
2486
2563
|
"gsd-t-model-tier-policy.cjs",
|
|
2564
|
+
// M86 — Model-profile config + resolver CLI (standard/pro/premium tier-spend switch).
|
|
2565
|
+
"gsd-t-model-profile.cjs",
|
|
2487
2566
|
];
|
|
2488
2567
|
|
|
2489
2568
|
// Files that older versions of this installer copied into project bin/ but
|
|
@@ -2543,7 +2622,37 @@ function _matchedStraySignature(name, content) {
|
|
|
2543
2622
|
return null;
|
|
2544
2623
|
}
|
|
2545
2624
|
|
|
2625
|
+
// Self-protection identity check: is this project dir GSD-T's own source repo?
|
|
2626
|
+
// Guards BOTH the bin-tool copy loop (copying the installed package's tools over
|
|
2627
|
+
// the source repo reverts in-flight work — Red Team M86 r2 HIGH, fired live) and
|
|
2628
|
+
// the deprecated-stray sweep (signature-matching bin/gsd-t.js — the installer
|
|
2629
|
+
// itself — would delete the source file). Identity is by package.json name, NOT
|
|
2630
|
+
// by path — when update-all runs from the globally-installed package, PKG_ROOT
|
|
2631
|
+
// points to the global install and realpath comparison against the local source
|
|
2632
|
+
// always fails.
|
|
2633
|
+
function _isGsdTSourcePackage(projectDir) {
|
|
2634
|
+
try {
|
|
2635
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
2636
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
2637
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
2638
|
+
return !!pkg && pkg.name === "@tekyzinc/gsd-t";
|
|
2639
|
+
} catch {
|
|
2640
|
+
return false;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2546
2644
|
function copyBinToolsToProject(projectDir, projectName) {
|
|
2645
|
+
// Self-protection for the COPY loop, not just the stray sweep below: the GSD-T
|
|
2646
|
+
// source repo is legitimately registered as a project during development, and
|
|
2647
|
+
// copying the installed package's bin tools over it REVERTS in-flight work —
|
|
2648
|
+
// fired live during M86 verify (Red Team r2 HIGH: a 4.4.11 update-all clobbered
|
|
2649
|
+
// the committed M86 policy module mid-run; every model-profile call then
|
|
2650
|
+
// hard-crashed until restored from HEAD). The source repo IS the canonical
|
|
2651
|
+
// origin of these tools; propagating into it is wrong in every case.
|
|
2652
|
+
if (_isGsdTSourcePackage(projectDir)) {
|
|
2653
|
+
info(`${projectName} — GSD-T source repo: bin propagation skipped (canonical origin)`);
|
|
2654
|
+
return false;
|
|
2655
|
+
}
|
|
2547
2656
|
const projectBinDir = path.join(projectDir, "bin");
|
|
2548
2657
|
if (!fs.existsSync(projectBinDir)) {
|
|
2549
2658
|
try {
|
|
@@ -2577,25 +2686,8 @@ function copyBinToolsToProject(projectDir, projectName) {
|
|
|
2577
2686
|
}
|
|
2578
2687
|
}
|
|
2579
2688
|
}
|
|
2580
|
-
// Self-protection: NEVER sweep GSD-T's own source repo. Without this guard,
|
|
2581
|
-
// running `gsd-t update-all` with the GSD-T source repo itself registered
|
|
2582
|
-
// as a project (legitimate during development) would signature-match
|
|
2583
|
-
// bin/gsd-t.js — which IS the installer — and delete the source file.
|
|
2584
|
-
// Identity is by package.json name, NOT by path — when update-all runs from
|
|
2585
|
-
// the globally-installed package, PKG_ROOT points to the global install and
|
|
2586
|
-
// realpath comparison against the local source always fails.
|
|
2587
|
-
const isSourcePackage = (() => {
|
|
2588
|
-
try {
|
|
2589
|
-
const pkgPath = path.join(projectDir, "package.json");
|
|
2590
|
-
if (!fs.existsSync(pkgPath)) return false;
|
|
2591
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
2592
|
-
return pkg && pkg.name === "@tekyzinc/gsd-t";
|
|
2593
|
-
} catch {
|
|
2594
|
-
return false;
|
|
2595
|
-
}
|
|
2596
|
-
})();
|
|
2597
2689
|
let cleaned = 0;
|
|
2598
|
-
if (!
|
|
2690
|
+
if (!_isGsdTSourcePackage(projectDir)) {
|
|
2599
2691
|
for (const stray of DEPRECATED_BIN_STRAYS) {
|
|
2600
2692
|
const strayPath = path.join(projectBinDir, stray);
|
|
2601
2693
|
if (!fs.existsSync(strayPath)) continue;
|
|
@@ -4590,6 +4682,16 @@ if (require.main === module) {
|
|
|
4590
4682
|
});
|
|
4591
4683
|
process.exit(res.status == null ? 1 : res.status);
|
|
4592
4684
|
}
|
|
4685
|
+
case "model-profile": {
|
|
4686
|
+
// M86 — `gsd-t model-profile` thin dispatcher to the profile config +
|
|
4687
|
+
// resolver (standard/pro/premium tier-spend switch, per-project config).
|
|
4688
|
+
const { spawnSync } = require("child_process");
|
|
4689
|
+
const js = path.join(__dirname, "gsd-t-model-profile.cjs");
|
|
4690
|
+
const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
|
|
4691
|
+
stdio: "inherit",
|
|
4692
|
+
});
|
|
4693
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
4694
|
+
}
|
|
4593
4695
|
case "metrics":
|
|
4594
4696
|
doMetrics(args.slice(1));
|
|
4595
4697
|
break;
|
package/commands/gsd-t-debug.md
CHANGED
|
@@ -19,7 +19,36 @@ preflight → brief → Cycle 1 (diagnose → hypothesis → fix → test → re
|
|
|
19
19
|
|
|
20
20
|
Read `.gsd-t/progress.md`. Note any failing tests or runtime errors in the Decision Log.
|
|
21
21
|
|
|
22
|
-
## Step 2:
|
|
22
|
+
## Step 2: Resolve the active model profile (M86 — invoke-time injection)
|
|
23
|
+
|
|
24
|
+
Before calling the Workflow, resolve the active model profile to build the `overrides` map:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Run via Bash at invoke time:
|
|
28
|
+
gsd-t model-profile resolve --json
|
|
29
|
+
# Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
|
|
30
|
+
# (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
|
|
31
|
+
# form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
|
|
32
|
+
# Output: { "ok": true, "profile": "...", "overrides": { "stage-key": "concrete-model-id", ... } }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Resolver-failure handling (M86 — SC(f), pre-mortem c2 #2):** if the resolve call fails
|
|
36
|
+
(`{ok:false}`, spawn error, or the `model-profile` subcommand is not present in the installed
|
|
37
|
+
binary), do NOT silently proceed on the premium fallback. Either:
|
|
38
|
+
- HALT with `blocked-needs-human` and explain the resolver is unavailable; OR
|
|
39
|
+
- Proceed ONLY with a **loud, surfaced warning** that names the effective posture:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
|
|
43
|
+
(configured profile unknown; stale global binary may lack model-profile subcommand)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
|
|
47
|
+
named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
|
|
48
|
+
`configError` as a visible warning naming the effective profile before proceeding. A clean-looking
|
|
49
|
+
run on a posture the user did not configure is the same silent-spend failure class.
|
|
50
|
+
|
|
51
|
+
## Step 3: Invoke the debug Workflow
|
|
23
52
|
|
|
24
53
|
Call the `Workflow` tool with:
|
|
25
54
|
|
|
@@ -31,14 +60,18 @@ Call the `Workflow` tool with:
|
|
|
31
60
|
scriptPath: "<absolute path printed by `gsd-t workflow-path debug`>",
|
|
32
61
|
args: {
|
|
33
62
|
symptom: "describe the failing test or runtime error in one sentence",
|
|
34
|
-
projectDir: "."
|
|
63
|
+
projectDir: ".",
|
|
64
|
+
// M86: inject the resolved overrides map so the workflow's ?? form for debug-cycle-2
|
|
65
|
+
// picks up the profile-tier assignment instead of the premium fable literal.
|
|
66
|
+
// Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
|
|
67
|
+
overrides: { /* ...from resolver result.overrides, or {} on failure */ }
|
|
35
68
|
}
|
|
36
69
|
}
|
|
37
70
|
```
|
|
38
71
|
|
|
39
72
|
The Workflow runs up to 2 cycles. Cycle 2 receives Cycle 1's failed hypothesis to prevent re-trying the same approach.
|
|
40
73
|
|
|
41
|
-
## Step
|
|
74
|
+
## Step 4: Interpret the result
|
|
42
75
|
|
|
43
76
|
```js
|
|
44
77
|
{
|
|
@@ -14,7 +14,32 @@ preflight → brief (kind=design-decompose) → design agent (opus, with phase p
|
|
|
14
14
|
|
|
15
15
|
Capture the design reference from `$ARGUMENTS` (Figma URL / image path). If Figma MCP is configured, the agent uses it to extract tokens. Read any existing `.gsd-t/contracts/design/` to avoid duplication.
|
|
16
16
|
|
|
17
|
-
## Step 2:
|
|
17
|
+
## Step 2: Resolve the active model profile (M86 — invoke-time injection)
|
|
18
|
+
|
|
19
|
+
Before calling the Workflow, resolve the active model profile to build the `overrides` map:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Run via Bash at invoke time:
|
|
23
|
+
gsd-t model-profile resolve --json
|
|
24
|
+
# Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
|
|
25
|
+
# (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
|
|
26
|
+
# form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Resolver-failure handling (M86 — pre-mortem c2 #2):** if the resolve call fails, do NOT
|
|
30
|
+
silently proceed on the premium fallback. Either HALT with `blocked-needs-human`, or proceed
|
|
31
|
+
ONLY with a loud, surfaced warning:
|
|
32
|
+
```
|
|
33
|
+
⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
|
|
34
|
+
(configured profile unknown; stale global binary may lack model-profile subcommand)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
|
|
38
|
+
named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
|
|
39
|
+
`configError` as a visible warning naming the effective profile before proceeding. A clean-looking
|
|
40
|
+
run on a posture the user did not configure is the same silent-spend failure class.
|
|
41
|
+
|
|
42
|
+
## Step 3: Invoke the phase Workflow
|
|
18
43
|
|
|
19
44
|
```js
|
|
20
45
|
{
|
|
@@ -25,7 +50,10 @@ Capture the design reference from `$ARGUMENTS` (Figma URL / image path). If Figm
|
|
|
25
50
|
args: {
|
|
26
51
|
phase: "design-decompose",
|
|
27
52
|
projectDir: ".",
|
|
28
|
-
userInput: "$ARGUMENTS"
|
|
53
|
+
userInput: "$ARGUMENTS",
|
|
54
|
+
// M86: inject the resolved overrides map (probe + judge use this).
|
|
55
|
+
// Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
|
|
56
|
+
overrides: { /* ...from resolver result.overrides, or {} on failure */ }
|
|
29
57
|
// M84 Competition Mode is AUTOMATIC — do NOT pass `competition` by default.
|
|
30
58
|
// The workflow probes (opus) and self-decides; it competes when a design is
|
|
31
59
|
// ambiguous or the element/widget/page boundaries aren't obvious (a blind,
|
|
@@ -37,7 +65,7 @@ Capture the design reference from `$ARGUMENTS` (Figma URL / image path). If Figm
|
|
|
37
65
|
|
|
38
66
|
**Competition Mode (`--competition N`).** When a design is ambiguous or the element/widget/page boundaries aren't obvious, `/gsd-t-design-decompose --competition 3` fans out N candidate decompositions and a blind, different-model rubric judge picks the best. Parse N (clamped 2..5). See `.gsd-t/contracts/competition-mode-contract.md`. Default off.
|
|
39
67
|
|
|
40
|
-
## Step
|
|
68
|
+
## Step 4: Interpret the result
|
|
41
69
|
|
|
42
70
|
The Workflow returns `{ status, artifacts, summary, decisions }` (plus `competition: { n, winner, ranked }` when Competition Mode ran).
|
|
43
71
|
|
|
@@ -14,7 +14,32 @@ The agent identifies the full blast radius of recent code changes and updates ev
|
|
|
14
14
|
|
|
15
15
|
Read the recent commit range (or `$ARGUMENTS` if a specific scope is given) and `.gsd-t/progress.md` Decision Log to learn what changed.
|
|
16
16
|
|
|
17
|
-
## Step 2:
|
|
17
|
+
## Step 2: Resolve the active model profile (M86 — invoke-time injection)
|
|
18
|
+
|
|
19
|
+
Before calling the Workflow, resolve the active model profile to build the `overrides` map:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Run via Bash at invoke time:
|
|
23
|
+
gsd-t model-profile resolve --json
|
|
24
|
+
# Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
|
|
25
|
+
# (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
|
|
26
|
+
# form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Resolver-failure handling (M86 — pre-mortem c2 #2):** if the resolve call fails, do NOT
|
|
30
|
+
silently proceed on the premium fallback. Either HALT with `blocked-needs-human`, or proceed
|
|
31
|
+
ONLY with a loud, surfaced warning:
|
|
32
|
+
```
|
|
33
|
+
⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
|
|
34
|
+
(configured profile unknown; stale global binary may lack model-profile subcommand)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
|
|
38
|
+
named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
|
|
39
|
+
`configError` as a visible warning naming the effective profile before proceeding. A clean-looking
|
|
40
|
+
run on a posture the user did not configure is the same silent-spend failure class.
|
|
41
|
+
|
|
42
|
+
## Step 3: Invoke the phase Workflow
|
|
18
43
|
|
|
19
44
|
```js
|
|
20
45
|
{
|
|
@@ -25,12 +50,15 @@ Read the recent commit range (or `$ARGUMENTS` if a specific scope is given) and
|
|
|
25
50
|
args: {
|
|
26
51
|
phase: "doc-ripple",
|
|
27
52
|
projectDir: ".",
|
|
28
|
-
userInput: "$ARGUMENTS"
|
|
53
|
+
userInput: "$ARGUMENTS",
|
|
54
|
+
// M86: inject the resolved overrides map.
|
|
55
|
+
// Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
|
|
56
|
+
overrides: { /* ...from resolver result.overrides, or {} on failure */ }
|
|
29
57
|
}
|
|
30
58
|
}
|
|
31
59
|
```
|
|
32
60
|
|
|
33
|
-
## Step
|
|
61
|
+
## Step 4: Interpret the result
|
|
34
62
|
|
|
35
63
|
The Workflow returns `{ status, artifacts, summary, decisions }`.
|
|
36
64
|
|
package/commands/gsd-t-help.md
CHANGED
|
@@ -500,7 +500,21 @@ Use these when user asks for help on a specific command:
|
|
|
500
500
|
- **Files**: `bin/gsd-t-model-tier-policy.cjs` (zero external deps — installer invariant).
|
|
501
501
|
- **Use when**: Any phase that needs to resolve a concrete model id from a stage key at invoke time (M69 pattern). Workflows NEVER `require` this module (sandbox ban) — they use hard-coded tier alias literals the lint proves match the policy.
|
|
502
502
|
- **CLI**: `gsd-t model-tier-policy resolve <stageKey> [--json]`. Emits `{ok, stageKey, tier, model, requiresThinkingOmitted}`. Exit 0 resolved · 1 unknown stage key.
|
|
503
|
-
- **Contract**: `.gsd-t/contracts/model-tier-policy-contract.md` v1.
|
|
503
|
+
- **Contract**: `.gsd-t/contracts/model-tier-policy-contract.md` v1.1.0 STABLE.
|
|
504
|
+
|
|
505
|
+
### model-profile (M86)
|
|
506
|
+
- **Summary**: Per-project model-tier profile switcher — a SECOND dimension over the M85 stage-tier policy. Manages `.gsd-t/model-profile.json` and resolves which concrete model id each workflow stage runs on under the active profile. Three named profiles: `standard` (zero Fable — pre-M85 posture), `pro` (Fable on red-team + pre-mortem + debug-cycle-2), `premium` (all 6 M85 designated Fable stages — global default). Switching profiles injects the overrides into workflow args at invoke time (M69 pattern — NO tracked-file rewriting). Competition producers are HELD at opus in ALL profiles (M82 blindness invariant). The active profile is surfaced in the session banner (`[GSD-T PROFILE]`), the statusline, and `gsd-t status` — always named, never an implicit fallback (SC(f)).
|
|
507
|
+
- **Files**: `bin/gsd-t-model-profile.cjs` (zero external deps — installer invariant). Config: `.gsd-t/model-profile.json` (`{ "profile": "pro", "stageOverrides": { ... } }`).
|
|
508
|
+
- **Use when**: A project needs a different spend posture than the global default (e.g., run `standard` on a CI cost budget, or `pro` for a production release with targeted Fable quality gates). Per-stage overrides allow fine-grained control beyond the named profiles.
|
|
509
|
+
- **CLI**:
|
|
510
|
+
- `gsd-t model-profile show [--json]` — display active profile + per-stage resolution
|
|
511
|
+
- `gsd-t model-profile set <standard|pro|premium>` — switch the project profile
|
|
512
|
+
- `gsd-t model-profile set-stage <stage> <tier>` — per-stage override (rejects `competition-producers` and `competition-judge→opus` — M82 blindness clamps)
|
|
513
|
+
- `gsd-t model-profile resolve --json` — resolve the ACTIVE config (profile + stageOverrides) into the overrides envelope consumed by workflow invokers (the invoker form)
|
|
514
|
+
- `gsd-t model-profile resolve --profile <p> [stage] [--json]` — diagnostic form: pure profile envelope, stageOverrides ZEROED by design (census/divergence checks only — never for invocation)
|
|
515
|
+
- Emits `{ok, profile, overrides: { "<stage>": "<concreteModelId>" }, requiresThinkingOmitted?}`. Exit 0 resolved · 1 unknown profile/tier.
|
|
516
|
+
- **Out of scope**: Session default model (`/model`) — profiles govern WORKFLOW STAGES only.
|
|
517
|
+
- **Contract**: `.gsd-t/contracts/model-profile-config-contract.md` v1.0.0 STABLE.
|
|
504
518
|
|
|
505
519
|
## Unknown Command
|
|
506
520
|
|
package/commands/gsd-t-impact.md
CHANGED
|
@@ -14,7 +14,32 @@ The agent analyzes the downstream effects of proposed changes: what might break,
|
|
|
14
14
|
|
|
15
15
|
Read `.gsd-t/progress.md`, the relevant domain `tasks.md`, and `docs/architecture.md`/`.gsd-t/contracts/` for the surfaces in scope.
|
|
16
16
|
|
|
17
|
-
## Step 2:
|
|
17
|
+
## Step 2: Resolve the active model profile (M86 — invoke-time injection)
|
|
18
|
+
|
|
19
|
+
Before calling the Workflow, resolve the active model profile to build the `overrides` map:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Run via Bash at invoke time:
|
|
23
|
+
gsd-t model-profile resolve --json
|
|
24
|
+
# Bare form (NO --profile flag): reads .gsd-t/model-profile.json — profile AND stageOverrides
|
|
25
|
+
# (set-stage overrides MUST win — contract precedence; --profile is a config-blind diagnostic
|
|
26
|
+
# form that ZEROES stageOverrides and must never be used for invocation — Red Team M86 r3)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Resolver-failure handling (M86 — pre-mortem c2 #2):** if the resolve call fails, do NOT
|
|
30
|
+
silently proceed on the premium fallback. Either HALT with `blocked-needs-human`, or proceed
|
|
31
|
+
ONLY with a loud, surfaced warning:
|
|
32
|
+
```
|
|
33
|
+
⚠ model-profile resolver unavailable — running on PREMIUM fallback literals
|
|
34
|
+
(configured profile unknown; stale global binary may lack model-profile subcommand)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Also surface a SUCCESSFUL resolve that carries a `configError` field (the resolver returns a
|
|
38
|
+
named default + `configError` for malformed/hand-edited configs — Red Team M86): print the
|
|
39
|
+
`configError` as a visible warning naming the effective profile before proceeding. A clean-looking
|
|
40
|
+
run on a posture the user did not configure is the same silent-spend failure class.
|
|
41
|
+
|
|
42
|
+
## Step 3: Invoke the phase Workflow
|
|
18
43
|
|
|
19
44
|
```js
|
|
20
45
|
{
|
|
@@ -26,12 +51,15 @@ Read `.gsd-t/progress.md`, the relevant domain `tasks.md`, and `docs/architectur
|
|
|
26
51
|
phase: "impact",
|
|
27
52
|
milestone: "M{NN}",
|
|
28
53
|
projectDir: ".",
|
|
29
|
-
userInput: "$ARGUMENTS"
|
|
54
|
+
userInput: "$ARGUMENTS",
|
|
55
|
+
// M86: inject the resolved overrides map.
|
|
56
|
+
// Pass {} when the resolver failed AND you chose the loud-warning path (not halt).
|
|
57
|
+
overrides: { /* ...from resolver result.overrides, or {} on failure */ }
|
|
30
58
|
}
|
|
31
59
|
}
|
|
32
60
|
```
|
|
33
61
|
|
|
34
|
-
## Step
|
|
62
|
+
## Step 4: Interpret the result
|
|
35
63
|
|
|
36
64
|
The Workflow returns `{ status, artifacts, summary, decisions }`.
|
|
37
65
|
|