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.
- package/README.md +4 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +7 -1
- package/dist/cli/router.js +28 -0
- package/dist/commands/doctor.js +65 -3
- package/dist/config/skills-manifest.json +58 -58
- package/dist/core/agent-bridge/agent-manifest.js +64 -0
- package/dist/core/agent-bridge/agent-mode.js +29 -0
- package/dist/core/agent-bridge/mcp-server.js +156 -0
- package/dist/core/agents/agent-orchestrator.js +1 -11
- package/dist/core/commands/agent-bridge-command.js +86 -0
- package/dist/core/commands/image-ux-review-command.js +6 -6
- package/dist/core/commands/loop-command.js +2 -6
- package/dist/core/commands/mad-sks-command.js +3 -3
- package/dist/core/commands/mcp-server-command.js +13 -0
- package/dist/core/commands/naruto-command.js +4 -4
- package/dist/core/commands/ppt-command.js +6 -3
- package/dist/core/commands/qa-loop-command.js +64 -26
- package/dist/core/commands/team-command.js +1 -1
- package/dist/core/commands/wiki-command.js +71 -3
- package/dist/core/config/secret-preservation.js +43 -1
- package/dist/core/doctor/browser-use-repair.js +149 -0
- package/dist/core/doctor/computer-use-repair.js +166 -0
- package/dist/core/doctor/imagegen-repair.js +9 -0
- package/dist/core/doctor/mcp-transport-collision-repair.js +129 -0
- package/dist/core/doctor/supabase-mcp-repair.js +85 -16
- package/dist/core/feature-fixtures.js +8 -0
- package/dist/core/feature-registry.js +21 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/mad-db/mad-db-capability.js +1 -1
- package/dist/core/mission.js +19 -3
- package/dist/core/naruto/naruto-real-worker-child.js +5 -4
- package/dist/core/naruto/naruto-real-worker-runtime.js +5 -4
- package/dist/core/naruto/normalize-worker-prompt-text.js +12 -0
- package/dist/core/routes.js +3 -1
- package/dist/core/triwiki/code-index-scanner.js +313 -0
- package/dist/core/triwiki/code-pack.js +169 -0
- package/dist/core/triwiki/triwiki-cache-key.js +24 -2
- package/dist/core/triwiki-attention.js +34 -5
- package/dist/core/update-check.js +50 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/doctor-imagegen-repair-check.js +6 -1
- 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,
|
|
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
|
-
|
|
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
|
-
|
|
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);
|