@uniqbit/mate-core 0.18.5 → 1.0.0-canary.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/claude-plugin/hooks/companion-instructions.mjs +9 -0
- package/claude-plugin/hooks/hooks.json +12 -2
- package/package.json +10 -2
- package/src/brand/art.generated.ts +69 -0
- package/src/brand/index.ts +12 -0
- package/src/brand/palette.ts +19 -0
- package/src/brand/svg.generated.ts +8 -0
- package/src/brand/terminal.ts +83 -0
- package/src/brand/types.ts +33 -0
- package/src/cli/commands/artifact/artifact.ts +8 -0
- package/src/cli/commands/artifact/delivery/archive.ts +117 -0
- package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
- package/src/cli/commands/artifact/delivery/environment.ts +104 -0
- package/src/cli/commands/artifact/delivery/link.ts +154 -0
- package/src/cli/commands/artifact/delivery/output.ts +58 -0
- package/src/cli/commands/artifact/finish/command.ts +33 -3
- package/src/cli/commands/artifact/finish/engine.ts +47 -3
- package/src/cli/commands/artifact/finish/finisher.ts +10 -0
- package/src/cli/commands/artifact/finish/openspec.ts +177 -138
- package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
- package/src/cli/commands/artifact/pending/command.ts +31 -3
- package/src/cli/commands/artifact/pending/discovery.ts +21 -57
- package/src/cli/commands/cap/graphify.ts +52 -3
- package/src/cli/commands/cap/index-cmd.ts +8 -1
- package/src/cli/commands/cap/openspec.ts +13 -25
- package/src/cli/commands/companion/open.ts +56 -39
- package/src/cli/commands/companion/update.ts +75 -42
- package/src/cli/commands/doctor.ts +176 -3
- package/src/cli/commands/plugin/install.ts +9 -4
- package/src/cli/commands/plugin/plugin.ts +3 -2
- package/src/cli/commands/report/adapter.ts +11 -2
- package/src/cli/commands/report/contract.ts +21 -0
- package/src/cli/commands/report/highlight.ts +34 -4
- package/src/cli/commands/report/markdown.ts +110 -0
- package/src/cli/commands/report/palette.ts +118 -0
- package/src/cli/commands/report/renderer.ts +456 -153
- package/src/cli/commands/report/types.ts +16 -0
- package/src/cli/commands/setup.ts +7 -15
- package/src/cli/commands/studio/client-asset.ts +16 -0
- package/src/cli/commands/studio/openspec-cli.ts +10 -58
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +19 -1
- package/src/cli/commands/studio/server.ts +42 -1
- package/src/cli/commands/studio/views/client.ts +6 -616
- package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
- package/src/cli/commands/studio/views/document.tsx +137 -69
- package/src/cli/commands/studio/views/markdown.tsx +250 -0
- package/src/cli/commands/studio/views/palette.ts +134 -0
- package/src/cli/commands/studio/views/reports/index.tsx +2 -7
- package/src/cli/commands/studio/views/skills/index.tsx +43 -62
- package/src/cli/commands/studio/views/specs/index.tsx +7 -15
- package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
- package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
- package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
- package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
- package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
- package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
- package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
- package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
- package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
- package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
- package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
- package/src/cli/commands/studio/views/styles.ts +217 -206
- package/src/cli/commands/studio/views/vault/state.ts +3 -5
- package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
- package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
- package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
- package/src/cli/commands/wrap.ts +5 -13
- package/src/cli/main.ts +4 -1
- package/src/cli/parse-flags.ts +25 -7
- package/src/cli/setup-selector.tsx +38 -162
- package/src/cli/usage.ts +4 -0
- package/src/hooks/companion-instructions.ts +190 -0
- package/src/hooks/hook-io.ts +63 -0
- package/src/hooks/session-banner.ts +21 -29
- package/src/hooks/session-guidance.ts +9 -17
- package/src/hooks/session-prepare.ts +115 -28
- package/src/hooks/validate-artifact-path.ts +82 -24
- package/src/index.ts +4 -0
- package/src/lib/change-layout.ts +154 -0
- package/src/lib/components/wizard-header.tsx +3 -1
- package/src/lib/context-mode-package.ts +24 -11
- package/src/lib/delivery/archive-change.ts +154 -0
- package/src/lib/delivery/branches.ts +108 -0
- package/src/lib/delivery/environment.ts +29 -0
- package/src/lib/delivery/forge.ts +279 -0
- package/src/lib/delivery/overlap.ts +202 -0
- package/src/lib/delivery/record-delivery.ts +196 -0
- package/src/lib/delivery/record.ts +343 -0
- package/src/lib/delivery/refresh.ts +174 -0
- package/src/lib/delivery/repository-access.ts +97 -0
- package/src/lib/delivery/stages.ts +111 -0
- package/src/lib/delivery/token.ts +58 -0
- package/src/lib/frontmatter.ts +18 -0
- package/src/lib/opencode-plugin-package.ts +5 -5
- package/src/lib/openspec-cli.ts +116 -0
- package/src/lib/openspec-scopes.ts +86 -0
- package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
- package/src/lib/orchestrator/adapters/claude.ts +48 -40
- package/src/lib/orchestrator/adapters/opencode.ts +2 -0
- package/src/lib/orchestrator/editor.ts +9 -9
- package/src/lib/orchestrator/openspec-store.ts +14 -54
- package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
- package/src/lib/orchestrator/projection-claude-links.ts +159 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +19 -137
- package/src/lib/orchestrator/projection-entries.ts +45 -14
- package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
- package/src/lib/orchestrator/projection-types.ts +3 -0
- package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
- package/src/lib/orchestrator/session-prepare.ts +45 -5
- package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
- package/src/lib/orchestrator/types.ts +2 -2
- package/src/lib/orchestrator/wrap-pass.ts +22 -1
- package/src/lib/package-paths.ts +8 -0
- package/src/opencode/companion-hooks.ts +64 -5
- package/src/opencode/companion-policy.ts +37 -18
- package/src/opencode/projected-guidance.ts +4 -1
- package/src/opencode/session-prepare.ts +2 -1
- package/src/opencode/tui.tsx +4 -2
- package/src/runtime/artifact-refusal.ts +63 -0
- package/src/runtime/claude-delivery-plan.ts +89 -7
- package/src/runtime/commit-attribution-guard.ts +181 -0
- package/src/runtime/companion-guidance.ts +15 -3
- package/src/runtime/companion-instructions.ts +340 -0
- package/src/runtime/error-message.ts +4 -0
- package/src/runtime/index.ts +5 -0
- package/src/runtime/managed-block.ts +117 -0
- package/src/runtime/markdown-scan.ts +266 -0
- package/src/runtime/policy.ts +15 -1
- package/src/runtime/projected-flag.ts +7 -0
- package/src/runtime/projected-guidance.ts +1 -0
- package/src/runtime/runtime-document-manifest.ts +62 -11
- package/src/runtime/secret-guard.ts +148 -0
- package/src/runtime/secret-patterns.ts +164 -0
- package/src/runtime/secret-scan.ts +90 -0
- package/src/runtime/session-kind.ts +13 -0
- package/src/runtime/shell-heredoc.ts +81 -0
- package/src/runtime/wrap-state.ts +36 -2
- package/src/runtime/write-atomic.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
- package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
- package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/{claude/mate-artifact-publish → agents/mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/{agents/mate-artifact-publish → claude/mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/openspec-references.ts +140 -0
- package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
- package/src/tools/setup/capabilities/context-mode.ts +25 -5
- package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
- package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
- package/src/tools/setup/capabilities/graphify.ts +12 -0
- package/src/tools/setup/capabilities/openspec.ts +151 -270
- package/src/tools/setup/capabilities/tokensave.ts +36 -3
- package/src/tools/setup/context-services.ts +9 -17
- package/src/tools/setup/dynamic-plugins/install.ts +24 -28
- package/src/tools/setup/engine.ts +97 -21
- package/src/tools/setup/managed-output.ts +51 -0
- package/src/tools/setup/mate.ts +86 -11
- package/src/tools/setup/plugin.ts +35 -0
- package/src/tools/setup/plugins/guidance.ts +7 -4
- package/src/tools/setup/providers/agent-file-sections.ts +4 -2
- package/src/tools/setup/providers/claude-attribution.ts +65 -0
- package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
- package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
- package/src/tools/setup/providers/claude.ts +284 -8
- package/src/tools/setup/providers/opencode.ts +180 -1
- package/src/tools/setup/utils.ts +14 -4
- package/src/tools/setup.ts +126 -10
- package/wrappers/bin/graphify +52 -4
- package/wrappers/bin/openspec +33 -9
- package/wrappers/lib/graphify-gate.mjs +8 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
- package/src/tools/setup/runtime-documents.ts +0 -291
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import { PROJECTED_BANNER_FLAG } from "../../../hooks/session-banner";
|
|
5
4
|
import { getClaudePluginRoot } from "../../../lib/package-paths";
|
|
5
|
+
import { PROJECTED_FLAG } from "../../../runtime/projected-flag";
|
|
6
6
|
import { runtimeClaudePluginRoot } from "../../../runtime/runtime-directory";
|
|
7
7
|
import { hookGroupKey, type ClaudeHookGroup } from "./claude-format";
|
|
8
8
|
|
|
@@ -12,12 +12,11 @@ import { hookGroupKey, type ClaudeHookGroup } from "./claude-format";
|
|
|
12
12
|
* all, so the working-target settings document has to carry it instead.
|
|
13
13
|
*
|
|
14
14
|
* A managed session loads the Working Repository's settings as its `local`
|
|
15
|
-
* source *alongside* the plugin, so every group carried over runs twice there
|
|
16
|
-
*
|
|
17
|
-
* input
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* launch's.
|
|
15
|
+
* source *alongside* the plugin, so every group carried over runs twice there;
|
|
16
|
+
* a plain session may load both too, through `--plugin-dir`. The guard is
|
|
17
|
+
* input-pure, the guidance hook yields under a launch environment, and the
|
|
18
|
+
* banner, prepare and SubagentStart instruction copies carried here hold
|
|
19
|
+
* `PROJECTED_FLAG`, so one copy of each acts.
|
|
21
20
|
*
|
|
22
21
|
* The wiring is read from the installed plugin, but every command names
|
|
23
22
|
* `<mate home>/runtime/claude-plugin`, so a reinstall or Node switch does not
|
|
@@ -28,6 +27,7 @@ const GUARD_HOOK_SHIM = "validate-artifact-path.mjs";
|
|
|
28
27
|
const GUIDANCE_HOOK_SHIM = "session-guidance.mjs";
|
|
29
28
|
const BANNER_HOOK_SHIM = "session-banner.mjs";
|
|
30
29
|
const PREPARE_HOOK_SHIM = "session-prepare.mjs";
|
|
30
|
+
const INSTRUCTIONS_HOOK_SHIM = "companion-instructions.mjs";
|
|
31
31
|
const PLUGIN_ROOT_TOKEN = "${CLAUDE_PLUGIN_ROOT}";
|
|
32
32
|
|
|
33
33
|
interface PluginHookWiring {
|
|
@@ -46,16 +46,23 @@ function readPluginHookWiring(pluginRoot: string): PluginHookWiring {
|
|
|
46
46
|
return {};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function
|
|
49
|
+
function mapCommands(
|
|
50
|
+
group: ClaudeHookGroup,
|
|
51
|
+
rewrite: (command: string) => string,
|
|
52
|
+
): ClaudeHookGroup {
|
|
50
53
|
return {
|
|
51
54
|
...group,
|
|
52
55
|
hooks: (group.hooks ?? []).map((hook) => ({
|
|
53
56
|
...hook,
|
|
54
|
-
...(hook.command ? { command: hook.command
|
|
57
|
+
...(hook.command ? { command: rewrite(hook.command) } : {}),
|
|
55
58
|
})),
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
function resolveCommands(group: ClaudeHookGroup, pluginRoot: string): ClaudeHookGroup {
|
|
63
|
+
return mapCommands(group, (command) => command.split(PLUGIN_ROOT_TOKEN).join(pluginRoot));
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
function runsShim(group: ClaudeHookGroup, shim: string): boolean {
|
|
60
67
|
return (group.hooks ?? []).some((hook) => (hook.command ?? "").includes(shim));
|
|
61
68
|
}
|
|
@@ -90,31 +97,43 @@ export function mateGuidanceHookGroups(
|
|
|
90
97
|
|
|
91
98
|
/**
|
|
92
99
|
* The banner's `SessionStart` group, resolved the same way and carrying
|
|
93
|
-
* `
|
|
100
|
+
* `PROJECTED_FLAG`. Without the marker the carried command would be
|
|
94
101
|
* byte-identical to the plugin's, and a managed session — which loads both —
|
|
95
102
|
* would print the banner twice.
|
|
96
103
|
*/
|
|
97
104
|
export function mateBannerHookGroups(
|
|
98
105
|
commandRoot: string = runtimeClaudePluginRoot(),
|
|
99
106
|
): ClaudeHookGroup[] {
|
|
100
|
-
return groupsFor("SessionStart", BANNER_HOOK_SHIM, commandRoot)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
return withProjectedFlag(groupsFor("SessionStart", BANNER_HOOK_SHIM, commandRoot));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function withProjectedFlag(groups: ClaudeHookGroup[]): ClaudeHookGroup[] {
|
|
111
|
+
return groups.map((group) => mapCommands(group, (command) => `${command} ${PROJECTED_FLAG}`));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The `SubagentStart` instruction group, resolved the same way and carrying
|
|
116
|
+
* `PROJECTED_FLAG`: a session that loads both this copy and the plugin's,
|
|
117
|
+
* through a launch or `--plugin-dir`, would otherwise deliver the instructions
|
|
118
|
+
* twice.
|
|
119
|
+
*/
|
|
120
|
+
export function mateInstructionHookGroups(
|
|
121
|
+
commandRoot: string = runtimeClaudePluginRoot(),
|
|
122
|
+
): ClaudeHookGroup[] {
|
|
123
|
+
return withProjectedFlag(groupsFor("SubagentStart", INSTRUCTIONS_HOOK_SHIM, commandRoot));
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
/**
|
|
110
127
|
* The session-prepare `SessionStart` group, resolved the same way. Its declared
|
|
111
128
|
* `timeout` sits above the shim's own bound, so Claude Code never kills a
|
|
112
|
-
* prepare the shim has not given up on.
|
|
129
|
+
* prepare the shim has not given up on. It carries `PROJECTED_FLAG`: this copy
|
|
130
|
+
* alone prepares, which it skips under a launch environment, and then delivers
|
|
131
|
+
* the main thread's instructions; the plugin's copy stays inert.
|
|
113
132
|
*/
|
|
114
133
|
export function mateSessionPrepareHookGroups(
|
|
115
134
|
commandRoot: string = runtimeClaudePluginRoot(),
|
|
116
135
|
): ClaudeHookGroup[] {
|
|
117
|
-
return groupsFor("SessionStart", PREPARE_HOOK_SHIM, commandRoot);
|
|
136
|
+
return withProjectedFlag(groupsFor("SessionStart", PREPARE_HOOK_SHIM, commandRoot));
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
function mateOwnHookGroups(commandRoot: string): ClaudeHookGroup[] {
|
|
@@ -123,6 +142,7 @@ function mateOwnHookGroups(commandRoot: string): ClaudeHookGroup[] {
|
|
|
123
142
|
...mateGuidanceHookGroups(commandRoot),
|
|
124
143
|
...mateBannerHookGroups(commandRoot),
|
|
125
144
|
...mateSessionPrepareHookGroups(commandRoot),
|
|
145
|
+
...mateInstructionHookGroups(commandRoot),
|
|
126
146
|
];
|
|
127
147
|
}
|
|
128
148
|
|
|
@@ -4,6 +4,17 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import { FRAMEWORK_NAME } from "../../../framework";
|
|
6
6
|
import type { FrameworkConfig } from "../../../lib/orchestrator/types";
|
|
7
|
+
import { getClaudePluginRoot } from "../../../lib/package-paths";
|
|
8
|
+
import { writeAtomicThroughLink } from "../../../runtime/write-atomic";
|
|
9
|
+
import { resolveSecretPolicy } from "../../../runtime/policy";
|
|
10
|
+
import {
|
|
11
|
+
CLAUDE_LOCAL_CONFIG_DOCUMENT,
|
|
12
|
+
CLAUDE_SETTINGS_DOCUMENT,
|
|
13
|
+
renderedDocument,
|
|
14
|
+
type ManagedRegion,
|
|
15
|
+
type RenderedRuntimeDocuments,
|
|
16
|
+
} from "../../../runtime/runtime-document-manifest";
|
|
17
|
+
import { projectableSecretDenyPatterns } from "../../../runtime/secret-patterns";
|
|
7
18
|
import { refreshFromTemplate, stripGuidanceBlock } from "../plugins/guidance";
|
|
8
19
|
import {
|
|
9
20
|
instructionBlockKey,
|
|
@@ -11,9 +22,19 @@ import {
|
|
|
11
22
|
upsertManagedBlock,
|
|
12
23
|
} from "../context-services";
|
|
13
24
|
import type { CapabilityContributionInput, ProviderPlugin, SetupContext } from "../plugin";
|
|
14
|
-
import { surfaceRoot } from "../surface-target";
|
|
25
|
+
import { projectionRepoRoot, surfaceRoot } from "../surface-target";
|
|
15
26
|
import { mergeDir, pruneEmptyAncestors } from "../utils";
|
|
16
27
|
import { stripSectionFromFile, type RemoveHeadingSectionOptions } from "./agent-file-sections";
|
|
28
|
+
import {
|
|
29
|
+
claudeUserConfigDir,
|
|
30
|
+
definesUserClaudeAttribution,
|
|
31
|
+
isManagedClaudeAttribution,
|
|
32
|
+
MANAGED_CLAUDE_ATTRIBUTION,
|
|
33
|
+
} from "./claude-attribution";
|
|
34
|
+
import {
|
|
35
|
+
inspectClaudePluginFolders,
|
|
36
|
+
type PublishedClaudePluginFolder,
|
|
37
|
+
} from "./claude-plugin-folders";
|
|
17
38
|
import { patchSkillTreeMarkdownFiles } from "./skill-tree";
|
|
18
39
|
import {
|
|
19
40
|
filterClaudeHookGroups,
|
|
@@ -28,6 +49,13 @@ import {
|
|
|
28
49
|
type ClaudeHookGroup,
|
|
29
50
|
type ClaudeSettings,
|
|
30
51
|
} from "./claude-format";
|
|
52
|
+
import {
|
|
53
|
+
mateBannerHookGroups,
|
|
54
|
+
mateGuardHookGroups,
|
|
55
|
+
mateGuidanceHookGroups,
|
|
56
|
+
mateInstructionHookGroups,
|
|
57
|
+
mateSessionPrepareHookGroups,
|
|
58
|
+
} from "./claude-plugin-hooks";
|
|
31
59
|
import { getSetupRootTemplates } from "./utils";
|
|
32
60
|
|
|
33
61
|
async function configureClaudeGuidance(companionPath: string): Promise<void> {
|
|
@@ -106,6 +134,54 @@ function getAllManagedPermissionEntries(companionPath: string): Set<string> {
|
|
|
106
134
|
]);
|
|
107
135
|
}
|
|
108
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Claude Code features Mate turns off because they compete with its own
|
|
139
|
+
* workflow: plan mode writes plans outside the Companion Repository, the
|
|
140
|
+
* Artifact tool competes with `mate report`, and DesignSync has no role in a
|
|
141
|
+
* Mate session. `ExitPlanMode` stays allowed so a user who enters plan mode
|
|
142
|
+
* by hand can still leave it. Fixed, not configurable.
|
|
143
|
+
*/
|
|
144
|
+
export const MANAGED_CLAUDE_DENY_ENTRIES: readonly string[] = ["EnterPlanMode", "DesignSync"];
|
|
145
|
+
|
|
146
|
+
const SECRET_DENY_TOOLS = ["Read", "Edit"] as const;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Claude deny rules for the default secret files, at any depth. Claude checks
|
|
150
|
+
* deny before allow, so only patterns that can't match an allow-listed file are
|
|
151
|
+
* rendered; the hook enforces the rest of the pattern set. Claude applies
|
|
152
|
+
* `Edit(...)` rules to every built-in file-editing tool, `Write` included, so
|
|
153
|
+
* no `Write(...)` rule is rendered: Claude never matches one and warns about it.
|
|
154
|
+
*/
|
|
155
|
+
export function renderSecretDenyEntries(): string[] {
|
|
156
|
+
return projectableSecretDenyPatterns().flatMap((pattern) =>
|
|
157
|
+
SECRET_DENY_TOOLS.map((tool) => `${tool}(**/${pattern})`),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The managed `permissions.deny` list: fixed tool denies, plus secret files while guardrails are on. */
|
|
162
|
+
function renderManagedClaudeDenyEntries(companionPath: string): string[] {
|
|
163
|
+
return [
|
|
164
|
+
...MANAGED_CLAUDE_DENY_ENTRIES,
|
|
165
|
+
...(resolveSecretPolicy(companionPath).guardrails ? renderSecretDenyEntries() : []),
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Every deny entry Mate may have written, whatever the current config says, so
|
|
171
|
+
* an opt-out strips the secret entries instead of keeping them as user entries.
|
|
172
|
+
*/
|
|
173
|
+
function getAllManagedDenyEntries(): Set<string> {
|
|
174
|
+
return new Set([
|
|
175
|
+
...MANAGED_CLAUDE_DENY_ENTRIES,
|
|
176
|
+
...renderSecretDenyEntries(),
|
|
177
|
+
// Legacy secret entries: Claude Code never matched Write() rules for file
|
|
178
|
+
// permission checks and warns about them, so setup no longer emits them.
|
|
179
|
+
// Keep them managed so re-running setup strips them from existing installs.
|
|
180
|
+
...projectableSecretDenyPatterns().map((pattern) => `Write(**/${pattern})`),
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
const MANAGED_CLAUDE_FORCED_SETTINGS = { enableArtifact: false } as const;
|
|
184
|
+
|
|
109
185
|
/** Companion-owned Claude settings: sole source of Mate-managed hooks, permissions and MCP servers. */
|
|
110
186
|
export function getCompanionClaudeSettingsPath(companionPath: string): string {
|
|
111
187
|
return path.join(companionPath, ".claude", "settings.local.json");
|
|
@@ -121,7 +197,7 @@ export function getCompanionClaudeMcpConfigPath(companionPath: string): string {
|
|
|
121
197
|
* Rendered separately from the settings document so a working-target pass can
|
|
122
198
|
* obtain them without a write.
|
|
123
199
|
*/
|
|
124
|
-
|
|
200
|
+
function renderManagedClaudePermissionEntries(
|
|
125
201
|
companionPath: string,
|
|
126
202
|
contributions: CapabilityContributionInput[] = [],
|
|
127
203
|
): string[] {
|
|
@@ -134,7 +210,7 @@ export function renderManagedClaudePermissionEntries(
|
|
|
134
210
|
}
|
|
135
211
|
|
|
136
212
|
/** The Mate-managed `.mcp.json` server map of one pass, as a value. */
|
|
137
|
-
|
|
213
|
+
function renderManagedClaudeMcpServers(
|
|
138
214
|
contributions: CapabilityContributionInput[] = [],
|
|
139
215
|
): Record<string, unknown> {
|
|
140
216
|
const servers: Record<string, unknown> = {};
|
|
@@ -151,7 +227,7 @@ export function renderManagedClaudeMcpServers(
|
|
|
151
227
|
* Enabled Capabilities' Claude hook groups per event, in declared order, as a
|
|
152
228
|
* value: what a Claude wrap carries into the Working Repository.
|
|
153
229
|
*/
|
|
154
|
-
|
|
230
|
+
function renderManagedClaudeHookGroups(
|
|
155
231
|
contributions: CapabilityContributionInput[] = [],
|
|
156
232
|
): Record<string, ClaudeHookGroup[]> {
|
|
157
233
|
const hooks: Record<string, ClaudeHookGroup[]> = {};
|
|
@@ -173,6 +249,7 @@ function renderManagedClaudeSettings(
|
|
|
173
249
|
companionPath: string,
|
|
174
250
|
config: FrameworkConfig,
|
|
175
251
|
contributions: CapabilityContributionInput[] = [],
|
|
252
|
+
userDefinesAttribution: boolean,
|
|
176
253
|
): ClaudeSettings {
|
|
177
254
|
// Declared markers and permission entries widen the managed strip set for
|
|
178
255
|
// every registered Capability, enabled or not, so deselection tears down.
|
|
@@ -221,6 +298,13 @@ function renderManagedClaudeSettings(
|
|
|
221
298
|
} else {
|
|
222
299
|
delete permissions.allow;
|
|
223
300
|
}
|
|
301
|
+
// permissions.deny: same reconciliation as allow, against the fixed set.
|
|
302
|
+
const managedDeny = getAllManagedDenyEntries();
|
|
303
|
+
const existingDeny = Array.isArray(existingPermissions.deny) ? existingPermissions.deny : [];
|
|
304
|
+
permissions.deny = [
|
|
305
|
+
...existingDeny.filter((entry) => !managedDeny.has(entry)),
|
|
306
|
+
...renderManagedClaudeDenyEntries(companionPath),
|
|
307
|
+
];
|
|
224
308
|
|
|
225
309
|
const mcpServers = { ...existing.mcpServers };
|
|
226
310
|
// tokensave (and any future Mate-managed MCP server) now lives in the
|
|
@@ -228,7 +312,12 @@ function renderManagedClaudeSettings(
|
|
|
228
312
|
// entry is removed so it cannot shadow the `.mcp.json` definition.
|
|
229
313
|
delete mcpServers.tokensave;
|
|
230
314
|
|
|
231
|
-
const settings: ClaudeSettings = {
|
|
315
|
+
const settings: ClaudeSettings = {
|
|
316
|
+
...existing,
|
|
317
|
+
hooks,
|
|
318
|
+
autoMemoryEnabled: false,
|
|
319
|
+
...MANAGED_CLAUDE_FORCED_SETTINGS,
|
|
320
|
+
};
|
|
232
321
|
if (Object.keys(hooks).length === 0) {
|
|
233
322
|
delete settings.hooks;
|
|
234
323
|
}
|
|
@@ -242,10 +331,190 @@ function renderManagedClaudeSettings(
|
|
|
242
331
|
} else {
|
|
243
332
|
delete settings.mcpServers;
|
|
244
333
|
}
|
|
334
|
+
/** The document's own value is kept; Mate's yields to one the user scope sets. */
|
|
335
|
+
if (!("attribution" in existing) || isManagedClaudeAttribution(existing.attribution)) {
|
|
336
|
+
if (userDefinesAttribution) delete settings.attribution;
|
|
337
|
+
else settings.attribution = { ...MANAGED_CLAUDE_ATTRIBUTION };
|
|
338
|
+
}
|
|
245
339
|
|
|
246
340
|
return settings;
|
|
247
341
|
}
|
|
248
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Loaded by a managed session beside the launch's plugin, so each Mate hook
|
|
345
|
+
* here runs twice there: the guard is input-pure, guidance yields under a
|
|
346
|
+
* launch env, and the banner, prepare and instruction copies carry
|
|
347
|
+
* `--projected`, so one copy of each acts. Capability groups run once: the
|
|
348
|
+
* launch omits them from `--settings`.
|
|
349
|
+
*
|
|
350
|
+
* `permissions.additionalDirectories` grants companion file access only; it
|
|
351
|
+
* loads no `CLAUDE.md`.
|
|
352
|
+
*
|
|
353
|
+
* Unlike {@link renderManagedClaudeSettings}, this carries no
|
|
354
|
+
* `autoMemoryEnabled: false`. Whether a Wrap should leave the user's choice
|
|
355
|
+
* alone there is an open question, not settled policy.
|
|
356
|
+
*/
|
|
357
|
+
function claudeSettingsRegions(
|
|
358
|
+
ctx: SetupContext,
|
|
359
|
+
inputs: CapabilityContributionInput[],
|
|
360
|
+
repoRoot: string,
|
|
361
|
+
pluginFolders: readonly PublishedClaudePluginFolder[],
|
|
362
|
+
): ManagedRegion[] {
|
|
363
|
+
const capabilityHooks = renderManagedClaudeHookGroups(inputs);
|
|
364
|
+
const mateHooks: Record<string, unknown[]> = {
|
|
365
|
+
PreToolUse: mateGuardHookGroups(),
|
|
366
|
+
SessionStart: [
|
|
367
|
+
...mateBannerHookGroups(),
|
|
368
|
+
...mateGuidanceHookGroups(),
|
|
369
|
+
...mateSessionPrepareHookGroups(),
|
|
370
|
+
],
|
|
371
|
+
SubagentStart: mateInstructionHookGroups(),
|
|
372
|
+
};
|
|
373
|
+
const events = [...new Set([...Object.keys(mateHooks), ...Object.keys(capabilityHooks)])];
|
|
374
|
+
return [
|
|
375
|
+
...events.map((event): ManagedRegion => ({
|
|
376
|
+
at: ["hooks", event],
|
|
377
|
+
kind: "list",
|
|
378
|
+
values: [...(mateHooks[event] ?? []), ...(capabilityHooks[event] ?? [])],
|
|
379
|
+
})),
|
|
380
|
+
/**
|
|
381
|
+
* `stripPrefix` heals a repository an older Mate wrapped. That version wrote
|
|
382
|
+
* the whole launch environment here, and Claude Code applies `env` to the
|
|
383
|
+
* session, so every hook in a bare session inherited `MATE_*` and read the
|
|
384
|
+
* repository as a Managed Session — silently, since looking managed is
|
|
385
|
+
* exactly what makes the guidance hook emit nothing. Guidance must be the
|
|
386
|
+
* projection's to give in an Unmanaged Session, so no `MATE_*` key may
|
|
387
|
+
* survive here. It sets no entry of its own: the
|
|
388
|
+
* `CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD` an earlier wrap set is
|
|
389
|
+
* reverted from the manifest, so a user's own value stays.
|
|
390
|
+
*/
|
|
391
|
+
{
|
|
392
|
+
at: ["env"],
|
|
393
|
+
kind: "map",
|
|
394
|
+
entries: {},
|
|
395
|
+
stripPrefix: "MATE_",
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
at: ["permissions", "allow"],
|
|
399
|
+
kind: "list",
|
|
400
|
+
values: renderManagedClaudePermissionEntries(ctx.companionPath, inputs),
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
at: ["permissions", "deny"],
|
|
404
|
+
kind: "list",
|
|
405
|
+
values: renderManagedClaudeDenyEntries(ctx.companionPath),
|
|
406
|
+
},
|
|
407
|
+
...claudePluginRegions(pluginFolders),
|
|
408
|
+
/**
|
|
409
|
+
* Yields: the document is the user's, so their own `enableArtifact` wins,
|
|
410
|
+
* and unwrapping restores it instead of deleting a key Mate overwrote.
|
|
411
|
+
*/
|
|
412
|
+
...Object.entries(MANAGED_CLAUDE_FORCED_SETTINGS).map(([key, value]): ManagedRegion => ({
|
|
413
|
+
at: [key],
|
|
414
|
+
kind: "value",
|
|
415
|
+
value,
|
|
416
|
+
yields: true,
|
|
417
|
+
})),
|
|
418
|
+
/**
|
|
419
|
+
* Placed only while no scope sets attribution: a project-scope value would
|
|
420
|
+
* lose to this local one, yielding or not. Mate's own earlier value does
|
|
421
|
+
* not count as the user's.
|
|
422
|
+
*/
|
|
423
|
+
...(definesUserClaudeAttribution({ repoRoot })
|
|
424
|
+
? []
|
|
425
|
+
: [
|
|
426
|
+
{
|
|
427
|
+
at: ["attribution"],
|
|
428
|
+
kind: "value",
|
|
429
|
+
value: { ...MANAGED_CLAUDE_ATTRIBUTION },
|
|
430
|
+
yields: true,
|
|
431
|
+
} satisfies ManagedRegion,
|
|
432
|
+
]),
|
|
433
|
+
];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Each published plugin folder as a local `directory` marketplace and its one
|
|
438
|
+
* enabled plugin, so a plain session in a trusted folder loads it in place. The
|
|
439
|
+
* path is the folder as the launch passes it, so the wrap state can name what
|
|
440
|
+
* the launch must leave to the wrap.
|
|
441
|
+
*/
|
|
442
|
+
function claudePluginRegions(published: readonly PublishedClaudePluginFolder[]): ManagedRegion[] {
|
|
443
|
+
return [
|
|
444
|
+
{
|
|
445
|
+
at: ["extraKnownMarketplaces"],
|
|
446
|
+
kind: "map",
|
|
447
|
+
entries: Object.fromEntries(
|
|
448
|
+
published.map(({ path: folder, marketplace }) => [
|
|
449
|
+
marketplace,
|
|
450
|
+
{ source: { source: "directory", path: folder } },
|
|
451
|
+
]),
|
|
452
|
+
),
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
at: ["enabledPlugins"],
|
|
456
|
+
kind: "map",
|
|
457
|
+
entries: Object.fromEntries(
|
|
458
|
+
published.map(({ plugin, marketplace }) => [`${plugin}@${marketplace}`, true]),
|
|
459
|
+
),
|
|
460
|
+
},
|
|
461
|
+
];
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* The working target's Claude documents, rendered as values. Each is a document
|
|
466
|
+
* the runtime finds by walking up from the current directory, and every path
|
|
467
|
+
* inside one still names the Companion Repository — only where the document is
|
|
468
|
+
* placed differs from the companion target.
|
|
469
|
+
*/
|
|
470
|
+
export async function renderClaudeWorkingDocuments(
|
|
471
|
+
ctx: SetupContext,
|
|
472
|
+
inputs: CapabilityContributionInput[],
|
|
473
|
+
): Promise<RenderedRuntimeDocuments> {
|
|
474
|
+
const repoRoot = projectionRepoRoot(ctx);
|
|
475
|
+
/** Reads the folders only; the pass published them before this render. */
|
|
476
|
+
const pluginFolders = (
|
|
477
|
+
await inspectClaudePluginFolders(inputs, { bundledPluginRoot: getClaudePluginRoot() })
|
|
478
|
+
).published;
|
|
479
|
+
return {
|
|
480
|
+
documents: [
|
|
481
|
+
...renderedDocument(
|
|
482
|
+
CLAUDE_SETTINGS_DOCUMENT,
|
|
483
|
+
claudeSettingsRegions(ctx, inputs, repoRoot, pluginFolders),
|
|
484
|
+
),
|
|
485
|
+
/**
|
|
486
|
+
* Local scope, not `.mcp.json`: a project-scoped server is inert until a
|
|
487
|
+
* human approves it in a session, which an Unmanaged Session is exactly the
|
|
488
|
+
* case that cannot rely on. The repo path is the key Claude Code files a
|
|
489
|
+
* project under, resolved so a symlinked working directory still matches.
|
|
490
|
+
*
|
|
491
|
+
* Confirmed against a real `~/.claude.json`: the keys there are the
|
|
492
|
+
* directories sessions started in, not repository roots — sibling
|
|
493
|
+
* directories of one checkout each hold their own entry, as do temporary
|
|
494
|
+
* directories. So this reaches exactly one session: the one started at the
|
|
495
|
+
* repository root. A session started in a subdirectory of a wrapped
|
|
496
|
+
* repository has its own key, finds no `mcpServers` under it, and sees none
|
|
497
|
+
* of these servers — the Unmanaged Session case this document exists for,
|
|
498
|
+
* missed. That limit is deliberate. The directories a session might start
|
|
499
|
+
* in are unbounded, and every one of them would be a fresh entry in the
|
|
500
|
+
* user's global configuration that nothing later reclaims; keying the
|
|
501
|
+
* repository root only keeps the projection's footprint there to a single
|
|
502
|
+
* key. A subdirectory session still gets the rest of the projection — the
|
|
503
|
+
* documents inside the repository are found by the runtime's own upward
|
|
504
|
+
* walk — it loses only the MCP servers.
|
|
505
|
+
*/
|
|
506
|
+
...renderedDocument(CLAUDE_LOCAL_CONFIG_DOCUMENT, [
|
|
507
|
+
{
|
|
508
|
+
at: ["projects", repoRoot, "mcpServers"],
|
|
509
|
+
kind: "map",
|
|
510
|
+
entries: renderManagedClaudeMcpServers(inputs),
|
|
511
|
+
},
|
|
512
|
+
]),
|
|
513
|
+
],
|
|
514
|
+
warnings: [],
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
249
518
|
// Write the Mate-managed Claude settings into the companion repo. This is the
|
|
250
519
|
// config surface Mate owns and loads at launch via `--settings`, so hooks,
|
|
251
520
|
// permissions, and MCP servers no longer need to live in the working repo.
|
|
@@ -253,10 +522,17 @@ export async function syncCompanionClaudeSettings(
|
|
|
253
522
|
companionPath: string,
|
|
254
523
|
config: FrameworkConfig,
|
|
255
524
|
contributions: CapabilityContributionInput[] = [],
|
|
525
|
+
env: Record<string, string | undefined> = process.env,
|
|
256
526
|
): Promise<void> {
|
|
257
527
|
const settingsPath = getCompanionClaudeSettingsPath(companionPath);
|
|
258
528
|
const existing = await readClaudeSettings(settingsPath);
|
|
259
|
-
const settings = renderManagedClaudeSettings(
|
|
529
|
+
const settings = renderManagedClaudeSettings(
|
|
530
|
+
existing,
|
|
531
|
+
companionPath,
|
|
532
|
+
config,
|
|
533
|
+
contributions,
|
|
534
|
+
definesUserClaudeAttribution({ userConfigDir: claudeUserConfigDir(env) }),
|
|
535
|
+
);
|
|
260
536
|
|
|
261
537
|
await writeClaudeSettings(settingsPath, settings);
|
|
262
538
|
|
|
@@ -395,7 +671,7 @@ async function configureClaude(companionPath: string): Promise<void> {
|
|
|
395
671
|
try {
|
|
396
672
|
await fs.access(agentsMdDest);
|
|
397
673
|
} catch {
|
|
398
|
-
await fs.
|
|
674
|
+
await writeAtomicThroughLink(agentsMdDest, await fs.readFile(agentsMdSrc));
|
|
399
675
|
}
|
|
400
676
|
|
|
401
677
|
const claudeMdSrc = path.join(getSetupRootTemplates(), "TEMPLATE_CLAUDE.md");
|
|
@@ -403,7 +679,7 @@ async function configureClaude(companionPath: string): Promise<void> {
|
|
|
403
679
|
try {
|
|
404
680
|
await fs.access(claudeMdDest);
|
|
405
681
|
} catch {
|
|
406
|
-
await fs.
|
|
682
|
+
await writeAtomicThroughLink(claudeMdDest, await fs.readFile(claudeMdSrc));
|
|
407
683
|
}
|
|
408
684
|
}
|
|
409
685
|
|