@uniqbit/mate-core 0.18.4 → 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.
Files changed (231) hide show
  1. package/claude-plugin/hooks/companion-instructions.mjs +9 -0
  2. package/claude-plugin/hooks/hooks.json +12 -2
  3. package/opencode-plugin/src/add-dir.ts +76 -10
  4. package/opencode-plugin/src/companion.ts +1 -8
  5. package/opencode-plugin/src/server.ts +2 -15
  6. package/package.json +10 -2
  7. package/src/brand/art.generated.ts +69 -0
  8. package/src/brand/index.ts +12 -0
  9. package/src/brand/palette.ts +19 -0
  10. package/src/brand/svg.generated.ts +8 -0
  11. package/src/brand/terminal.ts +83 -0
  12. package/src/brand/types.ts +33 -0
  13. package/src/cli/commands/artifact/artifact.ts +8 -0
  14. package/src/cli/commands/artifact/delivery/archive.ts +117 -0
  15. package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
  16. package/src/cli/commands/artifact/delivery/environment.ts +104 -0
  17. package/src/cli/commands/artifact/delivery/link.ts +154 -0
  18. package/src/cli/commands/artifact/delivery/output.ts +58 -0
  19. package/src/cli/commands/artifact/finish/command.ts +33 -3
  20. package/src/cli/commands/artifact/finish/engine.ts +47 -3
  21. package/src/cli/commands/artifact/finish/finisher.ts +10 -0
  22. package/src/cli/commands/artifact/finish/openspec.ts +200 -129
  23. package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
  24. package/src/cli/commands/artifact/pending/command.ts +31 -3
  25. package/src/cli/commands/artifact/pending/discovery.ts +21 -57
  26. package/src/cli/commands/cap/graphify.ts +52 -3
  27. package/src/cli/commands/cap/index-cmd.ts +8 -1
  28. package/src/cli/commands/cap/openspec.ts +13 -25
  29. package/src/cli/commands/companion/open.ts +56 -39
  30. package/src/cli/commands/companion/update.ts +75 -42
  31. package/src/cli/commands/doctor.ts +257 -78
  32. package/src/cli/commands/install.ts +0 -8
  33. package/src/cli/commands/plugin/install.ts +9 -4
  34. package/src/cli/commands/plugin/plugin.ts +3 -2
  35. package/src/cli/commands/report/adapter.ts +11 -2
  36. package/src/cli/commands/report/contract.ts +21 -0
  37. package/src/cli/commands/report/highlight.ts +34 -4
  38. package/src/cli/commands/report/markdown.ts +110 -0
  39. package/src/cli/commands/report/palette.ts +118 -0
  40. package/src/cli/commands/report/renderer.ts +456 -153
  41. package/src/cli/commands/report/types.ts +16 -0
  42. package/src/cli/commands/setup.ts +7 -15
  43. package/src/cli/commands/studio/client-asset.ts +16 -0
  44. package/src/cli/commands/studio/openspec-cli.ts +10 -58
  45. package/src/cli/commands/studio/routes.ts +2 -0
  46. package/src/cli/commands/studio/selection.ts +19 -1
  47. package/src/cli/commands/studio/server.ts +42 -1
  48. package/src/cli/commands/studio/terminal-launch.ts +7 -16
  49. package/src/cli/commands/studio/views/client.ts +6 -616
  50. package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
  51. package/src/cli/commands/studio/views/document.tsx +137 -69
  52. package/src/cli/commands/studio/views/markdown.tsx +250 -0
  53. package/src/cli/commands/studio/views/palette.ts +134 -0
  54. package/src/cli/commands/studio/views/reports/index.tsx +2 -7
  55. package/src/cli/commands/studio/views/skills/index.tsx +43 -62
  56. package/src/cli/commands/studio/views/specs/index.tsx +7 -15
  57. package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
  58. package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
  59. package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
  60. package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
  61. package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
  62. package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
  63. package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
  64. package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
  65. package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
  66. package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
  67. package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
  68. package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
  69. package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
  70. package/src/cli/commands/studio/views/styles.ts +217 -206
  71. package/src/cli/commands/studio/views/vault/state.ts +3 -5
  72. package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
  73. package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
  74. package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
  75. package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
  76. package/src/cli/commands/wrap.ts +9 -42
  77. package/src/cli/main.ts +4 -1
  78. package/src/cli/parse-flags.ts +25 -7
  79. package/src/cli/setup-selector.tsx +38 -162
  80. package/src/cli/usage.ts +5 -2
  81. package/src/hooks/companion-instructions.ts +190 -0
  82. package/src/hooks/hook-io.ts +63 -0
  83. package/src/hooks/session-banner.ts +21 -29
  84. package/src/hooks/session-guidance.ts +10 -36
  85. package/src/hooks/session-prepare.ts +115 -28
  86. package/src/hooks/validate-artifact-path.ts +82 -24
  87. package/src/index.ts +4 -0
  88. package/src/lib/change-layout.ts +154 -0
  89. package/src/lib/components/wizard-header.tsx +3 -1
  90. package/src/lib/context-mode-package.ts +24 -11
  91. package/src/lib/delivery/archive-change.ts +154 -0
  92. package/src/lib/delivery/branches.ts +108 -0
  93. package/src/lib/delivery/environment.ts +29 -0
  94. package/src/lib/delivery/forge.ts +279 -0
  95. package/src/lib/delivery/overlap.ts +202 -0
  96. package/src/lib/delivery/record-delivery.ts +196 -0
  97. package/src/lib/delivery/record.ts +343 -0
  98. package/src/lib/delivery/refresh.ts +174 -0
  99. package/src/lib/delivery/repository-access.ts +97 -0
  100. package/src/lib/delivery/stages.ts +111 -0
  101. package/src/lib/delivery/token.ts +58 -0
  102. package/src/lib/frontmatter.ts +18 -0
  103. package/src/lib/opencode-plugin-package.ts +5 -5
  104. package/src/lib/openspec-cli.ts +116 -0
  105. package/src/lib/openspec-scopes.ts +86 -0
  106. package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
  107. package/src/lib/orchestrator/adapters/claude.ts +60 -162
  108. package/src/lib/orchestrator/adapters/opencode.ts +9 -24
  109. package/src/lib/orchestrator/config-store.ts +2 -70
  110. package/src/lib/orchestrator/editor.ts +9 -9
  111. package/src/lib/orchestrator/openspec-store.ts +14 -54
  112. package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
  113. package/src/lib/orchestrator/projection-claude-links.ts +159 -0
  114. package/src/lib/orchestrator/projection-claude-skills.ts +19 -190
  115. package/src/lib/orchestrator/projection-entries.ts +45 -15
  116. package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
  117. package/src/lib/orchestrator/projection-types.ts +3 -2
  118. package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
  119. package/src/lib/orchestrator/session-prepare.ts +49 -87
  120. package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
  121. package/src/lib/orchestrator/types.ts +2 -2
  122. package/src/lib/orchestrator/wrap-pass.ts +22 -88
  123. package/src/lib/package-paths.ts +8 -0
  124. package/src/opencode/companion-hooks.ts +64 -5
  125. package/src/opencode/companion-policy.ts +37 -18
  126. package/src/opencode/projected-guidance.ts +4 -1
  127. package/src/opencode/session-prepare.ts +2 -1
  128. package/src/opencode/tui.tsx +4 -2
  129. package/src/runtime/artifact-refusal.ts +63 -0
  130. package/src/runtime/claude-delivery-plan.ts +89 -7
  131. package/src/runtime/commit-attribution-guard.ts +181 -0
  132. package/src/runtime/companion-guidance.ts +15 -3
  133. package/src/runtime/companion-instructions.ts +340 -0
  134. package/src/runtime/error-message.ts +4 -0
  135. package/src/runtime/guidance.ts +0 -17
  136. package/src/runtime/index.ts +5 -0
  137. package/src/runtime/managed-block.ts +117 -0
  138. package/src/runtime/markdown-scan.ts +266 -0
  139. package/src/runtime/policy.ts +16 -15
  140. package/src/runtime/projected-flag.ts +7 -0
  141. package/src/runtime/projected-guidance.ts +1 -0
  142. package/src/runtime/runtime-document-manifest.ts +62 -28
  143. package/src/runtime/secret-guard.ts +148 -0
  144. package/src/runtime/secret-patterns.ts +164 -0
  145. package/src/runtime/secret-scan.ts +90 -0
  146. package/src/runtime/session-kind.ts +13 -0
  147. package/src/runtime/shell-heredoc.ts +81 -0
  148. package/src/runtime/wrap-state.ts +36 -6
  149. package/src/runtime/write-atomic.ts +64 -0
  150. package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
  151. package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
  152. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
  153. package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
  154. package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
  155. package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
  156. package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
  157. package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
  158. package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
  159. package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
  160. package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
  161. package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
  162. package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
  163. package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
  164. package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
  165. package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
  166. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
  167. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
  168. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
  169. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  170. package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
  171. package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
  172. package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
  173. package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
  174. package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
  175. package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
  176. package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
  177. package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
  178. package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
  179. package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
  180. package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
  181. package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
  182. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
  183. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
  184. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
  185. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  186. package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
  187. package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
  188. package/src/templates/mate-skills/openspec-references.ts +140 -0
  189. package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
  190. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
  191. package/src/tools/setup/capabilities/context-mode.ts +25 -5
  192. package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
  193. package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
  194. package/src/tools/setup/capabilities/graphify.ts +12 -0
  195. package/src/tools/setup/capabilities/openspec.ts +273 -270
  196. package/src/tools/setup/capabilities/tokensave.ts +36 -3
  197. package/src/tools/setup/context-services.ts +9 -17
  198. package/src/tools/setup/dynamic-plugins/audiences.ts +7 -39
  199. package/src/tools/setup/dynamic-plugins/declarations.ts +1 -1
  200. package/src/tools/setup/dynamic-plugins/hydrate.ts +4 -7
  201. package/src/tools/setup/dynamic-plugins/install.ts +24 -28
  202. package/src/tools/setup/engine.ts +93 -148
  203. package/src/tools/setup/managed-output.ts +51 -0
  204. package/src/tools/setup/mate.ts +86 -11
  205. package/src/tools/setup/plugin.ts +37 -17
  206. package/src/tools/setup/plugins/gitignore.ts +1 -7
  207. package/src/tools/setup/plugins/guidance.ts +7 -4
  208. package/src/tools/setup/providers/agent-file-sections.ts +4 -2
  209. package/src/tools/setup/providers/claude-attribution.ts +65 -0
  210. package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
  211. package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
  212. package/src/tools/setup/providers/claude.ts +285 -34
  213. package/src/tools/setup/providers/opencode.ts +180 -11
  214. package/src/tools/setup/registry.ts +2 -24
  215. package/src/tools/setup/surface-target.ts +4 -13
  216. package/src/tools/setup/utils.ts +14 -4
  217. package/src/tools/setup.ts +131 -18
  218. package/wrappers/bin/graphify +52 -4
  219. package/wrappers/bin/openspec +33 -9
  220. package/wrappers/lib/graphify-gate.mjs +8 -0
  221. package/opencode-plugin/src/audience-layer.ts +0 -91
  222. package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
  223. package/src/runtime/audience-layer.ts +0 -84
  224. package/src/runtime/opencode-audience-layer.ts +0 -176
  225. package/src/tools/setup/audience-isolation.fixture.ts +0 -325
  226. package/src/tools/setup/audience-layer.ts +0 -207
  227. package/src/tools/setup/audience-registrations.ts +0 -52
  228. package/src/tools/setup/contribution-ledger.ts +0 -207
  229. package/src/tools/setup/isolation-diagnostics.ts +0 -139
  230. package/src/tools/setup/runtime-documents.ts +0 -291
  231. package/src/tools/setup/upgrade-sweep.ts +0 -153
@@ -0,0 +1,50 @@
1
+ /** Reads the operator's `claude` arguments as tokens, for where Mate's appended prompt goes. */
2
+
3
+ export const APPEND_PROMPT_FLAG = "--append-system-prompt";
4
+
5
+ function flagArgs(args: readonly string[]): readonly string[] {
6
+ const terminator = args.indexOf("--");
7
+ return terminator === -1 ? args : args.slice(0, terminator);
8
+ }
9
+
10
+ /**
11
+ * The operator's own `--append-system-prompt` values, lifted out of `args`:
12
+ * Claude keeps only the last such flag, so each would replace Mate's Agent
13
+ * Guidance instead of following it. A trailing flag without a value
14
+ * stays, for Claude to reject. `boundary` is where a lifted flag stood before
15
+ * an argument that is no flag: a variadic option such as `--add-dir` before
16
+ * it would otherwise read that argument as its own value, so Mate's flag goes
17
+ * there. Only the first such place is kept: Claude reads one prompt.
18
+ */
19
+ export function liftAppendedPrompts(args: readonly string[]): {
20
+ args: string[];
21
+ prompts: string[];
22
+ boundary?: number;
23
+ } {
24
+ const flags = flagArgs(args);
25
+ const kept: string[] = [];
26
+ const prompts: string[] = [];
27
+ const liftedAt: number[] = [];
28
+ for (let index = 0; index < flags.length; index += 1) {
29
+ const arg = flags[index]!;
30
+ if (arg === APPEND_PROMPT_FLAG && index + 1 < flags.length) {
31
+ prompts.push(flags[index + 1]!);
32
+ liftedAt.push(kept.length);
33
+ index += 1;
34
+ } else if (arg.startsWith(`${APPEND_PROMPT_FLAG}=`)) {
35
+ prompts.push(arg.slice(APPEND_PROMPT_FLAG.length + 1));
36
+ liftedAt.push(kept.length);
37
+ } else {
38
+ kept.push(arg);
39
+ }
40
+ }
41
+ const passthrough = [...kept, ...args.slice(flags.length)];
42
+ const boundary = liftedAt.find(
43
+ (at) => at > 0 && at < passthrough.length && !passthrough[at]!.startsWith("-"),
44
+ );
45
+ return {
46
+ args: passthrough,
47
+ prompts,
48
+ ...(boundary === undefined ? {} : { boundary }),
49
+ };
50
+ }
@@ -5,126 +5,75 @@ import path from "node:path";
5
5
  import { FRAMEWORK_NAME } from "../../../framework";
6
6
  import { buildCompanionGuidance } from "../../../playbooks/companion-guidance";
7
7
  import {
8
+ launchPluginFolders,
8
9
  planClaudeDelivery,
9
10
  type ClaudeDeliveryPlan,
10
- type SessionKind,
11
11
  } from "../../../runtime/claude-delivery-plan";
12
- import {
13
- audienceLayerDir,
14
- currentAudienceLayer,
15
- readLayerGuidanceSync,
16
- } from "../../../runtime/audience-layer";
17
12
  import { hookGroupKey, type ClaudeHookGroup } from "../../../runtime/claude-hook-group";
13
+ import { resolveSecretPolicy } from "../../../runtime/policy";
14
+ import { managedSessionKind } from "../../../runtime/session-kind";
18
15
  import { NOT_WRAPPED } from "../../../runtime/wrap-state";
19
16
  import { writeAtomic } from "../../../runtime/write-atomic";
20
17
  import {
21
18
  getCompanionClaudeMcpConfigPath,
22
19
  getCompanionClaudeSettingsPath,
23
20
  } from "../../../tools/setup/providers/claude";
21
+ import { definesUserClaudeAttribution } from "../../../tools/setup/providers/claude-attribution";
24
22
  import type { ClaudeSettings } from "../../../tools/setup/providers/claude-format";
25
- import { getContextModePackageRoot, validateContextModePackage } from "../../context-mode-package";
26
23
  import { getClaudePluginRoot, validateClaudePluginAssets } from "../../package-paths";
27
- import type { RenderedAudienceLayer } from "../../../tools/setup/audience-layer";
28
24
  import type { PrepareResult } from "../session-prepare";
29
25
  import { LaunchPreflightError } from "../types";
30
26
  import { type AdapterContext, LaunchAdapter } from "./base";
31
-
32
- /** The active audience's layer, attached to this launch only. */
33
- interface ClaudeLayerDelivery {
34
- audience: string;
35
- root: string;
36
- /** The layer's MCP configuration, when the layer holds one. */
37
- mcpConfigPath?: string;
38
- }
27
+ import { APPEND_PROMPT_FLAG, liftAppendedPrompts } from "./claude-args";
39
28
 
40
29
  /** The paths are present only where the plan names that channel and the file exists. */
41
30
  interface ClaudeDelivery {
42
31
  plan: ClaudeDeliveryPlan;
43
32
  settingsPath?: string;
44
33
  mcpConfigPath?: string;
45
- layer?: ClaudeLayerDelivery;
46
- /**
47
- * The guidance of the audience the checkout is wrapped for. The wrap carries
48
- * the rest of that audience; its guidance hook stays silent in a managed
49
- * session, so the launch's own system prompt carries this.
50
- */
51
- wrappedGuidance?: string;
52
34
  }
53
35
 
54
36
  /** A Claude launch's context: the delivery its plan chose, and the files it chose or wrote. */
55
37
  interface ClaudeLaunchContext extends AdapterContext {
56
38
  delivery: ClaudeDelivery;
39
+ /** The Capability plugin folders this launch's own prepare gathered, in order. */
40
+ pluginDirectories: readonly string[];
57
41
  }
58
42
 
59
- function safeName(value: string): string {
60
- return value.replace(/[^A-Za-z0-9._-]/g, "_");
61
- }
62
-
63
- /**
64
- * Where a launch writes its derived `--settings`. Per Working Repository and
65
- * audience, so concurrent launches never race; a companion-scoped launch
66
- * derives one only for an audience. Without an audience it is git-ignored
67
- * under the companion. With one it carries that audience's hooks and
68
- * permission entries, so it lives beside the audience's layer, outside the
69
- * companion, where no other audience's session can read it.
70
- */
71
- export function launchSettingsPath(
72
- companionPath: string,
73
- repositoryId: string | null,
74
- audience?: string,
75
- ): string {
76
- const scope = repositoryId === null ? "companion-scope" : `settings.${safeName(repositoryId)}`;
77
- if (audience) {
78
- return path.join(audienceLayerDir(companionPath, audience), `launch-${scope}.json`);
79
- }
80
- return path.join(companionPath, ".claude", "state", `launch-${scope}.json`);
81
- }
82
-
83
- async function readSettings(settingsPath: string): Promise<ClaudeSettings> {
84
- if (!existsSync(settingsPath)) return {};
85
- return JSON.parse(await fs.readFile(settingsPath, "utf8")) as ClaudeSettings;
43
+ /** Where a launch with a Working Repository writes its derived `--settings`; git-ignored. */
44
+ export function launchSettingsPath(companionPath: string, repositoryId: string): string {
45
+ const safeId = repositoryId.replace(/[^A-Za-z0-9._-]/g, "_");
46
+ return path.join(companionPath, ".claude", "state", `launch-settings.${safeId}.json`);
86
47
  }
87
48
 
88
49
  /**
89
- * Companion settings minus `exclude`, so each wrap-carried group runs once,
90
- * plus the layer's hook groups and permission entries. A file, not inline JSON
91
- * (platform arg limits). Claude reads one `--settings` file only — a second
92
- * one replaces the first — so the layer is merged here, never passed beside.
50
+ * Companion settings minus `exclude`, so each wrap-carried group runs once. A
51
+ * file, not inline JSON (platform arg limits); per repository, so concurrent
52
+ * launches never race. `--settings` outranks every file scope, so `attribution`
53
+ * is dropped when the user or the repository sets its own, even one added
54
+ * after the companion's last sync.
93
55
  */
94
56
  async function writeLaunchSettings(
95
57
  companionSettingsPath: string,
96
58
  target: string,
97
59
  exclude: readonly ClaudeHookGroup[],
98
- layerSettingsPath?: string,
60
+ repoRoot: string,
99
61
  ): Promise<void> {
100
- const settings = await readSettings(companionSettingsPath);
101
- const layer = layerSettingsPath ? await readSettings(layerSettingsPath) : {};
62
+ const settings = JSON.parse(await fs.readFile(companionSettingsPath, "utf8")) as ClaudeSettings;
102
63
  const excluded = new Set(exclude.map(hookGroupKey));
103
64
  const hooks: Record<string, ClaudeHookGroup[]> = {};
104
65
  for (const [event, groups] of Object.entries(settings.hooks ?? {})) {
105
66
  const kept = (groups ?? []).filter((group) => !excluded.has(hookGroupKey(group)));
106
67
  if (kept.length > 0) hooks[event] = kept;
107
68
  }
108
- for (const [event, groups] of Object.entries(layer.hooks ?? {})) {
109
- hooks[event] = [...(hooks[event] ?? []), ...(groups ?? [])];
110
- }
111
69
  const derived: ClaudeSettings = { ...settings, hooks };
112
70
  if (Object.keys(hooks).length === 0) delete derived.hooks;
113
- const layerAllow = layer.permissions?.allow ?? [];
114
- if (layerAllow.length > 0) {
115
- const allow = [...new Set([...(settings.permissions?.allow ?? []), ...layerAllow])];
116
- derived.permissions = { ...settings.permissions, allow };
71
+ if (definesUserClaudeAttribution({ repoRoot })) {
72
+ delete derived.attribution;
117
73
  }
118
74
 
119
- const text = `${JSON.stringify(derived, null, 2)}\n`;
120
- if (layerSettingsPath) {
121
- /** Beside the audience's layer: as private as the layer itself. */
122
- await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
123
- await writeAtomic(target, text, 0o600);
124
- } else {
125
- await fs.mkdir(path.dirname(target), { recursive: true });
126
- await writeAtomic(target, text);
127
- }
75
+ await fs.mkdir(path.dirname(target), { recursive: true });
76
+ await writeAtomic(target, `${JSON.stringify(derived, null, 2)}\n`);
128
77
  }
129
78
 
130
79
  /** The `--settings` file the plan's hook owner calls for, writing it when derived. */
@@ -132,38 +81,21 @@ async function settingsPathFor(
132
81
  plan: ClaudeDeliveryPlan,
133
82
  companionPath: string,
134
83
  repository: AdapterContext["repository"],
135
- layer: RenderedAudienceLayer | undefined,
136
84
  ): Promise<string | undefined> {
137
85
  const companionSettingsPath = getCompanionClaudeSettingsPath(companionPath);
138
- if (plan.hooks.owner === "none") return undefined;
139
- if (!layer && !existsSync(companionSettingsPath)) return undefined;
140
- if (plan.hooks.owner === "settings" && !layer) return companionSettingsPath;
86
+ if (plan.hooks.owner === "none" || !existsSync(companionSettingsPath)) return undefined;
87
+ if (plan.hooks.owner === "settings") return companionSettingsPath;
141
88
  /** Owner `wrap` is planned only for `managed-repo`, which has a repository. */
142
- const settingsPath = launchSettingsPath(
143
- companionPath,
144
- plan.hooks.owner === "wrap" ? repository!.id : null,
145
- layer?.audience,
146
- );
89
+ const settingsPath = launchSettingsPath(companionPath, repository!.id);
147
90
  await writeLaunchSettings(
148
91
  companionSettingsPath,
149
92
  settingsPath,
150
93
  plan.hooks.launchSettingsExclude,
151
- layer ? getCompanionClaudeSettingsPath(layer.root) : undefined,
94
+ repository!.path,
152
95
  );
153
96
  return settingsPath;
154
97
  }
155
98
 
156
- /** The layer as this launch attaches it: the directory, and its MCP configuration when present. */
157
- function layerDelivery(layer: RenderedAudienceLayer | undefined): ClaudeLayerDelivery | undefined {
158
- if (!layer) return undefined;
159
- const mcpConfigPath = getCompanionClaudeMcpConfigPath(layer.root);
160
- return {
161
- audience: layer.audience,
162
- root: layer.root,
163
- ...(existsSync(mcpConfigPath) ? { mcpConfigPath } : {}),
164
- };
165
- }
166
-
167
99
  export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
168
100
  readonly toolName = "claude";
169
101
  readonly interactive = true;
@@ -175,7 +107,7 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
175
107
  */
176
108
  async planLaunch(context: AdapterContext, prepared: PrepareResult): Promise<ClaudeLaunchContext> {
177
109
  const { repository, companionPath } = context;
178
- const kind: SessionKind = repository ? "managed-repo" : "managed-companion";
110
+ const kind = managedSessionKind(Boolean(repository));
179
111
  const wrap = prepared.wrapState ?? NOT_WRAPPED;
180
112
  if (repository && !wrap.runtimes.has("claude")) {
181
113
  throw new LaunchPreflightError(
@@ -184,24 +116,13 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
184
116
  }
185
117
  const plan = planClaudeDelivery(kind, wrap);
186
118
  const mcpConfigPath = getCompanionClaudeMcpConfigPath(companionPath);
187
- const settingsPath = await settingsPathFor(
188
- plan,
189
- companionPath,
190
- repository,
191
- prepared.audienceLayer,
192
- );
193
- const layer = layerDelivery(prepared.audienceLayer);
194
- const wrappedLayer =
195
- !layer && wrap.audience ? currentAudienceLayer(companionPath, wrap.audience) : null;
196
- const wrappedGuidance = wrappedLayer ? readLayerGuidanceSync(wrappedLayer, "CLAUDE.md") : "";
119
+ const settingsPath = await settingsPathFor(plan, companionPath, repository);
197
120
  const delivery: ClaudeDelivery = {
198
121
  plan,
199
122
  ...(plan.mcp === "mcp-config" && existsSync(mcpConfigPath) ? { mcpConfigPath } : {}),
200
123
  ...(settingsPath ? { settingsPath } : {}),
201
- ...(layer ? { layer } : {}),
202
- ...(wrappedGuidance ? { wrappedGuidance } : {}),
203
124
  };
204
- return { ...context, delivery };
125
+ return { ...context, delivery, pluginDirectories: prepared.claudePluginDirectories ?? [] };
205
126
  }
206
127
 
207
128
  /** A failed Claude wrap pass names the entry that failed. */
@@ -216,7 +137,7 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
216
137
  return super.launchFailure(prepared);
217
138
  }
218
139
 
219
- async validateLaunch(context: ClaudeLaunchContext): Promise<void> {
140
+ async validateLaunch(_context: ClaudeLaunchContext): Promise<void> {
220
141
  /** The bundled plugin carries the artifact-path guard; never launch without it. */
221
142
  try {
222
143
  validateClaudePluginAssets();
@@ -226,71 +147,48 @@ export class ClaudeAdapter extends LaunchAdapter<ClaudeLaunchContext> {
226
147
  { cause: error },
227
148
  );
228
149
  }
229
-
230
- if (context.capabilities.some((capability) => capability.name === "context-mode")) {
231
- try {
232
- await validateContextModePackage(context.companionPath);
233
- } catch (error) {
234
- throw new Error(
235
- `Claude context-mode plugin is unavailable: ${(error as Error).message} Repair it with \`mate companion setup\`.`,
236
- { cause: error },
237
- );
238
- }
239
- }
240
150
  }
241
151
 
242
152
  /**
243
- * Plan in, flags out; decides nothing. `--append-system-prompt`, `--plugin-dir`
244
- * and context-mode are unconditional: no settings document can carry them.
153
+ * Plan in, flags out; decides nothing. `--append-system-prompt` is
154
+ * unconditional: no settings document can carry it. It holds the Agent
155
+ * Guidance alone; the Companion Instructions come from the wrap's session hook.
156
+ * `--plugin-dir` passes the folders `launchPluginFolders` names, from the
157
+ * running install's bundled plugin.
245
158
  */
246
159
  buildArgs(context: ClaudeLaunchContext, args: string[]): string[] {
247
- const { plan, settingsPath, mcpConfigPath, layer, wrappedGuidance } = context.delivery;
248
- const companionGuidance = buildCompanionGuidance(context);
249
- const systemPrompt = wrappedGuidance
250
- ? `${companionGuidance}\n\n${wrappedGuidance}`
251
- : companionGuidance;
160
+ const { plan, settingsPath, mcpConfigPath } = context.delivery;
252
161
  /** Layered on the user's own sources so global skills and plugins still load. */
253
162
  const settingsArgs =
254
163
  plan.hooks.owner !== "none" && settingsPath
255
164
  ? ["--setting-sources", "user,project,local", "--settings", settingsPath]
256
165
  : [];
257
- /**
258
- * The layer adds its skills, commands, agents and `CLAUDE.md` as one more
259
- * directory, and its MCP servers as one more configuration beside every
260
- * server the launch delivered before. Both flags are variadic, so each is
261
- * followed by another flag, never by the operator's arguments.
262
- */
263
- const addDirArgs = [
264
- ...(plan.dirAccess === "add-dir" ? ["--add-dir", context.companionPath] : []),
265
- ...(layer ? ["--add-dir", layer.root] : []),
266
- ];
267
- const mcpConfigArgs = [
268
- ...(plan.mcp === "mcp-config" && mcpConfigPath ? ["--mcp-config", mcpConfigPath] : []),
269
- ...(layer?.mcpConfigPath ? ["--mcp-config", layer.mcpConfigPath] : []),
270
- ];
271
- const contextModeArgs = context.capabilities.some(
272
- (capability) => capability.name === "context-mode",
273
- )
274
- ? ["--plugin-dir", getContextModePackageRoot(context.companionPath)]
275
- : [];
276
-
166
+ const addDirArgs = plan.dirAccess === "add-dir" ? ["--add-dir", context.companionPath] : [];
167
+ const pluginDirArgs = launchPluginFolders(
168
+ plan,
169
+ context.pluginDirectories,
170
+ getClaudePluginRoot(),
171
+ ).flatMap((dir) => ["--plugin-dir", dir]);
172
+ const mcpConfigArgs =
173
+ plan.mcp === "mcp-config" && mcpConfigPath ? ["--mcp-config", mcpConfigPath] : [];
174
+
175
+ const guidance = buildCompanionGuidance({
176
+ ...context,
177
+ secretGuardrails: resolveSecretPolicy(context.companionPath).guardrails,
178
+ });
179
+ const { args: passthrough, prompts, boundary } = liftAppendedPrompts(args);
180
+ const promptArgs = [APPEND_PROMPT_FLAG, [guidance, ...prompts].filter(Boolean).join("\n\n")];
181
+ const launchArgs = [...settingsArgs, ...mcpConfigArgs, ...pluginDirArgs];
182
+
183
+ if (boundary === undefined) {
184
+ return [...addDirArgs, ...promptArgs, ...launchArgs, ...passthrough];
185
+ }
277
186
  return [
278
187
  ...addDirArgs,
279
- "--append-system-prompt",
280
- systemPrompt,
281
- ...settingsArgs,
282
- ...mcpConfigArgs,
283
- /** The bundled plugin from the running install, beside context-mode's. */
284
- "--plugin-dir",
285
- getClaudePluginRoot(),
286
- ...contextModeArgs,
287
- ...args,
188
+ ...launchArgs,
189
+ ...passthrough.slice(0, boundary),
190
+ ...promptArgs,
191
+ ...passthrough.slice(boundary),
288
192
  ];
289
193
  }
290
-
291
- extendEnvironment(_context: ClaudeLaunchContext): NodeJS.ProcessEnv {
292
- return {
293
- CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: "1",
294
- };
295
- }
296
194
  }
@@ -6,6 +6,7 @@ const path = await import("node:path");
6
6
 
7
7
  import { MATE_ENV } from "../../../runtime/env";
8
8
  import { validateGuidanceData, type MateGuidanceFile } from "../../../runtime/guidance";
9
+ import { resolveSecretPolicy } from "../../../runtime/policy";
9
10
 
10
11
  import { validateOpenCodePluginAssets } from "../../package-paths";
11
12
  import { readOpenCodeConfig } from "../../../tools/setup/providers/opencode-format";
@@ -56,12 +57,7 @@ function readInvocation(launchWorkingDirectory: string, args: string[]): OpenCod
56
57
  return { command, rest, hostsPrivateServer: Boolean(hostsServer) && !choosesRemoteServer };
57
58
  }
58
59
 
59
- /** An OpenCode launch's context: the audience layer its prepare rendered, if any. */
60
- interface OpenCodeLaunchContext extends AdapterContext {
61
- audienceLayer?: string;
62
- }
63
-
64
- export class OpenCodeAdapter extends LaunchAdapter<OpenCodeLaunchContext> {
60
+ export class OpenCodeAdapter extends LaunchAdapter {
65
61
  readonly toolName = "opencode";
66
62
  readonly interactive = true;
67
63
  private readonly requiredRuntimeAssets = [path.join(".opencode", "opencode.json")] as const;
@@ -84,15 +80,6 @@ export class OpenCodeAdapter extends LaunchAdapter<OpenCodeLaunchContext> {
84
80
  );
85
81
  }
86
82
 
87
- async planLaunch(
88
- context: AdapterContext,
89
- prepared: PrepareResult,
90
- ): Promise<OpenCodeLaunchContext> {
91
- return prepared.audienceLayer
92
- ? { ...context, audienceLayer: prepared.audienceLayer.root }
93
- : context;
94
- }
95
-
96
83
  /** A prepared repository reports its failure as one summary, as it always has. */
97
84
  launchFailure(prepared: PrepareResult): unknown {
98
85
  return prepared.repositoryId
@@ -116,29 +103,27 @@ export class OpenCodeAdapter extends LaunchAdapter<OpenCodeLaunchContext> {
116
103
  * overlay carried now lives in the wrapped `.opencode/opencode.json`. An
117
104
  * inherited `OPENCODE_CONFIG_DIR` or `OPENCODE_CONFIG_CONTENT` passes through.
118
105
  */
119
- extendEnvironment(context: OpenCodeLaunchContext, args: string[] = []): NodeJS.ProcessEnv {
106
+ extendEnvironment(context: AdapterContext, args: string[] = []): NodeJS.ProcessEnv {
120
107
  const { hostsPrivateServer } = readInvocation(context.launchWorkingDirectory, args);
121
108
  return {
122
109
  // Built fresh per launch from the live capability config; always set so
123
110
  // a stale value inherited from an outer Mate session can never leak in.
124
111
  // Masked unless the launch hosts its own server: a payload that reached
125
- // the shared service would apply this session's guidance — and its
126
- // audience layer — to every other.
112
+ // the shared service would apply this session's guidance to every other.
127
113
  [MATE_ENV.guidanceJson]: hostsPrivateServer
128
114
  ? JSON.stringify(this.buildGuidance(context))
129
115
  : "",
130
116
  };
131
117
  }
132
118
 
133
- /** The audience layer travels as a path; the plugin reads it through the surface's reader. */
134
- private buildGuidance(context: OpenCodeLaunchContext): MateGuidanceFile {
135
- const guidance = buildOpenCodeGuidance(context.capabilities, {
119
+ private buildGuidance(context: AdapterContext): MateGuidanceFile {
120
+ return buildOpenCodeGuidance(context.capabilities, {
136
121
  companionScoped: !context.repository,
122
+ secretGuardrails: resolveSecretPolicy(context.companionPath).guardrails,
137
123
  });
138
- return context.audienceLayer ? { ...guidance, audienceLayer: context.audienceLayer } : guidance;
139
124
  }
140
125
 
141
- async validateLaunch(context: OpenCodeLaunchContext): Promise<void> {
126
+ async validateLaunch(context: AdapterContext): Promise<void> {
142
127
  this.validateOpenCodeVersion();
143
128
 
144
129
  const missingAssets = await Promise.all(
@@ -202,7 +187,7 @@ export class OpenCodeAdapter extends LaunchAdapter<OpenCodeLaunchContext> {
202
187
 
203
188
  // Self-check the guidance payload this launch is about to inject so a
204
189
  // broken playbook template fails preflight instead of inside OpenCode.
205
- private validateGuidance(context: OpenCodeLaunchContext): string[] {
190
+ private validateGuidance(context: AdapterContext): string[] {
206
191
  const built = this.buildGuidance(context);
207
192
  const { guidance, errors } = validateGuidanceData(built);
208
193
 
@@ -1,14 +1,7 @@
1
1
  import path from "node:path";
2
2
 
3
- import semver from "semver";
3
+ import { normalizeBaseAudience } from "../../tools/setup/dynamic-plugins/audiences";
4
4
 
5
- import {
6
- BASE_AUDIENCE,
7
- BASE_AUDIENCE_KEYS,
8
- normalizeBaseAudience,
9
- } from "../../tools/setup/dynamic-plugins/audiences";
10
-
11
- import { getActiveDistribution } from "../../distribution";
12
5
  import { FRAMEWORK_NAME } from "../../framework";
13
6
  import {
14
7
  getDefaultSetupSelections,
@@ -300,69 +293,12 @@ function validateAudiences(config: FrameworkConfig): void {
300
293
  }
301
294
  }
302
295
 
303
- /**
304
- * The release that introduces the `audiences.base` shape. An older Mate would
305
- * read a saved file as an empty base plus an audience called `base`, so every
306
- * save in the new shape raises the companion's `engines` floor to it — capped
307
- * at the running release, so a pre-release build never locks itself out.
308
- */
309
- export const BASE_AUDIENCE_MIN_VERSION = "0.19.0";
310
-
311
- /** The floor version a save writes: the introducing release, or the running one when older. */
312
- export function baseAudienceFloorVersion(
313
- runningVersion: string = getActiveDistribution().config.version,
314
- ): string {
315
- const running = semver.coerce(runningVersion)?.version;
316
- return running && semver.lt(running, BASE_AUDIENCE_MIN_VERSION)
317
- ? running
318
- : BASE_AUDIENCE_MIN_VERSION;
319
- }
320
-
321
- /** Only companions carry audiences; a hub or a working repository keeps the root shape. */
322
- function usesAudiences(config: FrameworkConfig): boolean {
323
- return config.type === undefined || config.type === "companion";
324
- }
325
-
326
- /** The floor that admits the base audience shape, keeping a higher one. */
327
- function raisedEngineFloor(range: string | undefined): string {
328
- const version = baseAudienceFloorVersion();
329
- const floor = `>=${version}`;
330
- if (!range || semver.validRange(range) === null) return range ?? floor;
331
- const minimum = semver.minVersion(range);
332
- return minimum && semver.gte(minimum, version) ? range : floor;
333
- }
334
-
335
- /**
336
- * The file shape of a companion config: the base set's audience-scoped keys
337
- * under `audiences.base`, first among the audiences. The in-memory config
338
- * keeps them at the root, so its readers never see the difference.
339
- */
340
- function toFileShape(config: FrameworkConfig): FrameworkConfig {
341
- if (!usesAudiences(config)) return config;
342
- const base: Record<string, unknown> = {};
343
- const rest: Record<string, unknown> = { ...config };
344
- for (const key of BASE_AUDIENCE_KEYS) {
345
- if (rest[key] === undefined) continue;
346
- base[key] = rest[key];
347
- delete rest[key];
348
- }
349
- if (Object.keys(base).length === 0) return config;
350
- return {
351
- ...(rest as unknown as FrameworkConfig),
352
- audiences: { [BASE_AUDIENCE]: base, ...config.audiences } as FrameworkConfig["audiences"],
353
- engines: {
354
- ...config.engines,
355
- [FRAMEWORK_NAME]: raisedEngineFloor(config.engines?.[FRAMEWORK_NAME]),
356
- },
357
- };
358
- }
359
-
360
296
  export class ConfigStore extends YamlFileStore<FrameworkConfig> {
361
297
  constructor(configPath = process.env.MATE_CONFIG ?? defaultConfigPath()) {
362
298
  super(path.resolve(configPath));
363
299
  }
364
300
 
365
- /** Either file shape loads as the base set at the root; the new shape persists on the next save. */
301
+ /** Either file shape loads as the base set at the root; the next save writes the root shape. */
366
302
  override async load(): Promise<FrameworkConfig> {
367
303
  const merged = mergeWithDefaults(
368
304
  migrateProfilesToAllowedAgents(normalizeBaseAudience(await super.load())),
@@ -373,10 +309,6 @@ export class ConfigStore extends YamlFileStore<FrameworkConfig> {
373
309
  return merged;
374
310
  }
375
311
 
376
- override async save(config: FrameworkConfig): Promise<void> {
377
- await super.save(toFileShape(config));
378
- }
379
-
380
312
  protected async onMissing(): Promise<FrameworkConfig> {
381
313
  const config = defaultConfig();
382
314
  await this.save(config);
@@ -74,28 +74,28 @@ export const editorWorkspacePath = workspaceDocumentPath;
74
74
 
75
75
  interface WorkspaceDocument {
76
76
  workspacePath: string;
77
- folders: [string, string];
77
+ folders: string[];
78
78
  }
79
79
 
80
80
  /**
81
81
  * Writes the generated `.mate/workspace.code-workspace` file (working
82
- * repository first, companion second) and returns its path and folder
83
- * order. Called by editor-launching `injectEditorFolder`, which backs
84
- * `mate companion open`.
82
+ * repository first, then each companion in the given order) and returns its
83
+ * path and folder order. Called by editor-launching `injectEditorFolder`,
84
+ * which backs `mate companion open`.
85
85
  */
86
86
  async function writeWorkspaceDocument(
87
- companionPath: string,
87
+ companionPaths: string[],
88
88
  repoPath: string,
89
89
  ): Promise<WorkspaceDocument> {
90
- await project("workspace", { repoPath, companionPath });
90
+ await project("workspace", { repoPath, workspaceCompanionPaths: companionPaths });
91
91
  return {
92
92
  workspacePath: workspaceDocumentPath(repoPath),
93
- folders: [path.resolve(repoPath), path.resolve(companionPath)],
93
+ folders: [repoPath, ...companionPaths].map((folder) => path.resolve(folder)),
94
94
  };
95
95
  }
96
96
 
97
97
  export async function injectEditorFolder(
98
- companionPath: string,
98
+ companionPaths: string[],
99
99
  repoPath: string,
100
100
  cli = "code",
101
101
  spawnProcess: SpawnLike = spawn,
@@ -113,7 +113,7 @@ export async function injectEditorFolder(
113
113
 
114
114
  const { binary } = resolved;
115
115
 
116
- const { folders: workspaceFolders } = await writeWorkspaceDocument(companionPath, repoPath);
116
+ const { folders: workspaceFolders } = await writeWorkspaceDocument(companionPaths, repoPath);
117
117
 
118
118
  const child = spawnProcess(binary, ["--add", ...workspaceFolders], {
119
119
  stdio: "ignore",