@tekyzinc/gsd-t 4.13.12 → 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 +12 -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 +33 -1
- 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
|
|
@@ -287,6 +287,23 @@ async function runCli(verb, target, label) {
|
|
|
287
287
|
return r || { ok: false, reason: "graph-unavailable", via: "error" };
|
|
288
288
|
}
|
|
289
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(
|
|
297
|
+
[
|
|
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>"}.`,
|
|
302
|
+
].join("\n"),
|
|
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
|
|
305
|
+
}
|
|
306
|
+
|
|
290
307
|
// Preflight: an agent checks branch + whether a prior register exists, via Bash.
|
|
291
308
|
// (No fs in the body — that was the bug.)
|
|
292
309
|
phase("Preflight");
|
|
@@ -355,6 +372,7 @@ if (graphMode === "disabled") {
|
|
|
355
372
|
// ZERO graph calls in this path — [RULE] no-graph-baseline-proven-graph-free.
|
|
356
373
|
graphWiringMode = "disabled";
|
|
357
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
|
|
358
376
|
} else {
|
|
359
377
|
// graphMode === "wired": build index if absent, then query structural slice.
|
|
360
378
|
// Step 1: check if index exists and is queryable.
|
|
@@ -364,6 +382,7 @@ if (graphMode === "disabled") {
|
|
|
364
382
|
// [RULE] parser-fail-disables-loud-never-silent (from graph-query-cli-contract)
|
|
365
383
|
graphWiringMode = "fallback-announced";
|
|
366
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
|
|
367
386
|
} else {
|
|
368
387
|
// Step 2: query the structural slice (dead-code + dangling + clusters).
|
|
369
388
|
// These are the findings the deep-finders currently reconstruct by reading files (error-prone);
|
|
@@ -382,6 +401,7 @@ if (graphMode === "disabled") {
|
|
|
382
401
|
// All three verbs returned graph-unavailable — announce fallback.
|
|
383
402
|
graphWiringMode = "fallback-announced";
|
|
384
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
|
|
385
405
|
} else {
|
|
386
406
|
// Structural slice assembled — will be injected into scanSlice finder agents.
|
|
387
407
|
structuralSlice = {
|
|
@@ -396,8 +416,15 @@ if (graphMode === "disabled") {
|
|
|
396
416
|
},
|
|
397
417
|
tier: (deadCodeResult && deadCodeResult.tier) || (danglingResult && danglingResult.tier) || "unknown",
|
|
398
418
|
};
|
|
399
|
-
|
|
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";
|
|
400
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
|
|
401
428
|
}
|
|
402
429
|
}
|
|
403
430
|
}
|
|
@@ -722,6 +749,11 @@ function fmtChunks(today) {
|
|
|
722
749
|
head.push(`**Date:** ${today}`);
|
|
723
750
|
head.push(`**Slices run:** ${slices.length} | **Coverage:** ${coverageComplete ? `FULL - all ${slices.length} slices succeeded` : `PARTIAL - ${succeededCount}/${slices.length} succeeded`}`);
|
|
724
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)`, "");
|
|
725
757
|
head.push(`> Effort estimates use GSD-T-native units (domain / wave / spawn / token-spend). Never human-hours.`);
|
|
726
758
|
head.push(`> TD numbering continues from the prior register (if any, archived). This scan begins at **TD-${tdStart}**.`, "");
|
|
727
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);
|