@uniqbit/mate-core 0.15.5 → 0.16.0-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +2 -2
  2. package/claude-plugin/hooks/hooks.json +3 -6
  3. package/claude-plugin/hooks/session-guidance.mjs +8 -0
  4. package/claude-plugin/hooks/ts-loader.mjs +19 -0
  5. package/package.json +7 -5
  6. package/src/cli/commands/artifact/artifact.ts +19 -3
  7. package/src/cli/commands/artifact/finish/command.ts +183 -57
  8. package/src/cli/commands/artifact/finish/engine.ts +80 -91
  9. package/src/cli/commands/artifact/finish/finisher.ts +26 -33
  10. package/src/cli/commands/artifact/finish/git.ts +34 -23
  11. package/src/cli/commands/artifact/finish/index.ts +9 -3
  12. package/src/cli/commands/artifact/finish/openspec.ts +225 -97
  13. package/src/cli/commands/artifact/pending/command.ts +175 -0
  14. package/src/cli/commands/artifact/pending/discovery.ts +249 -0
  15. package/src/cli/commands/artifact/pending/index.ts +17 -0
  16. package/src/cli/commands/cap/index-cmd.ts +9 -1
  17. package/src/cli/commands/cap/index.ts +2 -6
  18. package/src/cli/commands/cap/tokensave.ts +4 -4
  19. package/src/cli/commands/companion/companion.ts +5 -1
  20. package/src/cli/commands/companion/link.ts +2 -2
  21. package/src/cli/commands/companion/sync.ts +92 -0
  22. package/src/cli/commands/doctor.ts +0 -3
  23. package/src/cli/commands/launch/shared.ts +23 -5
  24. package/src/cli/commands/report/collector.ts +72 -78
  25. package/src/cli/commands/report/contract.ts +40 -1
  26. package/src/cli/commands/report/highlight.ts +27 -0
  27. package/src/cli/commands/report/index.ts +11 -18
  28. package/src/cli/commands/report/renderer.ts +199 -2
  29. package/src/cli/commands/report/types.ts +26 -1
  30. package/src/cli/commands/shared/companion-selection.ts +107 -10
  31. package/src/cli/commands/studio/areas.ts +68 -0
  32. package/src/cli/commands/studio/index.ts +69 -0
  33. package/src/cli/commands/studio/inventory.ts +55 -0
  34. package/src/cli/commands/studio/mate-inventory.ts +43 -0
  35. package/src/cli/commands/studio/openspec-cli.ts +198 -0
  36. package/src/cli/commands/studio/payload.ts +184 -0
  37. package/src/cli/commands/studio/routes.ts +2 -0
  38. package/src/cli/commands/studio/selection.ts +61 -0
  39. package/src/cli/commands/studio/server.ts +201 -0
  40. package/src/cli/commands/studio/snapshot.ts +63 -0
  41. package/src/cli/commands/studio/topology.ts +199 -0
  42. package/src/cli/commands/studio/views/client.ts +197 -0
  43. package/src/cli/commands/studio/views/companion-picker.tsx +91 -0
  44. package/src/cli/commands/studio/views/companion-selector.tsx +90 -0
  45. package/src/cli/commands/studio/views/dashboard/changes.tsx +107 -0
  46. package/src/cli/commands/studio/views/dashboard/index.tsx +19 -0
  47. package/src/cli/commands/studio/views/document.tsx +256 -0
  48. package/src/cli/commands/studio/views/error.tsx +20 -0
  49. package/src/cli/commands/studio/views/model.ts +34 -0
  50. package/src/cli/commands/studio/views/pairings.tsx +38 -0
  51. package/src/cli/commands/studio/views/skills/index.tsx +87 -0
  52. package/src/cli/commands/studio/views/specs/index.tsx +101 -0
  53. package/src/cli/commands/studio/views/styles.ts +389 -0
  54. package/src/cli/commands/studio/views/warnings.tsx +21 -0
  55. package/src/cli/commands/studio/views/workflow/index.tsx +21 -0
  56. package/src/cli/commands/studio/views/workflow/steps.ts +329 -0
  57. package/src/cli/commands/studio/views/workflow/transcript.tsx +190 -0
  58. package/src/cli/commands/unwrap.ts +70 -0
  59. package/src/cli/commands/wrap.ts +164 -0
  60. package/src/cli/main.ts +68 -19
  61. package/src/cli/parse-flags.ts +36 -11
  62. package/src/cli/usage.ts +12 -3
  63. package/src/framework.ts +1 -7
  64. package/src/hooks/session-banner.ts +64 -11
  65. package/src/hooks/session-guidance.ts +40 -0
  66. package/src/hooks/validate-artifact-path.ts +108 -35
  67. package/src/lib/fs-utils.ts +9 -0
  68. package/src/lib/install.ts +33 -0
  69. package/src/lib/orchestrator/adapters/base.ts +14 -125
  70. package/src/lib/orchestrator/adapters/claude.ts +0 -11
  71. package/src/lib/orchestrator/adapters/opencode.ts +2 -32
  72. package/src/lib/orchestrator/companion-git-sync.ts +94 -84
  73. package/src/lib/orchestrator/config-store.ts +2 -21
  74. package/src/lib/orchestrator/editor.ts +12 -22
  75. package/src/lib/orchestrator/framework-context.ts +17 -6
  76. package/src/lib/orchestrator/global-config-store.ts +1 -1
  77. package/src/lib/orchestrator/launcher.ts +97 -7
  78. package/src/lib/orchestrator/opencode-guidance.ts +4 -56
  79. package/src/lib/orchestrator/projection-claude-entry.ts +198 -0
  80. package/src/lib/orchestrator/projection-claude-skills.ts +120 -0
  81. package/src/lib/orchestrator/projection-companion-link.ts +62 -0
  82. package/src/lib/orchestrator/projection-entries.ts +377 -0
  83. package/src/lib/orchestrator/projection-record.ts +56 -0
  84. package/src/lib/orchestrator/projection-runtime-documents.ts +424 -0
  85. package/src/lib/orchestrator/projection-types.ts +169 -0
  86. package/src/lib/orchestrator/repo-local-registry.ts +37 -133
  87. package/src/lib/orchestrator/repo-local-store.ts +96 -0
  88. package/src/lib/orchestrator/setup-compatibilities.ts +1 -9
  89. package/src/lib/orchestrator/types.ts +1 -0
  90. package/src/lib/orchestrator/working-repo-projection.ts +366 -0
  91. package/src/lib/orchestrator/workspace-inventory.ts +1 -1
  92. package/src/lib/package-paths.ts +11 -1
  93. package/src/lib/public-npm.ts +2 -1
  94. package/src/lib/update-checker.ts +15 -9
  95. package/src/opencode/companion-hooks.ts +89 -245
  96. package/src/opencode/companion-policy.ts +35 -10
  97. package/src/opencode/index.ts +1 -0
  98. package/src/opencode/projected-guidance.ts +56 -0
  99. package/src/opencode/tui.tsx +13 -4
  100. package/src/playbooks/companion-guidance.ts +32 -116
  101. package/src/plugins.ts +0 -1
  102. package/src/runtime/companion-git-state.ts +156 -0
  103. package/src/runtime/companion-git.ts +203 -0
  104. package/src/runtime/companion-guidance.ts +222 -0
  105. package/src/runtime/companion-sync.ts +298 -0
  106. package/src/runtime/env-names.ts +30 -0
  107. package/src/runtime/env.ts +67 -35
  108. package/src/runtime/framework.ts +10 -0
  109. package/src/runtime/freshness.ts +58 -0
  110. package/src/runtime/index.ts +104 -0
  111. package/src/runtime/install.ts +30 -0
  112. package/src/runtime/policy.ts +66 -0
  113. package/src/runtime/projected-guidance.ts +45 -0
  114. package/src/runtime/projection.ts +224 -0
  115. package/src/runtime/repo-local.ts +64 -0
  116. package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +56 -0
  117. package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +48 -0
  118. package/src/templates/capabilities/openspec-cap/mate-minimal/templates/tasks.md +22 -0
  119. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +31 -90
  120. package/src/templates/capabilities/openspec-cap/mate-v1/templates/design.md +3 -0
  121. package/src/templates/capabilities/openspec-cap/mate-v1/templates/explore-brief.md +6 -6
  122. package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
  123. package/src/templates/capabilities/openspec-cap/mate-v1/templates/tasks.md +3 -0
  124. package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +30 -0
  125. package/src/templates/capabilities/react-doctor/claude/hooks/react-doctor.sh +2 -2
  126. package/src/templates/mate-skills/agents/mate-artifact-publish/SKILL.md +185 -0
  127. package/src/templates/mate-skills/agents/mate-artifact-publish/references/openspec.md +227 -0
  128. package/src/templates/mate-skills/agents/mate-domain-modeling/SKILL.md +68 -0
  129. package/src/templates/mate-skills/agents/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
  130. package/src/templates/mate-skills/agents/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
  131. package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +14 -0
  132. package/src/templates/mate-skills/agents/mate-grill-with-docs/SKILL.md +29 -0
  133. package/src/templates/mate-skills/agents/mate-grilling/SKILL.md +49 -0
  134. package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +156 -0
  135. package/src/templates/{capabilities/openspec-cap/mate-skills → mate-skills}/agents/mate-openspec-backfill/SKILL.md +4 -2
  136. package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +139 -0
  137. package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +503 -0
  138. package/src/templates/mate-skills/claude/mate-artifact-publish/SKILL.md +185 -0
  139. package/src/templates/mate-skills/claude/mate-artifact-publish/references/openspec.md +227 -0
  140. package/src/templates/mate-skills/claude/mate-domain-modeling/SKILL.md +68 -0
  141. package/src/templates/mate-skills/claude/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
  142. package/src/templates/mate-skills/claude/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
  143. package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +14 -0
  144. package/src/templates/mate-skills/claude/mate-grill-with-docs/SKILL.md +29 -0
  145. package/src/templates/mate-skills/claude/mate-grilling/SKILL.md +49 -0
  146. package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +156 -0
  147. package/src/templates/mate-skills/claude/mate-openspec-backfill/SKILL.md +67 -0
  148. package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +139 -0
  149. package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +503 -0
  150. package/src/templates/report-assets/README.md +32 -0
  151. package/src/templates/report-assets/mermaid.LICENSE +21 -0
  152. package/src/templates/report-assets/mermaid.min.js +4376 -0
  153. package/src/templates/root/TEMPLATE_CLAUDE.md +1 -9
  154. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +1476 -197
  155. package/src/tools/setup/capabilities/graphify.ts +16 -7
  156. package/src/tools/setup/capabilities/openspec.ts +63 -56
  157. package/src/tools/setup/capabilities/tokensave.ts +116 -2
  158. package/src/tools/setup/engine.ts +34 -6
  159. package/src/tools/setup/mate.ts +42 -13
  160. package/src/tools/setup/plugin.ts +9 -0
  161. package/src/tools/setup/plugins/guidance.ts +11 -1
  162. package/src/tools/setup/providers/claude-format.ts +49 -4
  163. package/src/tools/setup/providers/claude-plugin-hooks.ts +117 -0
  164. package/src/tools/setup/providers/claude.ts +55 -220
  165. package/src/tools/setup/providers/opencode.ts +41 -14
  166. package/src/tools/setup/runtime-documents.ts +174 -0
  167. package/src/tools/setup/surface-target.ts +50 -0
  168. package/src/tools/setup/working-repo-cleanup.ts +33 -26
  169. package/src/tools/setup/working-repo-local-state.ts +21 -1
  170. package/src/tools/setup.ts +25 -3
  171. package/wrappers/bin/graphify +57 -8
  172. package/wrappers/bin/openspec +50 -3
  173. package/claude-plugin/hooks/artifact-finish-nudge.mjs +0 -8
  174. package/src/cli/commands/cap/headroom.ts +0 -52
  175. package/src/cli/commands/workspace/list.ts +0 -25
  176. package/src/cli/commands/workspace/materialize.ts +0 -46
  177. package/src/cli/commands/workspace/workspace.ts +0 -22
  178. package/src/hooks/artifact-finish-nudge.ts +0 -244
  179. package/src/lib/orchestrator/headroom/proxy.ts +0 -116
  180. package/src/lib/orchestrator/workspace-materialize.ts +0 -80
  181. package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/SKILL.md +0 -51
  182. package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/references/openspec.md +0 -134
  183. package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/SKILL.md +0 -58
  184. package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/references/openspec.md +0 -139
  185. package/src/tools/setup/capabilities/headroom.ts +0 -57
  186. /package/src/cli/commands/{workspace → companion}/open.ts +0 -0
@@ -0,0 +1,377 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { version } from "../../../package.json";
5
+ import {
6
+ PROJECTION_YAML_FILE,
7
+ computeProjectionStamp,
8
+ readProjectionFile,
9
+ writeProjectionPair,
10
+ } from "../../runtime/projection";
11
+ import {
12
+ repoLocalDirName,
13
+ repoLocalDirPath,
14
+ repoLocalRegistryPath,
15
+ } from "../../runtime/repo-local";
16
+ import { TOKENSAVE_WORKING_REPO_EXCLUDE_ENTRIES } from "../../tools/setup/capabilities/tokensave-shared";
17
+ import {
18
+ ensureWorkingRepoLocalExcludes,
19
+ managedWorkingRepoExcludesPresent,
20
+ reconcileWorkingRepoCapabilityExcludes,
21
+ removeWorkingRepoLocalExcludes,
22
+ workingRepoCapabilityExcludesPresent,
23
+ } from "../../tools/setup/working-repo-local-state";
24
+ import { companionLinkPresent, linkCompanion, unlinkCompanion } from "./projection-companion-link";
25
+ import {
26
+ reconcileWorkingRepoClaudeSkillLinks,
27
+ removeWorkingRepoClaudeSkillLinks,
28
+ } from "./projection-claude-skills";
29
+ import { buildProjection } from "./projection-record";
30
+ import {
31
+ CLAUDE_WORKING_SETTINGS_PATH,
32
+ LEGACY_TOKENSAVE_CLAUDE_MD_PATH,
33
+ legacyTokensaveClaudeMdPresent,
34
+ removeWorkingRepoClaudeSettings,
35
+ stripTokensaveClaudeMdAppend,
36
+ syncWorkingRepoClaudeAdditionalDirectories,
37
+ workingRepoClaudeSettingsPresent,
38
+ } from "./projection-claude-entry";
39
+ import {
40
+ CLAUDE_LOCAL_CONFIG_DOCUMENT,
41
+ CLAUDE_MCP_DOCUMENT,
42
+ CLAUDE_SETTINGS_DOCUMENT,
43
+ OPENCODE_CONFIG_DOCUMENT,
44
+ placeRuntimeDocument,
45
+ removeRuntimeDocument,
46
+ runtimeDocumentPresent,
47
+ } from "./projection-runtime-documents";
48
+ import {
49
+ upsertRepoLocalCompanionPointer,
50
+ upsertRepoLocalLinkedRepository,
51
+ writeRepoLocalFrameworkConfig,
52
+ } from "./repo-local-store";
53
+ import type { ProjectionEntry, ProjectionInput } from "./projection-types";
54
+
55
+ export const WORKSPACE_DOCUMENT_FILE = "workspace.code-workspace";
56
+
57
+ /** The shared editor workspace document, inside the Projection Root. */
58
+ export function workspaceDocumentPath(repoPath: string): string {
59
+ return path.join(repoLocalDirPath(repoPath), WORKSPACE_DOCUMENT_FILE);
60
+ }
61
+
62
+ async function pathExists(candidatePath: string): Promise<boolean> {
63
+ try {
64
+ await fs.access(candidatePath);
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ async function writeProjectionPairEntry(input: ProjectionInput): Promise<"written" | "current"> {
72
+ const { companionPath, repository } = input;
73
+ const resolvedCompanionPath = path.resolve(companionPath!);
74
+ const registryContent = await fs
75
+ .readFile(repoLocalRegistryPath(input.repoPath), "utf8")
76
+ .catch(() => "");
77
+ const stamp = computeProjectionStamp({ version, registryContent });
78
+
79
+ /**
80
+ * The stamp cannot see a re-pin: neither of its inputs — mate version,
81
+ * repo-local registry — changes when a differently-linked companion is
82
+ * resolved, so the projected companion is compared alongside it.
83
+ */
84
+ const existing = readProjectionFile(input.repoPath);
85
+ if (
86
+ existing?.stamp === stamp &&
87
+ path.resolve(existing.projection.companionPath) === resolvedCompanionPath
88
+ ) {
89
+ return "current";
90
+ }
91
+
92
+ writeProjectionPair(input.repoPath, {
93
+ stamp,
94
+ projection: buildProjection(resolvedCompanionPath, repository!),
95
+ });
96
+ return "written";
97
+ }
98
+
99
+ function tokensaveEnabled(input: ProjectionInput): boolean {
100
+ return (input.config?.capabilities ?? []).some((capability) => capability.name === "tokensave");
101
+ }
102
+
103
+ /**
104
+ * Built on first use rather than at module evaluation: the catalogue reaches
105
+ * modules that reach back for `project`, so nothing here may be needed before
106
+ * its own module finishes loading.
107
+ */
108
+ function buildEntries(): readonly ProjectionEntry[] {
109
+ const localDir = repoLocalDirName();
110
+
111
+ return [
112
+ /**
113
+ * First, always. Every scope below writes a path this block covers, so a
114
+ * reader that races a write sees an excluded file rather than a tracked one.
115
+ */
116
+ {
117
+ id: "git-excludes",
118
+ kind: "merged",
119
+ path: path.join(".git", "info", "exclude"),
120
+ scopes: ["link", "session", "launch", "wrap"],
121
+ write: async (input) =>
122
+ (await ensureWorkingRepoLocalExcludes(input.repoPath)) ? "written" : "current",
123
+ removal: {
124
+ by: "self",
125
+ remove: async (input) =>
126
+ (await removeWorkingRepoLocalExcludes(input.repoPath)) ? "removed" : "absent",
127
+ },
128
+ present: managedWorkingRepoExcludesPresent,
129
+ },
130
+ {
131
+ id: "projection-root",
132
+ kind: "owned",
133
+ path: localDir,
134
+ scopes: ["link", "session", "workspace", "wrap"],
135
+ write: async (input) => {
136
+ const root = repoLocalDirPath(input.repoPath);
137
+ if (await pathExists(root)) return "current";
138
+ await fs.mkdir(root, { recursive: true });
139
+ return "written";
140
+ },
141
+ removal: {
142
+ by: "self",
143
+ remove: async (input) => {
144
+ const root = repoLocalDirPath(input.repoPath);
145
+ if (!(await pathExists(root))) return "absent";
146
+ await fs.rm(root, { recursive: true, force: true });
147
+ return "removed";
148
+ },
149
+ },
150
+ },
151
+ /**
152
+ * Directly after the root that holds it, so every scope that writes the
153
+ * root also exposes the companion through it.
154
+ */
155
+ {
156
+ id: "companion-link",
157
+ kind: "owned",
158
+ path: path.join(localDir, "companion"),
159
+ scopes: ["link", "session", "wrap"],
160
+ write: async (input) =>
161
+ input.companionPath ? linkCompanion(input.repoPath, input.companionPath) : "skipped",
162
+ degradable: true,
163
+ removal: {
164
+ by: "self",
165
+ remove: (input) => unlinkCompanion(input.repoPath),
166
+ },
167
+ present: companionLinkPresent,
168
+ },
169
+ {
170
+ id: "repo-local-framework",
171
+ kind: "owned",
172
+ path: path.join(localDir, "config", "framework.yaml"),
173
+ scopes: ["link"],
174
+ write: async (input) =>
175
+ (await writeRepoLocalFrameworkConfig(input.repoPath)) ? "written" : "current",
176
+ removal: { by: "entry", entry: "projection-root" },
177
+ },
178
+ {
179
+ id: "repo-local-registry",
180
+ kind: "owned",
181
+ path: path.join(localDir, "config", "registry.yaml"),
182
+ scopes: ["link"],
183
+ write: async (input) => {
184
+ if (!input.companionPath || !input.repository || !input.source) return "skipped";
185
+ await upsertRepoLocalLinkedRepository(input.repoPath, input.repository);
186
+ await upsertRepoLocalCompanionPointer(
187
+ input.repoPath,
188
+ input.companionPath,
189
+ input.repository.id,
190
+ input.source,
191
+ );
192
+ return "written";
193
+ },
194
+ removal: { by: "entry", entry: "projection-root" },
195
+ },
196
+ /** One entry, two files: the pair is written as a unit or not at all. */
197
+ {
198
+ id: "projection-pair",
199
+ kind: "owned",
200
+ path: path.join(localDir, PROJECTION_YAML_FILE),
201
+ scopes: ["session"],
202
+ write: async (input) =>
203
+ input.companionPath && input.repository ? writeProjectionPairEntry(input) : "skipped",
204
+ removal: { by: "entry", entry: "projection-root" },
205
+ },
206
+ {
207
+ id: "claude-skill-links",
208
+ kind: "merged",
209
+ path: path.join(".claude", "skills"),
210
+ scopes: ["wrap"],
211
+ removeOnUnwrap: true,
212
+ write: async (input) => {
213
+ if (!input.config) return "skipped";
214
+ const registeredCompanionPaths = input.globalConfigStore
215
+ ? await input.globalConfigStore.list()
216
+ : [];
217
+ return reconcileWorkingRepoClaudeSkillLinks(
218
+ input.repoPath,
219
+ input.companionPath ?? null,
220
+ input.config.allowedAgents?.includes("claude") ?? false,
221
+ registeredCompanionPaths,
222
+ );
223
+ },
224
+ removal: {
225
+ by: "self",
226
+ remove: (input) =>
227
+ removeWorkingRepoClaudeSkillLinks(input.repoPath, input.registeredCompanionPaths ?? []),
228
+ },
229
+ },
230
+ /**
231
+ * Deliberately outside the managed block, so a Capability's exclusion
232
+ * survives a core-block rewrite — and therefore survives `unproject` too,
233
+ * because the Capability's local data does.
234
+ */
235
+ {
236
+ id: "capability-excludes",
237
+ kind: "merged",
238
+ path: path.join(".git", "info", "exclude"),
239
+ scopes: ["launch"],
240
+ write: async (input) => {
241
+ if (!input.config) return "skipped";
242
+ return (await reconcileWorkingRepoCapabilityExcludes(
243
+ input.repoPath,
244
+ tokensaveEnabled(input) ? TOKENSAVE_WORKING_REPO_EXCLUDE_ENTRIES : [],
245
+ TOKENSAVE_WORKING_REPO_EXCLUDE_ENTRIES,
246
+ ))
247
+ ? "written"
248
+ : "current";
249
+ },
250
+ removal: {
251
+ by: "retained",
252
+ because: "the Capability's local data is still in the Working Repository",
253
+ },
254
+ present: (repoPath) =>
255
+ workingRepoCapabilityExcludesPresent(repoPath, TOKENSAVE_WORKING_REPO_EXCLUDE_ENTRIES),
256
+ },
257
+ {
258
+ id: "claude-working-settings",
259
+ kind: "merged",
260
+ path: CLAUDE_WORKING_SETTINGS_PATH,
261
+ scopes: ["launch", "wrap"],
262
+ write: async (input) =>
263
+ input.companionPath
264
+ ? syncWorkingRepoClaudeAdditionalDirectories(
265
+ input.repoPath,
266
+ input.companionPath,
267
+ input.globalConfigStore,
268
+ )
269
+ : "skipped",
270
+ removal: {
271
+ by: "self",
272
+ remove: (input) =>
273
+ removeWorkingRepoClaudeSettings(input.repoPath, input.registeredCompanionPaths ?? []),
274
+ },
275
+ present: workingRepoClaudeSettingsPresent,
276
+ },
277
+ /**
278
+ * Removal-only: Mate never writes this block, an external installer does.
279
+ * Its `write` is the same strip, so a launch heals a repository the
280
+ * installer touched without waiting for a cleanup.
281
+ */
282
+ {
283
+ id: "legacy-tokensave-claude-md",
284
+ kind: "merged",
285
+ path: LEGACY_TOKENSAVE_CLAUDE_MD_PATH,
286
+ scopes: ["launch"],
287
+ write: async (input) =>
288
+ (await stripTokensaveClaudeMdAppend(input.repoPath)) === "removed" ? "written" : "current",
289
+ removal: { by: "self", remove: (input) => stripTokensaveClaudeMdAppend(input.repoPath) },
290
+ present: legacyTokensaveClaudeMdPresent,
291
+ },
292
+ {
293
+ id: "workspace-document",
294
+ kind: "owned",
295
+ path: path.join(localDir, WORKSPACE_DOCUMENT_FILE),
296
+ scopes: ["workspace"],
297
+ write: async (input) => {
298
+ if (!input.companionPath) return "skipped";
299
+ const target = workspaceDocumentPath(input.repoPath);
300
+ const folders = [path.resolve(input.repoPath), path.resolve(input.companionPath)];
301
+ const next = `${JSON.stringify({ folders: folders.map((candidate) => ({ path: candidate })) }, null, 2)}\n`;
302
+ if ((await fs.readFile(target, "utf8").catch(() => null)) === next) return "current";
303
+ await fs.mkdir(path.dirname(target), { recursive: true });
304
+ await fs.writeFile(target, next, "utf8");
305
+ return "written";
306
+ },
307
+ removal: { by: "entry", entry: "projection-root" },
308
+ },
309
+ /**
310
+ * Last: the documents an Agent Runtime discovers on its own. One declaration
311
+ * per destination, all three placing a value some Runtime Surface rendered —
312
+ * no format knowledge reaches this catalogue.
313
+ *
314
+ * A launch renders them again, because every value here is pinned to the
315
+ * mate that wrote it — the OpenCode plugin package's version, the plugin
316
+ * root the Claude hook commands name — and a repository wrapped once would
317
+ * otherwise keep the pins of whichever release wrapped it. Re-rendering is
318
+ * reconciliation, not an append: `placeRuntimeDocument` withdraws what the
319
+ * previous pass recorded before applying this one's, and a pass that
320
+ * supplied no render at all claims nothing and leaves the record standing.
321
+ *
322
+ * The `launch` scope declares them so `mate working cleanup` reaches them
323
+ * and so a launch answers for a destination it finds recorded, but a launch
324
+ * renders nothing for them: a Managed Session is refused in a wrapped
325
+ * repository, so the only pass that ever places one is `mate wrap`.
326
+ */
327
+ /**
328
+ * Removal-only, like the legacy CLAUDE.md append: releases before local
329
+ * scope projected the companion's MCP servers here, and a server left in
330
+ * `.mcp.json` would sit "pending approval" forever beside the live local
331
+ * one. Its `write` is the same strip, so a re-wrap heals a repository an
332
+ * older mate wrapped without waiting for a cleanup.
333
+ */
334
+ {
335
+ id: "mcp-runtime-document",
336
+ kind: "merged",
337
+ path: CLAUDE_MCP_DOCUMENT,
338
+ scopes: ["launch", "wrap"],
339
+ write: async (input) =>
340
+ (await removeRuntimeDocument(input.repoPath, CLAUDE_MCP_DOCUMENT)) === "removed"
341
+ ? "written"
342
+ : "current",
343
+ removal: {
344
+ by: "self",
345
+ remove: (input) => removeRuntimeDocument(input.repoPath, CLAUDE_MCP_DOCUMENT),
346
+ },
347
+ present: (repoPath) => runtimeDocumentPresent(repoPath, CLAUDE_MCP_DOCUMENT),
348
+ },
349
+ ...(
350
+ [
351
+ ["claude-runtime-document", CLAUDE_SETTINGS_DOCUMENT],
352
+ ["claude-local-mcp-document", CLAUDE_LOCAL_CONFIG_DOCUMENT],
353
+ ["opencode-runtime-document", OPENCODE_CONFIG_DOCUMENT],
354
+ ] as const
355
+ ).map(([id, documentPath]) => ({
356
+ id,
357
+ kind: "merged" as const,
358
+ path: documentPath.split("/").join(path.sep),
359
+ scopes: ["launch", "wrap"] as const,
360
+ write: (input: ProjectionInput) =>
361
+ placeRuntimeDocument(input.repoPath, documentPath, input.runtimeDocuments),
362
+ removal: {
363
+ by: "self" as const,
364
+ remove: (input: { repoPath: string }) =>
365
+ removeRuntimeDocument(input.repoPath, documentPath),
366
+ },
367
+ present: (repoPath: string) => runtimeDocumentPresent(repoPath, documentPath),
368
+ })),
369
+ ];
370
+ }
371
+
372
+ let entries: readonly ProjectionEntry[] | null = null;
373
+
374
+ export function projectionEntries(): readonly ProjectionEntry[] {
375
+ entries ??= buildEntries();
376
+ return entries;
377
+ }
@@ -0,0 +1,56 @@
1
+ import path from "node:path";
2
+
3
+ import { version } from "../../../package.json";
4
+ import {
5
+ PROJECTION_ENV_NAMES,
6
+ PROJECTION_FIELDS,
7
+ type MateProjection,
8
+ } from "../../runtime/projection";
9
+ import {
10
+ GRAPHIFY_OUTPUT_SUBDIR,
11
+ GRAPHIFY_STORE_SEGMENT,
12
+ } from "../../tools/setup/capabilities/graphify";
13
+ import { getReactDoctorBinPath, getWrapperBinPath } from "../package-paths";
14
+ import type { LinkedRepository } from "./types";
15
+
16
+ /**
17
+ * The one derivation of the projected paths. Both the Projection Root and a
18
+ * managed launch materialize this record, so neither can hold its own copy of a
19
+ * layout the other computes.
20
+ */
21
+
22
+ /**
23
+ * Both bin paths and `GRAPHIFY_OUT` are unconditional: a path exists whether or
24
+ * not its Capability is enabled, and enablement is a predicate, read live from
25
+ * the companion rather than cached here.
26
+ */
27
+ export function buildProjection(
28
+ companionPath: string,
29
+ repository: LinkedRepository,
30
+ ): MateProjection {
31
+ return {
32
+ version,
33
+ companionPath,
34
+ repositoryPath: repository.path,
35
+ repositoryId: repository.id,
36
+ wrapperBinPath: getWrapperBinPath(),
37
+ reactDoctorBinPath: getReactDoctorBinPath(),
38
+ graphifyOut: path.join(
39
+ companionPath,
40
+ GRAPHIFY_STORE_SEGMENT,
41
+ repository.id,
42
+ GRAPHIFY_OUTPUT_SUBDIR,
43
+ ),
44
+ };
45
+ }
46
+
47
+ /**
48
+ * The record spelled as environment variables. `graphifyy` resolves its output
49
+ * directory from `GRAPHIFY_OUT` at import time, so a launch that omitted it
50
+ * would leak `graphify-out/` into the Working Repository.
51
+ */
52
+ export function projectionEnvironment(projection: MateProjection): NodeJS.ProcessEnv {
53
+ return Object.fromEntries(
54
+ PROJECTION_FIELDS.map((field) => [PROJECTION_ENV_NAMES[field], projection[field]]),
55
+ );
56
+ }