@uniqbit/mate-core 0.18.4 → 0.18.5

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 (44) hide show
  1. package/opencode-plugin/src/add-dir.ts +76 -10
  2. package/opencode-plugin/src/companion.ts +1 -8
  3. package/opencode-plugin/src/server.ts +2 -15
  4. package/package.json +1 -1
  5. package/src/cli/commands/artifact/finish/openspec.ts +40 -8
  6. package/src/cli/commands/doctor.ts +92 -86
  7. package/src/cli/commands/install.ts +0 -8
  8. package/src/cli/commands/studio/terminal-launch.ts +7 -16
  9. package/src/cli/commands/wrap.ts +4 -29
  10. package/src/cli/usage.ts +1 -2
  11. package/src/hooks/session-guidance.ts +2 -20
  12. package/src/lib/orchestrator/adapters/claude.ts +20 -130
  13. package/src/lib/orchestrator/adapters/opencode.ts +7 -24
  14. package/src/lib/orchestrator/config-store.ts +2 -70
  15. package/src/lib/orchestrator/projection-claude-skills.ts +35 -88
  16. package/src/lib/orchestrator/projection-entries.ts +0 -1
  17. package/src/lib/orchestrator/projection-types.ts +0 -2
  18. package/src/lib/orchestrator/session-prepare.ts +4 -82
  19. package/src/lib/orchestrator/wrap-pass.ts +1 -88
  20. package/src/runtime/guidance.ts +0 -17
  21. package/src/runtime/policy.ts +1 -14
  22. package/src/runtime/runtime-document-manifest.ts +0 -17
  23. package/src/runtime/wrap-state.ts +0 -4
  24. package/src/tools/setup/capabilities/openspec.ts +140 -18
  25. package/src/tools/setup/dynamic-plugins/audiences.ts +7 -39
  26. package/src/tools/setup/dynamic-plugins/declarations.ts +1 -1
  27. package/src/tools/setup/dynamic-plugins/hydrate.ts +4 -7
  28. package/src/tools/setup/engine.ts +10 -141
  29. package/src/tools/setup/plugin.ts +4 -19
  30. package/src/tools/setup/plugins/gitignore.ts +1 -7
  31. package/src/tools/setup/providers/claude.ts +2 -27
  32. package/src/tools/setup/providers/opencode.ts +0 -10
  33. package/src/tools/setup/registry.ts +2 -24
  34. package/src/tools/setup/surface-target.ts +4 -13
  35. package/src/tools/setup.ts +6 -9
  36. package/opencode-plugin/src/audience-layer.ts +0 -91
  37. package/src/runtime/audience-layer.ts +0 -84
  38. package/src/runtime/opencode-audience-layer.ts +0 -176
  39. package/src/tools/setup/audience-isolation.fixture.ts +0 -325
  40. package/src/tools/setup/audience-layer.ts +0 -207
  41. package/src/tools/setup/audience-registrations.ts +0 -52
  42. package/src/tools/setup/contribution-ledger.ts +0 -207
  43. package/src/tools/setup/isolation-diagnostics.ts +0 -139
  44. package/src/tools/setup/upgrade-sweep.ts +0 -153
@@ -1,8 +1,6 @@
1
- // oxlint-disable no-await-in-loop
2
1
  import fs from "node:fs/promises";
3
2
  import path from "node:path";
4
3
 
5
- import { CURRENT_LAYER_LINK } from "../../runtime/audience-layer";
6
4
  import { readProjectionFile } from "../../runtime/projection";
7
5
  import { realLinkTarget, realOrResolved } from "../fs-utils";
8
6
  import {
@@ -66,114 +64,63 @@ async function createSkillLink(target: string, link: string): Promise<void> {
66
64
  await fs.symlink(target, link, process.platform === "win32" ? "junction" : "dir");
67
65
  }
68
66
 
69
- const CLAUDE_AGENTS_PATH = path.join(".claude", "agents");
70
-
71
- /** The manifest key of an agent link: repo-relative, POSIX separators. */
72
- function agentKey(file: string): string {
73
- return `.claude/agents/${file}`;
74
- }
75
-
76
- async function agentFiles(root: string): Promise<string[]> {
77
- const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
78
- return entries
79
- .filter((entry) => (entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".md"))
80
- .map((entry) => entry.name);
81
- }
82
-
83
- /**
84
- * Every link the wrap wants, by manifest key: the companion's skills, then the
85
- * wrapped audience's skills and agents. An audience link targets the layer
86
- * through its `current` link, so a re-rendered layer never strands it. A
87
- * companion skill wins a name both provide.
88
- */
89
- async function wantedLinks(
90
- currentRoot: string | null,
91
- layerRoot: string | null,
92
- ): Promise<Map<string, string>> {
93
- const wanted = new Map<string, string>();
94
- if (currentRoot) {
95
- for (const name of await skillNames(currentRoot)) {
96
- wanted.set(recordKey(name), path.join(currentRoot, name));
97
- }
98
- }
99
- if (layerRoot) {
100
- const layerSkills = path.join(layerRoot, "skills");
101
- for (const name of await skillNames(layerSkills)) {
102
- if (!wanted.has(recordKey(name))) wanted.set(recordKey(name), path.join(layerSkills, name));
103
- }
104
- const layerAgents = path.join(layerRoot, "agents");
105
- for (const file of await agentFiles(layerAgents)) {
106
- wanted.set(agentKey(file), path.join(layerAgents, file));
107
- }
108
- }
109
- return wanted;
110
- }
111
-
112
67
  /**
113
68
  * Recorded link: Mate's while it resolves to its recorded target. Unrecorded:
114
- * a skill link is adopted only when it resolves into a registered, current or
115
- * projected companion; an agent link never is. Records follow the filesystem,
116
- * so a partial failure re-adopts.
69
+ * adopted only when it resolves into a registered, current or projected
70
+ * companion. Records follow the filesystem, so a partial failure re-adopts.
117
71
  */
118
72
  export async function reconcileWorkingRepoClaudeSkillLinks(
119
73
  repoPath: string,
120
74
  companionPath: string | null,
121
75
  enabled: boolean,
122
76
  registeredCompanionPaths: string[] = [],
123
- audienceLayerDir: string | null = null,
124
77
  ): Promise<"written" | "current"> {
125
- const repoRoot = path.resolve(repoPath);
126
78
  const currentRoot = companionPath ? path.resolve(companionPath, CLAUDE_SKILLS_PATH) : null;
127
- const layerRoot =
128
- audienceLayerDir && enabled ? path.join(audienceLayerDir, CURRENT_LAYER_LINK, ".claude") : null;
129
79
  const roots = await registeredSkillRoots(repoPath, companionPath, registeredCompanionPaths);
80
+ const targetRoot = path.join(path.resolve(repoPath), CLAUDE_SKILLS_PATH);
130
81
  const recorded = links(await readManifest(repoPath), "claude");
131
- const wanted = await wantedLinks(enabled ? currentRoot : null, layerRoot);
82
+ const existing = await fs.readdir(targetRoot, { withFileTypes: true }).catch(() => []);
83
+ const sourceNames: Set<string> =
84
+ currentRoot && enabled ? new Set(await skillNames(currentRoot)) : new Set<string>();
132
85
  const records: Record<string, LinkRecord> = {};
133
86
  let changed = false;
134
87
 
135
- const linkDirs = [
136
- { dir: path.join(repoRoot, CLAUDE_SKILLS_PATH), keyOf: recordKey, adoptable: true },
137
- { dir: path.join(repoRoot, CLAUDE_AGENTS_PATH), keyOf: agentKey, adoptable: false },
138
- ];
139
- for (const { dir, keyOf, adoptable } of linkDirs) {
140
- const existing = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
141
- for (const entry of existing) {
142
- if (!entry.isSymbolicLink()) continue;
143
- const link = path.join(dir, entry.name);
144
- const target = await linkTarget(link);
145
- if (target === null) continue;
146
- const key = keyOf(entry.name);
147
- const record = recorded[key];
148
- const owned = record
149
- ? (await realOrResolved(record.target)) === target
150
- : adoptable && roots.some((root) => path.dirname(target) === root);
151
- if (!owned) continue;
88
+ for (const entry of existing) {
89
+ if (!entry.isSymbolicLink()) continue;
90
+ const link = path.join(targetRoot, entry.name);
91
+ const target = await linkTarget(link);
92
+ if (target === null) continue;
93
+ const record = recorded[recordKey(entry.name)];
94
+ const owned = record
95
+ ? (await realOrResolved(record.target)) === target
96
+ : roots.some((root) => path.dirname(target) === root);
97
+ if (!owned) continue;
98
+
99
+ const wanted = currentRoot ? path.join(currentRoot, entry.name) : null;
100
+ if (wanted && sourceNames.has(entry.name) && (await realOrResolved(wanted)) === target) {
101
+ records[recordKey(entry.name)] = { runtime: "claude", target: wanted };
102
+ continue;
103
+ }
152
104
 
153
- const want = wanted.get(key);
154
- if (want && (await realOrResolved(want)) === target) {
155
- records[key] = { runtime: "claude", target: want };
156
- continue;
157
- }
105
+ await fs.unlink(link);
106
+ changed = true;
107
+ }
158
108
 
159
- await fs.unlink(link);
109
+ if (currentRoot && sourceNames.size > 0) {
110
+ await fs.mkdir(targetRoot, { recursive: true });
111
+ for (const name of sourceNames) {
112
+ const link = path.join(targetRoot, name);
113
+ const existingLink = await fs.lstat(link).catch(() => null);
114
+ if (existingLink) continue;
115
+ const target = path.join(currentRoot, name);
116
+ await createSkillLink(target, link);
117
+ records[recordKey(name)] = { runtime: "claude", target };
160
118
  changed = true;
161
119
  }
162
120
  }
163
121
 
164
- for (const [key, target] of wanted) {
165
- const link = path.join(repoRoot, ...key.split("/"));
166
- const existingLink = await fs.lstat(link).catch(() => null);
167
- if (existingLink) continue;
168
- await fs.mkdir(path.dirname(link), { recursive: true });
169
- if (key.startsWith(".claude/agents/")) await fs.symlink(target, link, "file");
170
- else await createSkillLink(target, link);
171
- records[key] = { runtime: "claude", target };
172
- changed = true;
173
- }
174
-
175
122
  if (changed || !enabled) {
176
- for (const { dir } of linkDirs) await pruneEmptyAncestors(dir, repoRoot);
123
+ await pruneEmptyAncestors(targetRoot, path.resolve(repoPath));
177
124
  }
178
125
  if (JSON.stringify(sortedEntries(records)) !== JSON.stringify(sortedEntries(recorded))) {
179
126
  await updateManifest(repoPath, (manifest) => withLinks(manifest, "claude", records));
@@ -307,7 +307,6 @@ function buildEntries(): readonly ProjectionEntry[] {
307
307
  input.companionPath ?? null,
308
308
  input.config.allowedAgents?.includes("claude") ?? false,
309
309
  registeredCompanionPaths,
310
- input.audienceLayerDir ?? null,
311
310
  );
312
311
  },
313
312
  removal: {
@@ -50,8 +50,6 @@ export interface ProjectionInput {
50
50
  runtimeDocuments?: readonly RenderedRuntimeDocument[];
51
51
  /** Restricts runtime-tagged entries to these runtimes; every runtime when absent. */
52
52
  runtimes?: readonly WrapRuntime[];
53
- /** The layer directory of the audience the checkout is wrapped for; `null` or absent for base only. */
54
- audienceLayerDir?: string | null;
55
53
  }
56
54
 
57
55
  export interface ProjectionRemovalInput {
@@ -7,14 +7,8 @@ import {
7
7
  syncCompanionUnattended,
8
8
  type UnattendedSyncOutcome,
9
9
  } from "../../runtime/companion-sync";
10
- import { readWrapStateSync, type WrapState } from "../../runtime/wrap-state";
10
+ import type { WrapState } from "../../runtime/wrap-state";
11
11
  import { syncCompanionFiles } from "../../tools/setup";
12
- import { renderAudienceLayer, type RenderedAudienceLayer } from "../../tools/setup/audience-layer";
13
- import {
14
- readAudienceSelection,
15
- resolveAudience,
16
- } from "../../tools/setup/dynamic-plugins/audiences";
17
- import { buildSetupInstallationPlan } from "../../tools/setup/engine";
18
12
  import type { CapabilityPlugin, LaunchPreflightContext } from "../../tools/setup/plugin";
19
13
  import { attendedLockOptions, syncCompanionGit } from "./companion-git-sync";
20
14
  import { readCompanionConfig } from "./config-store";
@@ -40,8 +34,7 @@ export type PrepareStepName =
40
34
  | "file-sync"
41
35
  | "launch-projection"
42
36
  | "working-target"
43
- | "preflight"
44
- | "audience-layer";
37
+ | "preflight";
45
38
 
46
39
  export interface PrepareStep {
47
40
  step: PrepareStepName;
@@ -68,8 +61,6 @@ export interface PrepareResult {
68
61
  failure?: unknown;
69
62
  /** The wrap state the `working-target` step's pass left; a launch reads no manifest itself. */
70
63
  wrapState?: WrapState;
71
- /** The active audience's layer, rendered for an attended prepare only. */
72
- audienceLayer?: RenderedAudienceLayer;
73
64
  }
74
65
 
75
66
  export interface PrepareSessionInput {
@@ -98,10 +89,9 @@ const SYNC_LOCK_TIMEOUT_MS = 5 * 60_000;
98
89
  */
99
90
  async function runCapabilityPreflight(context: LaunchPreflightContext): Promise<void> {
100
91
  const { config, providerId } = context;
101
- /** Base only: an audience plugin's `forProvider` hooks never run. */
102
92
  const preflights = getActiveDistribution()
103
- .registry.baseRegistrations()
104
- .flatMap(({ plugin }) => {
93
+ .registry.getAll()
94
+ .flatMap((plugin) => {
105
95
  if (plugin.kind !== "capability" || !plugin.isEnabled(config)) return [];
106
96
  const capability = plugin as CapabilityPlugin;
107
97
  const preflight = capability.forProvider?.[providerId]?.preflight;
@@ -147,11 +137,6 @@ export const sessionPrepareDeps = {
147
137
  withCompanionLock,
148
138
  /** As the launch decides it: Git may prompt only with a terminal on both ends. */
149
139
  isInteractiveTerminal: () => !!(process.stdin.isTTY && process.stdout.isTTY),
150
- /** The launch's `MATE_AUDIENCE`; read by an attended prepare only. */
151
- audienceSelection: () => readAudienceSelection(process.env),
152
- /** The audience a checkout is wrapped for, from its manifest; `null` for base. */
153
- wrappedAudience: (repoPath: string) => readWrapStateSync(repoPath).audience,
154
- renderAudienceLayer,
155
140
  };
156
141
 
157
142
  function message(error: unknown): string {
@@ -164,7 +149,6 @@ class StepLog {
164
149
  readonly warnings: string[] = [];
165
150
  failure: unknown;
166
151
  wrapState: WrapState | undefined;
167
- audienceLayer: RenderedAudienceLayer | undefined;
168
152
  stopped = false;
169
153
  companionChanged = false;
170
154
 
@@ -337,71 +321,12 @@ async function runLocal(
337
321
  });
338
322
  }
339
323
 
340
- /**
341
- * The active audience's launch layer, after the base prepare. Only a managed
342
- * launch reaches here: a bare session's prepare never renders one, whatever
343
- * `MATE_AUDIENCE` its environment carries. An audience the companion does not
344
- * declare renders nothing; hydration already warned about it.
345
- */
346
- async function renderLayer(
347
- input: PrepareSessionInput,
348
- config: FrameworkConfig,
349
- run: StepLog,
350
- ): Promise<void> {
351
- const deps = sessionPrepareDeps;
352
- /** Adopted from the wrap: the wrap delivers it, so the launch renders no layer of its own. */
353
- const wrapped = input.repository ? deps.wrappedAudience(input.repository.path) : null;
354
- if (wrapped) {
355
- run.push({ step: "audience-layer", outcome: "ok", detail: `${wrapped}: from the wrap` });
356
- return;
357
- }
358
- const audience = resolveAudience(config, deps.audienceSelection());
359
- if (!audience.ok || audience.active === null) return;
360
- try {
361
- const registry = getActiveDistribution().registry;
362
- const layer = await deps.renderAudienceLayer({
363
- companionPath: input.companionPath,
364
- config,
365
- audience: audience.active,
366
- activeProviders: buildSetupInstallationPlan(config, registry.baseRegistrations())
367
- .activeProviders,
368
- registrations: registry.audienceRegistrations(audience.active),
369
- });
370
- run.audienceLayer = layer;
371
- run.push({
372
- step: "audience-layer",
373
- outcome: "ok",
374
- detail: `${layer.audience}: ${layer.rendered ? "rendered" : "current"}`,
375
- });
376
- } catch (error) {
377
- run.fail("audience-layer", error);
378
- }
379
- }
380
-
381
- /**
382
- * A checkout wrapped for one audience refuses a launch for another before
383
- * anything runs: the wrap's on-disk content would reach that session.
384
- */
385
- function audienceConflict(input: PrepareSessionInput, config: FrameworkConfig): string | null {
386
- if (!input.repository) return null;
387
- const wrapped = sessionPrepareDeps.wrappedAudience(input.repository.path);
388
- if (!wrapped) return null;
389
- const selected = resolveAudience(config, sessionPrepareDeps.audienceSelection());
390
- if (!selected.ok || selected.active === null || selected.active === wrapped) return null;
391
- return `${FRAMEWORK_NAME}: ${input.repository.id} is wrapped for audience "${wrapped}", but MATE_AUDIENCE selects "${selected.active}". Run \`${FRAMEWORK_NAME} wrap --audience ${selected.active}\` in ${input.repository.path}, or launch from another worktree wrapped for "${selected.active}".`;
392
- }
393
-
394
324
  async function runAttended(
395
325
  input: PrepareSessionInput,
396
326
  config: FrameworkConfig,
397
327
  run: StepLog,
398
328
  ): Promise<void> {
399
329
  const deps = sessionPrepareDeps;
400
- const conflict = audienceConflict(input, config);
401
- if (conflict) {
402
- run.fail("audience-layer", new LaunchPreflightError(conflict));
403
- return;
404
- }
405
330
  if (input.skipGit) {
406
331
  run.push({ step: "git-sync", outcome: "skipped", detail: "--no-git" });
407
332
  } else if (config.git !== "auto") {
@@ -422,8 +347,6 @@ async function runAttended(
422
347
  }
423
348
  }
424
349
  await runLocal(input, config, run);
425
- if (run.stopped) return;
426
- await renderLayer(input, config, run);
427
350
  }
428
351
 
429
352
  /** The sync's own config read: only a fast-forward can have changed it. */
@@ -478,7 +401,6 @@ export async function prepareSession(input: PrepareSessionInput): Promise<Prepar
478
401
  ...(run.companionChanged ? { companionChanged: true as const } : {}),
479
402
  ...(run.failure === undefined ? {} : { failure: run.failure }),
480
403
  ...(run.wrapState ? { wrapState: run.wrapState } : {}),
481
- ...(run.audienceLayer ? { audienceLayer: run.audienceLayer } : {}),
482
404
  });
483
405
 
484
406
  const locked = await deps.withCompanionLock(input.companionPath, lockOptions(input), async () => {
@@ -1,24 +1,11 @@
1
1
  import path from "node:path";
2
2
 
3
3
  import { renderWorkingRuntimeDocuments } from "../../tools/setup";
4
- import { renderAudienceLayer } from "../../tools/setup/audience-layer";
5
- import { loadAudienceRegistrations } from "../../tools/setup/audience-registrations";
6
- import { declaredAudiences } from "../../tools/setup/dynamic-plugins/audiences";
7
- import { buildSetupInstallationPlan } from "../../tools/setup/engine";
8
- import { getActiveDistribution } from "../../distribution";
9
- import { audienceLayerDir } from "../../runtime/audience-layer";
10
- import {
11
- readManifest,
12
- recordedAudience,
13
- updateManifest,
14
- withAudience,
15
- } from "../../runtime/runtime-document-manifest";
16
4
  import { readCompanionConfig } from "./config-store";
17
5
  import { GlobalConfigStore } from "./global-config-store";
18
6
  import { entryFailureMessage, firstFailure, type ProjectionChangedPaths } from "./projection-types";
19
7
  import type { WrapRuntime } from "../../runtime/runtime-document-manifest";
20
8
  import { readWrapState, type WrapState } from "../../runtime/wrap-state";
21
- import type { NormalizedRegistration } from "../../tools/setup/registry";
22
9
  import type { FrameworkConfig, LinkedRepository } from "./types";
23
10
  import { project } from "./working-repo-projection";
24
11
 
@@ -26,8 +13,6 @@ export interface WrapPassDependencies {
26
13
  renderWorkingRuntimeDocuments: typeof renderWorkingRuntimeDocuments;
27
14
  readCompanionConfig: typeof readCompanionConfig;
28
15
  project: typeof project;
29
- loadAudienceRegistrations: typeof loadAudienceRegistrations;
30
- renderAudienceLayer: typeof renderAudienceLayer;
31
16
  /** The registered companions, whose stale skill links a re-pin withdraws; the user's registry when absent. */
32
17
  globalConfigStore?: GlobalConfigStore;
33
18
  }
@@ -36,8 +21,6 @@ export const wrapPassDeps: WrapPassDependencies = {
36
21
  renderWorkingRuntimeDocuments,
37
22
  readCompanionConfig,
38
23
  project,
39
- loadAudienceRegistrations,
40
- renderAudienceLayer,
41
24
  };
42
25
 
43
26
  export interface WrapPassOptions {
@@ -45,12 +28,6 @@ export interface WrapPassOptions {
45
28
  runtimes?: readonly WrapRuntime[];
46
29
  /** The companion config the caller already read; read from files otherwise. */
47
30
  config?: FrameworkConfig;
48
- /**
49
- * The audience to wrap the checkout for: a name records it, `null` withdraws
50
- * it, absent keeps the one the manifest records. Never read from the
51
- * environment: it is the only source of a bare session's audience.
52
- */
53
- audience?: string | null;
54
31
  }
55
32
 
56
33
  export type WrapPassResult =
@@ -70,21 +47,9 @@ export type WrapPassResult =
70
47
  runtimes: ReadonlySet<WrapRuntime>;
71
48
  /** The wrap state as this pass left it. */
72
49
  wrapState: WrapState;
73
- /** The audience the checkout is wrapped for after this pass. */
74
- audience: string | null;
75
50
  }
76
51
  | { kind: "failed"; message: string };
77
52
 
78
- /** An audience a checkout may be wrapped for: declared by the companion, never the reserved base. */
79
- function wrappableAudience(config: FrameworkConfig, audience: string): boolean {
80
- return audience !== "base" && declaredAudiences(config).includes(audience);
81
- }
82
-
83
- function describeUnwrappable(config: FrameworkConfig, audience: string): string {
84
- const declared = declaredAudiences(config).filter((name) => name !== "base");
85
- return `audience "${audience}" cannot be wrapped: the companion declares ${declared.length > 0 ? declared.join(", ") : "no audiences"}.`;
86
- }
87
-
88
53
  /**
89
54
  * One pass of the `wrap` scope, restricted to the requested runtimes by each
90
55
  * entry's runtime tag. Best-effort per entry like every projection: a failed
@@ -98,50 +63,10 @@ export async function runWrapPass(
98
63
  ): Promise<WrapPassResult> {
99
64
  const resolvedCompanionPath = path.resolve(companionPath);
100
65
  const config = options.config ?? (await deps.readCompanionConfig(resolvedCompanionPath));
101
-
102
- const audienceWarnings: string[] = [];
103
- let audience: string | null;
104
- if (options.audience === undefined) {
105
- const recorded = recordedAudience(await readManifest(repository.path));
106
- /** A recorded audience the companion no longer declares falls back to base, never fails a prepare. */
107
- if (recorded && !wrappableAudience(config, recorded)) {
108
- audienceWarnings.push(
109
- `${describeUnwrappable(config, recorded)} ${repository.id} is wrapped for base only.`,
110
- );
111
- audience = null;
112
- } else {
113
- audience = recorded;
114
- }
115
- } else {
116
- audience = options.audience;
117
- if (audience && !wrappableAudience(config, audience)) {
118
- return { kind: "failed", message: describeUnwrappable(config, audience) };
119
- }
120
- }
121
-
122
- let wrapped: { audience: string; registrations: NormalizedRegistration[] } | undefined;
123
- if (audience) {
124
- const loaded = await deps.loadAudienceRegistrations(resolvedCompanionPath, config, audience);
125
- audienceWarnings.push(...loaded.warnings);
126
- await deps.renderAudienceLayer({
127
- companionPath: resolvedCompanionPath,
128
- config,
129
- audience,
130
- activeProviders: buildSetupInstallationPlan(
131
- config,
132
- getActiveDistribution().registry.baseRegistrations(),
133
- ).activeProviders,
134
- registrations: loaded.registrations,
135
- });
136
- wrapped = { audience, registrations: loaded.registrations };
137
- }
138
-
139
66
  const rendered = await deps.renderWorkingRuntimeDocuments(
140
67
  resolvedCompanionPath,
141
68
  config,
142
69
  repository.path,
143
- undefined,
144
- wrapped,
145
70
  );
146
71
  const result = await deps.project("wrap", {
147
72
  repoPath: repository.path,
@@ -149,7 +74,6 @@ export async function runWrapPass(
149
74
  repository,
150
75
  config,
151
76
  runtimeDocuments: rendered.documents,
152
- audienceLayerDir: audience ? audienceLayerDir(resolvedCompanionPath, audience) : null,
153
77
  globalConfigStore: deps.globalConfigStore ?? new GlobalConfigStore(),
154
78
  ...(options.runtimes ? { runtimes: options.runtimes } : {}),
155
79
  });
@@ -157,12 +81,6 @@ export async function runWrapPass(
157
81
  const failure = firstFailure(result.outcomes);
158
82
  if (failure) return { kind: "failed", message: entryFailureMessage(failure, repository.id) };
159
83
 
160
- const before = recordedAudience(await readManifest(repository.path));
161
- const audienceChanged = before !== audience;
162
- if (audienceChanged) {
163
- await updateManifest(repository.path, (manifest) => withAudience(manifest, audience));
164
- }
165
-
166
84
  /** Read once under the caller's lock, so it is exactly what the pass recorded. */
167
85
  let wrapState: WrapState;
168
86
  try {
@@ -194,10 +112,7 @@ export async function runWrapPass(
194
112
  .map((outcome) => outcome.path);
195
113
 
196
114
  return {
197
- kind:
198
- audienceChanged || result.outcomes.some((outcome) => outcome.state === "written")
199
- ? "written"
200
- : "current",
115
+ kind: result.outcomes.some((outcome) => outcome.state === "written") ? "written" : "current",
201
116
  projectionRoot: result.root,
202
117
  companionPath: resolvedCompanionPath,
203
118
  documents: [...new Set(documents)],
@@ -207,7 +122,6 @@ export async function runWrapPass(
207
122
  removed: result.outcomes.flatMap((outcome) => outcome.changed?.removed ?? []),
208
123
  },
209
124
  warnings: [
210
- ...audienceWarnings,
211
125
  ...rendered.warnings,
212
126
  ...result.outcomes.flatMap((outcome) => outcome.warnings ?? []),
213
127
  /** A degradable failure does not fail the pass, but it is never silent. */
@@ -217,6 +131,5 @@ export async function runWrapPass(
217
131
  ],
218
132
  runtimes,
219
133
  wrapState,
220
- audience,
221
134
  };
222
135
  }
@@ -1,5 +1,3 @@
1
- import path from "node:path";
2
-
3
1
  /**
4
2
  * Versioned companion guidance contract. The CLI builds the guidance data
5
3
  * from its playbooks at launch time and delivers it through the
@@ -18,12 +16,6 @@ export type MateGuidanceFile = {
18
16
  companionGuidance: string;
19
17
  codebaseExplorationGuidance: string;
20
18
  errors: string[];
21
- /**
22
- * The active audience's layer, for a launch that hosts its own server only.
23
- * The plugin applies it only from a payload it trusts; a masked payload
24
- * carries none, so the shared service and bare sessions never see one.
25
- */
26
- audienceLayer?: string;
27
19
  };
28
20
 
29
21
  export type GuidanceValidation = {
@@ -84,21 +76,12 @@ export function validateGuidanceData(value: unknown): GuidanceValidation {
84
76
  errors.push("injected codebase exploration guidance is malformed");
85
77
  }
86
78
 
87
- const audienceLayer =
88
- typeof value.audienceLayer === "string" && path.isAbsolute(value.audienceLayer)
89
- ? value.audienceLayer
90
- : undefined;
91
- if (value.audienceLayer !== undefined && audienceLayer === undefined) {
92
- errors.push("injected audience layer is not an absolute path");
93
- }
94
-
95
79
  return {
96
80
  guidance: {
97
81
  version: GUIDANCE_FILE_VERSION,
98
82
  companionGuidance,
99
83
  codebaseExplorationGuidance,
100
84
  errors,
101
- ...(audienceLayer ? { audienceLayer } : {}),
102
85
  },
103
86
  errors,
104
87
  };
@@ -40,17 +40,6 @@ function capabilityNames(value: unknown): string[] {
40
40
  .filter((name): name is string => typeof name === "string");
41
41
  }
42
42
 
43
- /**
44
- * A base-set key from `audiences.base`, the shape a companion is saved in. The
45
- * lenient twin of the setup normalizer, which `runtime/` cannot import.
46
- */
47
- function baseAudienceKey(config: Record<string, unknown>, key: string): unknown {
48
- const audiences = config.audiences;
49
- if (!audiences || typeof audiences !== "object") return undefined;
50
- const base = (audiences as Record<string, unknown>).base;
51
- return base && typeof base === "object" ? (base as Record<string, unknown>)[key] : undefined;
52
- }
53
-
54
43
  /**
55
44
  * Synchronous so it composes into `readCompanionRuntimeContext`. A missing or
56
45
  * unparseable companion config yields an inert policy rather than throwing.
@@ -67,9 +56,7 @@ export function readCompanionPolicy(companionPath: string): CompanionPolicy {
67
56
  const config = parsed as Record<string, unknown>;
68
57
  return {
69
58
  allowedAgents: stringList(config.allowedAgents),
70
- enabledCapabilities: capabilityNames(
71
- config.capabilities ?? baseAudienceKey(config, "capabilities"),
72
- ),
59
+ enabledCapabilities: capabilityNames(config.capabilities),
73
60
  gitAutoMode: config.git === "auto",
74
61
  };
75
62
  }
@@ -80,8 +80,6 @@ export interface Manifest {
80
80
  readonly runtimes: Readonly<Record<string, WrapRuntime>>;
81
81
  readonly copies: Readonly<Record<string, CopyRecord>>;
82
82
  readonly links: Readonly<Record<string, LinkRecord>>;
83
- /** The audience `mate wrap --audience` recorded; the only source of a bare session's audience. */
84
- readonly audience?: string;
85
83
  }
86
84
 
87
85
  export const EMPTY_MANIFEST: Manifest = Object.freeze({
@@ -126,23 +124,9 @@ function manifestOf(parsed: Record<string, unknown>): Manifest {
126
124
  runtimes: isRecord(parsed.runtimes) ? (parsed.runtimes as Manifest["runtimes"]) : {},
127
125
  copies: isRecord(parsed.copies) ? (parsed.copies as Manifest["copies"]) : {},
128
126
  links: isRecord(parsed.links) ? (parsed.links as Manifest["links"]) : {},
129
- ...(typeof parsed.audience === "string" && parsed.audience
130
- ? { audience: parsed.audience }
131
- : {}),
132
127
  };
133
128
  }
134
129
 
135
- /** The audience the checkout is wrapped for; `null` for base only. */
136
- export function recordedAudience(manifest: Manifest): string | null {
137
- return manifest.audience ?? null;
138
- }
139
-
140
- /** Records the checkout's audience, or none with `null`. */
141
- export function withAudience(manifest: Manifest, audience: string | null): Manifest {
142
- const { audience: _previous, ...rest } = manifest;
143
- return audience ? { ...rest, audience } : rest;
144
- }
145
-
146
130
  function recordedRuntime(manifest: Manifest, documentPath: string): WrapRuntime {
147
131
  return manifest.runtimes[documentPath] ?? documentRuntime(documentPath);
148
132
  }
@@ -166,7 +150,6 @@ export function serializeManifest(manifest: Manifest): string | null {
166
150
  ...(recorded.length > 0 ? { runtimes } : {}),
167
151
  ...(copied.length > 0 ? { copies: manifest.copies } : {}),
168
152
  ...(linked.length > 0 ? { links: manifest.links } : {}),
169
- ...(manifest.audience ? { audience: manifest.audience } : {}),
170
153
  },
171
154
  null,
172
155
  2,
@@ -14,7 +14,6 @@ import {
14
14
  parseManifestStrict,
15
15
  readManifestText,
16
16
  recordedListValues,
17
- recordedAudience,
18
17
  recordedMapKeys,
19
18
  type Manifest,
20
19
  type WrapRuntime,
@@ -31,8 +30,6 @@ export interface WrapState {
31
30
  carriesClaudeHookGroup(group: ClaudeHookGroup): boolean;
32
31
  /** MCP server names the wrap declared in `.opencode/opencode.json`; user servers are not listed. */
33
32
  readonly openCodeMcpServers: readonly string[];
34
- /** The audience the checkout is wrapped for; `null` for base only. */
35
- readonly audience: string | null;
36
33
  }
37
34
 
38
35
  export const WRAP_RUNTIMES: readonly WrapRuntime[] = ["claude", "opencode"];
@@ -64,7 +61,6 @@ export function wrapStateOf(manifest: Manifest): WrapState {
64
61
  OPENCODE_CONFIG_DOCUMENT,
65
62
  OPENCODE_MCP_SERVERS_AT,
66
63
  ),
67
- audience: recordedAudience(manifest),
68
64
  };
69
65
  }
70
66