@tekyzinc/gsd-t 5.7.10 → 5.9.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/README.md +2 -2
- package/bin/gsd-t-concise-rewrite.cjs +223 -0
- package/bin/gsd-t-fallback-detect.cjs +624 -0
- package/bin/gsd-t-install-check.cjs +342 -0
- package/bin/gsd-t-verify-gate.cjs +2 -0
- package/bin/gsd-t.js +193 -5
- package/commands/gsd-t-architect.md +261 -18
- package/commands/gsd-t-help.md +4 -2
- package/package.json +1 -1
- package/scripts/gsd-t-concise-hook.js +200 -0
- package/scripts/gsd-t-fallback-guard.js +237 -0
- package/scripts/gsd-t-install-heal.js +183 -0
- package/templates/CLAUDE-global.md +3 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gsd-t-fallback-guard.js
|
|
4
|
+
*
|
|
5
|
+
* M106-D2 — PreToolUse hook on Write|Edit. Denies a write that introduces a
|
|
6
|
+
* fallback the user never approved.
|
|
7
|
+
*
|
|
8
|
+
* [RULE] fallback-guard-denies-unapproved
|
|
9
|
+
* [RULE] fallback-guard-halts-never-allows-on-error
|
|
10
|
+
*
|
|
11
|
+
* A fallback added while chasing a bug never appears in any plan — this is the
|
|
12
|
+
* only trigger point that can catch it, because it fires at the moment the code
|
|
13
|
+
* is written.
|
|
14
|
+
*
|
|
15
|
+
* ─── Stdin (Claude Code PreToolUse payload) ─────────────────────────────────
|
|
16
|
+
* { "tool_name": "Write"|"Edit", "cwd": "...",
|
|
17
|
+
* "tool_input": { "file_path": "...", "content"|"new_string": "..." } }
|
|
18
|
+
*
|
|
19
|
+
* ─── Decision contract ──────────────────────────────────────────────────────
|
|
20
|
+
* Deny: {"hookSpecificOutput":{"hookEventName":"PreToolUse",
|
|
21
|
+
* "permissionDecision":"deny","permissionDecisionReason":"..."}}
|
|
22
|
+
* Allow: exit 0, no output.
|
|
23
|
+
*
|
|
24
|
+
* ─── HALT, never allow-on-error ─────────────────────────────────────────────
|
|
25
|
+
* This guard governs the No-Fallback rule, so it must not contain one. If the
|
|
26
|
+
* detector cannot run or its answer cannot be read, the write is DENIED with
|
|
27
|
+
* the reason — never quietly permitted. The single exception is a payload
|
|
28
|
+
* that is not a Write/Edit of source at all: that is "not applicable", not a
|
|
29
|
+
* failure, and it exits 0.
|
|
30
|
+
*
|
|
31
|
+
* Zero dependencies.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
"use strict";
|
|
35
|
+
|
|
36
|
+
const fs = require("fs");
|
|
37
|
+
const path = require("path");
|
|
38
|
+
const { spawnSync } = require("child_process");
|
|
39
|
+
|
|
40
|
+
const SOURCE_EXT = new Set([".js", ".cjs", ".mjs", ".jsx", ".ts", ".tsx"]);
|
|
41
|
+
const TEST_PATH_RE = /(?:^|[\\/])(?:test|tests|__tests__|spec|e2e|fixtures?)[\\/]|\.(?:test|spec)\.[cm]?[jt]sx?$/i;
|
|
42
|
+
|
|
43
|
+
function deny(reason) {
|
|
44
|
+
process.stdout.write(JSON.stringify({
|
|
45
|
+
hookSpecificOutput: {
|
|
46
|
+
hookEventName: "PreToolUse",
|
|
47
|
+
permissionDecision: "deny",
|
|
48
|
+
permissionDecisionReason: reason,
|
|
49
|
+
},
|
|
50
|
+
}) + "\n");
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function allow() { process.exit(0); }
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Locate the detector.
|
|
58
|
+
*
|
|
59
|
+
* Every project is supposed to carry its own copy. If it does not, the
|
|
60
|
+
* project's install is broken, and the answer is to REPAIR it — which the
|
|
61
|
+
* SessionStart heal hook does — not to hunt around for a copy somewhere else.
|
|
62
|
+
* Hunting is what let binvoice run for weeks on 20 of 38 tools while every
|
|
63
|
+
* update reported success.
|
|
64
|
+
*
|
|
65
|
+
* Returns the path, or throws with what is wrong. It never returns "not found"
|
|
66
|
+
* as if that were an ordinary answer.
|
|
67
|
+
*/
|
|
68
|
+
function findDetector(projectDir) {
|
|
69
|
+
const inProject = path.join(projectDir, "bin", "gsd-t-fallback-detect.cjs");
|
|
70
|
+
if (fs.existsSync(inProject)) return inProject;
|
|
71
|
+
|
|
72
|
+
// Running from inside the package itself (developing GSD-T).
|
|
73
|
+
const inPackage = path.join(__dirname, "..", "bin", "gsd-t-fallback-detect.cjs");
|
|
74
|
+
if (fs.existsSync(inPackage)) return inPackage;
|
|
75
|
+
|
|
76
|
+
throw new Error(
|
|
77
|
+
`This project has no copy of the fallback detector at ${inProject}, which means ` +
|
|
78
|
+
`its GSD-T install is incomplete. Run 'gsd-t install-check' to repair it — do not ` +
|
|
79
|
+
`work around it.`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Thrown when the settings file exists but cannot be understood. */
|
|
84
|
+
class ConfigUnreadable extends Error {}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Is the gate switched on for this project?
|
|
88
|
+
*
|
|
89
|
+
* A settings file that cannot be read is NOT assumed to mean "on" — that would
|
|
90
|
+
* be a guess about what the project wanted. It throws, and the caller denies
|
|
91
|
+
* the write and says why. Only an ABSENT file means "on", because absence is
|
|
92
|
+
* unambiguous.
|
|
93
|
+
*/
|
|
94
|
+
function isEnabled(projectDir) {
|
|
95
|
+
const p = path.join(projectDir, ".gsd-t", "fallback-gate.json");
|
|
96
|
+
if (!fs.existsSync(p)) return true; // absent = on, by design
|
|
97
|
+
let raw;
|
|
98
|
+
try {
|
|
99
|
+
raw = fs.readFileSync(p, "utf8");
|
|
100
|
+
} catch (e) {
|
|
101
|
+
throw new ConfigUnreadable(`${p} could not be read: ${e.message}`);
|
|
102
|
+
}
|
|
103
|
+
const cfg = JSON.parse(raw); // a parse failure throws, and the caller denies
|
|
104
|
+
return cfg.enabled !== false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildReason(findings, filePath) {
|
|
108
|
+
const lines = [
|
|
109
|
+
`This write adds ${findings.length === 1 ? "a fallback" : `${findings.length} fallbacks`} that was never approved.`,
|
|
110
|
+
"",
|
|
111
|
+
];
|
|
112
|
+
for (const f of findings.slice(0, 5)) {
|
|
113
|
+
lines.push(` ${f.what}`);
|
|
114
|
+
if (f.snippet) lines.push(` ${f.snippet}`);
|
|
115
|
+
lines.push("");
|
|
116
|
+
}
|
|
117
|
+
lines.push(
|
|
118
|
+
"A fallback continues after a failure. It produces wrong data that looks correct,",
|
|
119
|
+
"and it removes the alarm for the bug that caused the failure.",
|
|
120
|
+
"",
|
|
121
|
+
"Do one of these:",
|
|
122
|
+
"",
|
|
123
|
+
" 1. Replace it with a halt — stop, and report what could not be done.",
|
|
124
|
+
" This is almost always the right answer.",
|
|
125
|
+
"",
|
|
126
|
+
" 2. Ask David for approval. Tell him, in plain words:",
|
|
127
|
+
" - what fails",
|
|
128
|
+
" - how often it really fails, with evidence",
|
|
129
|
+
" - why stopping is worse than continuing",
|
|
130
|
+
" - what it does instead (never a guessed value, never a partial result)",
|
|
131
|
+
" If he agrees, add the entry to .gsd-t/fallbacks.json and write again.",
|
|
132
|
+
"",
|
|
133
|
+
` File: ${filePath}`
|
|
134
|
+
);
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function main() {
|
|
139
|
+
let input = "";
|
|
140
|
+
let done = false;
|
|
141
|
+
|
|
142
|
+
process.stdin.setEncoding("utf8");
|
|
143
|
+
process.stdin.on("data", (c) => { input += c; });
|
|
144
|
+
|
|
145
|
+
const finish = () => {
|
|
146
|
+
if (done) return;
|
|
147
|
+
done = true;
|
|
148
|
+
|
|
149
|
+
let data;
|
|
150
|
+
try {
|
|
151
|
+
data = JSON.parse(input);
|
|
152
|
+
} catch (_) {
|
|
153
|
+
// An unparseable payload is not a write we can inspect — not applicable.
|
|
154
|
+
return allow();
|
|
155
|
+
}
|
|
156
|
+
if (!data || typeof data !== "object") return allow();
|
|
157
|
+
|
|
158
|
+
const tool = data.tool_name;
|
|
159
|
+
if (tool !== "Write" && tool !== "Edit") return allow();
|
|
160
|
+
|
|
161
|
+
const ti = (data.tool_input && typeof data.tool_input === "object") ? data.tool_input : {};
|
|
162
|
+
const filePath = typeof ti.file_path === "string" ? ti.file_path : "";
|
|
163
|
+
if (!filePath) return allow();
|
|
164
|
+
|
|
165
|
+
const ext = path.extname(filePath);
|
|
166
|
+
if (!SOURCE_EXT.has(ext)) return allow(); // not source — not applicable
|
|
167
|
+
if (TEST_PATH_RE.test(filePath.replace(/\\/g, "/"))) return allow(); // tests are exempt
|
|
168
|
+
|
|
169
|
+
const content = typeof ti.content === "string" ? ti.content
|
|
170
|
+
: typeof ti.new_string === "string" ? ti.new_string
|
|
171
|
+
: "";
|
|
172
|
+
if (!content.trim()) return allow();
|
|
173
|
+
|
|
174
|
+
const cwd = (typeof data.cwd === "string" && data.cwd) ? data.cwd : process.cwd();
|
|
175
|
+
let on;
|
|
176
|
+
try {
|
|
177
|
+
on = isEnabled(cwd);
|
|
178
|
+
} catch (e) {
|
|
179
|
+
return deny(
|
|
180
|
+
"The fallback gate's settings could not be read, so it is unclear whether\n" +
|
|
181
|
+
`this project has switched the gate off.\n\n${e.message}\n\n` +
|
|
182
|
+
"Fix the file, or delete it to leave the gate on."
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (!on) return allow(); // switched off for this project
|
|
186
|
+
|
|
187
|
+
let detector;
|
|
188
|
+
try {
|
|
189
|
+
detector = findDetector(cwd);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
return deny(
|
|
192
|
+
`${e.message}\n\n` +
|
|
193
|
+
"This write cannot be checked, and allowing it unchecked is exactly what this\n" +
|
|
194
|
+
"guard exists to prevent."
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const run = spawnSync(process.execPath,
|
|
199
|
+
[detector, "--text", content, "--file", filePath, "--project", cwd, "--json"],
|
|
200
|
+
{ encoding: "utf8", timeout: 10000, maxBuffer: 8 * 1024 * 1024 });
|
|
201
|
+
|
|
202
|
+
if (run.error || typeof run.stdout !== "string" || !run.stdout.trim()) {
|
|
203
|
+
return deny(
|
|
204
|
+
"The fallback check could not run, so this write cannot be verified.\n" +
|
|
205
|
+
`Reason: ${run.error ? run.error.message : "the detector produced no output"}\n\n` +
|
|
206
|
+
"This guard halts rather than letting an unchecked write through."
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let result;
|
|
211
|
+
try {
|
|
212
|
+
result = JSON.parse(run.stdout);
|
|
213
|
+
} catch (_) {
|
|
214
|
+
return deny("The fallback check returned something unreadable, so this write cannot be verified.");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (result.exitCode === 64) {
|
|
218
|
+
return deny(
|
|
219
|
+
`The fallback check could not decide: ${result.error || "unknown reason"}\n\n` +
|
|
220
|
+
(result.halt || "Fix the problem above, then write again.")
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (result.ok) return allow();
|
|
225
|
+
|
|
226
|
+
return deny(buildReason(result.findings || [], filePath));
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
process.stdin.on("end", finish);
|
|
230
|
+
process.stdin.on("error", finish);
|
|
231
|
+
const wd = setTimeout(finish, 8000);
|
|
232
|
+
if (wd.unref) wd.unref();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (require.main === module) main();
|
|
236
|
+
|
|
237
|
+
module.exports = { buildReason, findDetector, isEnabled };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gsd-t-install-heal.js
|
|
4
|
+
*
|
|
5
|
+
* M108 — SessionStart hook. Checks this project's install, repairs what is
|
|
6
|
+
* missing, and reports anything it could not fix.
|
|
7
|
+
*
|
|
8
|
+
* [RULE] install-heal-repairs-before-work-starts
|
|
9
|
+
* [RULE] install-heal-reports-what-it-could-not-fix
|
|
10
|
+
*
|
|
11
|
+
* This is NOT a fallback. A fallback continues past a failure with a worse
|
|
12
|
+
* answer. This one FIXES the failure — copies the missing tool from the
|
|
13
|
+
* installed package — so the session then runs with everything present. When it
|
|
14
|
+
* cannot fix something it says so loudly rather than letting the session
|
|
15
|
+
* proceed on a broken install.
|
|
16
|
+
*
|
|
17
|
+
* It also surfaces the shared repair log: if the same tool keeps going missing
|
|
18
|
+
* across projects, the installer is what needs fixing, not each project.
|
|
19
|
+
*
|
|
20
|
+
* ─── Stdin (Claude Code SessionStart payload) ───────────────────────────────
|
|
21
|
+
* { "hook_event_name": "SessionStart", "cwd": "...", "session_id": "..." }
|
|
22
|
+
*
|
|
23
|
+
* ─── Output ─────────────────────────────────────────────────────────────────
|
|
24
|
+
* Prints to stdout, which Claude Code shows as session context. Silent when
|
|
25
|
+
* the install is already complete — the common case, and no news is good news.
|
|
26
|
+
*
|
|
27
|
+
* Zero dependencies.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
"use strict";
|
|
31
|
+
|
|
32
|
+
const fs = require("fs");
|
|
33
|
+
const os = require("os");
|
|
34
|
+
const path = require("path");
|
|
35
|
+
const { spawnSync, execFileSync } = require("child_process");
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find the install checker. It ships beside this hook, so one place answers it.
|
|
39
|
+
* Throws with what is wrong — never returns nothing as an ordinary answer.
|
|
40
|
+
*/
|
|
41
|
+
function findChecker() {
|
|
42
|
+
const beside = path.join(__dirname, "..", "bin", "gsd-t-install-check.cjs");
|
|
43
|
+
if (fs.existsSync(beside)) return beside;
|
|
44
|
+
throw new Error(
|
|
45
|
+
`The install checker is not at ${beside}, where it ships. The GSD-T install ` +
|
|
46
|
+
`itself is incomplete — reinstall it.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function main() {
|
|
51
|
+
let input = "";
|
|
52
|
+
let done = false;
|
|
53
|
+
|
|
54
|
+
process.stdin.setEncoding("utf8");
|
|
55
|
+
process.stdin.on("data", (c) => { input += c; });
|
|
56
|
+
|
|
57
|
+
const finish = () => {
|
|
58
|
+
if (done) return;
|
|
59
|
+
done = true;
|
|
60
|
+
|
|
61
|
+
// Which project is this? The payload says. Guessing with process.cwd()
|
|
62
|
+
// could check — and repair — a different project than the one the session
|
|
63
|
+
// is in, so an unreadable payload stops instead.
|
|
64
|
+
let data;
|
|
65
|
+
try {
|
|
66
|
+
data = JSON.parse(input);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
process.stdout.write(
|
|
69
|
+
`[GSD-T INSTALL] Could not tell which project this session is in, so its ` +
|
|
70
|
+
`install was not checked: ${e.message}\n`
|
|
71
|
+
);
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
const cwd = (data && typeof data.cwd === "string" && data.cwd) ? data.cwd : null;
|
|
75
|
+
if (!cwd) {
|
|
76
|
+
process.stdout.write(
|
|
77
|
+
"[GSD-T INSTALL] The session did not say which folder it is in, so this " +
|
|
78
|
+
"project's install was not checked.\n"
|
|
79
|
+
);
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Not a GSD-T project — nothing to check.
|
|
84
|
+
if (!fs.existsSync(path.join(cwd, ".gsd-t"))) { process.exit(0); }
|
|
85
|
+
|
|
86
|
+
let checker;
|
|
87
|
+
try {
|
|
88
|
+
checker = findChecker();
|
|
89
|
+
} catch (e) {
|
|
90
|
+
process.stdout.write(`[GSD-T INSTALL] ${e.message}\n`);
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const run = spawnSync(process.execPath, [checker, "--project", cwd, "--json"], {
|
|
95
|
+
encoding: "utf8", timeout: 60000, maxBuffer: 8 * 1024 * 1024,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (run.error || !run.stdout) {
|
|
99
|
+
process.stdout.write(
|
|
100
|
+
`[GSD-T INSTALL] The install check could not run: ${run.error ? run.error.message : "no output"}\n`
|
|
101
|
+
);
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let result;
|
|
106
|
+
try {
|
|
107
|
+
result = JSON.parse(run.stdout);
|
|
108
|
+
} catch (_) {
|
|
109
|
+
process.stdout.write("[GSD-T INSTALL] The install check returned something unreadable.\n");
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const repaired = result.repaired || [];
|
|
114
|
+
const unfixable = result.unrepairable || [];
|
|
115
|
+
|
|
116
|
+
if (repaired.length) {
|
|
117
|
+
process.stdout.write(
|
|
118
|
+
`[GSD-T INSTALL] This project was missing ${repaired.length} tool(s). ` +
|
|
119
|
+
`They have been restored from the installed package, and the session can proceed normally.\n`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (unfixable.length) {
|
|
124
|
+
process.stdout.write(
|
|
125
|
+
`[GSD-T INSTALL] ${unfixable.length} tool(s) could NOT be restored:\n`
|
|
126
|
+
);
|
|
127
|
+
for (const u of unfixable) {
|
|
128
|
+
process.stdout.write(` ${u.tool} — ${u.reason}\n`);
|
|
129
|
+
}
|
|
130
|
+
process.stdout.write(
|
|
131
|
+
"Anything that depends on these will fail. Tell David the install is broken " +
|
|
132
|
+
"before doing work that relies on them.\n"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// If one tool keeps going missing across several projects, the installer is
|
|
137
|
+
// the cause, not each project. Say so ONCE — repeating it every session
|
|
138
|
+
// turns a real signal into noise you stop reading.
|
|
139
|
+
if (repaired.length) {
|
|
140
|
+
try {
|
|
141
|
+
const lib = require(checker);
|
|
142
|
+
const { entries } = lib.readLog();
|
|
143
|
+
const suspects = lib.installerSuspects({}, entries).filter((s) => s.projects.length >= 3);
|
|
144
|
+
const seenPath = path.join(os.homedir(), ".claude", "gsd-t-install-suspects-seen.json");
|
|
145
|
+
let alreadyTold = [];
|
|
146
|
+
if (fs.existsSync(seenPath)) {
|
|
147
|
+
alreadyTold = JSON.parse(fs.readFileSync(seenPath, "utf8"));
|
|
148
|
+
}
|
|
149
|
+
const fresh = suspects.filter((s) => !alreadyTold.includes(s.tool));
|
|
150
|
+
if (fresh.length) {
|
|
151
|
+
process.stdout.write(
|
|
152
|
+
"[GSD-T INSTALL] These tools have gone missing across several projects, which " +
|
|
153
|
+
"points at the installer rather than any one project:\n"
|
|
154
|
+
);
|
|
155
|
+
for (const s of fresh.slice(0, 5)) {
|
|
156
|
+
process.stdout.write(` ${s.tool} — ${s.projects.length} projects\n`);
|
|
157
|
+
}
|
|
158
|
+
process.stdout.write("This is worth fixing in GSD-T itself.\n");
|
|
159
|
+
fs.writeFileSync(seenPath, JSON.stringify([...alreadyTold, ...fresh.map((s) => s.tool)]));
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
// The repair above already happened and is unaffected. But this is the
|
|
163
|
+
// signal that tells you the INSTALLER is at fault rather than each
|
|
164
|
+
// project — losing it silently is how the underlying cause stays hidden.
|
|
165
|
+
process.stdout.write(
|
|
166
|
+
`[GSD-T INSTALL] Repairs were made, but the cross-project pattern could ` +
|
|
167
|
+
`not be read: ${e.message}\n`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
process.exit(0);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
process.stdin.on("end", finish);
|
|
176
|
+
process.stdin.on("error", finish);
|
|
177
|
+
const wd = setTimeout(finish, 20000);
|
|
178
|
+
if (wd.unref) wd.unref();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (require.main === module) main();
|
|
182
|
+
|
|
183
|
+
module.exports = { findChecker };
|
|
@@ -449,10 +449,12 @@ See memory pointer: `feedback_auto_research_external_gaps`.
|
|
|
449
449
|
|
|
450
450
|
### Architect's Oversight Doctrine (M101 — governed, enforced)
|
|
451
451
|
|
|
452
|
-
**Contract:** `.gsd-t/contracts/architects-oversight-contract.md` v1.
|
|
452
|
+
**Contract:** `.gsd-t/contracts/architects-oversight-contract.md` v1.1.0 STABLE
|
|
453
453
|
|
|
454
454
|
**Never build before the design has passed the architect's interrogation.** GSD-T staffs verifiers (Red Team, QA, code-review, pre-mortem) — all asking "is this correct?" — but no seat asked "is this the *smartest, simplest* design given what we already have?" The result: the wrong thing built correctly, then thoroughly tested, then shipped (the Binvoice completeness-scan waste — a whole-page scan re-deriving a count already stored locally). This doctrine fills the empty architect seat. Sibling to the Unproven-Assumption Doctrine: that one bars unproven *facts*; this one bars unproven *necessity*.
|
|
455
455
|
|
|
456
|
+
**§Stage 0 — GROUND BEFORE YOU ASSESS (runs first; contract §0).** A field audit of 16 real architect runs found 21 user corrections, and **13 of them were facts the user already held** — settled rules the architect re-derived and got wrong ("that rule was implemented last week"), and runtime behavior it asserted from a saved page instead of asking ("I believe you're wrong. When scrolling the feed…"). **Asking is cheaper than deriving.** So before the pass: read the code AND the standing rules (CLAUDE.md constraints, `[RULE]` guard maps, contracts, recent Decision Log); label every evidence item **LIVE or SNAPSHOT** (a runtime claim resting on a snapshot is unproven); then **interview the user** — lead by showing your read of *how it works today* as a plain-English flow for confirmation, echo back the rules you're treating as fixed, and ask only what code cannot answer. **Research** (how others solve this class of problem) runs only when you're not confident, and only AFTER the interview so it can't anchor the questions. Loop interview↔research, **max 3 cycles** (1-2 expected); still unsure at the cap → ask the user whether to halt or proceed with the uncertainty flagged. A run succeeded if the build it directed needed few follow-ups — not if the report read well.
|
|
457
|
+
|
|
456
458
|
**The Six-Stage Pass — run IN ORDER before proposing or building any solution. Each stage can KILL the plan. Every "am I sure?" is answered with EVIDENCE (a grep, a Read, a graph query), never conviction — self-confidence is what produced the waste.**
|
|
457
459
|
|
|
458
460
|
1. **Objective** — What is the core objective? Why is it the core objective? *(Kills: building the wrong thing.)*
|