sneakoscope 7.3.0 → 7.5.0
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/README.md +117 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/codex-app-command.js +1 -1
- package/dist/cli/command-manifest-lite.js +2 -1
- package/dist/cli/install-helpers-codex-lb-selftest.js +2 -1
- package/dist/cli/install-helpers-install-support.js +6 -16
- package/dist/cli/install-helpers.js +59 -50
- package/dist/cli/router.js +9 -1
- package/dist/config/skills-manifest.json +50 -71
- package/dist/core/codex-app.js +5 -5
- package/dist/core/codex-native/core-skill-manifest.js +9 -6
- package/dist/core/commands/image-ux-review-command.js +243 -50
- package/dist/core/commands/naruto-command.js +245 -8
- package/dist/core/commands/ppt-command.js +2 -2
- package/dist/core/commands/telegram-command.js +123 -10
- package/dist/core/commands/triwiki-command.js +11 -1
- package/dist/core/commands/triwiki-graph-command.js +160 -0
- package/dist/core/commands/wiki-command.js +100 -20
- package/dist/core/errors/message.js +3 -0
- package/dist/core/feature-registry.js +2 -2
- package/dist/core/fsx.js +7 -3
- package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
- package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +28 -86
- package/dist/core/image-ux-review/callout-extraction.js +6 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +54 -18
- package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
- package/dist/core/image-ux-review.js +182 -7
- package/dist/core/imagegen/codex-lb-imagegen-target.js +22 -7
- package/dist/core/imagegen/imagegen-capability.js +11 -4
- package/dist/core/imagegen/require-imagegen.js +29 -5
- package/dist/core/init/skills.js +22 -10
- package/dist/core/install/installed-package-smoke.js +157 -4
- package/dist/core/managed-path-safety.js +8 -10
- package/dist/core/mcp-config/backup.js +2 -4
- package/dist/core/mcp-config/config-reader.js +2 -4
- package/dist/core/mcp-config/scope.js +3 -5
- package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
- package/dist/core/naruto/context-graph-advisor.js +0 -0
- package/dist/core/pipeline-internals/runtime-core.js +44 -0
- package/dist/core/policy/latest-version-guidance.js +113 -0
- package/dist/core/ppt.js +8 -8
- package/dist/core/release/gate-manifest.js +32 -1
- package/dist/core/release/npm-stage-contract.js +21 -0
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-gate-contract.js +5 -0
- package/dist/core/release/stage-publish.js +27 -0
- package/dist/core/responses-stream.js +65 -19
- package/dist/core/routes/design-policy.js +7 -2
- package/dist/core/routes/dollar-manifest-lite.js +0 -4
- package/dist/core/routes/evidence.js +1 -1
- package/dist/core/routes.js +12 -8
- package/dist/core/safety/command-contract/contracts.js +2 -0
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/search/context-graph-seeds.js +205 -0
- package/dist/core/search/context.js +0 -0
- package/dist/core/search/provider.js +2 -2
- package/dist/core/subagents/naruto-help-contract.js +2 -1
- package/dist/core/subagents/naruto-host-credentials.js +161 -0
- package/dist/core/subagents/official-subagent-preparation.js +4 -1
- package/dist/core/subagents/official-subagent-prompt.js +44 -2
- package/dist/core/subagents/official-subagent-runner.js +26 -8
- package/dist/core/subagents/triwiki-attention.js +135 -123
- package/dist/core/telegram/bot-api.js +59 -4
- package/dist/core/telegram/config.js +3 -2
- package/dist/core/telegram/hub.js +3 -2
- package/dist/core/telegram/owner-lock.js +4 -6
- package/dist/core/telegram/setup.js +223 -73
- package/dist/core/triwiki/code-pack.js +12 -120
- package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
- package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
- package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
- package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
- package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
- package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
- package/dist/core/triwiki/context-graph/contracts.js +236 -0
- package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
- package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
- package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
- package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
- package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
- package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
- package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
- package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
- package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
- package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
- package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
- package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
- package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
- package/dist/core/triwiki/context-graph/graph-index.js +143 -0
- package/dist/core/triwiki/context-graph/ids.js +0 -0
- package/dist/core/triwiki/context-graph/lint/index.js +0 -0
- package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
- package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
- package/dist/core/triwiki/context-graph/paths.js +116 -0
- package/dist/core/triwiki/context-graph/profiles.js +78 -0
- package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
- package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
- package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
- package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
- package/dist/core/triwiki/context-graph/projections/index.js +7 -0
- package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
- package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
- package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
- package/dist/core/triwiki/context-graph/query/explain.js +0 -0
- package/dist/core/triwiki/context-graph/query/index.js +178 -0
- package/dist/core/triwiki/context-graph/query/load.js +116 -0
- package/dist/core/triwiki/context-graph/query/pack.js +117 -0
- package/dist/core/triwiki/context-graph/query/rank.js +142 -0
- package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
- package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
- package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
- package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
- package/dist/core/triwiki/context-graph/query-types.js +29 -0
- package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
- package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
- package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
- package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
- package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
- package/dist/core/verification/context-graph-affected.js +271 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/context-graph-check.js +191 -0
- package/dist/scripts/helper-dedup-check.js +0 -1
- package/dist/scripts/installed-package-smoke-check.js +4 -1
- package/dist/scripts/latest-version-guidance-check.js +74 -0
- package/dist/scripts/release-metadata-check.js +20 -3
- package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
- package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
- package/package.json +5 -3
- package/release-gates.v2.json +174 -0
- package/schemas/triwiki/context-graph.schema.json +119 -0
- package/dist/core/triwiki/code-index-scanner.js +0 -309
|
@@ -30,8 +30,31 @@ export async function runInstalledPackageSmoke(root, options = {}) {
|
|
|
30
30
|
const npmCache = path.join(tmp, 'npm-cache');
|
|
31
31
|
const installPrefix = path.join(tmp, 'prefix');
|
|
32
32
|
const packDestination = path.join(tmp, 'pack');
|
|
33
|
-
|
|
33
|
+
const globalRoot = path.join(tmp, 'global-sks');
|
|
34
|
+
const installCwd = path.join(tmp, 'install-cwd');
|
|
35
|
+
const runtimeTmp = path.join(tmp, 'runtime-tmp');
|
|
36
|
+
const stubBin = path.join(tmp, 'bin');
|
|
37
|
+
const launchctlLog = path.join(tmp, 'launchctl-calls.log');
|
|
38
|
+
const launchctlStub = path.join(stubBin, process.platform === 'win32' ? 'launchctl.cmd' : 'launchctl');
|
|
39
|
+
const npmUserConfig = path.join(tmp, 'npmrc');
|
|
40
|
+
const npmGlobalConfig = path.join(tmp, 'npm-globalrc');
|
|
41
|
+
await Promise.all([home, codexHome, npmCache, installPrefix, packDestination, globalRoot, installCwd, runtimeTmp, stubBin, path.join(home, '.tmp')]
|
|
34
42
|
.map((dir) => fs.mkdir(dir, { recursive: true })));
|
|
43
|
+
await Promise.all([
|
|
44
|
+
fs.writeFile(launchctlLog, ''),
|
|
45
|
+
fs.writeFile(launchctlStub, launchctlStubSource()),
|
|
46
|
+
fs.writeFile(npmUserConfig, ''),
|
|
47
|
+
fs.writeFile(npmGlobalConfig, '')
|
|
48
|
+
]);
|
|
49
|
+
await fs.chmod(launchctlStub, 0o700).catch(() => { });
|
|
50
|
+
const externalRoots = [
|
|
51
|
+
{ label: 'home', root: home },
|
|
52
|
+
{ label: 'codex-home', root: codexHome },
|
|
53
|
+
{ label: 'global-sks', root: globalRoot },
|
|
54
|
+
{ label: 'install-cwd', root: installCwd },
|
|
55
|
+
{ label: 'runtime-tmp', root: runtimeTmp }
|
|
56
|
+
];
|
|
57
|
+
const externalBeforeInstall = await snapshotTrees(externalRoots);
|
|
35
58
|
const blockers = [];
|
|
36
59
|
const commands = [];
|
|
37
60
|
let tarball = null;
|
|
@@ -40,6 +63,14 @@ export async function runInstalledPackageSmoke(root, options = {}) {
|
|
|
40
63
|
let packedVersion = null;
|
|
41
64
|
let expectedSha256 = normalizeSha256(options.expectedSha256);
|
|
42
65
|
let receiptPath = null;
|
|
66
|
+
let postinstallDefault = {
|
|
67
|
+
scripts_enabled: true,
|
|
68
|
+
external_snapshot_match: false,
|
|
69
|
+
external_findings: [],
|
|
70
|
+
launchctl_calls: [],
|
|
71
|
+
package_local_stamp_present: false,
|
|
72
|
+
opt_in_guidance_present: false
|
|
73
|
+
};
|
|
43
74
|
if (options.expectedSha256 && !expectedSha256)
|
|
44
75
|
blockers.push('expected_sha256_invalid');
|
|
45
76
|
if (options.receipt && !options.tarball)
|
|
@@ -85,12 +116,66 @@ export async function runInstalledPackageSmoke(root, options = {}) {
|
|
|
85
116
|
}
|
|
86
117
|
}
|
|
87
118
|
if (tarball && blockers.length === 0) {
|
|
88
|
-
const install = await runJsonCommand(
|
|
89
|
-
'npm', 'install', '--global', '--prefix', installPrefix, '--
|
|
90
|
-
], {
|
|
119
|
+
const install = await runJsonCommand(installCwd, [
|
|
120
|
+
'npm', 'install', '--global', '--prefix', installPrefix, '--foreground-scripts', '--no-audit', '--no-fund', tarball
|
|
121
|
+
], {
|
|
122
|
+
home,
|
|
123
|
+
codexHome,
|
|
124
|
+
npmCache,
|
|
125
|
+
env: {
|
|
126
|
+
HOME: home,
|
|
127
|
+
USERPROFILE: home,
|
|
128
|
+
CODEX_HOME: codexHome,
|
|
129
|
+
XDG_CONFIG_HOME: path.join(home, '.config'),
|
|
130
|
+
XDG_CACHE_HOME: path.join(home, '.cache'),
|
|
131
|
+
XDG_DATA_HOME: path.join(home, '.local', 'share'),
|
|
132
|
+
INIT_CWD: installCwd,
|
|
133
|
+
TMPDIR: runtimeTmp,
|
|
134
|
+
PATH: `${stubBin}${path.delimiter}${String(process.env.PATH || '')}`,
|
|
135
|
+
NODE_DISABLE_COMPILE_CACHE: '1',
|
|
136
|
+
NODE_COMPILE_CACHE: '',
|
|
137
|
+
SKS_GLOBAL_ROOT: globalRoot,
|
|
138
|
+
SKS_MENUBAR_LAUNCHCTL: launchctlStub,
|
|
139
|
+
SKS_INSTALLED_SMOKE_LAUNCHCTL_LOG: launchctlLog,
|
|
140
|
+
SKS_POSTINSTALL_BOOTSTRAP: '',
|
|
141
|
+
SKS_POSTINSTALL_NO_BOOTSTRAP: '',
|
|
142
|
+
SKS_POSTINSTALL_PROMPT: '',
|
|
143
|
+
npm_config_userconfig: npmUserConfig,
|
|
144
|
+
NPM_CONFIG_USERCONFIG: npmUserConfig,
|
|
145
|
+
npm_config_globalconfig: npmGlobalConfig,
|
|
146
|
+
NPM_CONFIG_GLOBALCONFIG: npmGlobalConfig,
|
|
147
|
+
npm_config_ignore_scripts: 'false',
|
|
148
|
+
NPM_CONFIG_IGNORE_SCRIPTS: 'false',
|
|
149
|
+
NO_UPDATE_NOTIFIER: '1'
|
|
150
|
+
}
|
|
151
|
+
});
|
|
91
152
|
commands.push(summarizeInstalledSmokeCommand(install.command, 'install'));
|
|
92
153
|
if (install.exit_code !== 0)
|
|
93
154
|
blockers.push('npm_install_tarball_failed');
|
|
155
|
+
const externalAfterInstall = await snapshotTrees(externalRoots);
|
|
156
|
+
const externalFindings = diffTreeSnapshots(externalBeforeInstall, externalAfterInstall);
|
|
157
|
+
const launchctlCalls = await nonEmptyLines(launchctlLog);
|
|
158
|
+
const packageLocalStamp = path.join(installedPackageRoot(installPrefix), 'dist', '.sks-build-stamp.json');
|
|
159
|
+
const packageLocalStampPresent = await regularFileExists(packageLocalStamp);
|
|
160
|
+
const installOutput = `${install.stdout}\n${install.command.stderr_tail}`;
|
|
161
|
+
const optInGuidancePresent = /Automatic bootstrap was not run/.test(installOutput)
|
|
162
|
+
&& /SKS_POSTINSTALL_BOOTSTRAP=1/.test(installOutput);
|
|
163
|
+
postinstallDefault = {
|
|
164
|
+
scripts_enabled: true,
|
|
165
|
+
external_snapshot_match: externalFindings.length === 0,
|
|
166
|
+
external_findings: externalFindings,
|
|
167
|
+
launchctl_calls: launchctlCalls,
|
|
168
|
+
package_local_stamp_present: packageLocalStampPresent,
|
|
169
|
+
opt_in_guidance_present: optInGuidancePresent
|
|
170
|
+
};
|
|
171
|
+
if (externalFindings.length)
|
|
172
|
+
blockers.push(...externalFindings.map((finding) => `postinstall_default_external_mutation:${finding}`));
|
|
173
|
+
if (launchctlCalls.length)
|
|
174
|
+
blockers.push('postinstall_default_launchctl_called');
|
|
175
|
+
if (!packageLocalStampPresent)
|
|
176
|
+
blockers.push('postinstall_package_local_stamp_missing');
|
|
177
|
+
if (!optInGuidancePresent)
|
|
178
|
+
blockers.push('postinstall_default_opt_in_guidance_missing');
|
|
94
179
|
}
|
|
95
180
|
const bin = process.platform === 'win32'
|
|
96
181
|
? path.join(installPrefix, 'sks.cmd')
|
|
@@ -187,6 +272,7 @@ export async function runInstalledPackageSmoke(root, options = {}) {
|
|
|
187
272
|
required_dollar_commands: [...INSTALLED_REQUIRED_DOLLAR_COMMANDS],
|
|
188
273
|
closure
|
|
189
274
|
},
|
|
275
|
+
postinstall_default: postinstallDefault,
|
|
190
276
|
forbidden_findings: forbiddenFindings,
|
|
191
277
|
blockers: [...new Set(blockers)]
|
|
192
278
|
};
|
|
@@ -253,6 +339,7 @@ async function runJsonCommand(cwd, argv, opts) {
|
|
|
253
339
|
env.npm_config_cache = opts.npmCache;
|
|
254
340
|
env.NPM_CONFIG_CACHE = opts.npmCache;
|
|
255
341
|
}
|
|
342
|
+
Object.assign(env, opts.env || {});
|
|
256
343
|
const res = await runProcess(command || 'node', args, {
|
|
257
344
|
cwd,
|
|
258
345
|
timeoutMs: 120_000,
|
|
@@ -275,6 +362,72 @@ async function runJsonCommand(cwd, argv, opts) {
|
|
|
275
362
|
}
|
|
276
363
|
};
|
|
277
364
|
}
|
|
365
|
+
async function snapshotTrees(roots) {
|
|
366
|
+
const snapshot = new Map();
|
|
367
|
+
for (const entry of roots)
|
|
368
|
+
await walkSnapshot(entry.root, entry.root, entry.label, snapshot);
|
|
369
|
+
return snapshot;
|
|
370
|
+
}
|
|
371
|
+
async function walkSnapshot(root, current, label, snapshot) {
|
|
372
|
+
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
373
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
374
|
+
for (const entry of entries) {
|
|
375
|
+
const absolute = path.join(current, entry.name);
|
|
376
|
+
const relative = path.relative(root, absolute) || '.';
|
|
377
|
+
const key = `${label}:${relative}`;
|
|
378
|
+
const stat = await fs.lstat(absolute);
|
|
379
|
+
const mode = (stat.mode & 0o777).toString(8).padStart(3, '0');
|
|
380
|
+
if (entry.isDirectory()) {
|
|
381
|
+
snapshot.set(key, `directory:${mode}`);
|
|
382
|
+
await walkSnapshot(root, absolute, label, snapshot);
|
|
383
|
+
}
|
|
384
|
+
else if (entry.isFile()) {
|
|
385
|
+
const bytes = await fs.readFile(absolute);
|
|
386
|
+
snapshot.set(key, `file:${mode}:${bytes.length}:${crypto.createHash('sha256').update(bytes).digest('hex')}`);
|
|
387
|
+
}
|
|
388
|
+
else if (entry.isSymbolicLink()) {
|
|
389
|
+
snapshot.set(key, `symlink:${mode}:${await fs.readlink(absolute)}`);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
snapshot.set(key, `other:${mode}:${stat.size}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function diffTreeSnapshots(before, after) {
|
|
397
|
+
const findings = [];
|
|
398
|
+
const keys = [...new Set([...before.keys(), ...after.keys()])].sort();
|
|
399
|
+
for (const key of keys) {
|
|
400
|
+
if (!before.has(key))
|
|
401
|
+
findings.push(`added:${key}`);
|
|
402
|
+
else if (!after.has(key))
|
|
403
|
+
findings.push(`removed:${key}`);
|
|
404
|
+
else if (before.get(key) !== after.get(key))
|
|
405
|
+
findings.push(`changed:${key}`);
|
|
406
|
+
}
|
|
407
|
+
return findings;
|
|
408
|
+
}
|
|
409
|
+
async function nonEmptyLines(file) {
|
|
410
|
+
const value = await fs.readFile(file, 'utf8').catch(() => '');
|
|
411
|
+
return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
412
|
+
}
|
|
413
|
+
async function regularFileExists(file) {
|
|
414
|
+
const stat = await fs.lstat(file).catch(() => null);
|
|
415
|
+
return Boolean(stat?.isFile() && !stat.isSymbolicLink());
|
|
416
|
+
}
|
|
417
|
+
function installedPackageRoot(prefix) {
|
|
418
|
+
return process.platform === 'win32'
|
|
419
|
+
? path.join(prefix, 'node_modules', 'sneakoscope')
|
|
420
|
+
: path.join(prefix, 'lib', 'node_modules', 'sneakoscope');
|
|
421
|
+
}
|
|
422
|
+
function launchctlStubSource() {
|
|
423
|
+
if (process.platform === 'win32') {
|
|
424
|
+
return '@echo %*>>\"%SKS_INSTALLED_SMOKE_LAUNCHCTL_LOG%\"\r\n@exit /b 0\r\n';
|
|
425
|
+
}
|
|
426
|
+
return `#!/bin/sh
|
|
427
|
+
printf '%s\n' "$*" >> "$SKS_INSTALLED_SMOKE_LAUNCHCTL_LOG"
|
|
428
|
+
exit 0
|
|
429
|
+
`;
|
|
430
|
+
}
|
|
278
431
|
export function validateInstalledPublicSurface(commandManifest, dollarManifest) {
|
|
279
432
|
const commands = Array.isArray(commandManifest?.commands)
|
|
280
433
|
? commandManifest.commands.map((row) => String(row?.name || '')) : [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fsp from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { errorCodeOf } from './errors/message.js';
|
|
3
4
|
export class ManagedPathSafetyError extends Error {
|
|
4
5
|
code;
|
|
5
6
|
target;
|
|
@@ -73,7 +74,7 @@ export async function ensureConfinedDirectory(boundary, directory) {
|
|
|
73
74
|
await fsp.mkdir(current);
|
|
74
75
|
}
|
|
75
76
|
catch (error) {
|
|
76
|
-
if (
|
|
77
|
+
if (errorCodeOf(error) !== 'EEXIST')
|
|
77
78
|
throw error;
|
|
78
79
|
}
|
|
79
80
|
stat = await lstatOrNull(current);
|
|
@@ -111,7 +112,7 @@ export async function walkConfinedEntries(boundary, root) {
|
|
|
111
112
|
children = await fsp.readdir(current, { withFileTypes: true, encoding: 'utf8' });
|
|
112
113
|
}
|
|
113
114
|
catch (error) {
|
|
114
|
-
errors.push(`${
|
|
115
|
+
errors.push(`${errorCodeOf(error) || 'managed_path_readdir_failed'}:${current}`);
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
118
|
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
@@ -222,7 +223,7 @@ export async function removeEmptyTreeVerified(boundary, root) {
|
|
|
222
223
|
children = await fsp.readdir(current, { withFileTypes: true, encoding: 'utf8' });
|
|
223
224
|
}
|
|
224
225
|
catch (error) {
|
|
225
|
-
errors.push(`${
|
|
226
|
+
errors.push(`${errorCodeOf(error) || 'managed_path_readdir_failed'}:${current}`);
|
|
226
227
|
remainingPaths.push(current);
|
|
227
228
|
return;
|
|
228
229
|
}
|
|
@@ -239,7 +240,7 @@ export async function removeEmptyTreeVerified(boundary, root) {
|
|
|
239
240
|
after = await fsp.readdir(current);
|
|
240
241
|
}
|
|
241
242
|
catch (error) {
|
|
242
|
-
errors.push(`${
|
|
243
|
+
errors.push(`${errorCodeOf(error) || 'managed_path_readdir_failed'}:${current}`);
|
|
243
244
|
remainingPaths.push(current);
|
|
244
245
|
return;
|
|
245
246
|
}
|
|
@@ -251,7 +252,7 @@ export async function removeEmptyTreeVerified(boundary, root) {
|
|
|
251
252
|
await fsp.rmdir(current);
|
|
252
253
|
}
|
|
253
254
|
catch (error) {
|
|
254
|
-
errors.push(`${
|
|
255
|
+
errors.push(`${errorCodeOf(error) || 'managed_path_rmdir_failed'}:${current}`);
|
|
255
256
|
remainingPaths.push(current);
|
|
256
257
|
return;
|
|
257
258
|
}
|
|
@@ -270,18 +271,15 @@ export async function lstatConfinedOrNull(boundary, target) {
|
|
|
270
271
|
export function publicPathError(error, fallback) {
|
|
271
272
|
if (error instanceof ManagedPathSafetyError)
|
|
272
273
|
return `${error.code}:${error.target}`;
|
|
273
|
-
return `${
|
|
274
|
+
return `${errorCodeOf(error) || 'managed_path_operation_failed'}:${fallback}`;
|
|
274
275
|
}
|
|
275
276
|
async function lstatOrNull(target) {
|
|
276
277
|
try {
|
|
277
278
|
return await fsp.lstat(target);
|
|
278
279
|
}
|
|
279
280
|
catch (error) {
|
|
280
|
-
if (
|
|
281
|
+
if (errorCodeOf(error) === 'ENOENT')
|
|
281
282
|
return null;
|
|
282
283
|
throw error;
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
|
-
function errorCode(error) {
|
|
286
|
-
return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
287
|
-
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { errorCodeOf } from '../errors/message.js';
|
|
4
5
|
import { nowIso, sha256, writeTextAtomic } from '../fsx.js';
|
|
5
6
|
import { MCP_BACKUP_SCHEMA } from './types.js';
|
|
6
7
|
export async function createMcpBackup(ref, before, operation, server) {
|
|
@@ -86,7 +87,7 @@ async function assertSafeBackupDirectory(directory, codexHome) {
|
|
|
86
87
|
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
87
88
|
throw new Error('mcp_backup_path_escape');
|
|
88
89
|
for (const candidate of [path.join(codexHome, 'backups'), directory]) {
|
|
89
|
-
const stat = await fsp.lstat(candidate).catch((error) =>
|
|
90
|
+
const stat = await fsp.lstat(candidate).catch((error) => errorCodeOf(error) === 'ENOENT' ? null : Promise.reject(error));
|
|
90
91
|
if (stat?.isSymbolicLink() || (stat && !stat.isDirectory()))
|
|
91
92
|
throw new Error('mcp_backup_directory_invalid');
|
|
92
93
|
if (stat) {
|
|
@@ -97,6 +98,3 @@ async function assertSafeBackupDirectory(directory, codexHome) {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
|
-
function errorCode(error) {
|
|
101
|
-
return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
102
|
-
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fsp from 'node:fs/promises';
|
|
2
2
|
import { parseCodexConfigToml } from '../codex/codex-config-toml.js';
|
|
3
|
+
import { errorCodeOf } from '../errors/message.js';
|
|
3
4
|
import { publicMcpCommand, redactMcpError, redactMcpUrl, sanitizeMcpArgs } from './redaction.js';
|
|
4
5
|
import { normalizeApprovalMode } from './secret-policy.js';
|
|
5
6
|
import { MCP_DEFAULT_STARTUP_TIMEOUT_SEC, MCP_DEFAULT_TOOL_TIMEOUT_SEC, MCP_SERVER_CONFIG_SCHEMA } from './types.js';
|
|
@@ -16,7 +17,7 @@ export class McpConfigReadError extends Error {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
export async function readMcpConfigDocument(ref) {
|
|
19
|
-
const stat = await fsp.lstat(ref.configPath).catch((error) =>
|
|
20
|
+
const stat = await fsp.lstat(ref.configPath).catch((error) => errorCodeOf(error) === 'ENOENT' ? null : Promise.reject(error));
|
|
20
21
|
if (!stat)
|
|
21
22
|
return { ref, exists: false, text: '', parsed: {}, rawServers: {} };
|
|
22
23
|
if (stat.isSymbolicLink())
|
|
@@ -142,6 +143,3 @@ function positiveNumber(value) {
|
|
|
142
143
|
const number = Number(value);
|
|
143
144
|
return Number.isFinite(number) && number > 0 ? number : null;
|
|
144
145
|
}
|
|
145
|
-
function errorCode(error) {
|
|
146
|
-
return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
147
|
-
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fsp from 'node:fs/promises';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { errorCodeOf } from '../errors/message.js';
|
|
4
5
|
export class McpScopeError extends Error {
|
|
5
6
|
code;
|
|
6
7
|
constructor(code) {
|
|
@@ -41,7 +42,7 @@ export function isPathInside(root, candidate) {
|
|
|
41
42
|
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
42
43
|
}
|
|
43
44
|
async function assertSafeDirectory(directory, boundary, strictNoSymlink) {
|
|
44
|
-
const stat = await fsp.lstat(directory).catch((error) =>
|
|
45
|
+
const stat = await fsp.lstat(directory).catch((error) => errorCodeOf(error) === 'ENOENT' ? null : Promise.reject(error));
|
|
45
46
|
if (!stat)
|
|
46
47
|
return;
|
|
47
48
|
if (strictNoSymlink && stat.isSymbolicLink())
|
|
@@ -53,7 +54,7 @@ async function assertSafeDirectory(directory, boundary, strictNoSymlink) {
|
|
|
53
54
|
throw new McpScopeError('mcp_codex_home_escape_refused');
|
|
54
55
|
}
|
|
55
56
|
async function assertSafeConfig(configPath, boundary) {
|
|
56
|
-
const stat = await fsp.lstat(configPath).catch((error) =>
|
|
57
|
+
const stat = await fsp.lstat(configPath).catch((error) => errorCodeOf(error) === 'ENOENT' ? null : Promise.reject(error));
|
|
57
58
|
if (!stat)
|
|
58
59
|
return;
|
|
59
60
|
if (stat.isSymbolicLink())
|
|
@@ -64,6 +65,3 @@ async function assertSafeConfig(configPath, boundary) {
|
|
|
64
65
|
if (!resolved || !isPathInside(boundary, resolved))
|
|
65
66
|
throw new McpScopeError('mcp_config_escape_refused');
|
|
66
67
|
}
|
|
67
|
-
function errorCode(error) {
|
|
68
|
-
return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
69
|
-
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { incomingEdges, outgoingEdges } from '../triwiki/context-graph/graph-index.js';
|
|
2
|
+
import { contextGraphPathFromId } from '../triwiki/context-graph/ids.js';
|
|
3
|
+
import { isWorkspaceRelativePosixPath } from '../triwiki/context-graph/paths.js';
|
|
4
|
+
import { normalizeNarutoPath } from './naruto-work-item.js';
|
|
5
|
+
export const NARUTO_ADVISOR_CAPS = {
|
|
6
|
+
maxDepth: 2,
|
|
7
|
+
maxNodesPerWalk: 512,
|
|
8
|
+
maxEdgesPerWalk: 4096,
|
|
9
|
+
maxSharedPathsPerPair: 12,
|
|
10
|
+
maxRecommendationsPerSlice: 24,
|
|
11
|
+
maxTaskTokens: 32,
|
|
12
|
+
maxNodesPerLabel: 8
|
|
13
|
+
};
|
|
14
|
+
const DEPENDENCY_EDGE_TYPES = new Set(['imports', 'reexports', 'depends_on', 'references', 'calls', 'routes_to']);
|
|
15
|
+
const IMPACT_EDGE_TYPES = new Set([
|
|
16
|
+
'imports', 'reexports', 'references', 'calls', 'tests', 'affected_by', 'verified_by', 'gated_by', 'depends_on', 'owns', 'routes_to'
|
|
17
|
+
]);
|
|
18
|
+
const SEED_EXPANSION_EDGE_TYPES = new Set(['defines', 'contains']);
|
|
19
|
+
function explanationStep(edge) {
|
|
20
|
+
return { edgeId: edge.id, type: edge.type, from: edge.from, to: edge.to, confidence: edge.confidence, path: edge.provenance.path };
|
|
21
|
+
}
|
|
22
|
+
function nodePathOf(node) {
|
|
23
|
+
return node.path ?? contextGraphPathFromId(node.id);
|
|
24
|
+
}
|
|
25
|
+
function isTestNode(node) {
|
|
26
|
+
return node.kind === 'test' || node.metadata.isTest === true;
|
|
27
|
+
}
|
|
28
|
+
function isProtectedGateNode(node) {
|
|
29
|
+
return node.risk === 'protected' || node.metadata.requiredForPublish === true || node.metadata.alwaysOnRelease === true;
|
|
30
|
+
}
|
|
31
|
+
function riskDomainOf(node) {
|
|
32
|
+
if (node.kind === 'risk_domain')
|
|
33
|
+
return node.label || null;
|
|
34
|
+
if (node.kind !== 'gate')
|
|
35
|
+
return null;
|
|
36
|
+
const declared = node.metadata.namespace;
|
|
37
|
+
return (typeof declared === 'string' && declared ? declared : node.label.split(':')[0]) || null;
|
|
38
|
+
}
|
|
39
|
+
export function narutoAdvisorWorkspacePath(value) {
|
|
40
|
+
const normalized = normalizeNarutoPath(value);
|
|
41
|
+
return normalized && isWorkspaceRelativePosixPath(normalized) ? normalized : null;
|
|
42
|
+
}
|
|
43
|
+
export function narutoAdvisorPathList(values) {
|
|
44
|
+
return [...new Set((values ?? []).map(narutoAdvisorWorkspacePath).filter((value) => value !== null))].sort();
|
|
45
|
+
}
|
|
46
|
+
function rootHit(nodeId) {
|
|
47
|
+
return { nodeId, depth: 0, reasonPath: [nodeId], explanation: [] };
|
|
48
|
+
}
|
|
49
|
+
function walkGraph(index, roots, direction, types, maxDepth) {
|
|
50
|
+
const hits = new Map();
|
|
51
|
+
const queue = [];
|
|
52
|
+
for (const root of roots) {
|
|
53
|
+
if (hits.has(root.nodeId) || !index.nodesById.has(root.nodeId))
|
|
54
|
+
continue;
|
|
55
|
+
hits.set(root.nodeId, root);
|
|
56
|
+
queue.push(root);
|
|
57
|
+
}
|
|
58
|
+
let edgesVisited = 0;
|
|
59
|
+
for (let head = 0; head < queue.length; head += 1) {
|
|
60
|
+
const current = queue[head];
|
|
61
|
+
if (!current || current.depth >= maxDepth)
|
|
62
|
+
continue;
|
|
63
|
+
for (const edge of direction === 'out' ? outgoingEdges(index, current.nodeId) : incomingEdges(index, current.nodeId)) {
|
|
64
|
+
edgesVisited += 1;
|
|
65
|
+
if (edgesVisited > NARUTO_ADVISOR_CAPS.maxEdgesPerWalk)
|
|
66
|
+
return { hits, truncated: true };
|
|
67
|
+
if (!types.has(edge.type))
|
|
68
|
+
continue;
|
|
69
|
+
const next = direction === 'out' ? edge.to : edge.from;
|
|
70
|
+
if (hits.has(next) || !index.nodesById.has(next))
|
|
71
|
+
continue;
|
|
72
|
+
if (hits.size >= NARUTO_ADVISOR_CAPS.maxNodesPerWalk)
|
|
73
|
+
return { hits, truncated: true };
|
|
74
|
+
const hit = {
|
|
75
|
+
nodeId: next,
|
|
76
|
+
depth: current.depth + 1,
|
|
77
|
+
reasonPath: [...current.reasonPath, direction === 'out' ? edge.type : `<-${edge.type}`, next],
|
|
78
|
+
explanation: [...current.explanation, explanationStep(edge)]
|
|
79
|
+
};
|
|
80
|
+
hits.set(next, hit);
|
|
81
|
+
queue.push(hit);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { hits, truncated: false };
|
|
85
|
+
}
|
|
86
|
+
export function narutoAdvisorProvenance(index, hit) {
|
|
87
|
+
const out = [];
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
for (const step of hit.explanation) {
|
|
90
|
+
const edge = index.edgesById.get(step.edgeId);
|
|
91
|
+
if (!edge)
|
|
92
|
+
continue;
|
|
93
|
+
const key = `${edge.provenance.path}#${edge.provenance.line ?? 0}#${edge.provenance.hash}`;
|
|
94
|
+
if (seen.has(key))
|
|
95
|
+
continue;
|
|
96
|
+
seen.add(key);
|
|
97
|
+
const line = edge.provenance.line;
|
|
98
|
+
out.push({ path: edge.provenance.path, ...(line === undefined ? {} : { line }), hash: edge.provenance.hash });
|
|
99
|
+
}
|
|
100
|
+
if (out.length)
|
|
101
|
+
return out;
|
|
102
|
+
const node = index.nodesById.get(hit.nodeId);
|
|
103
|
+
const nodePath = node ? nodePathOf(node) : null;
|
|
104
|
+
if (node?.contentHash && nodePath)
|
|
105
|
+
out.push({ path: nodePath, hash: node.contentHash });
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
function resolveSeedNodes(index, paths, labels, unresolved) {
|
|
109
|
+
const out = [];
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
const push = (id) => {
|
|
112
|
+
if (!seen.has(id) && index.nodesById.has(id)) {
|
|
113
|
+
seen.add(id);
|
|
114
|
+
out.push(id);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const miss = (value) => {
|
|
118
|
+
if (value && !unresolved.includes(value))
|
|
119
|
+
unresolved.push(value);
|
|
120
|
+
};
|
|
121
|
+
for (const raw of paths) {
|
|
122
|
+
const rel = narutoAdvisorWorkspacePath(raw);
|
|
123
|
+
const ids = rel ? index.nodesByPath.get(rel) ?? [] : [];
|
|
124
|
+
if (!ids.length)
|
|
125
|
+
miss(raw);
|
|
126
|
+
for (const id of ids)
|
|
127
|
+
push(id);
|
|
128
|
+
}
|
|
129
|
+
for (const label of labels) {
|
|
130
|
+
const ids = index.nodesByLabel.get(String(label ?? '').toLowerCase()) ?? [];
|
|
131
|
+
if (!ids.length || ids.length > NARUTO_ADVISOR_CAPS.maxNodesPerLabel)
|
|
132
|
+
miss(String(label ?? ''));
|
|
133
|
+
else
|
|
134
|
+
for (const id of ids)
|
|
135
|
+
push(id);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
export function narutoAdvisorTaskTokens(text) {
|
|
140
|
+
const tokens = [];
|
|
141
|
+
for (const raw of String(text ?? '').split(/[^\w$./-]+/)) {
|
|
142
|
+
const token = raw.replace(/^[./-]+/, '').replace(/[./-]+$/, '');
|
|
143
|
+
if (token.length < 3 || tokens.includes(token))
|
|
144
|
+
continue;
|
|
145
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(token) && !/^[\w$./-]+\.[A-Za-z]\w*$/.test(token))
|
|
146
|
+
continue;
|
|
147
|
+
tokens.push(token);
|
|
148
|
+
if (tokens.length >= NARUTO_ADVISOR_CAPS.maxTaskTokens)
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
return tokens;
|
|
152
|
+
}
|
|
153
|
+
function expandRoots(index, seeds) {
|
|
154
|
+
const roots = seeds.map(rootHit);
|
|
155
|
+
const seen = new Set(seeds);
|
|
156
|
+
for (const seed of seeds) {
|
|
157
|
+
for (const edge of outgoingEdges(index, seed)) {
|
|
158
|
+
if (!SEED_EXPANSION_EDGE_TYPES.has(edge.type) || seen.has(edge.to))
|
|
159
|
+
continue;
|
|
160
|
+
seen.add(edge.to);
|
|
161
|
+
roots.push({ nodeId: edge.to, depth: 0, reasonPath: [seed, edge.type, edge.to], explanation: [explanationStep(edge)] });
|
|
162
|
+
if (roots.length >= NARUTO_ADVISOR_CAPS.maxNodesPerWalk)
|
|
163
|
+
return roots;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return roots;
|
|
167
|
+
}
|
|
168
|
+
function closurePaths(index, walk) {
|
|
169
|
+
const out = new Map();
|
|
170
|
+
for (const hit of walk.hits.values()) {
|
|
171
|
+
const node = index.nodesById.get(hit.nodeId);
|
|
172
|
+
if (!node || isTestNode(node))
|
|
173
|
+
continue;
|
|
174
|
+
const nodePath = nodePathOf(node);
|
|
175
|
+
if (nodePath && !out.has(nodePath))
|
|
176
|
+
out.set(nodePath, hit);
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
function recommendationsFrom(index, sliceId, writeSet, impact) {
|
|
181
|
+
const out = [];
|
|
182
|
+
for (const hit of impact.hits.values()) {
|
|
183
|
+
const node = index.nodesById.get(hit.nodeId);
|
|
184
|
+
if (!node)
|
|
185
|
+
continue;
|
|
186
|
+
const isGate = node.kind === 'gate';
|
|
187
|
+
if (!isGate && !isTestNode(node))
|
|
188
|
+
continue;
|
|
189
|
+
const nodePath = nodePathOf(node);
|
|
190
|
+
if (!nodePath || (!isGate && writeSet.has(nodePath)))
|
|
191
|
+
continue;
|
|
192
|
+
out.push({
|
|
193
|
+
slice_id: sliceId,
|
|
194
|
+
kind: isGate ? 'gate' : 'test',
|
|
195
|
+
id: isGate ? node.label : nodePath,
|
|
196
|
+
path: nodePath,
|
|
197
|
+
protected: isGate ? isProtectedGateNode(node) : false,
|
|
198
|
+
risk_domain: isGate ? riskDomainOf(node) : null,
|
|
199
|
+
reason_path: [...hit.reasonPath],
|
|
200
|
+
explanation: [...hit.explanation],
|
|
201
|
+
provenance: narutoAdvisorProvenance(index, hit)
|
|
202
|
+
});
|
|
203
|
+
if (out.length >= NARUTO_ADVISOR_CAPS.maxRecommendationsPerSlice)
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
return out.sort((a, b) => (a.kind === b.kind ? (a.id < b.id ? -1 : a.id > b.id ? 1 : 0) : a.kind < b.kind ? -1 : 1));
|
|
207
|
+
}
|
|
208
|
+
export function buildNarutoSliceState(index, slice, taskTokens, maxDepth) {
|
|
209
|
+
const unresolved = [];
|
|
210
|
+
const writePaths = narutoAdvisorPathList(slice.writePaths);
|
|
211
|
+
const labels = [...(slice.symbols ?? [])];
|
|
212
|
+
if (!writePaths.length && !labels.length)
|
|
213
|
+
labels.push(...narutoAdvisorTaskTokens(slice.title ?? ''), ...taskTokens);
|
|
214
|
+
const seeds = resolveSeedNodes(index, writePaths, labels, unresolved);
|
|
215
|
+
const walk = walkGraph(index, seeds.map(rootHit), 'out', DEPENDENCY_EDGE_TYPES, maxDepth);
|
|
216
|
+
const impact = walkGraph(index, expandRoots(index, seeds), 'in', IMPACT_EDGE_TYPES, maxDepth);
|
|
217
|
+
const closure = closurePaths(index, walk);
|
|
218
|
+
const writeSet = new Set(writePaths);
|
|
219
|
+
return {
|
|
220
|
+
scope: {
|
|
221
|
+
slice_id: slice.id,
|
|
222
|
+
seed_node_ids: [...seeds].sort(),
|
|
223
|
+
write_paths: writePaths,
|
|
224
|
+
write_closure: [...closure.keys()].sort(),
|
|
225
|
+
unresolved_seeds: [...new Set(unresolved)].sort(),
|
|
226
|
+
truncated: walk.truncated || impact.truncated
|
|
227
|
+
},
|
|
228
|
+
closure,
|
|
229
|
+
writeSet,
|
|
230
|
+
recommendations: recommendationsFrom(index, slice.id, writeSet, impact)
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export function emptyNarutoScope(sliceId, slice) {
|
|
234
|
+
return {
|
|
235
|
+
slice_id: sliceId,
|
|
236
|
+
seed_node_ids: [],
|
|
237
|
+
write_paths: narutoAdvisorPathList(slice?.writePaths),
|
|
238
|
+
write_closure: [],
|
|
239
|
+
unresolved_seeds: [],
|
|
240
|
+
truncated: false
|
|
241
|
+
};
|
|
242
|
+
}
|
|
Binary file
|
|
@@ -956,6 +956,16 @@ export async function activeRouteContext(root, state) {
|
|
|
956
956
|
return `Route contract sealed for ${state.route_command || state.route || state.mode}. Use decision-contract.json and ${PIPELINE_PLAN_ARTIFACT} before executing the route. Before the next route phase, read relevant TriWiki context, hydrate low-trust claims from source, and refresh/validate TriWiki again after new findings or artifact changes. Next atomic action: continue the original route lifecycle with the inferred goal, constraints, non-goals, risk boundary, and test scope.${planNote}`;
|
|
957
957
|
}
|
|
958
958
|
if (state.mode === 'NARUTO') {
|
|
959
|
+
if (state.session_scope) {
|
|
960
|
+
const parentEvidenceState = await appNarutoParentEvidenceState(root, state);
|
|
961
|
+
if (parentEvidenceState === 'completed') {
|
|
962
|
+
return `Active Codex App Naruto mission ${state.mission_id || 'latest'} has trustworthy current-run parent evidence and a passed Naruto gate. Do not resubmit or expose the sks.subagent-parent-summary.v1 JSON. Return concise Markdown in the user's language with an explicit completion summary, verification, remaining gaps, and Honest Mode.${reasoningNote}${planNote}`;
|
|
963
|
+
}
|
|
964
|
+
if (parentEvidenceState === 'blocked') {
|
|
965
|
+
return `Active Codex App Naruto mission ${state.mission_id || 'latest'} has trustworthy current-run blocked or failed parent evidence. Do not resubmit or expose the sks.subagent-parent-summary.v1 JSON. Return concise Markdown in the user's language that states the blocker or failure first, avoids completion or success wording, and includes verification, remaining gaps, and Honest Mode.${reasoningNote}${planNote}`;
|
|
966
|
+
}
|
|
967
|
+
return `Active Codex App Naruto mission ${state.mission_id || 'latest'} uses the official Codex subagent workflow. Read subagent-plan.json, keep ${SUBAGENT_EVENT_LOG_FILENAME} and ${SUBAGENT_EVIDENCE_FILENAME} current from SubagentStart/SubagentStop, and wait for all requested agent threads. Build the exact sks.subagent-parent-summary.v1 object with run_id matching subagent-plan.json.workflow_run_id, then send it only through stdin to "sks naruto parent-summary --mission ${state.mission_id} --stdin --json". Do not expose, paste, embed, quote, or fence that JSON in the user-visible response. Only after the command accepts it, return concise Markdown in the user's language with completion summary, verification, remaining gaps/blockers, and Honest Mode. A successful response must visibly include the completion summary and Honest Mode assessment. If any parent/thread status is blocked or failed, or blockers remain, state that first and do not use completion or success wording. Do not substitute process counts, PID evidence, retired process-pool artifacts, or a custom active pool.${reasoningNote}${planNote}`;
|
|
968
|
+
}
|
|
959
969
|
return `Active Naruto mission ${state.mission_id || 'latest'} uses the official Codex subagent workflow. Read subagent-plan.json, keep ${SUBAGENT_EVENT_LOG_FILENAME} and ${SUBAGENT_EVIDENCE_FILENAME} current from SubagentStart/SubagentStop, wait for all requested agent threads, then return the exact sks.subagent-parent-summary.v1 JSON result so SKS can persist ${SUBAGENT_PARENT_SUMMARY_FILENAME} and derive naruto-summary.json/naruto-gate.json. Do not substitute process counts, PID evidence, retired process-pool artifacts, or a custom active pool.${reasoningNote}${planNote}`;
|
|
960
970
|
}
|
|
961
971
|
if (state.subagents_required && !(await hasSubagentEvidence(root, state))) {
|
|
@@ -968,6 +978,40 @@ export async function activeRouteContext(root, state) {
|
|
|
968
978
|
}
|
|
969
979
|
return planNote.trim();
|
|
970
980
|
}
|
|
981
|
+
async function appNarutoParentEvidenceState(root, state) {
|
|
982
|
+
const missionId = String(state?.mission_id || '').trim();
|
|
983
|
+
const workflowRunId = String(state?.official_subagent_run_id || '').trim();
|
|
984
|
+
if (!missionId || !workflowRunId)
|
|
985
|
+
return null;
|
|
986
|
+
const dir = missionDir(root, missionId);
|
|
987
|
+
const [evidence, summary, gate] = await Promise.all([
|
|
988
|
+
subagentEvidence(root, state).catch(() => null),
|
|
989
|
+
readJson(path.join(dir, NARUTO_SUMMARY_FILENAME), null).catch(() => null),
|
|
990
|
+
readJson(path.join(dir, NARUTO_GATE_FILENAME), null).catch(() => null)
|
|
991
|
+
]);
|
|
992
|
+
const currentRun = evidence?.run_id === workflowRunId
|
|
993
|
+
&& summary?.workflow_run_id === workflowRunId
|
|
994
|
+
&& gate?.workflow_run_id === workflowRunId;
|
|
995
|
+
if (!currentRun || evidence?.parent_summary_trustworthy !== true)
|
|
996
|
+
return null;
|
|
997
|
+
if (evidence.ok === true
|
|
998
|
+
&& evidence.status === 'completed'
|
|
999
|
+
&& summary?.status === 'completed'
|
|
1000
|
+
&& summary?.completion_evidence === true
|
|
1001
|
+
&& gate?.passed === true
|
|
1002
|
+
&& gate?.terminal === true
|
|
1003
|
+
&& gate?.terminal_state === 'completed') {
|
|
1004
|
+
return 'completed';
|
|
1005
|
+
}
|
|
1006
|
+
if (evidence.parent_summary_status === 'failed'
|
|
1007
|
+
&& ['blocked', 'incomplete'].includes(String(summary?.status || ''))
|
|
1008
|
+
&& summary?.completion_evidence !== true
|
|
1009
|
+
&& gate?.passed !== true
|
|
1010
|
+
&& gate?.terminal_state === 'blocked') {
|
|
1011
|
+
return 'blocked';
|
|
1012
|
+
}
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
971
1015
|
async function activePipelinePlanNote(root, state = {}) {
|
|
972
1016
|
if (!state?.mission_id)
|
|
973
1017
|
return '';
|