@tekyzinc/gsd-t 4.13.11 → 4.14.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 +23 -0
- package/README.md +1 -1
- package/bin/gsd-t-graph-freshness.cjs +8 -4
- package/bin/gsd-t-graph-index.cjs +7 -2
- package/bin/gsd-t-graph-k1-sqlite-stream.cjs +1 -1
- package/bin/gsd-t-graph-metrics-rollup.cjs +527 -0
- package/bin/gsd-t-graph-query-cli.cjs +131 -31
- package/bin/gsd-t-graph-scip-upgrade.cjs +14 -5
- package/bin/gsd-t-graph-store-bakeoff.cjs +1 -1
- package/bin/gsd-t-graph-store-resolver.cjs +484 -0
- package/bin/gsd-t-verify-gate.cjs +9 -2
- package/bin/gsd-t.js +46 -1
- package/package.json +1 -1
- package/scripts/gsd-t-graph-intercept.js +89 -8
- package/scripts/gsd-t-read-intercept.js +87 -18
- package/templates/workflows/gsd-t-debug.workflow.js +19 -0
- package/templates/workflows/gsd-t-integrate.workflow.js +19 -0
- package/templates/workflows/gsd-t-phase.workflow.js +19 -0
- package/templates/workflows/gsd-t-quick.workflow.js +19 -0
- package/templates/workflows/gsd-t-scan.workflow.js +78 -22
- package/templates/workflows/gsd-t-verify.workflow.js +19 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* gsd-t-graph-intercept.js — M97
|
|
3
|
+
* gsd-t-graph-intercept.js — M97/M99
|
|
4
4
|
*
|
|
5
5
|
* A PostToolUse hook on the `Grep` tool. When Claude runs a grep that is actually
|
|
6
6
|
* a STRUCTURAL question (who-calls / who-imports / a bare symbol), this answers it
|
|
@@ -21,8 +21,19 @@
|
|
|
21
21
|
* - No graph in this project → pure no-op.
|
|
22
22
|
* - Original grep hits are RETAINED beneath the graph answer (no silent hiding).
|
|
23
23
|
*
|
|
24
|
+
* M99 D2:
|
|
25
|
+
* - Presence check uses D1's resolver (resolveStorePath) — never re-derives the path.
|
|
26
|
+
* - Layer-2a logging: one ledger line per decision (replaced + passthrough) via
|
|
27
|
+
* append_ledger_line. Consumer resolved from GSDT_GRAPH_CONSUMER env or
|
|
28
|
+
* payload hook_data.consumer; falls back to 'cli'. Fail-open.
|
|
29
|
+
*
|
|
24
30
|
* [RULE] graph-intercept-fail-open-never-breaks-grep
|
|
25
31
|
* [RULE] graph-intercept-structural-only-text-passes-through
|
|
32
|
+
* [RULE] presence-check-repointed
|
|
33
|
+
* [RULE] byte-identical-on-off
|
|
34
|
+
* [RULE] fail-open
|
|
35
|
+
* [RULE] import-resolver-never-hardcode
|
|
36
|
+
* [RULE] consumer-label-from-context-not-setenv
|
|
26
37
|
*/
|
|
27
38
|
|
|
28
39
|
'use strict';
|
|
@@ -58,29 +69,93 @@ function resolveQueryCli(cwd) {
|
|
|
58
69
|
return null;
|
|
59
70
|
}
|
|
60
71
|
|
|
72
|
+
// M99 D2: load D1's resolver module (resolveStorePath + append_ledger_line).
|
|
73
|
+
// [RULE] import-resolver-never-hardcode / [RULE] presence-check-repointed
|
|
74
|
+
function loadResolver(cwd) {
|
|
75
|
+
try {
|
|
76
|
+
const pkgLocal = path.join(__dirname, '..', 'bin', 'gsd-t-graph-store-resolver.cjs');
|
|
77
|
+
const projLocal = path.join(cwd, 'bin', 'gsd-t-graph-store-resolver.cjs');
|
|
78
|
+
const resolverPath = fs.existsSync(pkgLocal) ? pkgLocal
|
|
79
|
+
: fs.existsSync(projLocal) ? projLocal
|
|
80
|
+
: null;
|
|
81
|
+
if (!resolverPath) return null;
|
|
82
|
+
return require(resolverPath);
|
|
83
|
+
} catch { return null; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// M99 D2: resolve consumer label from env or payload; falls back to 'cli'.
|
|
87
|
+
// [RULE] consumer-label-from-context-not-setenv
|
|
88
|
+
function resolveConsumer(payload) {
|
|
89
|
+
if (process.env.GSDT_GRAPH_CONSUMER) return process.env.GSDT_GRAPH_CONSUMER;
|
|
90
|
+
if (payload && payload.hook_data && typeof payload.hook_data.consumer === 'string') {
|
|
91
|
+
return payload.hook_data.consumer;
|
|
92
|
+
}
|
|
93
|
+
return 'cli';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// M99 D2: emit one Layer-2a ledger line. Fail-open — never alters the decision.
|
|
97
|
+
// [RULE] fail-open [RULE] byte-identical-on-off
|
|
98
|
+
function logDecision(appendFn, cwd, consumer, classified, action, patternShape) {
|
|
99
|
+
if (!appendFn) return;
|
|
100
|
+
try {
|
|
101
|
+
appendFn({
|
|
102
|
+
kind: 'grep',
|
|
103
|
+
ts: new Date().toISOString(),
|
|
104
|
+
classified,
|
|
105
|
+
action,
|
|
106
|
+
patternShape: String(patternShape).slice(0, 200),
|
|
107
|
+
consumer,
|
|
108
|
+
}, cwd);
|
|
109
|
+
} catch { /* FAIL-OPEN: sink error never propagates */ }
|
|
110
|
+
}
|
|
111
|
+
|
|
61
112
|
function main(payload) {
|
|
62
113
|
// Only act on Grep.
|
|
63
114
|
if (!payload || payload.tool_name !== 'Grep') passThrough();
|
|
64
115
|
|
|
65
116
|
const cwd = payload.cwd || process.cwd();
|
|
66
117
|
|
|
67
|
-
// Must be a GSD-T project
|
|
118
|
+
// Must be a GSD-T project.
|
|
68
119
|
if (!fs.existsSync(path.join(cwd, '.gsd-t'))) passThrough();
|
|
69
|
-
|
|
120
|
+
|
|
121
|
+
// M99 D2: repoint presence check at D1's resolver. [RULE] presence-check-repointed
|
|
122
|
+
const resolver = loadResolver(cwd);
|
|
123
|
+
const storePath = resolver ? resolver.resolveStorePath(cwd) : null;
|
|
124
|
+
if (!storePath || !fs.existsSync(storePath)) passThrough();
|
|
70
125
|
|
|
71
126
|
const pattern = payload.tool_input && payload.tool_input.pattern;
|
|
72
127
|
if (typeof pattern !== 'string' || !pattern) passThrough();
|
|
73
128
|
|
|
129
|
+
// Resolve consumer and logging sink BEFORE the classify decision.
|
|
130
|
+
const consumer = resolveConsumer(payload);
|
|
131
|
+
const appendFn = resolver && typeof resolver.append_ledger_line === 'function'
|
|
132
|
+
? resolver.append_ledger_line : null;
|
|
133
|
+
|
|
74
134
|
// Classify (the classifier itself fails safe → text).
|
|
75
135
|
let cls;
|
|
76
136
|
try {
|
|
77
137
|
const { classifyGrep } = require(path.join(__dirname, '..', 'bin', 'gsd-t-grep-classifier.cjs'));
|
|
78
138
|
cls = classifyGrep(pattern);
|
|
79
|
-
} catch {
|
|
80
|
-
|
|
139
|
+
} catch {
|
|
140
|
+
// Classifier unavailable — passthrough without logging (no classified info to log)
|
|
141
|
+
passThrough();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!cls || !cls.structural) {
|
|
145
|
+
// Text-classified passthrough — log one line per the contract.
|
|
146
|
+
// [RULE] byte-identical-on-off: logging is a side-channel; passthrough is unchanged.
|
|
147
|
+
const classified = (cls && cls.classified) || 'text';
|
|
148
|
+
const patternShape = (cls && cls.patternShape) || pattern.slice(0, 80);
|
|
149
|
+
logDecision(appendFn, cwd, consumer, classified, 'passthrough', patternShape);
|
|
150
|
+
passThrough();
|
|
151
|
+
}
|
|
81
152
|
|
|
82
153
|
const cliPath = resolveQueryCli(cwd);
|
|
83
|
-
if (!cliPath)
|
|
154
|
+
if (!cliPath) {
|
|
155
|
+
// Structural but no CLI — log passthrough, pass through.
|
|
156
|
+
logDecision(appendFn, cwd, consumer, 'structural', 'passthrough', cls.patternShape || pattern.slice(0, 80));
|
|
157
|
+
passThrough();
|
|
158
|
+
}
|
|
84
159
|
|
|
85
160
|
// Query the graph. who-calls for symbols/calls; who-imports for imports.
|
|
86
161
|
// For a bare symbol we ALSO try who-imports so the model sees both usages.
|
|
@@ -118,8 +193,14 @@ function main(payload) {
|
|
|
118
193
|
}
|
|
119
194
|
}
|
|
120
195
|
|
|
121
|
-
// No graph answer →
|
|
122
|
-
if (!sections.length)
|
|
196
|
+
// No graph answer → log passthrough (structural query but empty result), pass through.
|
|
197
|
+
if (!sections.length) {
|
|
198
|
+
logDecision(appendFn, cwd, consumer, 'structural', 'passthrough', cls.patternShape || pattern.slice(0, 80));
|
|
199
|
+
passThrough();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Graph answered — log replaced, then emit.
|
|
203
|
+
logDecision(appendFn, cwd, consumer, 'structural', 'replaced', cls.patternShape || pattern.slice(0, 80));
|
|
123
204
|
|
|
124
205
|
// Build the replacement: graph answer first (labeled), original grep beneath.
|
|
125
206
|
const original = payload.tool_response || payload.tool_output || '';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* gsd-t-read-intercept.js — M98
|
|
3
|
+
* gsd-t-read-intercept.js — M98/M99
|
|
4
4
|
*
|
|
5
5
|
* A PostToolUse hook on the `Read` tool. When Claude reads an INDEXED code file
|
|
6
6
|
* with an offset+limit that lands inside exactly one known function's line range,
|
|
@@ -21,10 +21,22 @@
|
|
|
21
21
|
* updatedToolOutput: "<original file output> + graph note" } }
|
|
22
22
|
* ...or nothing (exit 0) to pass the Read through unchanged.
|
|
23
23
|
*
|
|
24
|
+
* M99 D2:
|
|
25
|
+
* - Presence check and Database open use D1's resolver (resolveStorePath) — never re-derive path.
|
|
26
|
+
* - Layer-2b logging: one line per augment/passthrough decision via append_ledger_line.
|
|
27
|
+
* Consumer resolved from GSDT_GRAPH_CONSUMER env or payload hook_data.consumer or 'cli'.
|
|
28
|
+
* Fail-open: a throwing sink never alters the decision or the output.
|
|
29
|
+
*
|
|
24
30
|
* INVARIANTS:
|
|
25
|
-
* [RULE] read-intercept-fail-open
|
|
26
|
-
* [RULE] read-intercept-augment-never-shrink
|
|
27
|
-
* [RULE] read-intercept-structural-only
|
|
31
|
+
* [RULE] read-intercept-fail-open — any error / missing graph / non-code → pass through
|
|
32
|
+
* [RULE] read-intercept-augment-never-shrink — only APPEND; never replace the file body
|
|
33
|
+
* [RULE] read-intercept-structural-only — augment ONLY when offset+limit ∈ one funcId range
|
|
34
|
+
* [RULE] presence-check-repointed — M99: presence + DB open via resolver, never literal
|
|
35
|
+
* [RULE] byte-identical-on-off — logging is a side-channel; decision unchanged
|
|
36
|
+
* [RULE] fail-open — sink error never propagates
|
|
37
|
+
* [RULE] import-resolver-never-hardcode — import resolver, never re-derive path
|
|
38
|
+
* [RULE] augment-never-shrink-kept — M98 augment-never-shrink rule KEPT
|
|
39
|
+
* [RULE] consumer-label-from-context-not-setenv
|
|
28
40
|
* - NEVER calls Read/Grep (loop guard). Reads the graph DB directly (read-only).
|
|
29
41
|
* - No graph in this project → pure no-op.
|
|
30
42
|
*/
|
|
@@ -63,15 +75,63 @@ function emitAugment(original, note) {
|
|
|
63
75
|
process.exit(0);
|
|
64
76
|
}
|
|
65
77
|
|
|
78
|
+
// M99 D2: load D1's resolver module.
|
|
79
|
+
// [RULE] import-resolver-never-hardcode / [RULE] presence-check-repointed
|
|
80
|
+
function loadResolver(cwd) {
|
|
81
|
+
try {
|
|
82
|
+
const pkgLocal = path.join(__dirname, '..', 'bin', 'gsd-t-graph-store-resolver.cjs');
|
|
83
|
+
const projLocal = path.join(cwd, 'bin', 'gsd-t-graph-store-resolver.cjs');
|
|
84
|
+
const resolverPath = fs.existsSync(pkgLocal) ? pkgLocal
|
|
85
|
+
: fs.existsSync(projLocal) ? projLocal
|
|
86
|
+
: null;
|
|
87
|
+
if (!resolverPath) return null;
|
|
88
|
+
return require(resolverPath);
|
|
89
|
+
} catch { return null; }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// M99 D2: resolve consumer label from env or payload.
|
|
93
|
+
// [RULE] consumer-label-from-context-not-setenv
|
|
94
|
+
function resolveConsumer(payload) {
|
|
95
|
+
if (process.env.GSDT_GRAPH_CONSUMER) return process.env.GSDT_GRAPH_CONSUMER;
|
|
96
|
+
if (payload && payload.hook_data && typeof payload.hook_data.consumer === 'string') {
|
|
97
|
+
return payload.hook_data.consumer;
|
|
98
|
+
}
|
|
99
|
+
return 'cli';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// M99 D2: emit one Layer-2b ledger line. Fail-open — never alters the decision.
|
|
103
|
+
// [RULE] fail-open [RULE] byte-identical-on-off
|
|
104
|
+
function logDecision(appendFn, cwd, consumer, action, filePath) {
|
|
105
|
+
if (!appendFn) return;
|
|
106
|
+
try {
|
|
107
|
+
appendFn({
|
|
108
|
+
kind: 'read',
|
|
109
|
+
ts: new Date().toISOString(),
|
|
110
|
+
action,
|
|
111
|
+
file: String(filePath).slice(0, 500),
|
|
112
|
+
consumer,
|
|
113
|
+
}, cwd);
|
|
114
|
+
} catch { /* FAIL-OPEN: sink error never propagates */ }
|
|
115
|
+
}
|
|
116
|
+
|
|
66
117
|
function main(payload) {
|
|
67
118
|
// Only act on Read.
|
|
68
119
|
if (!payload || payload.tool_name !== 'Read') passThrough();
|
|
69
120
|
|
|
70
121
|
const cwd = payload.cwd || process.cwd();
|
|
71
122
|
|
|
72
|
-
// Must be a GSD-T project
|
|
123
|
+
// Must be a GSD-T project.
|
|
73
124
|
if (!fs.existsSync(path.join(cwd, '.gsd-t'))) passThrough();
|
|
74
|
-
|
|
125
|
+
|
|
126
|
+
// M99 D2: resolve consumer and logging sink early (used in ALL decision branches).
|
|
127
|
+
const consumer = resolveConsumer(payload);
|
|
128
|
+
const resolver = loadResolver(cwd);
|
|
129
|
+
const appendFn = resolver && typeof resolver.append_ledger_line === 'function'
|
|
130
|
+
? resolver.append_ledger_line : null;
|
|
131
|
+
|
|
132
|
+
// M99 D2: repoint presence check at D1's resolver. [RULE] presence-check-repointed
|
|
133
|
+
const storePath = resolver ? resolver.resolveStorePath(cwd) : null;
|
|
134
|
+
if (!storePath || !fs.existsSync(storePath)) passThrough();
|
|
75
135
|
|
|
76
136
|
const input = payload.tool_input || {};
|
|
77
137
|
const filePath = input.file_path;
|
|
@@ -84,7 +144,11 @@ function main(payload) {
|
|
|
84
144
|
// full-file read has no structural target → pass through (no silent shrinking).
|
|
85
145
|
const offset = Number(input.offset);
|
|
86
146
|
const limit = Number(input.limit);
|
|
87
|
-
if (!Number.isFinite(offset) || !Number.isFinite(limit) || limit <= 0)
|
|
147
|
+
if (!Number.isFinite(offset) || !Number.isFinite(limit) || limit <= 0) {
|
|
148
|
+
// Log passthrough for reads with a code file but no structural signal.
|
|
149
|
+
logDecision(appendFn, cwd, consumer, 'passthrough', filePath);
|
|
150
|
+
passThrough();
|
|
151
|
+
}
|
|
88
152
|
const readStart = offset; // 1-based first line read (Read's offset is 1-based)
|
|
89
153
|
const readEnd = offset + limit - 1;
|
|
90
154
|
|
|
@@ -93,19 +157,17 @@ function main(payload) {
|
|
|
93
157
|
if (path.isAbsolute(filePath)) rel = path.relative(cwd, filePath);
|
|
94
158
|
rel = rel.split(path.sep).join('/');
|
|
95
159
|
|
|
96
|
-
// Find the function whose [start,end] range the read window lands inside
|
|
97
|
-
// enumerating this file's funcIds straight from the graph DB (read-only). Cheaper
|
|
98
|
-
// and more direct than spawning the query CLL — and it's the same store the CLI reads.
|
|
160
|
+
// Find the function whose [start,end] range the read window lands inside.
|
|
99
161
|
let match = null;
|
|
100
162
|
try {
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
// in which case we fail-open (pass through).
|
|
163
|
+
// M99 D2: open the DB via the resolver-provided store path.
|
|
164
|
+
// [RULE] presence-check-repointed — storePath comes from D1's resolver, not a literal.
|
|
104
165
|
let requireStore;
|
|
105
166
|
try { requireStore = require(path.join(__dirname, '..', 'bin', 'gsd-t-require-store.cjs')); }
|
|
106
167
|
catch { requireStore = require(path.join(cwd, 'bin', 'gsd-t-require-store.cjs')); }
|
|
107
168
|
const Database = requireStore.requireBetterSqlite();
|
|
108
|
-
|
|
169
|
+
// Use the resolver-provided storePath (M99 D2 repoint).
|
|
170
|
+
const db = new Database(storePath, { readonly: true });
|
|
109
171
|
try {
|
|
110
172
|
const hasEnd = db.prepare('PRAGMA table_info(nodes)').all().some((c) => c.name === 'end_line');
|
|
111
173
|
if (hasEnd) {
|
|
@@ -117,9 +179,7 @@ function main(payload) {
|
|
|
117
179
|
if (!m) continue;
|
|
118
180
|
const start = parseInt(m[1], 10);
|
|
119
181
|
const end = r.end_line;
|
|
120
|
-
// The read window starts inside this function's [start,end] body.
|
|
121
182
|
if (readStart >= start && readStart <= end) {
|
|
122
|
-
// Prefer the innermost (largest start) enclosing function.
|
|
123
183
|
if (!match || start > match.start) match = { funcId: r.func_id, name: r.name, start, end };
|
|
124
184
|
}
|
|
125
185
|
}
|
|
@@ -127,9 +187,15 @@ function main(payload) {
|
|
|
127
187
|
} finally {
|
|
128
188
|
try { db.close(); } catch { /* best-effort */ }
|
|
129
189
|
}
|
|
130
|
-
} catch (_e) {
|
|
190
|
+
} catch (_e) {
|
|
191
|
+
logDecision(appendFn, cwd, consumer, 'passthrough', rel);
|
|
192
|
+
passThrough();
|
|
193
|
+
}
|
|
131
194
|
|
|
132
|
-
if (!match)
|
|
195
|
+
if (!match) {
|
|
196
|
+
logDecision(appendFn, cwd, consumer, 'passthrough', rel);
|
|
197
|
+
passThrough();
|
|
198
|
+
}
|
|
133
199
|
|
|
134
200
|
// Build the augment note (appended beneath the original file output, kept intact).
|
|
135
201
|
const original = payload.tool_response || payload.tool_output || '';
|
|
@@ -139,6 +205,9 @@ function main(payload) {
|
|
|
139
205
|
`source + its imports, class header, and callers (≈10× fewer tokens), run:\n` +
|
|
140
206
|
` gsd-t graph body '${match.funcId}'`;
|
|
141
207
|
|
|
208
|
+
// Log augment decision BEFORE emitting (fail-open: if logging throws, decision unchanged).
|
|
209
|
+
logDecision(appendFn, cwd, consumer, 'augment', rel);
|
|
210
|
+
|
|
142
211
|
emitAugment(typeof original === 'string' ? original : JSON.stringify(original), note);
|
|
143
212
|
}
|
|
144
213
|
|
|
@@ -90,6 +90,23 @@ const projectDir = _args.projectDir || ".";
|
|
|
90
90
|
const symptom = _args.symptom || null;
|
|
91
91
|
const milestone = _args.milestone || null; // tags loop-ledger halts so they scope to this milestone
|
|
92
92
|
|
|
93
|
+
// M99 D2: persist a kind:'wiring' ledger line. M81 sandbox: all I/O through agent() Bash.
|
|
94
|
+
// Uses the `gsd-t graph wiring-log --auto` CLI shim (avoids embedding require() in strings).
|
|
95
|
+
// [RULE] wiring-mode-three-states / [RULE] consumer-label-from-context-not-setenv
|
|
96
|
+
async function persistWiringMode(phaseName) {
|
|
97
|
+
const consumer = "debug";
|
|
98
|
+
await agent(
|
|
99
|
+
[
|
|
100
|
+
`Persist one graph-wiring-mode ledger line for the debug workflow.`,
|
|
101
|
+
`Run: \`gsd-t graph wiring-log --consumer ${consumer} --auto --project '${projectDir}'\``,
|
|
102
|
+
`(--auto detects WIRED if the graph store exists, else fallback-announced)`,
|
|
103
|
+
`If the command is not found, exit 0 (ledger write is optional).`,
|
|
104
|
+
`Return ONLY: {"ok": true} or {"ok": false, "reason": "<short reason>"}.`,
|
|
105
|
+
].join("\n"),
|
|
106
|
+
{ label: "debug:wiring-ledger", phase: phaseName, model: "haiku", schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" }, reason: { type: "string" } } } }
|
|
107
|
+
).catch(() => null);
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
const DEBUG_CYCLE_SCHEMA = {
|
|
94
111
|
type: "object",
|
|
95
112
|
required: ["resolved", "rootCause", "filesEdited"],
|
|
@@ -359,6 +376,8 @@ phase("Preflight");
|
|
|
359
376
|
const pre = await runPreflight(projectDir);
|
|
360
377
|
if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
361
378
|
const brief = await generateBrief(projectDir, { kind: "execute", id: "debug-brief" });
|
|
379
|
+
// M99 D2: persist graphWiringMode for the debug consumer. [RULE] wiring-mode-three-states
|
|
380
|
+
await persistWiringMode("Preflight");
|
|
362
381
|
|
|
363
382
|
// M94-D11 §READER: query graph ONCE before cycles (the structural slice is the same
|
|
364
383
|
// for all cycles — same symptom). Injected into each cycle's prompt.
|
|
@@ -52,6 +52,23 @@ const projectDir = _args.projectDir || ".";
|
|
|
52
52
|
const milestone = _args.milestone || null;
|
|
53
53
|
const domains = _args.domains || [];
|
|
54
54
|
|
|
55
|
+
// M99 D2: persist a kind:'wiring' ledger line for this workflow.
|
|
56
|
+
// Uses the `gsd-t graph wiring-log --auto` CLI shim (avoids embedding require() in strings).
|
|
57
|
+
// [RULE] wiring-mode-three-states / [RULE] consumer-label-from-context-not-setenv
|
|
58
|
+
async function persistWiringMode(phaseName) {
|
|
59
|
+
const consumer = "integrate";
|
|
60
|
+
await agent(
|
|
61
|
+
[
|
|
62
|
+
`Persist one graph-wiring-mode ledger line for the integrate workflow.`,
|
|
63
|
+
`Run: \`gsd-t graph wiring-log --consumer ${consumer} --auto --project '${projectDir}'\``,
|
|
64
|
+
`(--auto detects WIRED if the graph store exists, else fallback-announced)`,
|
|
65
|
+
`If the command is not found, exit 0 (ledger write is optional).`,
|
|
66
|
+
`Return ONLY: {"ok": true, "mode": "<mode>"} or {"ok": false, "reason": "<short reason>"}.`,
|
|
67
|
+
].join("\n"),
|
|
68
|
+
{ label: "integrate:wiring-ledger", phase: phaseName, model: "haiku", schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" }, mode: { type: "string" }, reason: { type: "string" } } } }
|
|
69
|
+
).catch(() => null); // fail-open
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
const INTEGRATE_SCHEMA = {
|
|
56
73
|
type: "object",
|
|
57
74
|
required: ["status", "crossDomainEdits"],
|
|
@@ -71,6 +88,8 @@ phase("Preflight");
|
|
|
71
88
|
const pre = await runPreflight(projectDir);
|
|
72
89
|
if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
73
90
|
const brief = await generateBrief(projectDir, { kind: "execute", milestone, id: `integrate-${(milestone || "m").toLowerCase()}` });
|
|
91
|
+
// M99 D2: persist graphWiringMode for the integrate consumer. [RULE] wiring-mode-three-states
|
|
92
|
+
await persistWiringMode("Preflight");
|
|
74
93
|
|
|
75
94
|
// M94-D10-T6: Graph Structural Slice — who-imports + blast-radius (ADDITIVE, announced-degradation)
|
|
76
95
|
// [RULE] integrate-uses-graph-for-wiring-verification
|
|
@@ -733,6 +733,23 @@ const milestone = _args.milestone || null;
|
|
|
733
733
|
const userInput = _args.userInput || "";
|
|
734
734
|
const phaseName = _args.phase;
|
|
735
735
|
|
|
736
|
+
// M99 D2: persist a kind:'wiring' ledger line for this workflow.
|
|
737
|
+
// Uses the `gsd-t graph wiring-log --auto` CLI shim (avoids embedding require() in strings).
|
|
738
|
+
// [RULE] wiring-mode-three-states / [RULE] consumer-label-from-context-not-setenv
|
|
739
|
+
async function persistWiringMode(phaseNameArg) {
|
|
740
|
+
const consumer = "phase";
|
|
741
|
+
await agent(
|
|
742
|
+
[
|
|
743
|
+
`Persist one graph-wiring-mode ledger line for the phase workflow.`,
|
|
744
|
+
`Run: \`gsd-t graph wiring-log --consumer ${consumer} --auto --project '${projectDir}'\``,
|
|
745
|
+
`(--auto detects WIRED if the graph store exists, else fallback-announced)`,
|
|
746
|
+
`If the command is not found, exit 0 (ledger write is optional).`,
|
|
747
|
+
`Return ONLY: {"ok": true, "mode": "<mode>"} or {"ok": false, "reason": "<short reason>"}.`,
|
|
748
|
+
].join("\n"),
|
|
749
|
+
{ label: "phase:wiring-ledger", phase: phaseNameArg, model: "haiku", schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" }, mode: { type: "string" }, reason: { type: "string" } } } }
|
|
750
|
+
).catch(() => null); // fail-open
|
|
751
|
+
}
|
|
752
|
+
|
|
736
753
|
// M84: competition is AUTOMATIC. By default the workflow PROBES the solution space
|
|
737
754
|
// (after brief) and self-decides whether to run a 3-producer + judge competition —
|
|
738
755
|
// no flag needed. Optional manual OVERRIDES: `competition: N` (2-5) forces N
|
|
@@ -765,6 +782,8 @@ phase("Preflight");
|
|
|
765
782
|
const pre = await runPreflight(projectDir);
|
|
766
783
|
if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
767
784
|
const brief = await generateBrief(projectDir, { kind: phaseName, milestone, id: `${phaseName}-${(milestone || "m").toLowerCase()}` });
|
|
785
|
+
// M99 D2: persist graphWiringMode for the phase consumer. [RULE] wiring-mode-three-states
|
|
786
|
+
await persistWiringMode("Preflight");
|
|
768
787
|
|
|
769
788
|
// ── M84: resolve competition AUTOMATICALLY (after brief, before producing) ──
|
|
770
789
|
// Default: probe the solution space and self-decide. Overrides pin it.
|
|
@@ -88,6 +88,23 @@ const projectDir = _args.projectDir || ".";
|
|
|
88
88
|
const task = _args.task || null;
|
|
89
89
|
const model = _args.model || "sonnet";
|
|
90
90
|
|
|
91
|
+
// M99 D2: persist a kind:'wiring' ledger line. M81 sandbox: all I/O through agent() Bash.
|
|
92
|
+
// Uses the `gsd-t graph wiring-log --auto` CLI shim (avoids embedding require() in strings).
|
|
93
|
+
// [RULE] wiring-mode-three-states / [RULE] consumer-label-from-context-not-setenv
|
|
94
|
+
async function persistWiringMode(phaseName) {
|
|
95
|
+
const consumer = "quick";
|
|
96
|
+
await agent(
|
|
97
|
+
[
|
|
98
|
+
`Persist one graph-wiring-mode ledger line for the quick workflow.`,
|
|
99
|
+
`Run: \`gsd-t graph wiring-log --consumer ${consumer} --auto --project '${projectDir}'\``,
|
|
100
|
+
`(--auto detects WIRED if the graph store exists, else fallback-announced)`,
|
|
101
|
+
`If the command is not found, exit 0 (ledger write is optional).`,
|
|
102
|
+
`Return ONLY: {"ok": true} or {"ok": false, "reason": "<short reason>"}.`,
|
|
103
|
+
].join("\n"),
|
|
104
|
+
{ label: "quick:wiring-ledger", phase: phaseName, model: "haiku", schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" }, reason: { type: "string" } } } }
|
|
105
|
+
).catch(() => null);
|
|
106
|
+
}
|
|
107
|
+
|
|
91
108
|
const QUICK_SCHEMA = {
|
|
92
109
|
type: "object",
|
|
93
110
|
required: ["status", "filesEdited"],
|
|
@@ -228,6 +245,8 @@ phase("Preflight");
|
|
|
228
245
|
const pre = await runPreflight(projectDir);
|
|
229
246
|
if (!pre.ok) return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
230
247
|
const brief = await generateBrief(projectDir, { kind: "execute", id: "quick-brief" });
|
|
248
|
+
// M99 D2: persist graphWiringMode for the quick consumer. [RULE] wiring-mode-three-states
|
|
249
|
+
await persistWiringMode("Preflight");
|
|
231
250
|
|
|
232
251
|
// M94-D11 §READER: query graph for structural impact before the Execute agent reasons
|
|
233
252
|
// [RULE] quick-writer-pattern
|
|
@@ -238,29 +238,70 @@ const GRAPH_SLICE_SCHEMA = {
|
|
|
238
238
|
let structuralSlice = null; // { deadCode, dangling, clusters, coverage, tier } | null
|
|
239
239
|
let graphWiringMode = "pending"; // "wired" | "fallback-announced" | "disabled"
|
|
240
240
|
|
|
241
|
-
// M94-D6: runCli — inline async helper that delegates CLI
|
|
242
|
-
// M81 invariant: NO require/fs/child_process in the orchestrator body
|
|
243
|
-
//
|
|
244
|
-
//
|
|
241
|
+
// M94-D6: runCli — inline async helper that delegates the D5 graph-query CLI to an
|
|
242
|
+
// agent() Bash. M81 invariant: NO require/fs/child_process in the orchestrator body —
|
|
243
|
+
// the agent runs the command; we read back a SCHEMA-VALIDATED envelope.
|
|
244
|
+
//
|
|
245
|
+
// v4.13.12 HARDENING (root cause of the NiceNote 2026-06-29 silent grep-fallback):
|
|
246
|
+
// the old version told a HAIKU agent to "return ONLY the raw JSON line" and then
|
|
247
|
+
// JSON.parse()'d its free-text reply. Haiku wrapped the JSON in a ```json fence →
|
|
248
|
+
// JSON.parse threw → caught → graph-unavailable → grep-mode, even though the graph
|
|
249
|
+
// was live (status returned ok, 156 files, compiler-accurate). Three fixes, mirroring
|
|
250
|
+
// the proven gsd-t-verify.workflow.js runCli:
|
|
251
|
+
// 1. SCHEMA-validated agent output (StructuredOutput) — the model returns structured
|
|
252
|
+
// JSON via the tool layer, never fenced text; no brittle JSON.parse of prose.
|
|
253
|
+
// 2. project-local bin → GLOBAL `gsd-t` fallback (a project without a local bin copy
|
|
254
|
+
// no longer fails the probe — it was hardcoded to `node bin/...cjs`).
|
|
255
|
+
// 3. stderr captured (dropped `2>/dev/null`) and surfaced in `reason` so the fallback
|
|
256
|
+
// log can say WHY (parse-fail vs not-found vs CLI-error), never a bare "unavailable".
|
|
257
|
+
// [RULE] no-graph-baseline-proven-graph-free — called ONLY when graphMode==="wired".
|
|
258
|
+
// [RULE] graph-probe-schema-validated-never-fence-parsed
|
|
259
|
+
const _GRAPH_CLI_ENVELOPE_SCHEMA = {
|
|
260
|
+
type: "object",
|
|
261
|
+
required: ["ok"],
|
|
262
|
+
additionalProperties: true,
|
|
263
|
+
properties: {
|
|
264
|
+
ok: { type: "boolean", description: "true iff the CLI printed a JSON envelope with ok:true" },
|
|
265
|
+
reason: { type: "string", description: "on failure: graph-unavailable / cli-not-found / cli-error / non-json-output, plus any stderr" },
|
|
266
|
+
via: { type: "string", description: "local | global | error" },
|
|
267
|
+
results: { type: "array", items: {}, description: "the verb's results array (dead-code/dangling/cluster/etc.), [] if none" },
|
|
268
|
+
tier: { type: "string", description: "compiler-accurate | tree-sitter-floor | ... when present" },
|
|
269
|
+
coverage: { description: "coverage envelope when the verb returns one" },
|
|
270
|
+
},
|
|
271
|
+
};
|
|
245
272
|
async function runCli(verb, target, label) {
|
|
246
|
-
const targetArg = target ? ` ${
|
|
247
|
-
const
|
|
248
|
-
|
|
273
|
+
const targetArg = target ? ` '${String(target).replace(/'/g, "'\\''")}'` : "";
|
|
274
|
+
const prompt = [
|
|
275
|
+
`Run the GSD-T graph-query CLI for the project at \`${projectDir}\` and report its result. Steps:`,
|
|
276
|
+
`1. If \`${projectDir}/bin/gsd-t-graph-query-cli.cjs\` exists, run: \`node ${projectDir}/bin/gsd-t-graph-query-cli.cjs ${verb}${targetArg}\` (set via="local"). Otherwise run: \`gsd-t graph ${verb}${targetArg}\` (set via="global"). Use cwd \`${projectDir}\`. Do NOT redirect stderr — capture it.`,
|
|
277
|
+
`2. The command prints ONE JSON envelope to stdout. Parse it. Set ok = (the parsed envelope's "ok" field === true). Copy its "results", "tier", and "coverage" fields through if present.`,
|
|
278
|
+
`3. If the command exits non-zero, prints no JSON, or stdout is not valid JSON: set ok=false and put a short reason in "reason" — "cli-not-found" if the file/binary was missing, else "cli-error", else "non-json-output" — and append the first ~200 chars of stderr.`,
|
|
279
|
+
`Do NOT do any other work. ONLY run this one command and report the structured result.`,
|
|
280
|
+
].join("\n");
|
|
281
|
+
const r = await agent(prompt, {
|
|
282
|
+
label: `graph:${label || verb}`,
|
|
283
|
+
phase: "Graph-Wiring",
|
|
284
|
+
model: "haiku",
|
|
285
|
+
schema: _GRAPH_CLI_ENVELOPE_SCHEMA,
|
|
286
|
+
}).catch((e) => ({ ok: false, reason: `agent-error: ${e && e.message}`, via: "error" }));
|
|
287
|
+
return r || { ok: false, reason: "graph-unavailable", via: "error" };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// M99 D2: persist a kind:'wiring' ledger line for this workflow.
|
|
291
|
+
// M81 sandbox: all I/O through agent() Bash; no require/fs in the sandbox.
|
|
292
|
+
// Uses the `gsd-t graph wiring-log` CLI shim (avoids embedding require() in strings).
|
|
293
|
+
// [RULE] wiring-mode-three-states / [RULE] consumer-label-from-context-not-setenv
|
|
294
|
+
async function persistWiringMode(mode) {
|
|
295
|
+
const consumer = "scan";
|
|
296
|
+
await agent(
|
|
249
297
|
[
|
|
250
|
-
`
|
|
251
|
-
|
|
252
|
-
`
|
|
253
|
-
|
|
254
|
-
`If the command fails or prints no JSON, return: {"ok":false,"reason":"graph-unavailable"}`,
|
|
255
|
-
`Return ONLY the JSON, nothing else.`,
|
|
298
|
+
`Persist one graph-wiring-mode ledger line for the scan workflow.`,
|
|
299
|
+
`Run: \`gsd-t graph wiring-log --consumer ${consumer} --mode ${mode} --project '${projectDir}'\``,
|
|
300
|
+
`If the command is not found, exit 0 (ledger write is optional).`,
|
|
301
|
+
`Return ONLY: {"ok": true} or {"ok": false, "reason": "<short reason>"}.`,
|
|
256
302
|
].join("\n"),
|
|
257
|
-
{ label:
|
|
258
|
-
).catch(() => null);
|
|
259
|
-
try {
|
|
260
|
-
return (typeof result === "string") ? JSON.parse(result.trim()) : (result || { ok: false, reason: "graph-unavailable" });
|
|
261
|
-
} catch (_) {
|
|
262
|
-
return { ok: false, reason: "graph-unavailable" };
|
|
263
|
-
}
|
|
303
|
+
{ label: "scan:wiring-ledger", phase: "Graph-Wiring", model: "haiku", schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" }, reason: { type: "string" } } } }
|
|
304
|
+
).catch(() => null); // fail-open
|
|
264
305
|
}
|
|
265
306
|
|
|
266
307
|
// Preflight: an agent checks branch + whether a prior register exists, via Bash.
|
|
@@ -331,6 +372,7 @@ if (graphMode === "disabled") {
|
|
|
331
372
|
// ZERO graph calls in this path — [RULE] no-graph-baseline-proven-graph-free.
|
|
332
373
|
graphWiringMode = "disabled";
|
|
333
374
|
log("graph-wiring: DISABLED (no-graph baseline mode — graph-query call-count == 0; AC-4 baseline)");
|
|
375
|
+
await persistWiringMode("disabled"); // M99 D2: persist wiring mode [RULE] wiring-mode-three-states
|
|
334
376
|
} else {
|
|
335
377
|
// graphMode === "wired": build index if absent, then query structural slice.
|
|
336
378
|
// Step 1: check if index exists and is queryable.
|
|
@@ -339,7 +381,8 @@ if (graphMode === "disabled") {
|
|
|
339
381
|
// Graph unavailable — announce fallback, continue with intact grep-mode scan.
|
|
340
382
|
// [RULE] parser-fail-disables-loud-never-silent (from graph-query-cli-contract)
|
|
341
383
|
graphWiringMode = "fallback-announced";
|
|
342
|
-
log(`⚠ GRAPH-FALLBACK (ANNOUNCED): graph
|
|
384
|
+
log(`⚠ GRAPH-FALLBACK (ANNOUNCED): graph status probe returned not-ok [reason=${(statusResult && statusResult.reason) || "graph-unavailable"}, via=${(statusResult && statusResult.via) || "?"}] — scan continues in full grep-mode (today's architecture, intact). Structural findings from LLM reconstruction only. Build the index (gsd-t graph build) to enable graph-wired accuracy.`);
|
|
385
|
+
await persistWiringMode("fallback-announced"); // M99 D2 [RULE] wiring-mode-three-states
|
|
343
386
|
} else {
|
|
344
387
|
// Step 2: query the structural slice (dead-code + dangling + clusters).
|
|
345
388
|
// These are the findings the deep-finders currently reconstruct by reading files (error-prone);
|
|
@@ -358,6 +401,7 @@ if (graphMode === "disabled") {
|
|
|
358
401
|
// All three verbs returned graph-unavailable — announce fallback.
|
|
359
402
|
graphWiringMode = "fallback-announced";
|
|
360
403
|
log(`⚠ GRAPH-FALLBACK (ANNOUNCED): all structural-slice queries returned graph-unavailable — scan continues in full grep-mode. Dead-code / dangling / cluster findings from LLM reconstruction only.`);
|
|
404
|
+
await persistWiringMode("fallback-announced"); // M99 D2 [RULE] wiring-mode-three-states
|
|
361
405
|
} else {
|
|
362
406
|
// Structural slice assembled — will be injected into scanSlice finder agents.
|
|
363
407
|
structuralSlice = {
|
|
@@ -372,8 +416,15 @@ if (graphMode === "disabled") {
|
|
|
372
416
|
},
|
|
373
417
|
tier: (deadCodeResult && deadCodeResult.tier) || (danglingResult && danglingResult.tier) || "unknown",
|
|
374
418
|
};
|
|
375
|
-
|
|
419
|
+
// M99 Red Team fix: emit "WIRED" (uppercase) to MATCH the --auto router
|
|
420
|
+
// producers (bin/gsd-t.js:3915) AND the rollup's comparison
|
|
421
|
+
// (gsd-t-graph-metrics-rollup.cjs:333 `=== "WIRED"`). Lowercase "wired" here
|
|
422
|
+
// fell through all three rollup branches → a successfully-wired scan reported
|
|
423
|
+
// WIRED:0, defeating success criterion 13 (the metric M99 exists to surface).
|
|
424
|
+
// [RULE] wiring-mode-casing-matches-rollup
|
|
425
|
+
graphWiringMode = "WIRED";
|
|
376
426
|
log(`graph-wiring: WIRED — structural slice ready (dead-code: ${structuralSlice.deadCode.length} candidates, dangling: ${structuralSlice.dangling.length} edges, clusters: ${structuralSlice.clusters.length} groups, tier: ${structuralSlice.tier}). Slice will be INJECTED ADDITIVELY into scanSlice deep-finders. [RULE] scan-injects-structural-slice`);
|
|
427
|
+
await persistWiringMode("WIRED"); // M99 D2 [RULE] wiring-mode-three-states / wiring-mode-casing-matches-rollup
|
|
377
428
|
}
|
|
378
429
|
}
|
|
379
430
|
}
|
|
@@ -698,6 +749,11 @@ function fmtChunks(today) {
|
|
|
698
749
|
head.push(`**Date:** ${today}`);
|
|
699
750
|
head.push(`**Slices run:** ${slices.length} | **Coverage:** ${coverageComplete ? `FULL - all ${slices.length} slices succeeded` : `PARTIAL - ${succeededCount}/${slices.length} succeeded`}`);
|
|
700
751
|
head.push(`**Verified findings:** ${counts.total}`, "");
|
|
752
|
+
// M99 D2 T4: stamp graphWiringMode into the header (north-star: invisible fallback leaves a trace).
|
|
753
|
+
// A `fallback-announced` mode co-occurring with a same-window outcome:'hit' is the machine-visible
|
|
754
|
+
// NiceNote scan-#12 contradiction. [RULE] wiring-mode-three-states
|
|
755
|
+
const _wiringEmoji = { "wired": "✅", "fallback-announced": "⚠️", "disabled": "🚫", "pending": "⏳" };
|
|
756
|
+
head.push(`**Graph wiring:** ${_wiringEmoji[graphWiringMode] || "?"} \`${graphWiringMode}\` (WIRED = graph answered structural queries; fallback-announced = graph unavailable, fell back to grep; disabled = no-graph baseline)`, "");
|
|
701
757
|
head.push(`> Effort estimates use GSD-T-native units (domain / wave / spawn / token-spend). Never human-hours.`);
|
|
702
758
|
head.push(`> TD numbering continues from the prior register (if any, archived). This scan begins at **TD-${tdStart}**.`, "");
|
|
703
759
|
if (!coverageComplete) head.push(`> ⚠️ **PARTIAL COVERAGE - ${failedSlices.length} of ${slices.length} codebase areas were NOT scanned this pass** (failed to return findings): ${ascii(failedSlices.join(", "))}. Findings UNDER-COUNT the real debt. Re-run (resume) for full coverage.`, "");
|
|
@@ -230,6 +230,23 @@ const VERDICT_SCHEMA = {
|
|
|
230
230
|
},
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
+
// M99 D2: persist a kind:'wiring' ledger line. M81 sandbox: all I/O through agent() Bash.
|
|
234
|
+
// Uses the `gsd-t graph wiring-log --auto` CLI shim (avoids embedding require() in strings).
|
|
235
|
+
// [RULE] wiring-mode-three-states / [RULE] consumer-label-from-context-not-setenv
|
|
236
|
+
async function persistWiringMode(phaseName) {
|
|
237
|
+
const consumer = "verify";
|
|
238
|
+
await agent(
|
|
239
|
+
[
|
|
240
|
+
`Persist one graph-wiring-mode ledger line for the verify workflow.`,
|
|
241
|
+
`Run: \`gsd-t graph wiring-log --consumer ${consumer} --auto --project '${projectDir}'\``,
|
|
242
|
+
`(--auto detects WIRED if the graph store exists, else fallback-announced)`,
|
|
243
|
+
`If the command is not found, exit 0 (ledger write is optional).`,
|
|
244
|
+
`Return ONLY: {"ok": true} or {"ok": false, "reason": "<short reason>"}.`,
|
|
245
|
+
].join("\n"),
|
|
246
|
+
{ label: "verify:wiring-ledger", phase: phaseName, model: "haiku", schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" }, reason: { type: "string" } } } }
|
|
247
|
+
).catch(() => null);
|
|
248
|
+
}
|
|
249
|
+
|
|
233
250
|
// ───── Script body ─────
|
|
234
251
|
|
|
235
252
|
if (!milestone) {
|
|
@@ -244,6 +261,8 @@ if (!pre.ok) {
|
|
|
244
261
|
return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
|
|
245
262
|
}
|
|
246
263
|
const brief = await generateBrief(projectDir, { kind: "verify", milestone, id: `verify-${(milestone || "m").toLowerCase()}` });
|
|
264
|
+
// M99 D2: persist graphWiringMode for the verify consumer. [RULE] wiring-mode-three-states
|
|
265
|
+
await persistWiringMode("Preflight");
|
|
247
266
|
|
|
248
267
|
phase("Verify-Gate");
|
|
249
268
|
const vg = await runVerifyGate(projectDir);
|