@uniqbit/mate-core 0.15.3 → 0.15.4-canary.10
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/claude-plugin/.claude-plugin/plugin.json +4 -0
- package/claude-plugin/hooks/artifact-finish-nudge.mjs +8 -0
- package/claude-plugin/hooks/hooks.json +37 -0
- package/claude-plugin/hooks/session-banner.mjs +8 -0
- package/claude-plugin/hooks/ts-loader.mjs +28 -0
- package/claude-plugin/hooks/validate-artifact-path.mjs +8 -0
- package/package.json +4 -2
- package/src/cli/commands/artifact/finish/openspec.ts +1 -5
- package/src/cli/commands/companion/hub.ts +99 -0
- package/src/cli/commands/companion/link.ts +0 -5
- package/src/cli/commands/companion/tui.ts +3 -2
- package/src/cli/commands/doctor.ts +254 -170
- package/src/cli/commands/install.ts +18 -14
- package/src/cli/commands/launch/shared.ts +4 -4
- package/src/cli/commands/plugin/install.ts +94 -0
- package/src/cli/commands/plugin/plugin.ts +17 -0
- package/src/cli/commands/setup.ts +4 -6
- package/src/cli/commands/update.ts +9 -11
- package/src/cli/companion-link-wizard.tsx +22 -78
- package/src/cli/main.ts +57 -13
- package/src/cli/plugin-commands.ts +4 -4
- package/src/cli/repo-list-table.tsx +2 -7
- package/src/cli/usage.ts +7 -3
- package/src/distribution.ts +3 -5
- package/src/framework.ts +4 -36
- package/src/hooks/artifact-finish-nudge.ts +212 -0
- package/src/hooks/session-banner.ts +25 -0
- package/src/hooks/validate-artifact-path.ts +236 -0
- package/src/index.ts +4 -0
- package/src/lib/context-mode-package.ts +5 -3
- package/src/lib/install.ts +31 -43
- package/src/lib/orchestrator/adapters/base.ts +7 -8
- package/src/lib/orchestrator/adapters/claude.ts +16 -0
- package/src/lib/orchestrator/adapters/opencode.ts +10 -60
- package/src/lib/orchestrator/companion-hub.ts +380 -0
- package/src/lib/orchestrator/companion-store.ts +1 -35
- package/src/lib/orchestrator/config-store.ts +94 -7
- package/src/lib/orchestrator/editor.ts +4 -20
- package/src/lib/orchestrator/framework-context.ts +119 -24
- package/src/lib/orchestrator/global-config-store.ts +1 -5
- package/src/lib/orchestrator/launcher.ts +3 -9
- package/src/lib/orchestrator/migration.ts +0 -23
- package/src/lib/orchestrator/opencode-guidance.ts +1 -2
- package/src/lib/orchestrator/repo-local-registry.ts +3 -1
- package/src/lib/orchestrator/root-context.ts +117 -0
- package/src/lib/orchestrator/setup-compatibilities.ts +1 -1
- package/src/lib/orchestrator/setup-preflight.ts +5 -18
- package/src/lib/orchestrator/types.ts +27 -19
- package/src/lib/orchestrator/working-repo-store.ts +9 -3
- package/src/lib/package-paths.ts +37 -0
- package/src/lib/update-checker.ts +5 -5
- package/src/playbooks/companion-guidance.ts +7 -7
- package/src/runtime/env.ts +0 -4
- package/src/templates/capabilities/openspec-cap/mate-skills/{mate-artifact-finish → agents/mate-artifact-finish}/SKILL.md +8 -8
- package/src/templates/capabilities/openspec-cap/mate-skills/{mate-artifact-finish → agents/mate-artifact-finish}/references/openspec.md +3 -3
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/SKILL.md +58 -0
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/references/openspec.md +139 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +6 -5
- package/src/templates/root/TEMPLATE_AGENTS.md +2 -0
- package/src/templates/root/TEMPLATE_CLAUDE.md +2 -0
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3541 -0
- package/src/tools/setup/capabilities/context-mode.ts +57 -83
- package/src/tools/setup/capabilities/graphify-shared.ts +27 -0
- package/src/tools/setup/capabilities/graphify.ts +86 -295
- package/src/tools/setup/capabilities/openspec.ts +27 -30
- package/src/tools/setup/capabilities/react-doctor.ts +37 -37
- package/src/tools/setup/capabilities/rtk.ts +4 -1
- package/src/tools/setup/capabilities/tokensave-shared.ts +6 -0
- package/src/tools/setup/capabilities/tokensave.ts +50 -71
- package/src/tools/setup/context-services.ts +29 -0
- package/src/tools/setup/dynamic-plugins/host.ts +2 -1
- package/src/tools/setup/dynamic-plugins/hydrate.ts +3 -13
- package/src/tools/setup/dynamic-plugins/install.ts +124 -109
- package/src/tools/setup/dynamic-plugins/loader.ts +23 -58
- package/src/tools/setup/dynamic-plugins/paths.ts +8 -19
- package/src/tools/setup/dynamic-plugins/registry-hint.ts +14 -0
- package/src/tools/setup/engine.ts +65 -1
- package/src/tools/setup/mate.ts +28 -31
- package/src/tools/setup/plugin.ts +81 -0
- package/src/tools/setup/plugins/gitignore.ts +19 -9
- package/src/tools/setup/plugins/guidance.ts +2 -6
- package/src/tools/setup/policy.ts +4 -7
- package/src/tools/setup/providers/agent-file-sections.ts +78 -0
- package/src/tools/setup/providers/claude-format.ts +159 -0
- package/src/tools/setup/providers/claude.ts +283 -292
- package/src/tools/setup/providers/opencode-format.ts +146 -0
- package/src/tools/setup/providers/opencode.ts +171 -66
- package/src/tools/setup/providers/skill-tree.ts +38 -0
- package/src/tools/setup.ts +12 -7
- package/src/tui.ts +5 -0
- package/src/templates/capabilities/openspec-cap/claude/hooks/mate-artifact-finish.sh +0 -161
- package/src/templates/providers/claude/.claude/hooks/mate-session-banner +0 -28
- package/src/templates/providers/claude/.claude/hooks/validate-artifact-path +0 -242
- package/src/tools/setup/dynamic-plugins/pin-store.ts +0 -30
|
@@ -2,16 +2,41 @@
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { FRAMEWORK_NAME } from "../../../framework";
|
|
6
6
|
import { GlobalConfigStore } from "../../../lib/orchestrator/global-config-store";
|
|
7
|
-
import { getWrapperBinPath } from "../../../lib/package-paths";
|
|
8
7
|
import type { FrameworkConfig } from "../../../lib/orchestrator/types";
|
|
9
8
|
import { refreshFromTemplate, stripGuidanceBlock } from "../plugins/guidance";
|
|
10
|
-
import {
|
|
11
|
-
|
|
9
|
+
import {
|
|
10
|
+
TOKENSAVE_CLAUDE_MD_MARKER,
|
|
11
|
+
TOKENSAVE_WORKING_REPO_EXCLUDE_ENTRIES,
|
|
12
|
+
} from "../capabilities/tokensave-shared";
|
|
13
|
+
import {
|
|
14
|
+
instructionBlockKey,
|
|
15
|
+
removeManagedBlocksForPlugin,
|
|
16
|
+
upsertManagedBlock,
|
|
17
|
+
} from "../context-services";
|
|
18
|
+
import type { CapabilityContributionInput, ProviderPlugin, SetupContext } from "../plugin";
|
|
12
19
|
import { resolveGitInfoExcludePath } from "../git-utils";
|
|
13
|
-
import { mergeDir, pruneEmptyAncestors
|
|
14
|
-
import {
|
|
20
|
+
import { mergeDir, pruneEmptyAncestors } from "../utils";
|
|
21
|
+
import {
|
|
22
|
+
cutFromMarker,
|
|
23
|
+
stripSectionFromFile,
|
|
24
|
+
type RemoveHeadingSectionOptions,
|
|
25
|
+
} from "./agent-file-sections";
|
|
26
|
+
import { patchSkillTreeMarkdownFiles } from "./skill-tree";
|
|
27
|
+
import {
|
|
28
|
+
filterClaudeHookGroups,
|
|
29
|
+
mergeClaudeHookGroups,
|
|
30
|
+
readClaudeMcpConfig,
|
|
31
|
+
readClaudeSettings,
|
|
32
|
+
toClaudeMcpEntry,
|
|
33
|
+
updateClaudeMcpServer,
|
|
34
|
+
writeClaudeMcpConfig,
|
|
35
|
+
writeClaudeSettings,
|
|
36
|
+
type ClaudeHookGroup,
|
|
37
|
+
type ClaudeSettings,
|
|
38
|
+
} from "./claude-format";
|
|
39
|
+
import { getSetupRootTemplates } from "./utils";
|
|
15
40
|
|
|
16
41
|
async function configureClaudeGuidance(companionPath: string): Promise<void> {
|
|
17
42
|
const rootTemplates = getSetupRootTemplates();
|
|
@@ -43,6 +68,10 @@ const ALL_MANAGED_EXCLUDE_ENTRIES = new Set([
|
|
|
43
68
|
]);
|
|
44
69
|
|
|
45
70
|
// Command substrings that mark a working-repo hook group as Mate-managed.
|
|
71
|
+
// The mate plugin hooks (validate-artifact-path, mate-session-banner,
|
|
72
|
+
// mate-artifact-finish.sh) now ship in the bundled Claude plugin; their
|
|
73
|
+
// markers are retained migration-only so stale managed groups written by
|
|
74
|
+
// earlier releases keep being stripped, and are never re-added.
|
|
46
75
|
const MANAGED_HOOK_MARKERS = [
|
|
47
76
|
"validate-artifact-path",
|
|
48
77
|
"mate-session-banner",
|
|
@@ -51,17 +80,33 @@ const MANAGED_HOOK_MARKERS = [
|
|
|
51
80
|
"tokensave",
|
|
52
81
|
];
|
|
53
82
|
|
|
83
|
+
// Hook files earlier releases copied into the companion; the bundled Claude
|
|
84
|
+
// plugin replaced them. Setup and launch sync delete stale copies.
|
|
85
|
+
const LEGACY_MATE_HOOK_FILES = [
|
|
86
|
+
"validate-artifact-path",
|
|
87
|
+
"mate-session-banner",
|
|
88
|
+
"mate-artifact-finish.sh",
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
async function removeLegacyMateHookFiles(companionPath: string): Promise<void> {
|
|
92
|
+
const hooksDir = path.join(companionPath, ".claude", "hooks");
|
|
93
|
+
for (const name of LEGACY_MATE_HOOK_FILES) {
|
|
94
|
+
try {
|
|
95
|
+
await fs.unlink(path.join(hooksDir, name));
|
|
96
|
+
} catch {
|
|
97
|
+
/* not present */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
await pruneEmptyAncestors(hooksDir, companionPath);
|
|
101
|
+
}
|
|
102
|
+
|
|
54
103
|
// Base `permissions.allow` entries that Claude gets for Mate-managed workflows.
|
|
55
104
|
// Read/Edit are scoped to the companion path so routine reads and artifact
|
|
56
105
|
// writes of skills, specs, and change artifacts don't prompt for approval on
|
|
57
106
|
// every file. Claude Code ignores `Glob()` rules for file-permission checks
|
|
58
107
|
// (only Read/Edit rules gate file tools), so no Glob entry is emitted.
|
|
59
108
|
function getBaseManagedPermissionEntries(companionPath: string): string[] {
|
|
60
|
-
return [
|
|
61
|
-
`Bash(${frameworkCommandName()}:*)`,
|
|
62
|
-
`Read(${companionPath}/**)`,
|
|
63
|
-
`Edit(${companionPath}/**)`,
|
|
64
|
-
];
|
|
109
|
+
return [`Bash(${FRAMEWORK_NAME}:*)`, `Read(${companionPath}/**)`, `Edit(${companionPath}/**)`];
|
|
65
110
|
}
|
|
66
111
|
|
|
67
112
|
const LEGACY_MANAGED_PERMISSION_ENTRIES = [
|
|
@@ -69,46 +114,9 @@ const LEGACY_MANAGED_PERMISSION_ENTRIES = [
|
|
|
69
114
|
"Bash($MATE_COMPANION_BIN_PATH/graphify:*)",
|
|
70
115
|
];
|
|
71
116
|
|
|
72
|
-
// Enabled capability -> the `permissions.allow` entries it pre-seeds.
|
|
73
|
-
function getCapabilityPermissionEntries(): Record<string, string[]> {
|
|
74
|
-
const wrapperBinPath = getWrapperBinPath();
|
|
75
|
-
return {
|
|
76
|
-
openspec: [
|
|
77
|
-
"Skill(openspec-explore)",
|
|
78
|
-
"Skill(openspec-propose)",
|
|
79
|
-
"Skill(openspec-apply-change)",
|
|
80
|
-
"Skill(openspec-archive-change)",
|
|
81
|
-
"Skill(mate-artifact-finish)",
|
|
82
|
-
"Bash(openspec:*)",
|
|
83
|
-
`Bash(${frameworkCommandName()} cap graphify:*)`,
|
|
84
|
-
`Bash(${path.join(wrapperBinPath, "openspec")}:*)`,
|
|
85
|
-
],
|
|
86
|
-
rtk: ["Bash(rtk:*)"],
|
|
87
|
-
graphify: [
|
|
88
|
-
"Skill(graphify)",
|
|
89
|
-
"Bash(graphify:*)",
|
|
90
|
-
`Bash(${frameworkCommandName()} cap graphify:*)`,
|
|
91
|
-
`Bash(${path.join(wrapperBinPath, "graphify")}:*)`,
|
|
92
|
-
],
|
|
93
|
-
"react-doctor": [
|
|
94
|
-
"Skill(react-doctor)",
|
|
95
|
-
"Bash(npx react-doctor:*)",
|
|
96
|
-
"Bash(npx react-doctor@latest *)",
|
|
97
|
-
],
|
|
98
|
-
tokensave: ["mcp__tokensave__*"],
|
|
99
|
-
// The context-mode Claude plugin exposes its skill and MCP tools under the
|
|
100
|
-
// plugin namespace; pre-seed both so routine routing doesn't prompt.
|
|
101
|
-
"context-mode": [
|
|
102
|
-
"Skill(context-mode:context-mode)",
|
|
103
|
-
"mcp__plugin_context-mode_context-mode__*",
|
|
104
|
-
],
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
117
|
function getAllManagedPermissionEntries(companionPath: string): Set<string> {
|
|
109
118
|
return new Set([
|
|
110
119
|
...getBaseManagedPermissionEntries(companionPath),
|
|
111
|
-
...Object.values(getCapabilityPermissionEntries()).flat(),
|
|
112
120
|
...LEGACY_MANAGED_PERMISSION_ENTRIES,
|
|
113
121
|
// Legacy base entry: Claude Code never matched Glob() rules for file
|
|
114
122
|
// permission checks and warns about them, so setup no longer emits it.
|
|
@@ -117,37 +125,22 @@ function getAllManagedPermissionEntries(companionPath: string): Set<string> {
|
|
|
117
125
|
]);
|
|
118
126
|
}
|
|
119
127
|
|
|
120
|
-
|
|
121
|
-
type?: string;
|
|
122
|
-
command?: string;
|
|
123
|
-
args?: string[];
|
|
124
|
-
timeout?: number;
|
|
125
|
-
}
|
|
126
|
-
interface HookGroup {
|
|
127
|
-
matcher?: string;
|
|
128
|
-
hooks?: HookCommand[];
|
|
129
|
-
}
|
|
130
|
-
interface WorkingRepoSettings {
|
|
131
|
-
hooks?: Record<string, HookGroup[]>;
|
|
132
|
-
permissions?: { additionalDirectories?: string[]; allow?: string[] } & Record<string, unknown>;
|
|
133
|
-
mcpServers?: Record<string, { command?: string; args?: string[] }>;
|
|
134
|
-
[key: string]: unknown;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function isManagedHookGroup(group: HookGroup): boolean {
|
|
128
|
+
function isManagedHookGroup(group: ClaudeHookGroup, extraMarkers: string[] = []): boolean {
|
|
138
129
|
return (group.hooks ?? []).some((hook) =>
|
|
139
|
-
MANAGED_HOOK_MARKERS.some((marker) =>
|
|
130
|
+
[...MANAGED_HOOK_MARKERS, ...extraMarkers].some((marker) =>
|
|
131
|
+
(hook.command ?? "").includes(marker),
|
|
132
|
+
),
|
|
140
133
|
);
|
|
141
134
|
}
|
|
142
135
|
|
|
143
|
-
function removeManagedHookGroups(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
136
|
+
function removeManagedHookGroups(
|
|
137
|
+
settings: ClaudeSettings,
|
|
138
|
+
extraMarkers: string[] = [],
|
|
139
|
+
): ClaudeSettings {
|
|
140
|
+
const hooks = filterClaudeHookGroups(
|
|
141
|
+
settings.hooks ?? {},
|
|
142
|
+
(group) => !isManagedHookGroup(group, extraMarkers),
|
|
143
|
+
);
|
|
151
144
|
|
|
152
145
|
const next = { ...settings };
|
|
153
146
|
if (Object.keys(hooks).length > 0) {
|
|
@@ -158,18 +151,6 @@ function removeManagedHookGroups(settings: WorkingRepoSettings): WorkingRepoSett
|
|
|
158
151
|
return next;
|
|
159
152
|
}
|
|
160
153
|
|
|
161
|
-
async function readWorkingRepoSettings(settingsPath: string): Promise<WorkingRepoSettings> {
|
|
162
|
-
try {
|
|
163
|
-
const parsed = JSON.parse(await fs.readFile(settingsPath, "utf8")) as unknown;
|
|
164
|
-
if (parsed && typeof parsed === "object") {
|
|
165
|
-
return parsed as WorkingRepoSettings;
|
|
166
|
-
}
|
|
167
|
-
} catch {
|
|
168
|
-
// Absent or unparseable — start from an empty object.
|
|
169
|
-
}
|
|
170
|
-
return {};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
154
|
export async function ensureWorkingRepoLocalExcludes(
|
|
174
155
|
workingRepoPath: string,
|
|
175
156
|
config: FrameworkConfig,
|
|
@@ -214,9 +195,6 @@ export async function ensureWorkingRepoLocalExcludes(
|
|
|
214
195
|
await fs.writeFile(excludePath, nextContent, "utf8");
|
|
215
196
|
}
|
|
216
197
|
|
|
217
|
-
// Marker that identifies the tokensave installer's CLAUDE.md append block.
|
|
218
|
-
const TOKENSAVE_CLAUDE_MD_MARKER = "## MANDATORY: No Explore Agents When Tokensave Is Available";
|
|
219
|
-
|
|
220
198
|
async function stripTokensaveClaudeMdAppend(workingRepoPath: string): Promise<void> {
|
|
221
199
|
const claudeMdPath = path.join(workingRepoPath, "CLAUDE.md");
|
|
222
200
|
let content: string;
|
|
@@ -226,13 +204,10 @@ async function stripTokensaveClaudeMdAppend(workingRepoPath: string): Promise<vo
|
|
|
226
204
|
return;
|
|
227
205
|
}
|
|
228
206
|
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const before = content.slice(0, idx).replace(/\s+$/, "");
|
|
234
|
-
if (before.length > 0) {
|
|
235
|
-
await fs.writeFile(claudeMdPath, before + "\n", "utf8");
|
|
207
|
+
const stripped = cutFromMarker(content, TOKENSAVE_CLAUDE_MD_MARKER);
|
|
208
|
+
if (stripped === content) return;
|
|
209
|
+
if (stripped.length > 0) {
|
|
210
|
+
await fs.writeFile(claudeMdPath, stripped, "utf8");
|
|
236
211
|
} else {
|
|
237
212
|
await fs.unlink(claudeMdPath);
|
|
238
213
|
}
|
|
@@ -257,94 +232,37 @@ export function getCompanionClaudeMcpConfigPath(companionPath: string): string {
|
|
|
257
232
|
// groups/entries always lead their arrays so the emitted shape stays stable
|
|
258
233
|
// across syncs; unmanaged content is preserved untouched.
|
|
259
234
|
function buildManagedClaudeSettings(
|
|
260
|
-
existing:
|
|
235
|
+
existing: ClaudeSettings,
|
|
261
236
|
companionPath: string,
|
|
262
237
|
config: FrameworkConfig,
|
|
263
|
-
|
|
264
|
-
):
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
hooks
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
{
|
|
292
|
-
type: "command",
|
|
293
|
-
command: `sh "${companionPath}/.claude/hooks/mate-artifact-finish.sh"`,
|
|
294
|
-
},
|
|
295
|
-
],
|
|
296
|
-
},
|
|
297
|
-
...(hooks.PostToolUse ?? []),
|
|
298
|
-
];
|
|
299
|
-
}
|
|
300
|
-
if (reactDoctorEnabled) {
|
|
301
|
-
// Record edits cheaply, then scan once when the edited turn finishes.
|
|
302
|
-
hooks.PostToolUse = [
|
|
303
|
-
{
|
|
304
|
-
matcher: "Write|Edit|MultiEdit|NotebookEdit|ApplyPatch",
|
|
305
|
-
hooks: [
|
|
306
|
-
{
|
|
307
|
-
type: "command",
|
|
308
|
-
command: `sh "${companionPath}/.claude/hooks/react-doctor.sh"`,
|
|
309
|
-
timeout: 5,
|
|
310
|
-
},
|
|
311
|
-
],
|
|
312
|
-
},
|
|
313
|
-
...(hooks.PostToolUse ?? []),
|
|
314
|
-
];
|
|
315
|
-
hooks.Stop = [
|
|
316
|
-
{
|
|
317
|
-
hooks: [
|
|
318
|
-
{
|
|
319
|
-
type: "command",
|
|
320
|
-
command: `sh "${companionPath}/.claude/hooks/react-doctor.sh"`,
|
|
321
|
-
timeout: 45,
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
},
|
|
325
|
-
...(hooks.Stop ?? []),
|
|
326
|
-
];
|
|
327
|
-
}
|
|
328
|
-
if (enabledNames.has("tokensave")) {
|
|
329
|
-
hooks.PreToolUse = [
|
|
330
|
-
{
|
|
331
|
-
matcher: "Agent|Grep|Bash",
|
|
332
|
-
hooks: [{ type: "command", command: tokensaveCommandPath, args: ["hook-pre-tool-use"] }],
|
|
333
|
-
},
|
|
334
|
-
...(hooks.PreToolUse ?? []),
|
|
335
|
-
];
|
|
336
|
-
hooks.UserPromptSubmit = [
|
|
337
|
-
{
|
|
338
|
-
hooks: [{ type: "command", command: tokensaveCommandPath, args: ["hook-prompt-submit"] }],
|
|
339
|
-
},
|
|
340
|
-
...(hooks.UserPromptSubmit ?? []),
|
|
341
|
-
];
|
|
342
|
-
hooks.Stop = [
|
|
343
|
-
{
|
|
344
|
-
hooks: [{ type: "command", command: tokensaveCommandPath, args: ["hook-stop"] }],
|
|
345
|
-
},
|
|
346
|
-
...(hooks.Stop ?? []),
|
|
347
|
-
];
|
|
238
|
+
contributions: CapabilityContributionInput[] = [],
|
|
239
|
+
): ClaudeSettings {
|
|
240
|
+
// Declared markers and permission entries widen the managed strip set for
|
|
241
|
+
// every registered Capability, enabled or not, so deselection tears down.
|
|
242
|
+
const declaredMarkers = contributions.flatMap((input) =>
|
|
243
|
+
(input.contributions.hookGroups ?? []).map((hook) => hook.marker),
|
|
244
|
+
);
|
|
245
|
+
const declaredPermissionEntries = contributions.flatMap(
|
|
246
|
+
(input) => input.contributions.permissionEntries ?? [],
|
|
247
|
+
);
|
|
248
|
+
const enabledDeclaredPermissionEntries = contributions
|
|
249
|
+
.filter((input) => input.enabled)
|
|
250
|
+
.flatMap((input) => input.contributions.permissionEntries ?? []);
|
|
251
|
+
|
|
252
|
+
// Mate's own hooks (artifact-path guard, session banner, archive-finish
|
|
253
|
+
// nudge) ship in the bundled Claude plugin loaded at launch; settings-sync
|
|
254
|
+
// only strips their legacy managed groups (via removeManagedHookGroups) and
|
|
255
|
+
// reconciles the capability hooks that remain settings-delivered.
|
|
256
|
+
const hooks = removeManagedHookGroups(existing, declaredMarkers).hooks ?? {};
|
|
257
|
+
// Declared hook groups are applied after the table-driven blocks so managed
|
|
258
|
+
// groups keep leading their event arrays in the same order as before the
|
|
259
|
+
// capabilities migrated to declarations.
|
|
260
|
+
for (const input of contributions) {
|
|
261
|
+
if (!input.enabled) continue;
|
|
262
|
+
// Reversed so multiple groups of one declaration end up in declared order.
|
|
263
|
+
for (const hook of (input.contributions.hookGroups ?? []).toReversed()) {
|
|
264
|
+
hooks[hook.event] = [hook.group, ...(hooks[hook.event] ?? [])];
|
|
265
|
+
}
|
|
348
266
|
}
|
|
349
267
|
for (const event of Object.keys(hooks)) {
|
|
350
268
|
if (hooks[event].length === 0) delete hooks[event];
|
|
@@ -353,13 +271,13 @@ function buildManagedClaudeSettings(
|
|
|
353
271
|
// permissions.allow: preserve unmanaged entries in place, then union the
|
|
354
272
|
// Mate-managed base entries and enabled capability entries. Dropping a
|
|
355
273
|
// capability removes only its managed entry.
|
|
356
|
-
const
|
|
357
|
-
|
|
274
|
+
const allManagedPermissionEntries = new Set([
|
|
275
|
+
...getAllManagedPermissionEntries(companionPath),
|
|
276
|
+
...declaredPermissionEntries,
|
|
277
|
+
]);
|
|
358
278
|
const managedAllow = [
|
|
359
279
|
...getBaseManagedPermissionEntries(companionPath),
|
|
360
|
-
...
|
|
361
|
-
.filter(([name]) => enabledNames.has(name))
|
|
362
|
-
.flatMap(([, entries]) => entries),
|
|
280
|
+
...enabledDeclaredPermissionEntries,
|
|
363
281
|
];
|
|
364
282
|
const existingPermissions = existing.permissions ?? {};
|
|
365
283
|
const existingAllow = Array.isArray(existingPermissions.allow) ? existingPermissions.allow : [];
|
|
@@ -380,7 +298,10 @@ function buildManagedClaudeSettings(
|
|
|
380
298
|
// entry is removed so it cannot shadow the `.mcp.json` definition.
|
|
381
299
|
delete mcpServers.tokensave;
|
|
382
300
|
|
|
383
|
-
const settings:
|
|
301
|
+
const settings: ClaudeSettings = { ...existing, hooks, autoMemoryEnabled: false };
|
|
302
|
+
if (Object.keys(hooks).length === 0) {
|
|
303
|
+
delete settings.hooks;
|
|
304
|
+
}
|
|
384
305
|
if (Object.keys(permissions).length > 0) {
|
|
385
306
|
settings.permissions = permissions;
|
|
386
307
|
} else {
|
|
@@ -401,54 +322,84 @@ function buildManagedClaudeSettings(
|
|
|
401
322
|
export async function syncCompanionClaudeSettings(
|
|
402
323
|
companionPath: string,
|
|
403
324
|
config: FrameworkConfig,
|
|
325
|
+
contributions: CapabilityContributionInput[] = [],
|
|
404
326
|
): Promise<void> {
|
|
405
|
-
const tokensaveCommandPath =
|
|
406
|
-
resolveCommandOnPath("tokensave", process.env.PATH ?? "") ?? "tokensave";
|
|
407
|
-
|
|
408
327
|
const settingsPath = getCompanionClaudeSettingsPath(companionPath);
|
|
409
|
-
const existing = await
|
|
410
|
-
const settings = buildManagedClaudeSettings(
|
|
411
|
-
existing,
|
|
412
|
-
companionPath,
|
|
413
|
-
config,
|
|
414
|
-
tokensaveCommandPath,
|
|
415
|
-
);
|
|
328
|
+
const existing = await readClaudeSettings(settingsPath);
|
|
329
|
+
const settings = buildManagedClaudeSettings(existing, companionPath, config, contributions);
|
|
416
330
|
|
|
417
|
-
await
|
|
418
|
-
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
331
|
+
await writeClaudeSettings(settingsPath, settings);
|
|
419
332
|
|
|
420
|
-
await syncCompanionClaudeMcpConfig(companionPath
|
|
333
|
+
await syncCompanionClaudeMcpConfig(companionPath);
|
|
421
334
|
}
|
|
422
335
|
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Runtime Surface reconciliation: apply/remove declared Capability
|
|
338
|
+
// contributions (spec: runtime-surface). Managed identity uses the existing
|
|
339
|
+
// marker scheme — hook markers, permission-entry membership, managed guidance
|
|
340
|
+
// blocks, and named skill trees / MCP servers.
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
export async function reconcileClaudeContributions(
|
|
344
|
+
ctx: SetupContext,
|
|
345
|
+
inputs: CapabilityContributionInput[],
|
|
431
346
|
): Promise<void> {
|
|
432
|
-
const
|
|
433
|
-
|
|
347
|
+
const { companionPath, config } = ctx;
|
|
348
|
+
|
|
349
|
+
// The settings sync (re)creates the managed settings document, so it only
|
|
350
|
+
// runs while the Claude runtime is active; a deactivated runtime's pass is
|
|
351
|
+
// teardown-only and must not resurrect files the provider teardown removed.
|
|
352
|
+
if (ctx.activeProviders.includes("claude")) {
|
|
353
|
+
await syncCompanionClaudeSettings(companionPath, config, inputs);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
for (const input of inputs) {
|
|
357
|
+
for (const descriptor of input.contributions.mcpServers ?? []) {
|
|
358
|
+
await updateClaudeMcpServer(
|
|
359
|
+
getCompanionClaudeMcpConfigPath(companionPath),
|
|
360
|
+
descriptor.name,
|
|
361
|
+
input.enabled ? toClaudeMcpEntry(descriptor) : null,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
434
364
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
365
|
+
// Guidance sections are managed blocks in CLAUDE.md. Current sections are
|
|
366
|
+
// upserted, then stale keys (content changes, disabled capability) are
|
|
367
|
+
// swept. CLAUDE.md is Claude-exclusive, so no shared-file guard applies.
|
|
368
|
+
const guidancePath = path.join(companionPath, "CLAUDE.md");
|
|
369
|
+
const sections = input.enabled ? (input.contributions.guidanceSections ?? []) : [];
|
|
370
|
+
const keepKeys = new Set<string>();
|
|
371
|
+
for (const section of sections) {
|
|
372
|
+
const blockKey = instructionBlockKey(input.pluginId, section.content);
|
|
373
|
+
keepKeys.add(blockKey);
|
|
374
|
+
await upsertManagedBlock(guidancePath, FRAMEWORK_NAME, blockKey, section.content);
|
|
375
|
+
}
|
|
376
|
+
if ((input.contributions.guidanceSections ?? []).length > 0) {
|
|
377
|
+
await removeManagedBlocksForPlugin(guidancePath, FRAMEWORK_NAME, input.pluginId, keepKeys);
|
|
440
378
|
}
|
|
441
|
-
} catch {
|
|
442
|
-
// Absent or unparseable — start from an empty object.
|
|
443
|
-
}
|
|
444
379
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
380
|
+
for (const skillTree of input.contributions.skillTrees ?? []) {
|
|
381
|
+
const skillDir = path.join(companionPath, ".claude", "skills", skillTree.name);
|
|
382
|
+
if (input.enabled) {
|
|
383
|
+
await mergeDir(skillTree.sourceDir, skillDir);
|
|
384
|
+
} else {
|
|
385
|
+
await fs.rm(skillDir, { recursive: true, force: true });
|
|
386
|
+
await pruneEmptyAncestors(path.join(companionPath, ".claude", "skills"), companionPath);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
448
389
|
}
|
|
390
|
+
}
|
|
449
391
|
|
|
450
|
-
|
|
451
|
-
|
|
392
|
+
// Maintain the companion `.mcp.json` shell. Managed MCP servers are reconciled
|
|
393
|
+
// from declared Capability contributions (and legacy `ctx.mcp` hosting); this
|
|
394
|
+
// only guarantees the file exists with an `mcpServers` map. Loaded at launch
|
|
395
|
+
// via `claude --mcp-config`.
|
|
396
|
+
async function syncCompanionClaudeMcpConfig(companionPath: string): Promise<void> {
|
|
397
|
+
const mcpConfigPath = getCompanionClaudeMcpConfigPath(companionPath);
|
|
398
|
+
const { config: existing } = await readClaudeMcpConfig(mcpConfigPath);
|
|
399
|
+
await writeClaudeMcpConfig(mcpConfigPath, {
|
|
400
|
+
...existing,
|
|
401
|
+
mcpServers: { ...existing.mcpServers },
|
|
402
|
+
});
|
|
452
403
|
}
|
|
453
404
|
|
|
454
405
|
// Reconcile the working repo for a Claude launch. Mate-managed Claude settings
|
|
@@ -481,7 +432,7 @@ async function syncWorkingRepoClaudeAdditionalDirectories(
|
|
|
481
432
|
const settingsPath = path.join(workingRepoPath, ".claude", "settings.local.json");
|
|
482
433
|
// Migrate hooks only. Existing permissions and MCP entries stay in the
|
|
483
434
|
// working repo; additionalDirectories is reconciled below.
|
|
484
|
-
const existing = removeManagedHookGroups(await
|
|
435
|
+
const existing = removeManagedHookGroups(await readClaudeSettings(settingsPath));
|
|
485
436
|
const permissions = { ...existing.permissions };
|
|
486
437
|
const existingAdditionalDirectories = Array.isArray(permissions.additionalDirectories)
|
|
487
438
|
? permissions.additionalDirectories
|
|
@@ -504,7 +455,7 @@ async function syncWorkingRepoClaudeAdditionalDirectories(
|
|
|
504
455
|
),
|
|
505
456
|
);
|
|
506
457
|
|
|
507
|
-
const settings:
|
|
458
|
+
const settings: ClaudeSettings = {
|
|
508
459
|
...existing,
|
|
509
460
|
permissions: {
|
|
510
461
|
...permissions,
|
|
@@ -512,12 +463,14 @@ async function syncWorkingRepoClaudeAdditionalDirectories(
|
|
|
512
463
|
},
|
|
513
464
|
};
|
|
514
465
|
|
|
515
|
-
await
|
|
516
|
-
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
466
|
+
await writeClaudeSettings(settingsPath, settings);
|
|
517
467
|
}
|
|
518
468
|
|
|
519
|
-
async function configureClaude(
|
|
520
|
-
|
|
469
|
+
async function configureClaude(companionPath: string): Promise<void> {
|
|
470
|
+
// Mate hooks are delivered by the bundled Claude plugin at launch; nothing
|
|
471
|
+
// is copied into `companion/.claude/hooks/` anymore. Stale copies from
|
|
472
|
+
// earlier releases are stripped so only the plugin-shipped hooks run.
|
|
473
|
+
await removeLegacyMateHookFiles(companionPath);
|
|
521
474
|
|
|
522
475
|
// Note: `.claude/settings.local.json` is now Mate-owned and generated by
|
|
523
476
|
// `syncCompanionClaudeSettings`; do not delete it here. `settings.json` is
|
|
@@ -528,16 +481,6 @@ async function configureClaude(src: string, companionPath: string): Promise<void
|
|
|
528
481
|
/* not present */
|
|
529
482
|
}
|
|
530
483
|
|
|
531
|
-
const hooksDir = path.join(companionPath, ".claude", "hooks");
|
|
532
|
-
try {
|
|
533
|
-
const entries = await fs.readdir(hooksDir);
|
|
534
|
-
for (const entry of entries) {
|
|
535
|
-
await fs.chmod(path.join(hooksDir, entry), 0o755);
|
|
536
|
-
}
|
|
537
|
-
} catch {
|
|
538
|
-
// hooks dir may not exist if no hooks are defined
|
|
539
|
-
}
|
|
540
|
-
|
|
541
484
|
const agentsMdSrc = path.join(getSetupRootTemplates(), "TEMPLATE_AGENTS.md");
|
|
542
485
|
const agentsMdDest = path.join(companionPath, "AGENTS.md");
|
|
543
486
|
try {
|
|
@@ -556,17 +499,9 @@ async function configureClaude(src: string, companionPath: string): Promise<void
|
|
|
556
499
|
}
|
|
557
500
|
|
|
558
501
|
async function teardownClaude(companionPath: string, allowedAgents: string[]): Promise<void> {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
/* not present */
|
|
563
|
-
}
|
|
564
|
-
try {
|
|
565
|
-
await fs.unlink(path.join(companionPath, ".claude", "hooks", "mate-session-banner"));
|
|
566
|
-
} catch {
|
|
567
|
-
/* not present */
|
|
568
|
-
}
|
|
569
|
-
await pruneEmptyAncestors(path.join(companionPath, ".claude", "hooks"), companionPath);
|
|
502
|
+
// Migration-only: a companion last synced by a pre-plugin release may still
|
|
503
|
+
// carry copied mate hook files.
|
|
504
|
+
await removeLegacyMateHookFiles(companionPath);
|
|
570
505
|
try {
|
|
571
506
|
await fs.unlink(path.join(companionPath, ".claude", "settings.local.json"));
|
|
572
507
|
} catch {
|
|
@@ -626,48 +561,100 @@ async function teardownLegacyClaudeBin(companionPath: string): Promise<void> {
|
|
|
626
561
|
await pruneEmptyAncestors(path.join(companionPath, ".claude"), companionPath);
|
|
627
562
|
}
|
|
628
563
|
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
|
|
564
|
+
// ---------------------------------------------------------------------------
|
|
565
|
+
// Runtime Surface escape hatch (spec: runtime-surface). Imperative operations
|
|
566
|
+
// for effects a declaration cannot express — patching skill trees an external
|
|
567
|
+
// CLI wrote, absorbing/stripping foreign config another tool produced. Format
|
|
568
|
+
// knowledge stays in this module; capabilities pass only predicates.
|
|
569
|
+
// ---------------------------------------------------------------------------
|
|
570
|
+
|
|
571
|
+
/** Patch markdown files of an externally written `.claude/skills/<name>` tree. */
|
|
572
|
+
export async function patchClaudeSkillTree(
|
|
632
573
|
companionPath: string,
|
|
633
574
|
name: string,
|
|
634
|
-
|
|
575
|
+
transform: (content: string) => string,
|
|
576
|
+
options: { excludeFiles?: readonly string[] } = {},
|
|
635
577
|
): Promise<void> {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
578
|
+
await patchSkillTreeMarkdownFiles(
|
|
579
|
+
path.join(companionPath, ".claude", "skills", name),
|
|
580
|
+
transform,
|
|
581
|
+
new Set(options.excludeFiles ?? []),
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Strip a foreign heading section from the Claude guidance surfaces: the
|
|
587
|
+
* companion CLAUDE.md, the legacy `.claude/CLAUDE.md`, and — when syncing from
|
|
588
|
+
* a linked repo — the repo-level CLAUDE.md.
|
|
589
|
+
*/
|
|
590
|
+
export async function stripClaudeForeignSections(
|
|
591
|
+
companionPath: string,
|
|
592
|
+
options: RemoveHeadingSectionOptions & { repoPath?: string },
|
|
593
|
+
): Promise<void> {
|
|
594
|
+
await stripSectionFromFile(path.join(companionPath, "CLAUDE.md"), options);
|
|
595
|
+
await stripSectionFromFile(path.join(companionPath, ".claude", "CLAUDE.md"), options);
|
|
596
|
+
await pruneEmptyAncestors(path.join(companionPath, ".claude"), companionPath);
|
|
597
|
+
if (options.repoPath) {
|
|
598
|
+
await stripSectionFromFile(path.join(options.repoPath, "CLAUDE.md"), options);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Merge hook groups an external tool wrote to `.claude/settings.json` into the
|
|
604
|
+
* Mate-owned `settings.local.json`, then remove the tracked file so it cannot
|
|
605
|
+
* shadow companion config. Malformed or absent files skip the merge but the
|
|
606
|
+
* `settings.json` removal still happens.
|
|
607
|
+
*/
|
|
608
|
+
export async function mergeClaudeSettingsJsonHooks(companionPath: string): Promise<void> {
|
|
609
|
+
const settingsJsonPath = path.join(companionPath, ".claude", "settings.json");
|
|
610
|
+
const settingsLocalPath = getCompanionClaudeSettingsPath(companionPath);
|
|
639
611
|
try {
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
612
|
+
const settingsJson = JSON.parse(await fs.readFile(settingsJsonPath, "utf8")) as ClaudeSettings;
|
|
613
|
+
const settingsLocal = JSON.parse(
|
|
614
|
+
await fs.readFile(settingsLocalPath, "utf8"),
|
|
615
|
+
) as ClaudeSettings;
|
|
616
|
+
|
|
617
|
+
if (settingsJson.hooks && Object.keys(settingsJson.hooks).length > 0) {
|
|
618
|
+
settingsLocal.hooks = mergeClaudeHookGroups(settingsLocal.hooks ?? {}, settingsJson.hooks);
|
|
644
619
|
}
|
|
620
|
+
|
|
621
|
+
await writeClaudeSettings(settingsLocalPath, settingsLocal);
|
|
645
622
|
} catch {
|
|
646
|
-
|
|
623
|
+
/* settings files absent or malformed — just remove settings.json */
|
|
647
624
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if (!(name in mcpServers)) return;
|
|
653
|
-
delete mcpServers[name];
|
|
654
|
-
} else {
|
|
655
|
-
mcpServers[name] = entry;
|
|
625
|
+
try {
|
|
626
|
+
await fs.unlink(settingsJsonPath);
|
|
627
|
+
} catch {
|
|
628
|
+
/* not present */
|
|
656
629
|
}
|
|
657
|
-
await fs.writeFile(
|
|
658
|
-
mcpConfigPath,
|
|
659
|
-
JSON.stringify({ ...existing, mcpServers }, null, 2) + "\n",
|
|
660
|
-
"utf8",
|
|
661
|
-
);
|
|
662
630
|
}
|
|
663
631
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
632
|
+
/** Remove settings hook groups matching `isForeign`, pruning emptied containers. */
|
|
633
|
+
export async function removeClaudeHookGroupsWhere(
|
|
634
|
+
companionPath: string,
|
|
635
|
+
isForeign: (group: ClaudeHookGroup) => boolean,
|
|
636
|
+
): Promise<void> {
|
|
637
|
+
const settingsLocalPath = getCompanionClaudeSettingsPath(companionPath);
|
|
638
|
+
let settings: ClaudeSettings;
|
|
639
|
+
try {
|
|
640
|
+
settings = JSON.parse(await fs.readFile(settingsLocalPath, "utf8")) as ClaudeSettings;
|
|
641
|
+
} catch {
|
|
642
|
+
return; /* settings absent or malformed */
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const existingHooks = settings.hooks ?? {};
|
|
646
|
+
const hooks = filterClaudeHookGroups(existingHooks, (group) => !isForeign(group));
|
|
647
|
+
const changed =
|
|
648
|
+
JSON.stringify(hooks) !== JSON.stringify(existingHooks) ||
|
|
649
|
+
(Object.keys(hooks).length === 0 && Object.prototype.hasOwnProperty.call(settings, "hooks"));
|
|
650
|
+
if (!changed) return;
|
|
651
|
+
|
|
652
|
+
if (Object.keys(hooks).length > 0) {
|
|
653
|
+
settings.hooks = hooks;
|
|
654
|
+
} else {
|
|
655
|
+
delete settings.hooks;
|
|
656
|
+
}
|
|
657
|
+
await writeClaudeSettings(settingsLocalPath, settings);
|
|
671
658
|
}
|
|
672
659
|
|
|
673
660
|
export function createClaudePlugin(): ProviderPlugin {
|
|
@@ -677,19 +664,23 @@ export function createClaudePlugin(): ProviderPlugin {
|
|
|
677
664
|
label: "Claude",
|
|
678
665
|
description: "Install Claude companion files, hooks, and guidance.",
|
|
679
666
|
defaultSelected: true,
|
|
680
|
-
isEnabled: (config) => (config.
|
|
667
|
+
isEnabled: (config) => (config.allowedAgents ?? []).includes("claude"),
|
|
681
668
|
gitignoreEntries: () => COMPANION_GITIGNORE_ENTRIES,
|
|
682
669
|
hosting: {
|
|
683
670
|
mcp: {
|
|
684
|
-
async register(ctx: SetupContext, descriptor
|
|
685
|
-
await
|
|
686
|
-
ctx.companionPath,
|
|
671
|
+
async register(ctx: SetupContext, descriptor) {
|
|
672
|
+
await updateClaudeMcpServer(
|
|
673
|
+
getCompanionClaudeMcpConfigPath(ctx.companionPath),
|
|
687
674
|
descriptor.name,
|
|
688
|
-
|
|
675
|
+
toClaudeMcpEntry(descriptor),
|
|
689
676
|
);
|
|
690
677
|
},
|
|
691
678
|
async unregister(ctx: SetupContext, name: string) {
|
|
692
|
-
await
|
|
679
|
+
await updateClaudeMcpServer(
|
|
680
|
+
getCompanionClaudeMcpConfigPath(ctx.companionPath),
|
|
681
|
+
name,
|
|
682
|
+
null,
|
|
683
|
+
);
|
|
693
684
|
},
|
|
694
685
|
},
|
|
695
686
|
instructions: {
|
|
@@ -697,13 +688,13 @@ export function createClaudePlugin(): ProviderPlugin {
|
|
|
697
688
|
},
|
|
698
689
|
},
|
|
699
690
|
async apply(ctx) {
|
|
700
|
-
await configureClaude(
|
|
691
|
+
await configureClaude(ctx.companionPath);
|
|
701
692
|
await configureClaudeGuidance(ctx.companionPath);
|
|
702
693
|
await teardownLegacyClaudeBin(ctx.companionPath);
|
|
703
694
|
await syncCompanionClaudeSettings(ctx.companionPath, ctx.config);
|
|
704
695
|
},
|
|
705
696
|
async teardown(ctx) {
|
|
706
|
-
await teardownClaude(ctx.companionPath, ctx.config.
|
|
697
|
+
await teardownClaude(ctx.companionPath, ctx.config.allowedAgents ?? []);
|
|
707
698
|
},
|
|
708
699
|
};
|
|
709
700
|
}
|