code-ai-installer 4.0.1-b → 4.0.1
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/LICENSE +1 -1
- package/README.md +5 -5
- package/dist/catalog.js +1 -1
- package/dist/contentTransformer.d.ts +1 -1
- package/dist/contentTransformer.js +39 -0
- package/dist/index.js +10 -5
- package/dist/mcp/cli.js +4 -4
- package/dist/mcp/scorecard.d.ts +2 -2
- package/dist/mcp/task_state.d.ts +2 -2
- package/dist/mcp/tools/advance_gate.js +1 -1
- package/dist/mcp/tools/classify_gate.d.ts +2 -2
- package/dist/mcp/tools/classify_gate.js +2 -2
- package/dist/mcp/tools/load_role.d.ts +2 -2
- package/dist/mcp/tools/load_role.js +2 -2
- package/dist/mcp/tools/report_exception.d.ts +3 -3
- package/dist/mcp/tools/report_exception.js +4 -4
- package/dist/mcp/tools/request_decision.d.ts +3 -3
- package/dist/mcp/tools/request_decision.js +5 -5
- package/dist/mcp/tools/review_proposal.d.ts +1 -1
- package/dist/mcp/tools/review_proposal.js +6 -6
- package/dist/mcp/tools/sign_off.d.ts +2 -2
- package/dist/mcp/tools/sign_off.js +7 -7
- package/dist/mcp/tools/verify_claim.d.ts +1 -1
- package/dist/mcp/tools/verify_claim.js +1 -1
- package/dist/mcp_setup.d.ts +84 -31
- package/dist/mcp_setup.js +182 -66
- package/dist/platforms/adapters.js +54 -19
- package/dist/shared/frontmatter.js +1 -1
- package/dist/shared/persona.d.ts +1 -1
- package/dist/shared/persona.js +1 -1
- package/dist/shared/pipeline.d.ts +10 -10
- package/dist/shared/pipeline.js +7 -7
- package/dist/shared/tools.d.ts +15 -15
- package/dist/shared/tools.js +3 -3
- package/dist/shared/vocabulary.d.ts +4 -4
- package/dist/shared/vocabulary.js +4 -4
- package/dist/types.d.ts +1 -1
- package/domains/analytics/.agents/workflows/analytics-pipeline-rules.md +13 -3
- package/domains/analytics/.agents/workflows/analyze.md +1 -0
- package/domains/analytics/.agents/workflows/quick-insight.md +1 -0
- package/domains/analytics/locales/en/.agents/workflows/analytics-pipeline-rules.md +13 -3
- package/domains/analytics/locales/en/.agents/workflows/analyze.md +1 -0
- package/domains/analytics/locales/en/.agents/workflows/quick-insight.md +1 -0
- package/domains/analytics/locales/en/agents/interviewer.md +2 -1
- package/domains/analytics/locales/en/agents/layouter.md +2 -1
- package/domains/analytics/locales/en/agents/mediator.md +2 -1
- package/domains/analytics/locales/en/agents/researcher.md +2 -1
- package/domains/analytics/locales/en/agents/strategist.md +2 -1
- package/domains/analytics/pipeline.yaml +10 -10
- package/domains/content/.agents/skills/content-release-gate/SKILL.md +3 -5
- package/domains/content/.agents/workflows/content-pipeline-rules.md +14 -11
- package/domains/content/.agents/workflows/edit-content.md +0 -1
- package/domains/content/.agents/workflows/quick-post.md +0 -1
- package/domains/content/.agents/workflows/start-content.md +0 -1
- package/domains/content/agents/conductor.md +1 -2
- package/domains/content/locales/en/.agents/skills/content-release-gate/SKILL.md +3 -5
- package/domains/content/locales/en/.agents/workflows/content-pipeline-rules.md +14 -11
- package/domains/content/locales/en/.agents/workflows/edit-content.md +0 -1
- package/domains/content/locales/en/.agents/workflows/quick-post.md +0 -1
- package/domains/content/locales/en/.agents/workflows/start-content.md +0 -1
- package/domains/content/locales/en/agents/conductor.md +1 -2
- package/domains/content/pipeline.yaml +8 -8
- package/domains/development/.agents/skills/handoff/SKILL.md +276 -276
- package/domains/development/.agents/skills/lava-flow-legacy-detection/SKILL.md +197 -197
- package/domains/development/.agents/skills/mcp-integration/SKILL.md +211 -211
- package/domains/development/.agents/skills/qa-test-data-management/SKILL.md +250 -250
- package/domains/development/.agents/workflows/bugfix.md +16 -82
- package/domains/development/.agents/workflows/hotfix.md +16 -66
- package/domains/development/.agents/workflows/pipeline-rules.md +49 -132
- package/domains/development/.agents/workflows/start-task.md +17 -121
- package/domains/development/AGENTS.md +8 -3
- package/domains/development/agents/architect.md +247 -247
- package/domains/development/agents/conductor.md +363 -363
- package/domains/development/agents/devops.md +297 -297
- package/domains/development/agents/reviewer.md +293 -293
- package/domains/development/agents/senior_full_stack.md +295 -295
- package/domains/development/agents/tester.md +395 -395
- package/domains/development/locales/en/.agents/skills/handoff/SKILL.md +276 -276
- package/domains/development/locales/en/.agents/skills/lava-flow-legacy-detection/SKILL.md +197 -197
- package/domains/development/locales/en/.agents/skills/mcp-integration/SKILL.md +211 -211
- package/domains/development/locales/en/.agents/skills/qa-test-data-management/SKILL.md +250 -250
- package/domains/development/locales/en/.agents/workflows/bugfix.md +16 -82
- package/domains/development/locales/en/.agents/workflows/hotfix.md +15 -65
- package/domains/development/locales/en/.agents/workflows/pipeline-rules.md +48 -131
- package/domains/development/locales/en/.agents/workflows/start-task.md +17 -121
- package/domains/development/locales/en/AGENTS.md +15 -0
- package/domains/development/locales/en/agents/architect.md +247 -247
- package/domains/development/locales/en/agents/conductor.md +363 -363
- package/domains/development/locales/en/agents/devops.md +297 -297
- package/domains/development/locales/en/agents/reviewer.md +293 -293
- package/domains/development/locales/en/agents/senior_full_stack.md +295 -295
- package/domains/development/locales/en/agents/tester.md +395 -395
- package/domains/development/locales/en/prompt-examples.md +34 -120
- package/domains/development/pipeline.yaml +150 -135
- package/domains/development/prompt-examples.md +33 -119
- package/domains/product/.agents/workflows/product-pipeline-rules.md +13 -2
- package/domains/product/.agents/workflows/quick-pm.md +1 -1
- package/domains/product/.agents/workflows/shape-prioritize.md +1 -0
- package/domains/product/.agents/workflows/ship-right-thing.md +1 -0
- package/domains/product/.agents/workflows/spec.md +1 -0
- package/domains/product/agents/tech_lead.md +1 -1
- package/domains/product/locales/en/.agents/workflows/product-pipeline-rules.md +13 -2
- package/domains/product/locales/en/.agents/workflows/quick-pm.md +1 -1
- package/domains/product/locales/en/.agents/workflows/shape-prioritize.md +1 -0
- package/domains/product/locales/en/.agents/workflows/ship-right-thing.md +1 -0
- package/domains/product/locales/en/.agents/workflows/spec.md +1 -0
- package/domains/product/locales/en/agents/conductor.md +2 -2
- package/domains/product/locales/en/agents/data_analyst.md +2 -1
- package/domains/product/locales/en/agents/designer.md +2 -1
- package/domains/product/locales/en/agents/discovery.md +2 -1
- package/domains/product/locales/en/agents/layouter.md +2 -1
- package/domains/product/locales/en/agents/mediator.md +2 -1
- package/domains/product/locales/en/agents/pm.md +2 -1
- package/domains/product/locales/en/agents/product_strategist.md +2 -1
- package/domains/product/locales/en/agents/tech_lead.md +3 -2
- package/domains/product/locales/en/agents/ux_designer.md +2 -1
- package/domains/product/pipeline.yaml +12 -12
- package/package.json +5 -5
- package/domains/analytics/CONTEXT.md +0 -25
- package/domains/analytics/locales/en/CONTEXT.md +0 -25
- package/domains/content/CONTEXT.md +0 -19
- package/domains/content/locales/en/CONTEXT.md +0 -19
- package/domains/development/.agents/workflows/auto-restart-containers.md +0 -56
- package/domains/development/CONTEXT.md +0 -62
- package/domains/development/locales/en/.agents/workflows/auto-restart-containers.md +0 -24
- package/domains/development/locales/en/CONTEXT.md +0 -62
- package/domains/product/CONTEXT.md +0 -40
- package/domains/product/locales/en/CONTEXT.md +0 -40
package/dist/mcp_setup.d.ts
CHANGED
|
@@ -2,16 +2,26 @@ import type { DomainId } from "./shared/index.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* MCP auto-setup for `--target=claude` installs.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Responsibilities:
|
|
6
6
|
* 1. Detect / install MemPalace as an opt-in mirror for decision storage.
|
|
7
|
-
* 2.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* 2. Register `code-ai-mcp` (always) and `mempalace` (when accepted) in
|
|
8
|
+
* Claude Code's USER (global) scope via `claude mcp add --scope user`,
|
|
9
|
+
* so the servers are available across all the user's projects.
|
|
10
|
+
* 3. Write `.code-ai/config.json` so `code-ai-mcp` knows which backend +
|
|
11
|
+
* domain to use. This stays PROJECT-local — the global server reads it
|
|
12
|
+
* from the project cwd at runtime.
|
|
13
|
+
*
|
|
14
|
+
* Why user scope (not a project `.mcp.json`): MCP servers are tools the user
|
|
15
|
+
* wants everywhere, not per-project copies. Writing a project `.mcp.json` both
|
|
16
|
+
* scattered `code-ai-mcp` per-project and duplicated an already-global
|
|
17
|
+
* `mempalace` into the project. Registration is idempotent — a server already
|
|
18
|
+
* present in user scope is left untouched.
|
|
10
19
|
*
|
|
11
20
|
* Non-Claude targets skip this whole flow — MCP is Claude-specific.
|
|
12
21
|
*
|
|
13
|
-
* Graceful degradation: every step is best-effort.
|
|
14
|
-
*
|
|
22
|
+
* Graceful degradation: every step is best-effort. If the `claude` CLI is not
|
|
23
|
+
* on PATH we never touch the user's config by hand — we print the exact
|
|
24
|
+
* `claude mcp add` commands to run manually instead.
|
|
15
25
|
*/
|
|
16
26
|
export type PythonRuntimeTool = "uv" | "pipx" | "pip";
|
|
17
27
|
export interface PythonRuntime {
|
|
@@ -25,15 +35,22 @@ export interface McpServerEntry {
|
|
|
25
35
|
args: string[];
|
|
26
36
|
env?: Record<string, string>;
|
|
27
37
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Minimal `claude` CLI surface this module needs. Injectable so tests can
|
|
40
|
+
* assert the constructed argv without spawning the real CLI.
|
|
41
|
+
*/
|
|
42
|
+
export interface ClaudeCli {
|
|
43
|
+
/** Run `claude <args>`; resolve { ok, output } (combined stdout+stderr). */
|
|
44
|
+
run(args: string[]): Promise<{
|
|
45
|
+
ok: boolean;
|
|
46
|
+
output: string;
|
|
47
|
+
}>;
|
|
31
48
|
}
|
|
32
49
|
export interface McpSetupOptions {
|
|
33
50
|
destinationDir: string;
|
|
34
51
|
/** User's answer to the MemPalace prompt (true = wants it). */
|
|
35
52
|
wantMempalace: boolean;
|
|
36
|
-
/** When true, no files are written;
|
|
53
|
+
/** When true, no commands run and no files are written; report reflects plan. */
|
|
37
54
|
dryRun: boolean;
|
|
38
55
|
/**
|
|
39
56
|
* The selected domain, written into `.code-ai/config.json` so the gate tools
|
|
@@ -47,19 +64,29 @@ export interface McpSetupReport {
|
|
|
47
64
|
mempalaceInstallAttempted: boolean;
|
|
48
65
|
mempalaceInstallSucceeded: boolean;
|
|
49
66
|
pythonRuntime: PythonRuntime | null;
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
/** Was the `claude` CLI found on PATH? When false, registration falls back to manual instructions. */
|
|
68
|
+
claudeCliAvailable: boolean;
|
|
69
|
+
/** How servers were registered. */
|
|
70
|
+
registration: "user-scope" | "manual-fallback";
|
|
71
|
+
/** Server names freshly added to user scope this run. */
|
|
72
|
+
serversRegistered: string[];
|
|
73
|
+
/** Server names already present in user scope (left untouched). */
|
|
74
|
+
serversAlreadyPresent: string[];
|
|
75
|
+
/** Server names whose registration failed. */
|
|
76
|
+
serversFailed: string[];
|
|
52
77
|
configPath: string;
|
|
53
78
|
notices: string[];
|
|
54
79
|
}
|
|
55
80
|
/**
|
|
56
81
|
* Try `mempalace-mcp --help` — the dedicated MCP-server bin, which is exactly
|
|
57
|
-
* what we register
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
82
|
+
* what we register. Resolves true on exit code 0, false otherwise (including
|
|
83
|
+
* ENOENT — bin not on PATH). Probing the actual server bin (not the `mempalace`
|
|
84
|
+
* CLI) means a stale MemPalace without `mempalace-mcp` triggers a (re)install
|
|
85
|
+
* instead of registering a config that can't launch.
|
|
61
86
|
*/
|
|
62
87
|
export declare function detectMemPalace(): Promise<boolean>;
|
|
88
|
+
/** True when the `claude` CLI is on PATH (probed via `claude --version`). */
|
|
89
|
+
export declare function detectClaudeCli(cli?: ClaudeCli): Promise<boolean>;
|
|
63
90
|
/**
|
|
64
91
|
* Find the first available Python install runtime, in preference order:
|
|
65
92
|
* uv → pipx → pip. Returns null if none available.
|
|
@@ -75,21 +102,24 @@ export declare function installMemPalace(runtime: PythonRuntime): Promise<{
|
|
|
75
102
|
output: string;
|
|
76
103
|
}>;
|
|
77
104
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* - Existing valid JSON: merge `mcpServers`; if a server name already exists,
|
|
82
|
-
* keep the existing entry untouched and add a notice.
|
|
83
|
-
* - Malformed JSON: abort (return action="skipped") with notice — never clobber.
|
|
105
|
+
* Build the argv for `claude mcp add --scope user <name> -- <command> [args]`.
|
|
106
|
+
* Pure — exported for testing. Env vars become `-e KEY=VALUE` flags placed
|
|
107
|
+
* before the server name (as `claude mcp add` expects options first).
|
|
84
108
|
*/
|
|
85
|
-
export declare function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
export declare function buildClaudeAddArgs(name: string, entry: McpServerEntry): string[];
|
|
110
|
+
/** Build the argv for `claude mcp remove --scope user <name>`. Pure — exported for testing. */
|
|
111
|
+
export declare function buildClaudeRemoveArgs(name: string): string[];
|
|
112
|
+
/**
|
|
113
|
+
* Is `name` already registered in Claude's USER scope? Uses `claude mcp get`,
|
|
114
|
+
* whose output prints `Scope: User config` for user-scope servers. A server in
|
|
115
|
+
* a different scope (local/project) returns false here — we still want it in
|
|
116
|
+
* user scope.
|
|
117
|
+
*/
|
|
118
|
+
export declare function isRegisteredInUserScope(cli: ClaudeCli, name: string): Promise<boolean>;
|
|
90
119
|
/**
|
|
91
120
|
* Write `<destinationDir>/.code-ai/config.json` with the chosen backend.
|
|
92
121
|
* Idempotent — re-running overwrites with the same content if backend unchanged.
|
|
122
|
+
* Stays project-local: the user-scope `code-ai-mcp` reads it from the cwd.
|
|
93
123
|
*/
|
|
94
124
|
export declare function writeCodeAiConfig(destinationDir: string, config: {
|
|
95
125
|
decision_store: "jsonl" | "mempalace";
|
|
@@ -103,10 +133,33 @@ export declare function writeCodeAiConfig(destinationDir: string, config: {
|
|
|
103
133
|
* Order:
|
|
104
134
|
* 1. If user wants MemPalace: detect → install if absent → fall back if install fails.
|
|
105
135
|
* 2. Build server entries (code-ai-mcp always; mempalace only when usable).
|
|
106
|
-
* 3.
|
|
107
|
-
*
|
|
136
|
+
* 3. Register them in Claude USER scope via `claude mcp add --scope user`
|
|
137
|
+
* (idempotent; skip if already present). If `claude` CLI is absent, emit
|
|
138
|
+
* manual commands instead of touching the config by hand.
|
|
139
|
+
* 4. Write project-local `.code-ai/config.json` with the backend choice.
|
|
108
140
|
*
|
|
109
141
|
* Reports all decisions in `McpSetupReport.notices` so callers can surface
|
|
110
|
-
* them to the user verbatim.
|
|
142
|
+
* them to the user verbatim. `cli` is injectable for testing.
|
|
111
143
|
*/
|
|
112
|
-
export declare function setupMcp(opts: McpSetupOptions): Promise<McpSetupReport>;
|
|
144
|
+
export declare function setupMcp(opts: McpSetupOptions, cli?: ClaudeCli): Promise<McpSetupReport>;
|
|
145
|
+
export interface McpTeardownReport {
|
|
146
|
+
claudeCliAvailable: boolean;
|
|
147
|
+
removal: "user-scope" | "manual-fallback";
|
|
148
|
+
serversRemoved: string[];
|
|
149
|
+
serversNotPresent: string[];
|
|
150
|
+
serversFailed: string[];
|
|
151
|
+
notices: string[];
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Remove the installer-owned MCP server(s) from Claude's USER scope via
|
|
155
|
+
* `claude mcp remove --scope user`. Idempotent — a server that isn't registered
|
|
156
|
+
* is reported as "nothing to remove". If the `claude` CLI is absent, prints the
|
|
157
|
+
* manual commands instead of touching the config. `cli` is injectable for tests.
|
|
158
|
+
*
|
|
159
|
+
* NOTE: the registration is global (shared across all projects), so this removes
|
|
160
|
+
* code-ai-mcp for every project. That is the chosen behaviour (symmetric with
|
|
161
|
+
* install); a multi-project user re-runs the installer to restore it.
|
|
162
|
+
*/
|
|
163
|
+
export declare function teardownMcp(opts: {
|
|
164
|
+
dryRun: boolean;
|
|
165
|
+
}, cli?: ClaudeCli): Promise<McpTeardownReport>;
|
package/dist/mcp_setup.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { mkdir,
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
const defaultClaudeCli = {
|
|
5
|
+
run: (args) => spawnCapture("claude", args),
|
|
6
|
+
};
|
|
4
7
|
/**
|
|
5
8
|
* Try `mempalace-mcp --help` — the dedicated MCP-server bin, which is exactly
|
|
6
|
-
* what we register
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* what we register. Resolves true on exit code 0, false otherwise (including
|
|
10
|
+
* ENOENT — bin not on PATH). Probing the actual server bin (not the `mempalace`
|
|
11
|
+
* CLI) means a stale MemPalace without `mempalace-mcp` triggers a (re)install
|
|
12
|
+
* instead of registering a config that can't launch.
|
|
10
13
|
*/
|
|
11
14
|
export async function detectMemPalace() {
|
|
12
15
|
return spawnExitZero("mempalace-mcp", ["--help"]);
|
|
13
16
|
}
|
|
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
|
+
}
|
|
14
22
|
/**
|
|
15
23
|
* Find the first available Python install runtime, in preference order:
|
|
16
24
|
* uv → pipx → pip. Returns null if none available.
|
|
@@ -48,49 +56,60 @@ function installCommandArgs(runtime) {
|
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* - Existing valid JSON: merge `mcpServers`; if a server name already exists,
|
|
55
|
-
* keep the existing entry untouched and add a notice.
|
|
56
|
-
* - Malformed JSON: abort (return action="skipped") with notice — never clobber.
|
|
59
|
+
* Build the argv for `claude mcp add --scope user <name> -- <command> [args]`.
|
|
60
|
+
* Pure — exported for testing. Env vars become `-e KEY=VALUE` flags placed
|
|
61
|
+
* before the server name (as `claude mcp add` expects options first).
|
|
57
62
|
*/
|
|
58
|
-
export
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
export function buildClaudeAddArgs(name, entry) {
|
|
64
|
+
const envFlags = entry.env
|
|
65
|
+
? Object.entries(entry.env).flatMap(([k, v]) => ["-e", `${k}=${v}`])
|
|
66
|
+
: [];
|
|
67
|
+
return ["mcp", "add", ...envFlags, "--scope", "user", name, "--", entry.command, ...entry.args];
|
|
68
|
+
}
|
|
69
|
+
/** Build the argv for `claude mcp remove --scope user <name>`. Pure — exported for testing. */
|
|
70
|
+
export function buildClaudeRemoveArgs(name) {
|
|
71
|
+
return ["mcp", "remove", "--scope", "user", name];
|
|
72
|
+
}
|
|
73
|
+
/** Human-readable manual command, for the no-CLI fallback notice. */
|
|
74
|
+
function manualAddCommand(name, entry) {
|
|
75
|
+
return `claude ${buildClaudeAddArgs(name, entry).join(" ")}`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Is `name` already registered in Claude's USER scope? Uses `claude mcp get`,
|
|
79
|
+
* whose output prints `Scope: User config` for user-scope servers. A server in
|
|
80
|
+
* a different scope (local/project) returns false here — we still want it in
|
|
81
|
+
* user scope.
|
|
82
|
+
*/
|
|
83
|
+
export async function isRegisteredInUserScope(cli, name) {
|
|
84
|
+
const res = await cli.run(["mcp", "get", name]);
|
|
85
|
+
return res.ok && /Scope:\s*User config/i.test(res.output);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Register one server in user scope, idempotently. Skips if already present in
|
|
89
|
+
* user scope. Never throws — returns the outcome + a notice for the report.
|
|
90
|
+
*/
|
|
91
|
+
async function addServerToUserScope(cli, name, entry) {
|
|
92
|
+
if (await isRegisteredInUserScope(cli, name)) {
|
|
93
|
+
return {
|
|
94
|
+
outcome: "already-present",
|
|
95
|
+
notice: `MCP server '${name}' already registered in user scope — left untouched.`,
|
|
96
|
+
};
|
|
84
97
|
}
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
98
|
+
const res = await cli.run(buildClaudeAddArgs(name, entry));
|
|
99
|
+
if (res.ok) {
|
|
100
|
+
return { outcome: "registered", notice: `Registered MCP server '${name}' in user scope (global).` };
|
|
88
101
|
}
|
|
89
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
outcome: "failed",
|
|
104
|
+
notice: `Failed to register '${name}' in user scope. Run it manually:\n` +
|
|
105
|
+
` ${manualAddCommand(name, entry)}\n` +
|
|
106
|
+
` Output:\n${res.output}`,
|
|
107
|
+
};
|
|
90
108
|
}
|
|
91
109
|
/**
|
|
92
110
|
* Write `<destinationDir>/.code-ai/config.json` with the chosen backend.
|
|
93
111
|
* Idempotent — re-running overwrites with the same content if backend unchanged.
|
|
112
|
+
* Stays project-local: the user-scope `code-ai-mcp` reads it from the cwd.
|
|
94
113
|
*/
|
|
95
114
|
export async function writeCodeAiConfig(destinationDir, config, dryRun = false) {
|
|
96
115
|
const dir = join(destinationDir, ".code-ai");
|
|
@@ -101,19 +120,43 @@ export async function writeCodeAiConfig(destinationDir, config, dryRun = false)
|
|
|
101
120
|
}
|
|
102
121
|
return { path };
|
|
103
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Build the server entries to register. `code-ai-mcp` always; `mempalace` only
|
|
125
|
+
* when usable.
|
|
126
|
+
*/
|
|
127
|
+
function buildServerEntries(mempalaceUsed) {
|
|
128
|
+
// DEV-100 consolidation: the code-ai-mcp bin lives inside the
|
|
129
|
+
// `code-ai-installer` npm package. `npx -p <package> <bin>` tells npm to
|
|
130
|
+
// install that package and run the named bin from it.
|
|
131
|
+
const servers = {
|
|
132
|
+
"code-ai-mcp": {
|
|
133
|
+
command: "npx",
|
|
134
|
+
args: ["-y", "-p", "code-ai-installer", "code-ai-mcp"],
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
if (mempalaceUsed) {
|
|
138
|
+
// Use the dedicated `mempalace-mcp` stdio server bin (NOT `mempalace mcp`):
|
|
139
|
+
// the full CLI subcommand can emit to stdout before the MCP handshake and
|
|
140
|
+
// corrupt the JSON-RPC stream, so Claude Code fails to launch it.
|
|
141
|
+
servers["mempalace"] = { command: "mempalace-mcp", args: [] };
|
|
142
|
+
}
|
|
143
|
+
return servers;
|
|
144
|
+
}
|
|
104
145
|
/**
|
|
105
146
|
* End-to-end orchestrator. Called from the install flow when `target=claude`.
|
|
106
147
|
*
|
|
107
148
|
* Order:
|
|
108
149
|
* 1. If user wants MemPalace: detect → install if absent → fall back if install fails.
|
|
109
150
|
* 2. Build server entries (code-ai-mcp always; mempalace only when usable).
|
|
110
|
-
* 3.
|
|
111
|
-
*
|
|
151
|
+
* 3. Register them in Claude USER scope via `claude mcp add --scope user`
|
|
152
|
+
* (idempotent; skip if already present). If `claude` CLI is absent, emit
|
|
153
|
+
* manual commands instead of touching the config by hand.
|
|
154
|
+
* 4. Write project-local `.code-ai/config.json` with the backend choice.
|
|
112
155
|
*
|
|
113
156
|
* Reports all decisions in `McpSetupReport.notices` so callers can surface
|
|
114
|
-
* them to the user verbatim.
|
|
157
|
+
* them to the user verbatim. `cli` is injectable for testing.
|
|
115
158
|
*/
|
|
116
|
-
export async function setupMcp(opts) {
|
|
159
|
+
export async function setupMcp(opts, cli = defaultClaudeCli) {
|
|
117
160
|
const notices = [];
|
|
118
161
|
let mempalaceUsed = false;
|
|
119
162
|
let mempalaceInstallAttempted = false;
|
|
@@ -144,26 +187,39 @@ export async function setupMcp(opts) {
|
|
|
144
187
|
}
|
|
145
188
|
}
|
|
146
189
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
const servers = buildServerEntries(mempalaceUsed);
|
|
191
|
+
const serversRegistered = [];
|
|
192
|
+
const serversAlreadyPresent = [];
|
|
193
|
+
const serversFailed = [];
|
|
194
|
+
const claudeCliAvailable = !opts.dryRun && (await detectClaudeCli(cli));
|
|
195
|
+
let registration;
|
|
196
|
+
if (opts.dryRun) {
|
|
197
|
+
registration = "user-scope";
|
|
198
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
199
|
+
notices.push(`Would register MCP server '${name}' in user scope: ${manualAddCommand(name, entry)}`);
|
|
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);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
registration = "manual-fallback";
|
|
217
|
+
notices.push("The `claude` CLI was not found on PATH — not modifying your global config automatically. " +
|
|
218
|
+
"Register the MCP server(s) in user scope by running:");
|
|
219
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
220
|
+
notices.push(` ${manualAddCommand(name, entry)}`);
|
|
221
|
+
}
|
|
164
222
|
}
|
|
165
|
-
const mcp = await mergeMcpJson(opts.destinationDir, servers, opts.dryRun);
|
|
166
|
-
notices.push(...mcp.notices);
|
|
167
223
|
const cfg = await writeCodeAiConfig(opts.destinationDir, {
|
|
168
224
|
decision_store: mempalaceUsed ? "mempalace" : "jsonl",
|
|
169
225
|
...(opts.domain ? { domain: opts.domain } : {}),
|
|
@@ -173,12 +229,72 @@ export async function setupMcp(opts) {
|
|
|
173
229
|
mempalaceInstallAttempted,
|
|
174
230
|
mempalaceInstallSucceeded,
|
|
175
231
|
pythonRuntime,
|
|
176
|
-
|
|
177
|
-
|
|
232
|
+
claudeCliAvailable,
|
|
233
|
+
registration,
|
|
234
|
+
serversRegistered,
|
|
235
|
+
serversAlreadyPresent,
|
|
236
|
+
serversFailed,
|
|
178
237
|
configPath: cfg.path,
|
|
179
238
|
notices,
|
|
180
239
|
};
|
|
181
240
|
}
|
|
241
|
+
// ─── Teardown (uninstall) ───────────────────────────────────────────────────
|
|
242
|
+
/**
|
|
243
|
+
* Servers the installer registers and is therefore responsible for removing on
|
|
244
|
+
* uninstall. `mempalace` is intentionally absent — it is the user's own memory
|
|
245
|
+
* server and may predate code-ai, so uninstall never touches it.
|
|
246
|
+
*/
|
|
247
|
+
const INSTALLER_OWNED_SERVERS = ["code-ai-mcp"];
|
|
248
|
+
/**
|
|
249
|
+
* Remove the installer-owned MCP server(s) from Claude's USER scope via
|
|
250
|
+
* `claude mcp remove --scope user`. Idempotent — a server that isn't registered
|
|
251
|
+
* is reported as "nothing to remove". If the `claude` CLI is absent, prints the
|
|
252
|
+
* manual commands instead of touching the config. `cli` is injectable for tests.
|
|
253
|
+
*
|
|
254
|
+
* NOTE: the registration is global (shared across all projects), so this removes
|
|
255
|
+
* code-ai-mcp for every project. That is the chosen behaviour (symmetric with
|
|
256
|
+
* install); a multi-project user re-runs the installer to restore it.
|
|
257
|
+
*/
|
|
258
|
+
export async function teardownMcp(opts, cli = defaultClaudeCli) {
|
|
259
|
+
const notices = [];
|
|
260
|
+
const serversRemoved = [];
|
|
261
|
+
const serversNotPresent = [];
|
|
262
|
+
const serversFailed = [];
|
|
263
|
+
if (opts.dryRun) {
|
|
264
|
+
for (const name of INSTALLER_OWNED_SERVERS) {
|
|
265
|
+
notices.push(`Would remove MCP server '${name}' from user scope: claude ${buildClaudeRemoveArgs(name).join(" ")}`);
|
|
266
|
+
}
|
|
267
|
+
return { claudeCliAvailable: false, removal: "user-scope", serversRemoved, serversNotPresent, serversFailed, notices };
|
|
268
|
+
}
|
|
269
|
+
const claudeCliAvailable = await detectClaudeCli(cli);
|
|
270
|
+
if (!claudeCliAvailable) {
|
|
271
|
+
notices.push("The `claude` CLI was not found on PATH — not modifying your global config automatically. " +
|
|
272
|
+
"Remove the MCP server(s) from user scope by running:");
|
|
273
|
+
for (const name of INSTALLER_OWNED_SERVERS) {
|
|
274
|
+
notices.push(` claude ${buildClaudeRemoveArgs(name).join(" ")}`);
|
|
275
|
+
}
|
|
276
|
+
return { claudeCliAvailable, removal: "manual-fallback", serversRemoved, serversNotPresent, serversFailed, notices };
|
|
277
|
+
}
|
|
278
|
+
for (const name of INSTALLER_OWNED_SERVERS) {
|
|
279
|
+
if (!(await isRegisteredInUserScope(cli, name))) {
|
|
280
|
+
serversNotPresent.push(name);
|
|
281
|
+
notices.push(`MCP server '${name}' is not registered in user scope — nothing to remove.`);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const res = await cli.run(buildClaudeRemoveArgs(name));
|
|
285
|
+
if (res.ok) {
|
|
286
|
+
serversRemoved.push(name);
|
|
287
|
+
notices.push(`Removed MCP server '${name}' from user scope.`);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
serversFailed.push(name);
|
|
291
|
+
notices.push(`Failed to remove '${name}' from user scope. Run it manually:\n` +
|
|
292
|
+
` claude ${buildClaudeRemoveArgs(name).join(" ")}\n` +
|
|
293
|
+
` Output:\n${res.output}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return { claudeCliAvailable, removal: "user-scope", serversRemoved, serversNotPresent, serversFailed, notices };
|
|
297
|
+
}
|
|
182
298
|
// ─── Subprocess helpers ─────────────────────────────────────────────────────
|
|
183
299
|
async function spawnExitZero(command, args) {
|
|
184
300
|
return new Promise((resolve) => {
|
|
@@ -15,7 +15,8 @@ const targetLayouts = {
|
|
|
15
15
|
agentsDir: ".claude/agents",
|
|
16
16
|
skillsDir: ".claude/skills",
|
|
17
17
|
workflowsDir: ".claude/workflows",
|
|
18
|
-
|
|
18
|
+
commandsDir: ".claude/commands",
|
|
19
|
+
notes: "Uses CLAUDE.md and local .claude folder for role/skill docs; command-type workflows install as slash commands under .claude/commands.",
|
|
19
20
|
},
|
|
20
21
|
"qwen-3.5": {
|
|
21
22
|
instructionFile: "QWEN.md",
|
|
@@ -192,15 +193,7 @@ function planForLayout(layout, catalog, destinationDir, selectedAgents, selected
|
|
|
192
193
|
target,
|
|
193
194
|
});
|
|
194
195
|
}
|
|
195
|
-
|
|
196
|
-
for (const workflowPath of Object.values(catalog.workflowFiles)) {
|
|
197
|
-
operations.push({
|
|
198
|
-
sourcePath: workflowPath,
|
|
199
|
-
destinationPath: path.join(destinationDir, layout.workflowsDir, path.basename(workflowPath)),
|
|
200
|
-
generated: false,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
196
|
+
appendWorkflowOperations(operations, layout, catalog, destinationDir, target);
|
|
204
197
|
appendExtraFileOperations(operations, catalog, destinationDir);
|
|
205
198
|
return operations;
|
|
206
199
|
}
|
|
@@ -288,18 +281,60 @@ function planForGeminiLayout(layout, catalog, destinationDir, selectedAgents, se
|
|
|
288
281
|
target,
|
|
289
282
|
});
|
|
290
283
|
}
|
|
291
|
-
|
|
292
|
-
for (const workflowPath of Object.values(catalog.workflowFiles)) {
|
|
293
|
-
operations.push({
|
|
294
|
-
sourcePath: workflowPath,
|
|
295
|
-
destinationPath: path.join(destinationDir, layout.workflowsDir, path.basename(workflowPath)),
|
|
296
|
-
generated: false,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
284
|
+
appendWorkflowOperations(operations, layout, catalog, destinationDir, target);
|
|
300
285
|
appendExtraFileOperations(operations, catalog, destinationDir);
|
|
301
286
|
return operations;
|
|
302
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Returns true when a workflow file is a user-invokable command (installed as a
|
|
290
|
+
* slash command on platforms that expose a commands directory), false for
|
|
291
|
+
* pipeline rule / reference docs that stay in the workflows directory.
|
|
292
|
+
*
|
|
293
|
+
* Convention-based classifier (no source-file metadata): files ending in
|
|
294
|
+
* `pipeline-rules.md` and the implicit `auto-restart-containers.md` rule are
|
|
295
|
+
* NOT commands; everything else is. Fail-safe — an unrecognised name defaults
|
|
296
|
+
* to a command so it stays visible under commands/ rather than being hidden.
|
|
297
|
+
* @param fileName Workflow file base name.
|
|
298
|
+
* @returns True when the file is a command, false for a rule/reference doc.
|
|
299
|
+
*/
|
|
300
|
+
function isCommandWorkflow(fileName) {
|
|
301
|
+
const lower = fileName.toLowerCase();
|
|
302
|
+
if (lower.endsWith("pipeline-rules.md")) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (lower === "auto-restart-containers.md") {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Appends workflow copy operations, routing command-type files to the platform
|
|
312
|
+
* commands directory (when the layout defines one) and rule / reference docs to
|
|
313
|
+
* the workflows directory. Platforms without a commands directory keep all
|
|
314
|
+
* workflow files in the workflows directory (unchanged behaviour).
|
|
315
|
+
* @param operations Mutable operations list.
|
|
316
|
+
* @param layout Platform layout.
|
|
317
|
+
* @param catalog Source catalog with workflowFiles.
|
|
318
|
+
* @param destinationDir Destination root.
|
|
319
|
+
*/
|
|
320
|
+
function appendWorkflowOperations(operations, layout, catalog, destinationDir, target) {
|
|
321
|
+
for (const workflowPath of Object.values(catalog.workflowFiles)) {
|
|
322
|
+
const fileName = path.basename(workflowPath);
|
|
323
|
+
const targetDir = layout.commandsDir && isCommandWorkflow(fileName) ? layout.commandsDir : layout.workflowsDir;
|
|
324
|
+
if (!targetDir) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
operations.push({
|
|
328
|
+
sourcePath: workflowPath,
|
|
329
|
+
destinationPath: path.join(destinationDir, targetDir, fileName),
|
|
330
|
+
generated: false,
|
|
331
|
+
transform: {
|
|
332
|
+
target,
|
|
333
|
+
assetType: "workflow",
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
303
338
|
/**
|
|
304
339
|
* Appends copy operations for extra root-level files (e.g. prompt-examples.md).
|
|
305
340
|
* @param operations Mutable operations list.
|
|
@@ -61,7 +61,7 @@ export const SkillFrontmatter = z.object({
|
|
|
61
61
|
* n8n-pinecone-qdrant-supabase-reference). Workflows may use tactical per-skill raises
|
|
62
62
|
* up to 400 (precedent ADR-DEV-021 raised k8s-manifests-conventions workflow to 350).
|
|
63
63
|
* Raised to 800 in ADR-DEV-034 for state-zustand-beast-practices-reference (750)
|
|
64
|
-
* — densest examples skill in codebase,
|
|
64
|
+
* — densest examples skill in codebase, the user-mandated cap prevents trim. */
|
|
65
65
|
budget_lines: z.number().int().positive().max(SKILL_BUDGET_LINES_MAX).default(300),
|
|
66
66
|
/** Frontmatter version. Bumped when SkillFrontmatter shape changes. */
|
|
67
67
|
schema_version: z.literal(1).default(1),
|
package/dist/shared/persona.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
* - persona-user-{handle}.md is local-only (gitignored, frontmatter flag, files-whitelist excluded)
|
|
8
8
|
* - the merged persona is what `load_role` returns to SessionStart
|
|
9
9
|
*
|
|
10
|
-
* Provenance per property is mandatory (
|
|
10
|
+
* Provenance per property is mandatory (the user: "must be visible where each rule came from").
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
13
13
|
* Provenance pointer — where a persona property was derived from.
|
package/dist/shared/persona.js
CHANGED
|
@@ -7,7 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
* - persona-user-{handle}.md is local-only (gitignored, frontmatter flag, files-whitelist excluded)
|
|
8
8
|
* - the merged persona is what `load_role` returns to SessionStart
|
|
9
9
|
*
|
|
10
|
-
* Provenance per property is mandatory (
|
|
10
|
+
* Provenance per property is mandatory (the user: "must be visible where each rule came from").
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
13
13
|
* Provenance pointer — where a persona property was derived from.
|