code-ai-installer 4.0.1 → 4.3.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 +5 -3
- package/dist/mcp/audit_ledger.d.ts +10 -0
- package/dist/mcp/audit_ledger.js +15 -0
- package/dist/mcp/cli.js +1 -0
- package/dist/mcp/tools/render_diff.d.ts +25 -0
- package/dist/mcp/tools/render_diff.js +138 -0
- package/dist/mcp/tools/sign_off.js +10 -5
- package/dist/mcp/tools/stubs.js +2 -0
- package/dist/mcp_setup.d.ts +79 -48
- package/dist/mcp_setup.js +215 -107
- package/dist/shared/tools.d.ts +38 -0
- package/dist/shared/tools.js +24 -0
- package/domains/analytics/agents/conductor.md +15 -1
- package/domains/analytics/locales/en/agents/conductor.md +15 -1
- package/domains/content/agents/conductor.md +15 -1
- package/domains/content/locales/en/agents/conductor.md +15 -1
- package/domains/development/.agents/skills/mcp-integration/SKILL.md +3 -1
- package/domains/development/.agents/workflows/audit.md +25 -0
- package/domains/development/.agents/workflows/pipeline-rules.md +1 -0
- package/domains/development/AGENTS.md +1 -0
- package/domains/development/agents/architect.md +1 -1
- package/domains/development/agents/auditor.md +4 -3
- package/domains/development/agents/conductor.md +4 -1
- package/domains/development/agents/devops.md +1 -1
- package/domains/development/agents/reviewer.md +2 -1
- package/domains/development/agents/senior_full_stack.md +1 -1
- package/domains/development/agents/tester.md +1 -1
- package/domains/development/locales/en/.agents/skills/mcp-integration/SKILL.md +3 -1
- package/domains/development/locales/en/.agents/workflows/audit.md +25 -0
- package/domains/development/locales/en/.agents/workflows/pipeline-rules.md +1 -0
- package/domains/development/locales/en/AGENTS.md +2 -0
- package/domains/development/locales/en/agents/architect.md +1 -1
- package/domains/development/locales/en/agents/auditor.md +4 -3
- package/domains/development/locales/en/agents/conductor.md +4 -1
- package/domains/development/locales/en/agents/devops.md +1 -1
- package/domains/development/locales/en/agents/reviewer.md +2 -1
- package/domains/development/locales/en/agents/senior_full_stack.md +1 -1
- package/domains/development/locales/en/agents/tester.md +1 -1
- package/domains/product/agents/conductor.md +15 -1
- package/domains/product/locales/en/agents/conductor.md +15 -1
- package/package.json +1 -1
package/dist/mcp_setup.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { copyFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
const defaultClaudeCli = {
|
|
5
|
-
run: (args) => spawnCapture("claude", args),
|
|
6
|
-
};
|
|
7
5
|
/**
|
|
8
6
|
* Try `mempalace-mcp --help` — the dedicated MCP-server bin, which is exactly
|
|
9
7
|
* what we register. Resolves true on exit code 0, false otherwise (including
|
|
@@ -14,11 +12,6 @@ const defaultClaudeCli = {
|
|
|
14
12
|
export async function detectMemPalace() {
|
|
15
13
|
return spawnExitZero("mempalace-mcp", ["--help"]);
|
|
16
14
|
}
|
|
17
|
-
/** True when the `claude` CLI is on PATH (probed via `claude --version`). */
|
|
18
|
-
export async function detectClaudeCli(cli = defaultClaudeCli) {
|
|
19
|
-
const res = await cli.run(["--version"]);
|
|
20
|
-
return res.ok;
|
|
21
|
-
}
|
|
22
15
|
/**
|
|
23
16
|
* Find the first available Python install runtime, in preference order:
|
|
24
17
|
* uv → pipx → pip. Returns null if none available.
|
|
@@ -56,55 +49,118 @@ function installCommandArgs(runtime) {
|
|
|
56
49
|
}
|
|
57
50
|
}
|
|
58
51
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* before the server name (as `claude mcp add` expects options first).
|
|
52
|
+
* Resolve the Claude user config path. Honours `CLAUDE_CONFIG_DIR` when set,
|
|
53
|
+
* else `~/.claude.json`.
|
|
62
54
|
*/
|
|
63
|
-
export function
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
export function userConfigPath() {
|
|
56
|
+
const dir = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
57
|
+
return dir ? join(dir, ".claude.json") : join(homedir(), ".claude.json");
|
|
58
|
+
}
|
|
59
|
+
/** Real filesystem-backed UserConfigIO. Factory so tests can target a temp file. */
|
|
60
|
+
export function createUserConfigIO(configPath) {
|
|
61
|
+
return {
|
|
62
|
+
configPath,
|
|
63
|
+
async read() {
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = await readFile(configPath, "utf8");
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const e = err;
|
|
70
|
+
return { ok: false, error: e.code === "ENOENT" ? "file not found" : e.message };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const data = JSON.parse(raw);
|
|
74
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
75
|
+
return { ok: false, error: "config is not a JSON object" };
|
|
76
|
+
}
|
|
77
|
+
return { ok: true, data: data };
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return { ok: false, error: `JSON parse error: ${err.message}` };
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
async writeWithBackup(data) {
|
|
84
|
+
const backupPath = `${configPath}.bak`;
|
|
85
|
+
try {
|
|
86
|
+
try {
|
|
87
|
+
await copyFile(configPath, backupPath);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
// Only tolerate "original did not exist"; surface anything else.
|
|
91
|
+
if (err.code !== "ENOENT")
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
const tmpPath = `${configPath}.code-ai.tmp`;
|
|
95
|
+
await writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
96
|
+
await rename(tmpPath, configPath);
|
|
97
|
+
return { ok: true, backupPath };
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return { ok: false, error: err.message };
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
68
104
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
const defaultUserConfigIO = createUserConfigIO(userConfigPath());
|
|
106
|
+
/** Shape a server entry the way Claude stores user-scope stdio servers. */
|
|
107
|
+
function toUserScopeEntry(entry) {
|
|
108
|
+
return {
|
|
109
|
+
type: "stdio",
|
|
110
|
+
command: entry.command,
|
|
111
|
+
args: [...entry.args],
|
|
112
|
+
env: entry.env ?? {},
|
|
113
|
+
};
|
|
72
114
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
115
|
+
function readMcpServers(config) {
|
|
116
|
+
const existing = config.mcpServers;
|
|
117
|
+
return existing && typeof existing === "object" && !Array.isArray(existing)
|
|
118
|
+
? { ...existing }
|
|
119
|
+
: {};
|
|
76
120
|
}
|
|
77
121
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
122
|
+
* Idempotently merge `servers` into the config's top-level `mcpServers`. Pure —
|
|
123
|
+
* returns a new config object plus which names were freshly added vs already
|
|
124
|
+
* present. An existing key is NEVER overwritten (so a pre-existing global
|
|
125
|
+
* `mempalace` is preserved untouched). Exported for unit testing.
|
|
82
126
|
*/
|
|
83
|
-
export
|
|
84
|
-
const
|
|
85
|
-
|
|
127
|
+
export function mergeUserScopeServers(config, servers) {
|
|
128
|
+
const next = { ...config };
|
|
129
|
+
const mcpServers = readMcpServers(config);
|
|
130
|
+
const registered = [];
|
|
131
|
+
const alreadyPresent = [];
|
|
132
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
133
|
+
if (Object.prototype.hasOwnProperty.call(mcpServers, name)) {
|
|
134
|
+
alreadyPresent.push(name);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
mcpServers[name] = toUserScopeEntry(entry);
|
|
138
|
+
registered.push(name);
|
|
139
|
+
}
|
|
140
|
+
next.mcpServers = mcpServers;
|
|
141
|
+
return { config: next, registered, alreadyPresent };
|
|
86
142
|
}
|
|
87
143
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
144
|
+
* Idempotently remove `names` from the config's `mcpServers`. Pure — returns a
|
|
145
|
+
* new config object plus which names were removed vs were not present. Exported
|
|
146
|
+
* for unit testing.
|
|
90
147
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
148
|
+
export function removeUserScopeServers(config, names) {
|
|
149
|
+
const next = { ...config };
|
|
150
|
+
const mcpServers = readMcpServers(config);
|
|
151
|
+
const removed = [];
|
|
152
|
+
const notPresent = [];
|
|
153
|
+
for (const name of names) {
|
|
154
|
+
if (Object.prototype.hasOwnProperty.call(mcpServers, name)) {
|
|
155
|
+
delete mcpServers[name];
|
|
156
|
+
removed.push(name);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
notPresent.push(name);
|
|
160
|
+
}
|
|
101
161
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
notice: `Failed to register '${name}' in user scope. Run it manually:\n` +
|
|
105
|
-
` ${manualAddCommand(name, entry)}\n` +
|
|
106
|
-
` Output:\n${res.output}`,
|
|
107
|
-
};
|
|
162
|
+
next.mcpServers = mcpServers;
|
|
163
|
+
return { config: next, removed, notPresent };
|
|
108
164
|
}
|
|
109
165
|
/**
|
|
110
166
|
* Write `<destinationDir>/.code-ai/config.json` with the chosen backend.
|
|
@@ -142,21 +198,25 @@ function buildServerEntries(mempalaceUsed) {
|
|
|
142
198
|
}
|
|
143
199
|
return servers;
|
|
144
200
|
}
|
|
201
|
+
/** Format a single "name: entry-json" line for the manual-fallback notice. */
|
|
202
|
+
function manualEntryLine(name, entry) {
|
|
203
|
+
return ` "${name}": ${JSON.stringify(toUserScopeEntry(entry))}`;
|
|
204
|
+
}
|
|
145
205
|
/**
|
|
146
206
|
* End-to-end orchestrator. Called from the install flow when `target=claude`.
|
|
147
207
|
*
|
|
148
208
|
* Order:
|
|
149
209
|
* 1. If user wants MemPalace: detect → install if absent → fall back if install fails.
|
|
150
210
|
* 2. Build server entries (code-ai-mcp always; mempalace only when usable).
|
|
151
|
-
* 3.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
211
|
+
* 3. Merge them into the Claude user config's `mcpServers` (idempotent; skip
|
|
212
|
+
* already-present). If the config can't be read/written, print the exact
|
|
213
|
+
* JSON to add by hand instead of guessing.
|
|
154
214
|
* 4. Write project-local `.code-ai/config.json` with the backend choice.
|
|
155
215
|
*
|
|
156
216
|
* Reports all decisions in `McpSetupReport.notices` so callers can surface
|
|
157
|
-
* them to the user verbatim. `
|
|
217
|
+
* them to the user verbatim. `io` is injectable for testing.
|
|
158
218
|
*/
|
|
159
|
-
export async function setupMcp(opts,
|
|
219
|
+
export async function setupMcp(opts, io = defaultUserConfigIO) {
|
|
160
220
|
const notices = [];
|
|
161
221
|
let mempalaceUsed = false;
|
|
162
222
|
let mempalaceInstallAttempted = false;
|
|
@@ -191,33 +251,49 @@ export async function setupMcp(opts, cli = defaultClaudeCli) {
|
|
|
191
251
|
const serversRegistered = [];
|
|
192
252
|
const serversAlreadyPresent = [];
|
|
193
253
|
const serversFailed = [];
|
|
194
|
-
const claudeCliAvailable = !opts.dryRun && (await detectClaudeCli(cli));
|
|
195
254
|
let registration;
|
|
196
255
|
if (opts.dryRun) {
|
|
197
256
|
registration = "user-scope";
|
|
198
257
|
for (const [name, entry] of Object.entries(servers)) {
|
|
199
|
-
notices.push(`Would register MCP server '${name}' in user
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
else if (claudeCliAvailable) {
|
|
203
|
-
registration = "user-scope";
|
|
204
|
-
for (const [name, entry] of Object.entries(servers)) {
|
|
205
|
-
const { outcome, notice } = await addServerToUserScope(cli, name, entry);
|
|
206
|
-
notices.push(notice);
|
|
207
|
-
if (outcome === "registered")
|
|
208
|
-
serversRegistered.push(name);
|
|
209
|
-
else if (outcome === "already-present")
|
|
210
|
-
serversAlreadyPresent.push(name);
|
|
211
|
-
else
|
|
212
|
-
serversFailed.push(name);
|
|
258
|
+
notices.push(`Would register MCP server '${name}' in Claude user config (${io.configPath}): ${JSON.stringify(toUserScopeEntry(entry))}`);
|
|
213
259
|
}
|
|
214
260
|
}
|
|
215
261
|
else {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
262
|
+
const read = await io.read();
|
|
263
|
+
if (!read.ok || !read.data) {
|
|
264
|
+
registration = "manual-fallback";
|
|
265
|
+
notices.push(`Could not read Claude user config at ${io.configPath} (${read.error ?? "unknown error"}). ` +
|
|
266
|
+
`Not modifying it automatically — add these entries to its "mcpServers" object by hand:`);
|
|
267
|
+
for (const [name, entry] of Object.entries(servers))
|
|
268
|
+
notices.push(manualEntryLine(name, entry));
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
const merged = mergeUserScopeServers(read.data, servers);
|
|
272
|
+
serversAlreadyPresent.push(...merged.alreadyPresent);
|
|
273
|
+
for (const name of merged.alreadyPresent) {
|
|
274
|
+
notices.push(`MCP server '${name}' already in user config — left untouched.`);
|
|
275
|
+
}
|
|
276
|
+
if (merged.registered.length === 0) {
|
|
277
|
+
registration = "user-scope";
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
const written = await io.writeWithBackup(merged.config);
|
|
281
|
+
if (written.ok) {
|
|
282
|
+
registration = "user-scope";
|
|
283
|
+
serversRegistered.push(...merged.registered);
|
|
284
|
+
notices.push(`Registered MCP server(s) [${merged.registered.join(", ")}] in Claude user config ${io.configPath}` +
|
|
285
|
+
(written.backupPath ? ` (backup: ${written.backupPath})` : "") +
|
|
286
|
+
". Restart your Claude Code session to load them.");
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
registration = "manual-fallback";
|
|
290
|
+
serversFailed.push(...merged.registered);
|
|
291
|
+
notices.push(`Failed to write Claude user config at ${io.configPath} (${written.error ?? "unknown error"}). ` +
|
|
292
|
+
`No changes made — add these entries to its "mcpServers" object by hand:`);
|
|
293
|
+
for (const name of merged.registered)
|
|
294
|
+
notices.push(manualEntryLine(name, servers[name]));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
221
297
|
}
|
|
222
298
|
}
|
|
223
299
|
const cfg = await writeCodeAiConfig(opts.destinationDir, {
|
|
@@ -229,7 +305,7 @@ export async function setupMcp(opts, cli = defaultClaudeCli) {
|
|
|
229
305
|
mempalaceInstallAttempted,
|
|
230
306
|
mempalaceInstallSucceeded,
|
|
231
307
|
pythonRuntime,
|
|
232
|
-
|
|
308
|
+
userConfigPath: io.configPath,
|
|
233
309
|
registration,
|
|
234
310
|
serversRegistered,
|
|
235
311
|
serversAlreadyPresent,
|
|
@@ -246,54 +322,86 @@ export async function setupMcp(opts, cli = defaultClaudeCli) {
|
|
|
246
322
|
*/
|
|
247
323
|
const INSTALLER_OWNED_SERVERS = ["code-ai-mcp"];
|
|
248
324
|
/**
|
|
249
|
-
* Remove the installer-owned MCP server(s) from Claude's
|
|
250
|
-
* `
|
|
251
|
-
*
|
|
252
|
-
*
|
|
325
|
+
* Remove the installer-owned MCP server(s) from the Claude user config's
|
|
326
|
+
* `mcpServers`. Idempotent — a server that isn't present is reported as
|
|
327
|
+
* "nothing to remove". If the config can't be read/written, prints guidance
|
|
328
|
+
* instead of guessing. `mempalace` is never touched. `io` is injectable.
|
|
253
329
|
*
|
|
254
330
|
* NOTE: the registration is global (shared across all projects), so this removes
|
|
255
331
|
* code-ai-mcp for every project. That is the chosen behaviour (symmetric with
|
|
256
332
|
* install); a multi-project user re-runs the installer to restore it.
|
|
257
333
|
*/
|
|
258
|
-
export async function teardownMcp(opts,
|
|
334
|
+
export async function teardownMcp(opts, io = defaultUserConfigIO) {
|
|
259
335
|
const notices = [];
|
|
260
336
|
const serversRemoved = [];
|
|
261
337
|
const serversNotPresent = [];
|
|
262
338
|
const serversFailed = [];
|
|
263
339
|
if (opts.dryRun) {
|
|
264
340
|
for (const name of INSTALLER_OWNED_SERVERS) {
|
|
265
|
-
notices.push(`Would remove MCP server '${name}' from user
|
|
341
|
+
notices.push(`Would remove MCP server '${name}' from Claude user config (${io.configPath}).`);
|
|
266
342
|
}
|
|
267
|
-
return {
|
|
343
|
+
return {
|
|
344
|
+
userConfigPath: io.configPath,
|
|
345
|
+
removal: "user-scope",
|
|
346
|
+
serversRemoved,
|
|
347
|
+
serversNotPresent,
|
|
348
|
+
serversFailed,
|
|
349
|
+
notices,
|
|
350
|
+
};
|
|
268
351
|
}
|
|
269
|
-
const
|
|
270
|
-
if (!
|
|
271
|
-
notices.push(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
352
|
+
const read = await io.read();
|
|
353
|
+
if (!read.ok || !read.data) {
|
|
354
|
+
notices.push(`Could not read Claude user config at ${io.configPath} (${read.error ?? "unknown error"}). ` +
|
|
355
|
+
`Remove the server(s) [${INSTALLER_OWNED_SERVERS.join(", ")}] from its "mcpServers" object by hand.`);
|
|
356
|
+
return {
|
|
357
|
+
userConfigPath: io.configPath,
|
|
358
|
+
removal: "manual-fallback",
|
|
359
|
+
serversRemoved,
|
|
360
|
+
serversNotPresent,
|
|
361
|
+
serversFailed,
|
|
362
|
+
notices,
|
|
363
|
+
};
|
|
277
364
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
365
|
+
const result = removeUserScopeServers(read.data, INSTALLER_OWNED_SERVERS);
|
|
366
|
+
serversNotPresent.push(...result.notPresent);
|
|
367
|
+
for (const name of result.notPresent) {
|
|
368
|
+
notices.push(`MCP server '${name}' is not in user config — nothing to remove.`);
|
|
369
|
+
}
|
|
370
|
+
if (result.removed.length === 0) {
|
|
371
|
+
return {
|
|
372
|
+
userConfigPath: io.configPath,
|
|
373
|
+
removal: "user-scope",
|
|
374
|
+
serversRemoved,
|
|
375
|
+
serversNotPresent,
|
|
376
|
+
serversFailed,
|
|
377
|
+
notices,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const written = await io.writeWithBackup(result.config);
|
|
381
|
+
if (written.ok) {
|
|
382
|
+
serversRemoved.push(...result.removed);
|
|
383
|
+
notices.push(`Removed MCP server(s) [${result.removed.join(", ")}] from Claude user config ${io.configPath}` +
|
|
384
|
+
(written.backupPath ? ` (backup: ${written.backupPath})` : "") +
|
|
385
|
+
".");
|
|
386
|
+
return {
|
|
387
|
+
userConfigPath: io.configPath,
|
|
388
|
+
removal: "user-scope",
|
|
389
|
+
serversRemoved,
|
|
390
|
+
serversNotPresent,
|
|
391
|
+
serversFailed,
|
|
392
|
+
notices,
|
|
393
|
+
};
|
|
295
394
|
}
|
|
296
|
-
|
|
395
|
+
serversFailed.push(...result.removed);
|
|
396
|
+
notices.push(`Failed to write Claude user config at ${io.configPath} (${written.error ?? "unknown error"}). No changes made.`);
|
|
397
|
+
return {
|
|
398
|
+
userConfigPath: io.configPath,
|
|
399
|
+
removal: "manual-fallback",
|
|
400
|
+
serversRemoved,
|
|
401
|
+
serversNotPresent,
|
|
402
|
+
serversFailed,
|
|
403
|
+
notices,
|
|
404
|
+
};
|
|
297
405
|
}
|
|
298
406
|
// ─── Subprocess helpers ─────────────────────────────────────────────────────
|
|
299
407
|
async function spawnExitZero(command, args) {
|
package/dist/shared/tools.d.ts
CHANGED
|
@@ -687,6 +687,10 @@ export declare const SignOffInput: z.ZodObject<{
|
|
|
687
687
|
}>;
|
|
688
688
|
evidence: z.ZodOptional<z.ZodUnknown>;
|
|
689
689
|
}, z.core.$strip>;
|
|
690
|
+
/** Surfacing-only Auditor reminder, emitted when completed runs hit the A1 cadence (every 3rd). */
|
|
691
|
+
export declare const AuditNudge: z.ZodObject<{
|
|
692
|
+
runs_total: z.ZodNumber;
|
|
693
|
+
}, z.core.$strip>;
|
|
690
694
|
export declare const SignOffOutput: z.ZodObject<{
|
|
691
695
|
signed: z.ZodLiteral<true>;
|
|
692
696
|
signer: z.ZodEnum<{
|
|
@@ -695,6 +699,9 @@ export declare const SignOffOutput: z.ZodObject<{
|
|
|
695
699
|
system: "system";
|
|
696
700
|
}>;
|
|
697
701
|
timestamp: z.ZodString;
|
|
702
|
+
audit_nudge: z.ZodOptional<z.ZodObject<{
|
|
703
|
+
runs_total: z.ZodNumber;
|
|
704
|
+
}, z.core.$strip>>;
|
|
698
705
|
}, z.core.$strip>;
|
|
699
706
|
export type SignOffInput = z.infer<typeof SignOffInput>;
|
|
700
707
|
export type SignOffOutput = z.infer<typeof SignOffOutput>;
|
|
@@ -1578,6 +1585,20 @@ export declare const E2EPlaywrightOutput: z.ZodObject<{
|
|
|
1578
1585
|
}, z.core.$strip>;
|
|
1579
1586
|
export type E2EPlaywrightInput = z.infer<typeof E2EPlaywrightInput>;
|
|
1580
1587
|
export type E2EPlaywrightOutput = z.infer<typeof E2EPlaywrightOutput>;
|
|
1588
|
+
export declare const RenderDiffInput: z.ZodObject<{
|
|
1589
|
+
diff: z.ZodString;
|
|
1590
|
+
title: z.ZodOptional<z.ZodString>;
|
|
1591
|
+
task_id: z.ZodOptional<z.ZodString>;
|
|
1592
|
+
}, z.core.$strip>;
|
|
1593
|
+
export declare const RenderDiffOutput: z.ZodObject<{
|
|
1594
|
+
path: z.ZodString;
|
|
1595
|
+
file_url: z.ZodString;
|
|
1596
|
+
files: z.ZodNumber;
|
|
1597
|
+
added: z.ZodNumber;
|
|
1598
|
+
removed: z.ZodNumber;
|
|
1599
|
+
}, z.core.$strip>;
|
|
1600
|
+
export type RenderDiffInput = z.infer<typeof RenderDiffInput>;
|
|
1601
|
+
export type RenderDiffOutput = z.infer<typeof RenderDiffOutput>;
|
|
1581
1602
|
export declare const TOOL_REGISTRY: {
|
|
1582
1603
|
readonly load_role: {
|
|
1583
1604
|
readonly input: z.ZodObject<{
|
|
@@ -2245,6 +2266,9 @@ export declare const TOOL_REGISTRY: {
|
|
|
2245
2266
|
system: "system";
|
|
2246
2267
|
}>;
|
|
2247
2268
|
timestamp: z.ZodString;
|
|
2269
|
+
audit_nudge: z.ZodOptional<z.ZodObject<{
|
|
2270
|
+
runs_total: z.ZodNumber;
|
|
2271
|
+
}, z.core.$strip>>;
|
|
2248
2272
|
}, z.core.$strip>;
|
|
2249
2273
|
};
|
|
2250
2274
|
readonly submit_artifact: {
|
|
@@ -3017,5 +3041,19 @@ export declare const TOOL_REGISTRY: {
|
|
|
3017
3041
|
}, z.core.$strip>>>;
|
|
3018
3042
|
}, z.core.$strip>;
|
|
3019
3043
|
};
|
|
3044
|
+
readonly render_diff: {
|
|
3045
|
+
readonly input: z.ZodObject<{
|
|
3046
|
+
diff: z.ZodString;
|
|
3047
|
+
title: z.ZodOptional<z.ZodString>;
|
|
3048
|
+
task_id: z.ZodOptional<z.ZodString>;
|
|
3049
|
+
}, z.core.$strip>;
|
|
3050
|
+
readonly output: z.ZodObject<{
|
|
3051
|
+
path: z.ZodString;
|
|
3052
|
+
file_url: z.ZodString;
|
|
3053
|
+
files: z.ZodNumber;
|
|
3054
|
+
added: z.ZodNumber;
|
|
3055
|
+
removed: z.ZodNumber;
|
|
3056
|
+
}, z.core.$strip>;
|
|
3057
|
+
};
|
|
3020
3058
|
};
|
|
3021
3059
|
export type ToolName = keyof typeof TOOL_REGISTRY;
|
package/dist/shared/tools.js
CHANGED
|
@@ -159,10 +159,16 @@ export const SignOffInput = z.object({
|
|
|
159
159
|
/** Evidence — for mcp signer this is the auto_check tool output; for user it's free-form. */
|
|
160
160
|
evidence: z.unknown().optional(),
|
|
161
161
|
});
|
|
162
|
+
/** Surfacing-only Auditor reminder, emitted when completed runs hit the A1 cadence (every 3rd). */
|
|
163
|
+
export const AuditNudge = z.object({
|
|
164
|
+
runs_total: z.number().int().nonnegative(),
|
|
165
|
+
});
|
|
162
166
|
export const SignOffOutput = z.object({
|
|
163
167
|
signed: z.literal(true),
|
|
164
168
|
signer: Signer,
|
|
165
169
|
timestamp: z.string(),
|
|
170
|
+
/** Present only when RG is signed AND the run count hits the cadence. Best-effort; never load-bearing. */
|
|
171
|
+
audit_nudge: AuditNudge.optional(),
|
|
166
172
|
});
|
|
167
173
|
// ─── submit_artifact ─────────────────────────────────────────────────────────
|
|
168
174
|
export const SubmitArtifactInput = z.object({
|
|
@@ -598,6 +604,23 @@ export const E2EPlaywrightOutput = z.object({
|
|
|
598
604
|
failed: z.number().int().nonnegative(),
|
|
599
605
|
failures: z.array(ExceptionItem).default([]),
|
|
600
606
|
});
|
|
607
|
+
export const RenderDiffInput = z.object({
|
|
608
|
+
/** A unified diff (e.g. the output of `git diff`). */
|
|
609
|
+
diff: z.string().min(1),
|
|
610
|
+
/** Heading shown at the top of the review page. */
|
|
611
|
+
title: z.string().optional(),
|
|
612
|
+
/** Optional task id — only used to label the output filename. */
|
|
613
|
+
task_id: TaskId.optional(),
|
|
614
|
+
});
|
|
615
|
+
export const RenderDiffOutput = z.object({
|
|
616
|
+
/** Absolute path of the written HTML file (in the OS temp dir). */
|
|
617
|
+
path: z.string(),
|
|
618
|
+
/** file:// URL the user can open in a browser. */
|
|
619
|
+
file_url: z.string(),
|
|
620
|
+
files: z.number().int().nonnegative(),
|
|
621
|
+
added: z.number().int().nonnegative(),
|
|
622
|
+
removed: z.number().int().nonnegative(),
|
|
623
|
+
});
|
|
601
624
|
// ════════════════════════════════════════════════════════════════════════════
|
|
602
625
|
// TOOL REGISTRY — name → { input, output } for runtime dispatch
|
|
603
626
|
// ════════════════════════════════════════════════════════════════════════════
|
|
@@ -639,4 +662,5 @@ export const TOOL_REGISTRY = {
|
|
|
639
662
|
},
|
|
640
663
|
docker_compose: { input: DockerComposeInput, output: DockerComposeOutput },
|
|
641
664
|
e2e_playwright: { input: E2EPlaywrightInput, output: E2EPlaywrightOutput },
|
|
665
|
+
render_diff: { input: RenderDiffInput, output: RenderDiffOutput },
|
|
642
666
|
};
|
|
@@ -5,7 +5,7 @@ domain: analytics
|
|
|
5
5
|
signs_off_at:
|
|
6
6
|
- RELEASE_GATE
|
|
7
7
|
tool_allowlist: role:conductor
|
|
8
|
-
budget_lines:
|
|
8
|
+
budget_lines: 473
|
|
9
9
|
schema_version: 1
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -360,6 +360,20 @@ $handoff → RES-02 (Researcher Beta).
|
|
|
360
360
|
|
|
361
361
|
---
|
|
362
362
|
|
|
363
|
+
## MCP-интеграция и операционные гарантии
|
|
364
|
+
|
|
365
|
+
Дирижёр оркестрирует весь поток гейтов через машину состояний `code-ai` MCP — общий поток см. в `analytics-pipeline-rules.md` § «Машина гейтов (code-ai MCP)». Conductor-specific операционные гарантии:
|
|
366
|
+
|
|
367
|
+
- **Оркестрация гейтов** — Дирижёр ведёт последовательность гейтов выбранного режима через MCP: `classify_gate` (на какой гейт ложится задача), `current_gate` (где сейчас), `advance_gate` (переход к следующему гейту только после deliverable + Handoff Envelope). Переход без полного Handoff Envelope → `advance_gate` блокирует.
|
|
368
|
+
- **`sign_off` — все гейты `user`, Дирижёр не подписывает за пользователя** — в аналитике каждый гейт закрывает ПОЛЬЗОВАТЕЛЬ. Дирижёр ОСТАНАВЛИВАЕТСЯ, показывает артефакт гейта и запрашивает `sign_off(signer="user")` — по одному гейту, без батчинга нескольких подтверждений, и не начинает работу следующего гейта, пока текущий не подписан. Авто-пас на зелёном не применяется (домен суждения, детерминированных авто-чеков нет).
|
|
369
|
+
- **RELEASE_GATE** фиксируется как `sign_off(gate="RELEASE_GATE", signer="user", decision=..., evidence=<RG checklist>)` — не «прозой».
|
|
370
|
+
- **`request_decision` для эскалаций** — конфликты между агентами и waiver'ы обязательных пунктов: `request_decision(...)` → решает пользователь → `record_decision`. См. § Conflict Resolution Protocol.
|
|
371
|
+
- **`record_decision` для ADR-достойных решений** — зафиксированные конфликты и отклонения от брифа, согласованные с пользователем: `record_decision(signer="user", domain="analytics", task_id, decision_text)`.
|
|
372
|
+
- **Circuit breaker отключён** — в аналитике нет полосы откатов в стиле development (DEV/REV/OPS/TEST); возвраты идут через reverse `$handoff` (см. Reverse Handoff).
|
|
373
|
+
- **Degraded mode** — если MCP-поток недоступен: Дирижёр ведёт Master Checklist (`$board`) и статусы Handoff Envelope вручную, sign-off фиксируется явным "Approved" пользователя, эскалации — вручную.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
363
377
|
## Формат ответа агента
|
|
364
378
|
|
|
365
379
|
### Full Pipeline
|
|
@@ -5,7 +5,7 @@ domain: analytics
|
|
|
5
5
|
signs_off_at:
|
|
6
6
|
- RELEASE_GATE
|
|
7
7
|
tool_allowlist: role:conductor
|
|
8
|
-
budget_lines:
|
|
8
|
+
budget_lines: 471
|
|
9
9
|
schema_version: 1
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -358,6 +358,20 @@ If an agent's submission encounters `$gates` failure:
|
|
|
358
358
|
|
|
359
359
|
---
|
|
360
360
|
|
|
361
|
+
## MCP integration & operational guardrails
|
|
362
|
+
|
|
363
|
+
The Conductor orchestrates the whole gate-flow through the `code-ai` MCP state machine — for the general flow see `analytics-pipeline-rules.md` § "Gate machine (code-ai MCP)". Conductor-specific operational guardrails:
|
|
364
|
+
|
|
365
|
+
- **Gate-flow orchestration** — the Conductor drives the active mode's gate sequence through MCP: `classify_gate` (which gate the task lands on), `current_gate` (where we are now), `advance_gate` (move to the next gate only after the deliverable + Handoff Envelope). A transition without a complete Handoff Envelope → `advance_gate` blocks.
|
|
366
|
+
- **`sign_off` — all gates are `user`; the Conductor does not sign for the user** — in analytics every gate is closed by the USER. The Conductor STOPS, presents the gate artifact, and requests `sign_off(signer="user")` — one gate at a time, with no batching of approvals, and does NOT start next-gate work until the current one is signed. No auto-pass on green (a judgment domain, no deterministic auto-checks).
|
|
367
|
+
- **RELEASE_GATE** is recorded as `sign_off(gate="RELEASE_GATE", signer="user", decision=..., evidence=<RG checklist>)` — not prose approval.
|
|
368
|
+
- **`request_decision` for escalations** — conflicts between agents and waivers of mandatory items: `request_decision(...)` → the user decides → `record_decision`. See § Conflict Resolution Protocol.
|
|
369
|
+
- **`record_decision` for ADR-worthy outcomes** — recorded conflicts and brief deviations approved by the user: `record_decision(signer="user", domain="analytics", task_id, decision_text)`.
|
|
370
|
+
- **Circuit breaker disabled** — analytics has no development-style rollback lane (DEV/REV/OPS/TEST); returns go through reverse `$handoff` (see Reverse Handoff).
|
|
371
|
+
- **Degraded mode** — if the MCP flow is unavailable: the Conductor keeps the Master Checklist (`$board`) and Handoff Envelope statuses by hand, sign-off is recorded by the user's explicit "Approved", and escalations are manual.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
361
375
|
## Agent Response Template Format
|
|
362
376
|
|
|
363
377
|
### Full Pipeline Sequence
|
|
@@ -5,7 +5,7 @@ domain: content
|
|
|
5
5
|
signs_off_at:
|
|
6
6
|
- RELEASE_GATE
|
|
7
7
|
tool_allowlist: role:conductor
|
|
8
|
-
budget_lines:
|
|
8
|
+
budget_lines: 407
|
|
9
9
|
schema_version: 1
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -267,6 +267,20 @@ Conductor отслеживает соответствие контента ис
|
|
|
267
267
|
|
|
268
268
|
---
|
|
269
269
|
|
|
270
|
+
## MCP-интеграция и операционные гарантии
|
|
271
|
+
|
|
272
|
+
Дирижёр оркестрирует весь поток гейтов через машину состояний `code-ai` MCP — общий поток см. в `content-pipeline-rules.md` § «Машина гейтов (code-ai MCP)». Conductor-specific операционные гарантии:
|
|
273
|
+
|
|
274
|
+
- **Оркестрация гейтов** — Дирижёр ведёт последовательность гейтов выбранного режима через MCP: `classify_gate` (на какой гейт ложится задача), `current_gate` (где сейчас), `advance_gate` (переход к следующему гейту только после deliverable + Handoff Envelope). Переход без полного Handoff Envelope → `advance_gate` блокирует.
|
|
275
|
+
- **`sign_off` — все гейты `user`, Дирижёр не подписывает за пользователя** — в контенте каждый гейт закрывает ПОЛЬЗОВАТЕЛЬ. Дирижёр ОСТАНАВЛИВАЕТСЯ, показывает артефакт гейта и запрашивает `sign_off(signer="user")` — по одному гейту, без батчинга нескольких подтверждений, и не начинает работу следующего гейта, пока текущий не подписан. Авто-пас на зелёном не применяется (домен суждения, детерминированных авто-чеков нет).
|
|
276
|
+
- **RELEASE_GATE** фиксируется как `sign_off(gate="RELEASE_GATE", signer="user", decision=..., evidence=<RG checklist>)` — не «прозой».
|
|
277
|
+
- **`request_decision` для эскалаций** — конфликты между агентами и waiver'ы обязательных пунктов: `request_decision(...)` → решает пользователь → `record_decision`. См. § Conflict Resolution Protocol.
|
|
278
|
+
- **`record_decision` для ADR-достойных решений** — зафиксированные конфликты и отклонения от брифа, согласованные с пользователем: `record_decision(signer="user", domain="content", task_id, decision_text)`.
|
|
279
|
+
- **Circuit breaker отключён** — в контенте нет полосы откатов в стиле development (DEV/REV/OPS/TEST); возвраты идут через reverse `$handoff` (см. Reverse Handoff).
|
|
280
|
+
- **Degraded mode** — если MCP-поток недоступен: Дирижёр ведёт Master Checklist (`$board`) и статусы Handoff Envelope вручную, sign-off фиксируется явным "Approved" пользователя, эскалации — вручную.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
270
284
|
## Формат ответа Conductor (строго)
|
|
271
285
|
|
|
272
286
|
### Инициализация
|