sneakoscope 5.5.2 → 5.6.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.
Files changed (46) hide show
  1. package/README.md +4 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +7 -1
  7. package/dist/cli/router.js +28 -0
  8. package/dist/commands/doctor.js +65 -3
  9. package/dist/config/skills-manifest.json +58 -58
  10. package/dist/core/agent-bridge/agent-manifest.js +64 -0
  11. package/dist/core/agent-bridge/agent-mode.js +29 -0
  12. package/dist/core/agent-bridge/mcp-server.js +156 -0
  13. package/dist/core/agents/agent-orchestrator.js +1 -11
  14. package/dist/core/commands/agent-bridge-command.js +86 -0
  15. package/dist/core/commands/image-ux-review-command.js +6 -6
  16. package/dist/core/commands/loop-command.js +2 -6
  17. package/dist/core/commands/mad-sks-command.js +3 -3
  18. package/dist/core/commands/mcp-server-command.js +13 -0
  19. package/dist/core/commands/naruto-command.js +4 -4
  20. package/dist/core/commands/ppt-command.js +6 -3
  21. package/dist/core/commands/qa-loop-command.js +64 -26
  22. package/dist/core/commands/team-command.js +1 -1
  23. package/dist/core/commands/wiki-command.js +71 -3
  24. package/dist/core/config/secret-preservation.js +43 -1
  25. package/dist/core/doctor/browser-use-repair.js +149 -0
  26. package/dist/core/doctor/computer-use-repair.js +166 -0
  27. package/dist/core/doctor/imagegen-repair.js +9 -0
  28. package/dist/core/doctor/mcp-transport-collision-repair.js +129 -0
  29. package/dist/core/doctor/supabase-mcp-repair.js +85 -16
  30. package/dist/core/feature-fixtures.js +8 -0
  31. package/dist/core/feature-registry.js +21 -0
  32. package/dist/core/fsx.js +1 -1
  33. package/dist/core/mad-db/mad-db-capability.js +1 -1
  34. package/dist/core/mission.js +19 -3
  35. package/dist/core/naruto/naruto-real-worker-child.js +5 -4
  36. package/dist/core/naruto/naruto-real-worker-runtime.js +5 -4
  37. package/dist/core/naruto/normalize-worker-prompt-text.js +12 -0
  38. package/dist/core/routes.js +3 -1
  39. package/dist/core/triwiki/code-index-scanner.js +313 -0
  40. package/dist/core/triwiki/code-pack.js +169 -0
  41. package/dist/core/triwiki/triwiki-cache-key.js +24 -2
  42. package/dist/core/triwiki-attention.js +34 -5
  43. package/dist/core/update-check.js +50 -0
  44. package/dist/core/version.js +1 -1
  45. package/dist/scripts/doctor-imagegen-repair-check.js +6 -1
  46. package/package.json +2 -2
@@ -0,0 +1,156 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { buildAgentManifest } from './agent-manifest.js';
3
+ import { AGENT_MODE_ENV_PASSTHROUGH } from './agent-mode.js';
4
+ import { exists, runProcess } from '../fsx.js';
5
+ const MCP_SERVER_NAME = 'sks-mcp-server';
6
+ const MCP_SERVER_VERSION = '1.0.0';
7
+ // Same dynamic-import trick as src/core/mad-db/mad-db-executor.ts: avoids TS
8
+ // bundling the SDK's ESM entrypoints as a static import target this file must
9
+ // resolve at compile time, while still using the real installed SDK.
10
+ async function loadMcpSdk() {
11
+ const dynamicImport = new Function('specifier', 'return import(specifier)');
12
+ const [{ Server }, { StdioServerTransport }, types] = await Promise.all([
13
+ dynamicImport('@modelcontextprotocol/sdk/server/index.js'),
14
+ dynamicImport('@modelcontextprotocol/sdk/server/stdio.js'),
15
+ dynamicImport('@modelcontextprotocol/sdk/types.js')
16
+ ]);
17
+ return {
18
+ Server,
19
+ StdioServerTransport,
20
+ ListToolsRequestSchema: types.ListToolsRequestSchema,
21
+ CallToolRequestSchema: types.CallToolRequestSchema
22
+ };
23
+ }
24
+ function exposedTools(manifest, exposeExec) {
25
+ return exposeExec ? manifest : manifest.filter((tool) => tool.read_only === true);
26
+ }
27
+ function toMcpToolDescriptor(entry) {
28
+ return {
29
+ name: entry.name,
30
+ description: entry.description,
31
+ inputSchema: {
32
+ type: 'object',
33
+ properties: {},
34
+ additionalProperties: true
35
+ },
36
+ annotations: {
37
+ readOnlyHint: entry.read_only,
38
+ destructiveHint: entry.requires_explicit_opt_in,
39
+ title: entry.name
40
+ }
41
+ };
42
+ }
43
+ async function resolveSksEntrypoint() {
44
+ // Mirrors src/core/commands/run-command.ts's runSks() bin resolution: prefer the
45
+ // packaged dist entrypoint, fall back to the source-tree relative path in dev.
46
+ const packedBin = fileURLToPath(new URL('../../bin/sks.js', import.meta.url));
47
+ const sourceBin = fileURLToPath(new URL('../../../bin/sks.js', import.meta.url));
48
+ return (await exists(packedBin)) ? packedBin : sourceBin;
49
+ }
50
+ async function invokeSksTool(entry) {
51
+ const entrypoint = await resolveSksEntrypoint();
52
+ const commandArgs = [entry.name, ...(entry.json_output_supported ? ['--json'] : [])];
53
+ const passthroughEnv = {};
54
+ for (const name of AGENT_MODE_ENV_PASSTHROUGH)
55
+ passthroughEnv[name] = '1';
56
+ const result = await runProcess(process.execPath, [entrypoint, ...commandArgs], {
57
+ env: { ...passthroughEnv, SKS_AGENT_MODE: '1' },
58
+ timeoutMs: 180_000,
59
+ maxOutputBytes: 512 * 1024
60
+ });
61
+ return { ok: result.code === 0, stdout: result.stdout, stderr: result.stderr, code: result.code };
62
+ }
63
+ function mcpToolErrorResult(message) {
64
+ return {
65
+ content: [{ type: 'text', text: message }],
66
+ isError: true
67
+ };
68
+ }
69
+ export async function runMcpServer(opts = {}) {
70
+ const exposeExec = opts.exposeExec === true;
71
+ const input = opts.input ?? process.stdin;
72
+ const output = opts.output ?? process.stdout;
73
+ const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } = await loadMcpSdk();
74
+ const server = new Server({ name: MCP_SERVER_NAME, version: MCP_SERVER_VERSION }, { capabilities: { tools: {} } });
75
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
76
+ const manifest = buildAgentManifest();
77
+ const tools = exposedTools(manifest.tools, exposeExec).map(toMcpToolDescriptor);
78
+ return { tools };
79
+ });
80
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
81
+ const toolName = String(request?.params?.name || '');
82
+ const manifest = buildAgentManifest();
83
+ const allowed = exposedTools(manifest.tools, exposeExec);
84
+ // Never spawn a child process for a name the caller invented; only names present
85
+ // in the (possibly exec-filtered) manifest are legitimate `sks <name>` invocations.
86
+ const entry = allowed.find((candidate) => candidate.name === toolName);
87
+ if (!entry) {
88
+ return mcpToolErrorResult(`Unknown or unexposed tool: ${toolName}`);
89
+ }
90
+ try {
91
+ const result = await invokeSksTool(entry);
92
+ if (!result.ok) {
93
+ return mcpToolErrorResult(result.stderr || result.stdout || `sks ${toolName} exited with code ${result.code}`);
94
+ }
95
+ return {
96
+ content: [{ type: 'text', text: result.stdout }],
97
+ isError: false
98
+ };
99
+ }
100
+ catch (err) {
101
+ const message = err instanceof Error ? err.message : String(err);
102
+ return mcpToolErrorResult(`Failed to run sks ${toolName}: ${message}`);
103
+ }
104
+ });
105
+ const transport = new StdioServerTransport(input, output);
106
+ await server.connect(transport);
107
+ }
108
+ /** Round-trips initialize -> tools/list over in-memory streams and returns, instead of
109
+ * staying resident on real stdio — this is what `sks mcp-server --probe` runs, so a
110
+ * fixture/CI check can prove the server actually works without hanging on a real client. */
111
+ export async function probeMcpServer(opts = {}) {
112
+ const { PassThrough } = await import('node:stream');
113
+ const clientToServer = new PassThrough();
114
+ const serverToClient = new PassThrough();
115
+ const responses = [];
116
+ let buffer = '';
117
+ serverToClient.on('data', (chunk) => {
118
+ buffer += chunk.toString('utf8');
119
+ let idx;
120
+ while ((idx = buffer.indexOf('\n')) !== -1) {
121
+ const line = buffer.slice(0, idx);
122
+ buffer = buffer.slice(idx + 1);
123
+ if (line.trim()) {
124
+ try {
125
+ responses.push(JSON.parse(line));
126
+ }
127
+ catch { /* not a complete JSON line yet */ }
128
+ }
129
+ }
130
+ });
131
+ const send = (message) => clientToServer.write(`${JSON.stringify(message)}\n`);
132
+ const waitForId = async (id, timeoutMs) => {
133
+ const started = Date.now();
134
+ while (Date.now() - started < timeoutMs) {
135
+ const found = responses.find((r) => r.id === id);
136
+ if (found)
137
+ return found;
138
+ await new Promise((resolve) => setTimeout(resolve, 10));
139
+ }
140
+ throw new Error(`mcp_probe_timed_out_waiting_for_response_id_${id}`);
141
+ };
142
+ runMcpServer({ exposeExec: opts.exposeExec === true, input: clientToServer, output: serverToClient }).catch(() => undefined);
143
+ const timeoutMs = opts.timeoutMs ?? 10_000;
144
+ send({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'sks-mcp-probe', version: '1.0.0' } } });
145
+ const initResponse = await waitForId(1, timeoutMs);
146
+ send({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
147
+ const listResponse = await waitForId(2, timeoutMs);
148
+ clientToServer.end();
149
+ return {
150
+ ok: Boolean(initResponse?.result) && Array.isArray(listResponse?.result?.tools),
151
+ server_name: initResponse?.result?.serverInfo?.name || null,
152
+ protocol_version: initResponse?.result?.protocolVersion || null,
153
+ tool_count: Array.isArray(listResponse?.result?.tools) ? listResponse.result.tools.length : 0
154
+ };
155
+ }
156
+ //# sourceMappingURL=mcp-server.js.map
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { createMission, missionDir, setCurrent } from '../mission.js';
3
+ import { normalizeWorkerPromptText } from '../naruto/normalize-worker-prompt-text.js';
3
4
  import { exists, nowIso, readJson, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
5
  import { buildAgentRoster, normalizeAgentConcurrency } from './agent-roster.js';
5
6
  import { buildAgentWorkPartition } from './agent-work-partition.js';
@@ -2049,17 +2050,6 @@ function buildDirectSdkWorkerPrompt(slice) {
2049
2050
  'Required JSON fields: status, summary, findings, changed_files, patch_envelopes, verification, rollback_notes, blockers.'
2050
2051
  ].join('\n');
2051
2052
  }
2052
- const WORKER_PROMPT_TEXT_MAX_CHARS = 32000;
2053
- function normalizeWorkerPromptText(value) {
2054
- const normalized = String(value || '').replace(/[^\S\n]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
2055
- const truncated = normalized.length > WORKER_PROMPT_TEXT_MAX_CHARS;
2056
- const text = truncated ? normalized.slice(0, WORKER_PROMPT_TEXT_MAX_CHARS) : normalized;
2057
- return {
2058
- text,
2059
- truncated,
2060
- dropped_chars: truncated ? normalized.length - WORKER_PROMPT_TEXT_MAX_CHARS : 0
2061
- };
2062
- }
2063
2053
  function buildDirectNoPatchReason(slice, opts) {
2064
2054
  const writePathCount = sdkWritePaths(slice, opts).length;
2065
2055
  return {
@@ -0,0 +1,86 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { ensureDir, exists, nowIso, projectRoot, runProcess, writeJsonAtomic } from '../fsx.js';
4
+ import { buildAgentManifest } from '../agent-bridge/agent-manifest.js';
5
+ import { flag } from './command-utils.js';
6
+ async function resolveSksEntrypoint() {
7
+ // Mirrors src/core/agent-bridge/mcp-server.ts's own bin resolution: prefer the
8
+ // packaged dist entrypoint, fall back to the source-tree relative path in dev.
9
+ const packedBin = fileURLToPath(new URL('../../bin/sks.js', import.meta.url));
10
+ const sourceBin = fileURLToPath(new URL('../../../bin/sks.js', import.meta.url));
11
+ return (await exists(packedBin)) ? packedBin : sourceBin;
12
+ }
13
+ async function runNonInteractiveSmoke(entrypoint) {
14
+ const result = await runProcess(process.execPath, [entrypoint, 'status', '--json'], {
15
+ env: { SKS_AGENT_MODE: '1' },
16
+ timeoutMs: 15_000,
17
+ maxOutputBytes: 32 * 1024
18
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err instanceof Error ? err.message : String(err) }));
19
+ let stdoutIsCleanJson = false;
20
+ try {
21
+ JSON.parse(result.stdout);
22
+ stdoutIsCleanJson = true;
23
+ }
24
+ catch {
25
+ stdoutIsCleanJson = false;
26
+ }
27
+ return {
28
+ ok: result.code === 0 && stdoutIsCleanJson,
29
+ command: `SKS_AGENT_MODE=1 ${entrypoint} status --json`,
30
+ exit_code: result.code,
31
+ note: stdoutIsCleanJson
32
+ ? 'stdout parsed as clean JSON with SKS_AGENT_MODE=1 set — non-interactive contract verified end-to-end for this one command.'
33
+ : 'stdout did not parse as clean JSON; a real agent host would fail to consume this — see exit_code/stderr in the report.'
34
+ };
35
+ }
36
+ function registrationSnippets() {
37
+ return {
38
+ generic_mcp_host: { command: 'sks', args: ['mcp-server'] },
39
+ codex_cli: 'codex mcp add sks -- sks mcp-server',
40
+ non_interactive_cli: {
41
+ env: { SKS_AGENT_MODE: '1' },
42
+ example: 'SKS_AGENT_MODE=1 sks status --json',
43
+ streaming_example: 'SKS_AGENT_MODE=1 sks qa-loop run <mission> --mock --stream --json'
44
+ }
45
+ };
46
+ }
47
+ export async function agentBridgeCommand(subcommand, args = []) {
48
+ const sub = subcommand || 'setup';
49
+ if (sub !== 'setup') {
50
+ const result = { ok: false, error: `unknown_subcommand:${sub}`, supported: ['setup'] };
51
+ if (flag(args, '--json'))
52
+ console.log(JSON.stringify(result, null, 2));
53
+ else
54
+ console.log(`Unknown agent-bridge subcommand "${sub}". Supported: setup`);
55
+ return result;
56
+ }
57
+ const root = await projectRoot();
58
+ const manifest = buildAgentManifest();
59
+ const manifestPath = path.join(root, '.sneakoscope', 'agent-bridge', 'manifest.json');
60
+ await ensureDir(path.dirname(manifestPath));
61
+ await writeJsonAtomic(manifestPath, manifest);
62
+ const entrypoint = await resolveSksEntrypoint();
63
+ const smoke = await runNonInteractiveSmoke(entrypoint);
64
+ const result = {
65
+ schema: 'sks.agent-bridge-setup.v1',
66
+ generated_at: nowIso(),
67
+ ok: smoke.ok,
68
+ manifest_path: manifestPath,
69
+ tool_count: manifest.tools.length,
70
+ registration_snippets: registrationSnippets(),
71
+ non_interactive_smoke: smoke
72
+ };
73
+ if (flag(args, '--json')) {
74
+ console.log(JSON.stringify(result, null, 2));
75
+ }
76
+ else {
77
+ console.log(`Agent bridge manifest written: ${manifestPath} (${manifest.tools.length} tools)`);
78
+ console.log('Register with a generic MCP host:');
79
+ console.log(` ${JSON.stringify(registrationSnippets().generic_mcp_host)}`);
80
+ console.log('Register with Codex CLI:');
81
+ console.log(` ${registrationSnippets().codex_cli}`);
82
+ console.log(`Non-interactive smoke test: ${smoke.ok ? 'ok' : 'FAILED'} (${smoke.note})`);
83
+ }
84
+ return result;
85
+ }
86
+ //# sourceMappingURL=agent-bridge-command.js.map
@@ -65,7 +65,7 @@ export async function imageUxReviewCommand(command, args = []) {
65
65
  async function runImageUxReview(root, command, args = []) {
66
66
  const missionRequested = readOption(args, '--mission', null);
67
67
  const missionId = missionRequested
68
- ? missionRequested === 'latest' ? await findLatestMission(root) : missionRequested
68
+ ? missionRequested === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review', route: '$Image-UX-Review', gateFile: IMAGE_UX_REVIEW_GATE_ARTIFACT }) : missionRequested
69
69
  : null;
70
70
  const imagePath = readOption(args, '--image', null) || readOption(args, '--screenshot', null);
71
71
  const generatedImage = readOption(args, '--generated-image', null);
@@ -277,7 +277,7 @@ async function extractIssuesImageUxReview(root, command, args = []) {
277
277
  }
278
278
  async function attachGeneratedImageCommand(root, command, args = []) {
279
279
  const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
280
- const missionId = missionArg === 'latest' ? await findLatestMission(root) : missionArg;
280
+ const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review', route: '$Image-UX-Review', gateFile: IMAGE_UX_REVIEW_GATE_ARTIFACT }) : missionArg;
281
281
  const imagePath = readOption(args, '--image', null) || readOption(args, '--generated-image', null);
282
282
  if (!missionId || !imagePath) {
283
283
  const result = { schema: 'sks.image-ux-review-attach-generated.v1', ok: false, status: 'blocked', blocker: !missionId ? 'mission_required' : 'generated_image_required' };
@@ -298,7 +298,7 @@ async function attachGeneratedImageCommand(root, command, args = []) {
298
298
  }
299
299
  async function attachAfterImageCommand(root, command, args = []) {
300
300
  const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
301
- const missionId = missionArg === 'latest' ? await findLatestMission(root) : missionArg;
301
+ const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review', route: '$Image-UX-Review', gateFile: IMAGE_UX_REVIEW_GATE_ARTIFACT }) : missionArg;
302
302
  const imagePath = readOption(args, '--image', null) || readOption(args, '--screenshot', null);
303
303
  if (!missionId || !imagePath) {
304
304
  const result = { schema: 'sks.image-ux-review-attach-after.v1', ok: false, status: 'blocked', blocker: !missionId ? 'mission_required' : 'after_image_required' };
@@ -327,7 +327,7 @@ async function attachAfterImageCommand(root, command, args = []) {
327
327
  }
328
328
  async function rebuildExistingMission(root, command, args = [], opts = {}) {
329
329
  const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
330
- const missionId = missionArg === 'latest' ? await findLatestMission(root) : missionArg;
330
+ const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review', route: '$Image-UX-Review', gateFile: IMAGE_UX_REVIEW_GATE_ARTIFACT }) : missionArg;
331
331
  if (!missionId)
332
332
  return missingMission(args);
333
333
  const { dir, mission } = await loadMission(root, missionId);
@@ -352,7 +352,7 @@ async function rebuildExistingMission(root, command, args = [], opts = {}) {
352
352
  }
353
353
  async function statusImageUxReview(root, args = []) {
354
354
  const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
355
- const missionId = missionArg === 'latest' ? await findLatestMission(root) : missionArg;
355
+ const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review', route: '$Image-UX-Review', gateFile: IMAGE_UX_REVIEW_GATE_ARTIFACT }) : missionArg;
356
356
  if (!missionId)
357
357
  return missingMission(args);
358
358
  const { dir } = await loadMission(root, missionId);
@@ -372,7 +372,7 @@ async function statusImageUxReview(root, args = []) {
372
372
  }
373
373
  async function explainImageUxReview(root, args = []) {
374
374
  const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
375
- const missionId = missionArg === 'latest' ? await findLatestMission(root) : missionArg;
375
+ const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'image-ux-review', route: '$Image-UX-Review', gateFile: IMAGE_UX_REVIEW_GATE_ARTIFACT }) : missionArg;
376
376
  if (!missionId)
377
377
  return missingMission(args);
378
378
  const { dir } = await loadMission(root, missionId);
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { printJson } from '../../cli/output.js';
3
- import { createMission, findLatestMission, loadMission, setCurrent } from '../mission.js';
3
+ import { createMission, findLatestMission, setCurrent } from '../mission.js';
4
4
  import { readJson, sksRoot } from '../fsx.js';
5
5
  import { loopActiveWorkerHandlesPath, loopIntegrationMergePath, loopLatestCheckpointPath, loopPlanPath, loopProofPath, loopRoot, loopSideEffectReportPath } from '../loops/loop-artifacts.js';
6
6
  import { finalizeLoopGraph } from '../loops/loop-finalizer.js';
@@ -192,11 +192,7 @@ async function loopResume(args) {
192
192
  async function resolveLoopMission(root, arg) {
193
193
  if (arg && arg !== 'latest')
194
194
  return arg;
195
- const latest = await findLatestMission(root);
196
- if (!latest)
197
- return null;
198
- const loaded = await loadMission(root, latest).catch(() => null);
199
- return loaded?.mission?.mode === 'loop' || await readJson(loopPlanPath(root, latest), null) ? latest : null;
195
+ return findLatestMission(root, { mode: 'loop' });
200
196
  }
201
197
  function normalizeParallelism(value) {
202
198
  return value === 'safe' || value === 'extreme' ? value : 'balanced';
@@ -1014,7 +1014,7 @@ async function madSksSubcommand(subcommand, args = []) {
1014
1014
  if (subcommand === 'doctor' || subcommand === 'status') {
1015
1015
  const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot });
1016
1016
  const before = await snapshotProtectedCore(packageRoot(), 'status');
1017
- const statusMissionId = await findLatestMission(root);
1017
+ const statusMissionId = await findLatestMission(root, { mode: 'mad-sks', route: '$MAD-SKS', gateFile: 'mad-sks-gate.json' });
1018
1018
  const gateVerdict = statusMissionId
1019
1019
  ? await evaluateGate(root, statusMissionId, 'mad-sks-gate.json')
1020
1020
  : await evaluateGate(root, 'no-mission', 'mad-sks-gate.json');
@@ -1330,7 +1330,7 @@ async function materializeMadSksRun(root, targetRoot, permission, userIntent, js
1330
1330
  }, json);
1331
1331
  }
1332
1332
  async function closeMadSks(root, args = [], json = false, action = 'close') {
1333
- const missionId = readOption(args, '--mission', null) || args.find((arg) => !String(arg).startsWith('--')) || await findLatestMission(root);
1333
+ const missionId = readOption(args, '--mission', null) || args.find((arg) => !String(arg).startsWith('--')) || await findLatestMission(root, { mode: 'mad-sks', route: '$MAD-SKS', gateFile: 'mad-sks-gate.json' });
1334
1334
  if (!missionId) {
1335
1335
  const result = { schema: 'sks.mad-sks-close.v1', ok: false, action, mission_id: null, blockers: ['mad_sks_mission_missing'] };
1336
1336
  process.exitCode = 1;
@@ -1354,7 +1354,7 @@ async function closeMadSks(root, args = [], json = false, action = 'close') {
1354
1354
  return emit(result, json);
1355
1355
  }
1356
1356
  async function cleanupExpiredMadSks(root) {
1357
- const missionId = await findLatestMission(root);
1357
+ const missionId = await findLatestMission(root, { mode: 'mad-sks', route: '$MAD-SKS', gateFile: 'mad-sks-gate.json' });
1358
1358
  if (!missionId)
1359
1359
  return null;
1360
1360
  const gate = await readJson(path.join(missionDir(root, missionId), 'mad-sks-gate.json'), null);
@@ -0,0 +1,13 @@
1
+ import { probeMcpServer, runMcpServer } from '../agent-bridge/mcp-server.js';
2
+ import { flag } from './command-utils.js';
3
+ export async function mcpServerCommand(args = []) {
4
+ const exposeExec = flag(args, '--expose-exec');
5
+ if (flag(args, '--probe')) {
6
+ const result = await probeMcpServer({ exposeExec });
7
+ console.log(JSON.stringify({ schema: 'sks.mcp-server-probe.v1', ...result }, null, 2));
8
+ process.exitCode = result.ok ? 0 : 1;
9
+ return;
10
+ }
11
+ await runMcpServer({ exposeExec });
12
+ }
13
+ //# sourceMappingURL=mcp-server-command.js.map
@@ -741,7 +741,7 @@ function validRegressionProof(proof) {
741
741
  }
742
742
  async function narutoStatus(parsed) {
743
743
  const root = await sksRoot();
744
- const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
744
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root, { mode: 'naruto' });
745
745
  if (!id)
746
746
  return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'status', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
747
747
  const { dir } = await loadMission(root, id);
@@ -780,7 +780,7 @@ async function narutoStatus(parsed) {
780
780
  }
781
781
  async function narutoDashboard(parsed) {
782
782
  const root = await sksRoot();
783
- const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
783
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root, { mode: 'naruto' });
784
784
  if (!id)
785
785
  return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'dashboard', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
786
786
  const { dir } = await loadMission(root, id);
@@ -804,7 +804,7 @@ async function narutoDashboard(parsed) {
804
804
  }
805
805
  async function narutoWorkers(parsed) {
806
806
  const root = await sksRoot();
807
- const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
807
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root, { mode: 'naruto' });
808
808
  if (!id)
809
809
  return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'workers', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
810
810
  const { dir } = await loadMission(root, id);
@@ -831,7 +831,7 @@ async function narutoWorkers(parsed) {
831
831
  }
832
832
  async function narutoProof(parsed) {
833
833
  const root = await sksRoot();
834
- const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
834
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root, { mode: 'naruto' });
835
835
  if (!id)
836
836
  return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'proof', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
837
837
  const summary = await buildRuntimeProofSummary(root, id, { maxMessages: parsed.messages });
@@ -20,7 +20,10 @@ export async function pptCommand(command, args = []) {
20
20
  if (action === 'explain')
21
21
  return pptExplain(root, args.slice(1));
22
22
  const missionArg = args[1] && !String(args[1]).startsWith('--') ? args[1] : 'latest';
23
- const missionId = missionArg === 'latest' ? await findLatestMission(root) : missionArg;
23
+ // build/status act on ppt-gate.json (written by build, not the imagegen-review gate),
24
+ // so scope by mode only here - requiring the imagegen-review gate would wrongly exclude
25
+ // a fresh ppt mission that hasn't gone through review yet.
26
+ const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'ppt' }) : missionArg;
24
27
  if (!missionId)
25
28
  return missingMission(args);
26
29
  const { dir, mission } = await loadMission(root, missionId);
@@ -93,7 +96,7 @@ async function pptImagegenReview(root, command, action, args = []) {
93
96
  const shouldCreate = Boolean(deckPath || mockMode || action === 'review' || action === 'export-slides' || action === 'slide-export' || action === 'callouts' || action === 'extract-issues');
94
97
  const missionId = shouldCreate && !missionArg
95
98
  ? null
96
- : missionArg === 'latest' || !missionArg ? await findLatestMission(root) : missionArg;
99
+ : missionArg === 'latest' || !missionArg ? await findLatestMission(root, { mode: 'ppt', route: '$PPT', gateFile: PPT_IMAGEGEN_REVIEW_GATE_ARTIFACT }) : missionArg;
97
100
  const loaded = missionId ? await loadMission(root, missionId) : await createMission(root, { mode: 'ppt', prompt: `PPT imagegen review ${deckPath || 'fixture'}` });
98
101
  const id = 'id' in loaded ? loaded.id : missionId;
99
102
  const dir = loaded.dir;
@@ -243,7 +246,7 @@ async function pptFixture(root, command, args) {
243
246
  }
244
247
  async function pptExplain(root, args = []) {
245
248
  const missionArg = args.find((arg) => !String(arg).startsWith('--')) || 'latest';
246
- const missionId = missionArg === 'latest' ? await findLatestMission(root) : missionArg;
249
+ const missionId = missionArg === 'latest' ? await findLatestMission(root, { mode: 'ppt', route: '$PPT', gateFile: PPT_IMAGEGEN_REVIEW_GATE_ARTIFACT }) : missionArg;
247
250
  if (!missionId)
248
251
  return missingMission(args);
249
252
  const { dir } = await loadMission(root, missionId);