@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.
- package/claude-plugin/hooks/companion-instructions.mjs +9 -0
- package/claude-plugin/hooks/hooks.json +12 -2
- package/opencode-plugin/src/add-dir.ts +76 -10
- package/opencode-plugin/src/companion.ts +1 -8
- package/opencode-plugin/src/server.ts +2 -15
- package/package.json +10 -2
- package/src/brand/art.generated.ts +69 -0
- package/src/brand/index.ts +12 -0
- package/src/brand/palette.ts +19 -0
- package/src/brand/svg.generated.ts +8 -0
- package/src/brand/terminal.ts +83 -0
- package/src/brand/types.ts +33 -0
- package/src/cli/commands/artifact/artifact.ts +8 -0
- package/src/cli/commands/artifact/delivery/archive.ts +117 -0
- package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
- package/src/cli/commands/artifact/delivery/environment.ts +104 -0
- package/src/cli/commands/artifact/delivery/link.ts +154 -0
- package/src/cli/commands/artifact/delivery/output.ts +58 -0
- package/src/cli/commands/artifact/finish/command.ts +33 -3
- package/src/cli/commands/artifact/finish/engine.ts +47 -3
- package/src/cli/commands/artifact/finish/finisher.ts +10 -0
- package/src/cli/commands/artifact/finish/openspec.ts +200 -129
- package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
- package/src/cli/commands/artifact/pending/command.ts +31 -3
- package/src/cli/commands/artifact/pending/discovery.ts +21 -57
- package/src/cli/commands/cap/graphify.ts +52 -3
- package/src/cli/commands/cap/index-cmd.ts +8 -1
- package/src/cli/commands/cap/openspec.ts +13 -25
- package/src/cli/commands/companion/open.ts +56 -39
- package/src/cli/commands/companion/update.ts +75 -42
- package/src/cli/commands/doctor.ts +257 -78
- package/src/cli/commands/install.ts +0 -8
- package/src/cli/commands/plugin/install.ts +9 -4
- package/src/cli/commands/plugin/plugin.ts +3 -2
- package/src/cli/commands/report/adapter.ts +11 -2
- package/src/cli/commands/report/contract.ts +21 -0
- package/src/cli/commands/report/highlight.ts +34 -4
- package/src/cli/commands/report/markdown.ts +110 -0
- package/src/cli/commands/report/palette.ts +118 -0
- package/src/cli/commands/report/renderer.ts +456 -153
- package/src/cli/commands/report/types.ts +16 -0
- package/src/cli/commands/setup.ts +7 -15
- package/src/cli/commands/studio/client-asset.ts +16 -0
- package/src/cli/commands/studio/openspec-cli.ts +10 -58
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +19 -1
- package/src/cli/commands/studio/server.ts +42 -1
- package/src/cli/commands/studio/terminal-launch.ts +7 -16
- package/src/cli/commands/studio/views/client.ts +6 -616
- package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
- package/src/cli/commands/studio/views/document.tsx +137 -69
- package/src/cli/commands/studio/views/markdown.tsx +250 -0
- package/src/cli/commands/studio/views/palette.ts +134 -0
- package/src/cli/commands/studio/views/reports/index.tsx +2 -7
- package/src/cli/commands/studio/views/skills/index.tsx +43 -62
- package/src/cli/commands/studio/views/specs/index.tsx +7 -15
- package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
- package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
- package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
- package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
- package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
- package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
- package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
- package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
- package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
- package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
- package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
- package/src/cli/commands/studio/views/styles.ts +217 -206
- package/src/cli/commands/studio/views/vault/state.ts +3 -5
- package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
- package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
- package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
- package/src/cli/commands/wrap.ts +9 -42
- package/src/cli/main.ts +4 -1
- package/src/cli/parse-flags.ts +25 -7
- package/src/cli/setup-selector.tsx +38 -162
- package/src/cli/usage.ts +5 -2
- package/src/hooks/companion-instructions.ts +190 -0
- package/src/hooks/hook-io.ts +63 -0
- package/src/hooks/session-banner.ts +21 -29
- package/src/hooks/session-guidance.ts +10 -36
- package/src/hooks/session-prepare.ts +115 -28
- package/src/hooks/validate-artifact-path.ts +82 -24
- package/src/index.ts +4 -0
- package/src/lib/change-layout.ts +154 -0
- package/src/lib/components/wizard-header.tsx +3 -1
- package/src/lib/context-mode-package.ts +24 -11
- package/src/lib/delivery/archive-change.ts +154 -0
- package/src/lib/delivery/branches.ts +108 -0
- package/src/lib/delivery/environment.ts +29 -0
- package/src/lib/delivery/forge.ts +279 -0
- package/src/lib/delivery/overlap.ts +202 -0
- package/src/lib/delivery/record-delivery.ts +196 -0
- package/src/lib/delivery/record.ts +343 -0
- package/src/lib/delivery/refresh.ts +174 -0
- package/src/lib/delivery/repository-access.ts +97 -0
- package/src/lib/delivery/stages.ts +111 -0
- package/src/lib/delivery/token.ts +58 -0
- package/src/lib/frontmatter.ts +18 -0
- package/src/lib/opencode-plugin-package.ts +5 -5
- package/src/lib/openspec-cli.ts +116 -0
- package/src/lib/openspec-scopes.ts +86 -0
- package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
- package/src/lib/orchestrator/adapters/claude.ts +60 -162
- package/src/lib/orchestrator/adapters/opencode.ts +9 -24
- package/src/lib/orchestrator/config-store.ts +2 -70
- package/src/lib/orchestrator/editor.ts +9 -9
- package/src/lib/orchestrator/openspec-store.ts +14 -54
- package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
- package/src/lib/orchestrator/projection-claude-links.ts +159 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +19 -190
- package/src/lib/orchestrator/projection-entries.ts +45 -15
- package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
- package/src/lib/orchestrator/projection-types.ts +3 -2
- package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
- package/src/lib/orchestrator/session-prepare.ts +49 -87
- package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
- package/src/lib/orchestrator/types.ts +2 -2
- package/src/lib/orchestrator/wrap-pass.ts +22 -88
- package/src/lib/package-paths.ts +8 -0
- package/src/opencode/companion-hooks.ts +64 -5
- package/src/opencode/companion-policy.ts +37 -18
- package/src/opencode/projected-guidance.ts +4 -1
- package/src/opencode/session-prepare.ts +2 -1
- package/src/opencode/tui.tsx +4 -2
- package/src/runtime/artifact-refusal.ts +63 -0
- package/src/runtime/claude-delivery-plan.ts +89 -7
- package/src/runtime/commit-attribution-guard.ts +181 -0
- package/src/runtime/companion-guidance.ts +15 -3
- package/src/runtime/companion-instructions.ts +340 -0
- package/src/runtime/error-message.ts +4 -0
- package/src/runtime/guidance.ts +0 -17
- package/src/runtime/index.ts +5 -0
- package/src/runtime/managed-block.ts +117 -0
- package/src/runtime/markdown-scan.ts +266 -0
- package/src/runtime/policy.ts +16 -15
- package/src/runtime/projected-flag.ts +7 -0
- package/src/runtime/projected-guidance.ts +1 -0
- package/src/runtime/runtime-document-manifest.ts +62 -28
- package/src/runtime/secret-guard.ts +148 -0
- package/src/runtime/secret-patterns.ts +164 -0
- package/src/runtime/secret-scan.ts +90 -0
- package/src/runtime/session-kind.ts +13 -0
- package/src/runtime/shell-heredoc.ts +81 -0
- package/src/runtime/wrap-state.ts +36 -6
- package/src/runtime/write-atomic.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
- package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
- package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/openspec-references.ts +140 -0
- package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
- package/src/tools/setup/capabilities/context-mode.ts +25 -5
- package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
- package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
- package/src/tools/setup/capabilities/graphify.ts +12 -0
- package/src/tools/setup/capabilities/openspec.ts +273 -270
- package/src/tools/setup/capabilities/tokensave.ts +36 -3
- package/src/tools/setup/context-services.ts +9 -17
- package/src/tools/setup/dynamic-plugins/audiences.ts +7 -39
- package/src/tools/setup/dynamic-plugins/declarations.ts +1 -1
- package/src/tools/setup/dynamic-plugins/hydrate.ts +4 -7
- package/src/tools/setup/dynamic-plugins/install.ts +24 -28
- package/src/tools/setup/engine.ts +93 -148
- package/src/tools/setup/managed-output.ts +51 -0
- package/src/tools/setup/mate.ts +86 -11
- package/src/tools/setup/plugin.ts +37 -17
- package/src/tools/setup/plugins/gitignore.ts +1 -7
- package/src/tools/setup/plugins/guidance.ts +7 -4
- package/src/tools/setup/providers/agent-file-sections.ts +4 -2
- package/src/tools/setup/providers/claude-attribution.ts +65 -0
- package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
- package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
- package/src/tools/setup/providers/claude.ts +285 -34
- package/src/tools/setup/providers/opencode.ts +180 -11
- package/src/tools/setup/registry.ts +2 -24
- package/src/tools/setup/surface-target.ts +4 -13
- package/src/tools/setup/utils.ts +14 -4
- package/src/tools/setup.ts +131 -18
- package/wrappers/bin/graphify +52 -4
- package/wrappers/bin/openspec +33 -9
- package/wrappers/lib/graphify-gate.mjs +8 -0
- package/opencode-plugin/src/audience-layer.ts +0 -91
- package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
- package/src/runtime/audience-layer.ts +0 -84
- package/src/runtime/opencode-audience-layer.ts +0 -176
- package/src/tools/setup/audience-isolation.fixture.ts +0 -325
- package/src/tools/setup/audience-layer.ts +0 -207
- package/src/tools/setup/audience-registrations.ts +0 -52
- package/src/tools/setup/contribution-ledger.ts +0 -207
- package/src/tools/setup/isolation-diagnostics.ts +0 -139
- package/src/tools/setup/runtime-documents.ts +0 -291
- package/src/tools/setup/upgrade-sweep.ts +0 -153
|
@@ -1,43 +1,32 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
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
3
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "../../runtime/runtime-document-manifest";
|
|
4
|
+
publishWorkingClaudePluginMarketplaces,
|
|
5
|
+
renderWorkingRuntimeDocuments,
|
|
6
|
+
} from "../../tools/setup";
|
|
7
|
+
import type { ClaudePluginFolders } from "../../tools/setup/providers/claude-plugin-folders";
|
|
16
8
|
import { readCompanionConfig } from "./config-store";
|
|
17
9
|
import { GlobalConfigStore } from "./global-config-store";
|
|
18
10
|
import { entryFailureMessage, firstFailure, type ProjectionChangedPaths } from "./projection-types";
|
|
19
11
|
import type { WrapRuntime } from "../../runtime/runtime-document-manifest";
|
|
20
12
|
import { readWrapState, type WrapState } from "../../runtime/wrap-state";
|
|
21
|
-
import type { NormalizedRegistration } from "../../tools/setup/registry";
|
|
22
13
|
import type { FrameworkConfig, LinkedRepository } from "./types";
|
|
23
14
|
import { project } from "./working-repo-projection";
|
|
24
15
|
|
|
25
16
|
export interface WrapPassDependencies {
|
|
17
|
+
publishWorkingClaudePluginMarketplaces: typeof publishWorkingClaudePluginMarketplaces;
|
|
26
18
|
renderWorkingRuntimeDocuments: typeof renderWorkingRuntimeDocuments;
|
|
27
19
|
readCompanionConfig: typeof readCompanionConfig;
|
|
28
20
|
project: typeof project;
|
|
29
|
-
loadAudienceRegistrations: typeof loadAudienceRegistrations;
|
|
30
|
-
renderAudienceLayer: typeof renderAudienceLayer;
|
|
31
21
|
/** The registered companions, whose stale skill links a re-pin withdraws; the user's registry when absent. */
|
|
32
22
|
globalConfigStore?: GlobalConfigStore;
|
|
33
23
|
}
|
|
34
24
|
|
|
35
25
|
export const wrapPassDeps: WrapPassDependencies = {
|
|
26
|
+
publishWorkingClaudePluginMarketplaces,
|
|
36
27
|
renderWorkingRuntimeDocuments,
|
|
37
28
|
readCompanionConfig,
|
|
38
29
|
project,
|
|
39
|
-
loadAudienceRegistrations,
|
|
40
|
-
renderAudienceLayer,
|
|
41
30
|
};
|
|
42
31
|
|
|
43
32
|
export interface WrapPassOptions {
|
|
@@ -45,12 +34,6 @@ export interface WrapPassOptions {
|
|
|
45
34
|
runtimes?: readonly WrapRuntime[];
|
|
46
35
|
/** The companion config the caller already read; read from files otherwise. */
|
|
47
36
|
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
37
|
}
|
|
55
38
|
|
|
56
39
|
export type WrapPassResult =
|
|
@@ -70,21 +53,14 @@ export type WrapPassResult =
|
|
|
70
53
|
runtimes: ReadonlySet<WrapRuntime>;
|
|
71
54
|
/** The wrap state as this pass left it. */
|
|
72
55
|
wrapState: WrapState;
|
|
73
|
-
/**
|
|
74
|
-
|
|
56
|
+
/**
|
|
57
|
+
* The Claude plugin folders this pass published, when it covered Claude;
|
|
58
|
+
* what publication left undone is the caller's to present, never in `warnings`.
|
|
59
|
+
*/
|
|
60
|
+
claudePluginFolders?: ClaudePluginFolders;
|
|
75
61
|
}
|
|
76
62
|
| { kind: "failed"; message: string };
|
|
77
63
|
|
|
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
64
|
/**
|
|
89
65
|
* One pass of the `wrap` scope, restricted to the requested runtimes by each
|
|
90
66
|
* entry's runtime tag. Best-effort per entry like every projection: a failed
|
|
@@ -98,50 +74,19 @@ export async function runWrapPass(
|
|
|
98
74
|
): Promise<WrapPassResult> {
|
|
99
75
|
const resolvedCompanionPath = path.resolve(companionPath);
|
|
100
76
|
const config = options.config ?? (await deps.readCompanionConfig(resolvedCompanionPath));
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
77
|
+
/** The render reads the marketplace files, so they are written first. */
|
|
78
|
+
const published =
|
|
79
|
+
!options.runtimes || options.runtimes.includes("claude")
|
|
80
|
+
? await deps.publishWorkingClaudePluginMarketplaces(
|
|
81
|
+
resolvedCompanionPath,
|
|
82
|
+
config,
|
|
83
|
+
repository.path,
|
|
84
|
+
)
|
|
85
|
+
: undefined;
|
|
139
86
|
const rendered = await deps.renderWorkingRuntimeDocuments(
|
|
140
87
|
resolvedCompanionPath,
|
|
141
88
|
config,
|
|
142
89
|
repository.path,
|
|
143
|
-
undefined,
|
|
144
|
-
wrapped,
|
|
145
90
|
);
|
|
146
91
|
const result = await deps.project("wrap", {
|
|
147
92
|
repoPath: repository.path,
|
|
@@ -149,7 +94,6 @@ export async function runWrapPass(
|
|
|
149
94
|
repository,
|
|
150
95
|
config,
|
|
151
96
|
runtimeDocuments: rendered.documents,
|
|
152
|
-
audienceLayerDir: audience ? audienceLayerDir(resolvedCompanionPath, audience) : null,
|
|
153
97
|
globalConfigStore: deps.globalConfigStore ?? new GlobalConfigStore(),
|
|
154
98
|
...(options.runtimes ? { runtimes: options.runtimes } : {}),
|
|
155
99
|
});
|
|
@@ -157,12 +101,6 @@ export async function runWrapPass(
|
|
|
157
101
|
const failure = firstFailure(result.outcomes);
|
|
158
102
|
if (failure) return { kind: "failed", message: entryFailureMessage(failure, repository.id) };
|
|
159
103
|
|
|
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
104
|
/** Read once under the caller's lock, so it is exactly what the pass recorded. */
|
|
167
105
|
let wrapState: WrapState;
|
|
168
106
|
try {
|
|
@@ -194,10 +132,7 @@ export async function runWrapPass(
|
|
|
194
132
|
.map((outcome) => outcome.path);
|
|
195
133
|
|
|
196
134
|
return {
|
|
197
|
-
kind:
|
|
198
|
-
audienceChanged || result.outcomes.some((outcome) => outcome.state === "written")
|
|
199
|
-
? "written"
|
|
200
|
-
: "current",
|
|
135
|
+
kind: result.outcomes.some((outcome) => outcome.state === "written") ? "written" : "current",
|
|
201
136
|
projectionRoot: result.root,
|
|
202
137
|
companionPath: resolvedCompanionPath,
|
|
203
138
|
documents: [...new Set(documents)],
|
|
@@ -207,7 +142,6 @@ export async function runWrapPass(
|
|
|
207
142
|
removed: result.outcomes.flatMap((outcome) => outcome.changed?.removed ?? []),
|
|
208
143
|
},
|
|
209
144
|
warnings: [
|
|
210
|
-
...audienceWarnings,
|
|
211
145
|
...rendered.warnings,
|
|
212
146
|
...result.outcomes.flatMap((outcome) => outcome.warnings ?? []),
|
|
213
147
|
/** A degradable failure does not fail the pass, but it is never silent. */
|
|
@@ -217,6 +151,6 @@ export async function runWrapPass(
|
|
|
217
151
|
],
|
|
218
152
|
runtimes,
|
|
219
153
|
wrapState,
|
|
220
|
-
|
|
154
|
+
...(published ? { claudePluginFolders: published } : {}),
|
|
221
155
|
};
|
|
222
156
|
}
|
package/src/lib/package-paths.ts
CHANGED
|
@@ -32,10 +32,18 @@ export function getClaudePluginRoot(): string {
|
|
|
32
32
|
return path.resolve(import.meta.dirname, "../../claude-plugin");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The `name` in the bundled Claude plugin's manifest. Claude Code identifies a
|
|
37
|
+
* plugin by its name, so a contributed folder using this one could take the
|
|
38
|
+
* place of Mate's plugin and its guardrail hooks.
|
|
39
|
+
*/
|
|
40
|
+
export const MATE_CLAUDE_PLUGIN_NAME = "mate-claude-plugin";
|
|
41
|
+
|
|
35
42
|
const CLAUDE_PLUGIN_HOOK_SHIMS = [
|
|
36
43
|
"validate-artifact-path.mjs",
|
|
37
44
|
"session-banner.mjs",
|
|
38
45
|
"session-guidance.mjs",
|
|
46
|
+
"companion-instructions.mjs",
|
|
39
47
|
] as const;
|
|
40
48
|
|
|
41
49
|
/**
|
|
@@ -2,6 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import type { Context } from "@opencode/plugin/promise/plugin";
|
|
4
4
|
|
|
5
|
+
import { resolveArtifactTarget } from "../runtime/artifact-refusal";
|
|
6
|
+
import { checkCommitAttribution } from "../runtime/commit-attribution-guard";
|
|
5
7
|
import {
|
|
6
8
|
companionForkRefusal,
|
|
7
9
|
syncCompanionUnattended,
|
|
@@ -10,12 +12,14 @@ import {
|
|
|
10
12
|
import { hasLaunchEnvironment } from "../runtime/env";
|
|
11
13
|
import { MATE_ENV } from "../runtime/env-names";
|
|
12
14
|
import { resolveProjection } from "../runtime/projection";
|
|
15
|
+
import { checkSecretAccess, type SecretToolCall } from "../runtime/secret-guard";
|
|
13
16
|
import { readWrapStateSync } from "../runtime/wrap-state";
|
|
14
17
|
import {
|
|
15
18
|
buildArtifactError,
|
|
19
|
+
extractPatchAdditions,
|
|
16
20
|
extractPatchPaths,
|
|
17
21
|
isArtifactPath,
|
|
18
|
-
|
|
22
|
+
isCompanionTarget,
|
|
19
23
|
shouldBlockArtifactWrite,
|
|
20
24
|
type CompanionContext,
|
|
21
25
|
} from "./companion-policy";
|
|
@@ -87,14 +91,55 @@ export async function refuseForkedCompanionWrite(
|
|
|
87
91
|
env: Record<string, string | undefined> = process.env,
|
|
88
92
|
): Promise<void> {
|
|
89
93
|
if (!filePath || !isArtifactPath(filePath)) return;
|
|
90
|
-
if (!
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
94
|
+
if (!isCompanionTarget(context, filePath)) return;
|
|
93
95
|
const refusal = await companionForkRefusal(env, context.companionPath);
|
|
94
96
|
if (refusal) throw new Error(refusal);
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
/**
|
|
99
|
+
/** OpenCode tool inputs in the shape the shared secret guard reads. */
|
|
100
|
+
function secretToolCall(
|
|
101
|
+
context: CompanionContext,
|
|
102
|
+
tool: string,
|
|
103
|
+
args: Record<string, unknown>,
|
|
104
|
+
): SecretToolCall | null {
|
|
105
|
+
const text = (value: unknown) => (typeof value === "string" ? value : "");
|
|
106
|
+
const target = (value: unknown) =>
|
|
107
|
+
text(value) ? resolveArtifactTarget(text(value), context.repositoryPath) : "";
|
|
108
|
+
switch (tool) {
|
|
109
|
+
case "read":
|
|
110
|
+
return { kind: "read", paths: [target(args.filePath)] };
|
|
111
|
+
case "write":
|
|
112
|
+
return {
|
|
113
|
+
kind: "write",
|
|
114
|
+
writes: [{ path: target(args.filePath), content: text(args.content) }],
|
|
115
|
+
};
|
|
116
|
+
case "edit":
|
|
117
|
+
return {
|
|
118
|
+
kind: "write",
|
|
119
|
+
writes: [{ path: target(args.filePath), content: text(args.newString) }],
|
|
120
|
+
};
|
|
121
|
+
case "apply_patch":
|
|
122
|
+
return {
|
|
123
|
+
kind: "write",
|
|
124
|
+
writes: extractPatchAdditions(text(args.patchText)).map((addition) => ({
|
|
125
|
+
path: target(addition.path),
|
|
126
|
+
content: addition.content,
|
|
127
|
+
})),
|
|
128
|
+
};
|
|
129
|
+
case "bash":
|
|
130
|
+
return { kind: "shell", command: text(args.command) };
|
|
131
|
+
case "grep":
|
|
132
|
+
return { kind: "search", paths: [target(args.path)], globs: [text(args.include)] };
|
|
133
|
+
default:
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Rejects when the tool call touches a secret, commits with a model co-author
|
|
140
|
+
* trailer, or would write an artifact outside the companion. Checked in that
|
|
141
|
+
* order, as in the Claude hook.
|
|
142
|
+
*/
|
|
98
143
|
export async function guardToolInput(
|
|
99
144
|
context: CompanionContext,
|
|
100
145
|
tool: string,
|
|
@@ -102,6 +147,20 @@ export async function guardToolInput(
|
|
|
102
147
|
env: Record<string, string | undefined> = process.env,
|
|
103
148
|
): Promise<void> {
|
|
104
149
|
const args = (input ?? {}) as Record<string, unknown>;
|
|
150
|
+
|
|
151
|
+
const secretCall = context.companionPath ? secretToolCall(context, tool, args) : null;
|
|
152
|
+
if (secretCall) {
|
|
153
|
+
const companionRoot = path.normalize(context.companionPath);
|
|
154
|
+
const refusal = await checkSecretAccess(secretCall, {
|
|
155
|
+
companionPath: context.companionPath,
|
|
156
|
+
isCompanionPath: (filePath) => filePath.startsWith(companionRoot),
|
|
157
|
+
});
|
|
158
|
+
if (refusal) throw new Error(refusal);
|
|
159
|
+
}
|
|
160
|
+
if (tool === "bash") {
|
|
161
|
+
const refusal = checkCommitAttribution(String(args.command ?? ""));
|
|
162
|
+
if (refusal) throw new Error(refusal);
|
|
163
|
+
}
|
|
105
164
|
const filePaths =
|
|
106
165
|
tool === "write" || tool === "edit"
|
|
107
166
|
? [String(args.filePath ?? "")]
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { artifactRefusal, resolveArtifactTarget } from "../runtime/artifact-refusal";
|
|
5
6
|
import { persistedCompanionGitStalenessLines } from "../runtime/companion-sync";
|
|
6
7
|
import { resolveCompanionRuntime, type CompanionRuntimeContext } from "../runtime/env";
|
|
7
8
|
import { projectionFreshness, projectionStalenessLines } from "../runtime/freshness";
|
|
@@ -60,7 +61,7 @@ export function isArtifactPath(filePath: string): boolean {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function isProductDocumentationPath(context: CompanionContext, filePath: string): boolean {
|
|
63
|
-
const normalized =
|
|
64
|
+
const normalized = resolveArtifactTarget(filePath, context.repositoryPath);
|
|
64
65
|
const relativePath = path.relative(context.repositoryPath, normalized);
|
|
65
66
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
66
67
|
return false;
|
|
@@ -86,12 +87,10 @@ export function isProductDocumentationPath(context: CompanionContext, filePath:
|
|
|
86
87
|
return false;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
export function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return path.normalize(path.join(context.repositoryPath, filePath));
|
|
90
|
+
export function isCompanionTarget(context: CompanionContext, filePath: string): boolean {
|
|
91
|
+
return resolveArtifactTarget(filePath, context.repositoryPath).startsWith(
|
|
92
|
+
path.normalize(context.companionPath),
|
|
93
|
+
);
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
export function isGitignored(context: CompanionContext, filePath: string): boolean {
|
|
@@ -99,7 +98,7 @@ export function isGitignored(context: CompanionContext, filePath: string): boole
|
|
|
99
98
|
return false;
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
const normalized =
|
|
101
|
+
const normalized = resolveArtifactTarget(filePath, context.repositoryPath);
|
|
103
102
|
if (!normalized.startsWith(path.normalize(context.repositoryPath))) {
|
|
104
103
|
return false;
|
|
105
104
|
}
|
|
@@ -126,7 +125,7 @@ export function shouldBlockArtifactWrite(context: CompanionContext, filePath: st
|
|
|
126
125
|
return false;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
if (
|
|
128
|
+
if (isCompanionTarget(context, filePath)) {
|
|
130
129
|
return false;
|
|
131
130
|
}
|
|
132
131
|
|
|
@@ -155,16 +154,36 @@ export function extractPatchPaths(patchText: string): string[] {
|
|
|
155
154
|
return paths;
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
/**
|
|
158
|
+
* What a patch would add to each file it touches: the `+` lines of every
|
|
159
|
+
* section, keyed by the section's target (a move's destination when present).
|
|
160
|
+
*/
|
|
161
|
+
export function extractPatchAdditions(patchText: string): { path: string; content: string }[] {
|
|
162
|
+
const additions: { path: string; content: string[] }[] = [];
|
|
163
|
+
let current: { path: string; content: string[] } | undefined;
|
|
164
|
+
for (const line of patchText.split("\n")) {
|
|
165
|
+
const marker = /^\*\*\* (Add|Update|Delete|Move to) File:\s*(.+)$/.exec(line);
|
|
166
|
+
if (marker) {
|
|
167
|
+
if (marker[1] === "Move to" && current) {
|
|
168
|
+
current.path = marker[2].trim();
|
|
169
|
+
} else {
|
|
170
|
+
current = { path: marker[2].trim(), content: [] };
|
|
171
|
+
additions.push(current);
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (current && line.startsWith("+")) current.content.push(line.slice(1));
|
|
176
|
+
}
|
|
177
|
+
return additions.map((entry) => ({ path: entry.path, content: entry.content.join("\n") }));
|
|
178
|
+
}
|
|
161
179
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
export function buildArtifactError(context: CompanionContext, filePath: string): string {
|
|
181
|
+
return artifactRefusal({
|
|
182
|
+
target: filePath,
|
|
183
|
+
resolved: resolveArtifactTarget(filePath, context.repositoryPath),
|
|
184
|
+
companion: context.companionPath,
|
|
185
|
+
repoRoot: context.repositoryPath,
|
|
186
|
+
});
|
|
168
187
|
}
|
|
169
188
|
|
|
170
189
|
/**
|
|
@@ -44,6 +44,9 @@ export function resolveOpenCodeGuidance(
|
|
|
44
44
|
if (!projection) return { guidance: null, errors: [] };
|
|
45
45
|
|
|
46
46
|
const policy = readCompanionPolicy(projection.companionPath);
|
|
47
|
-
const guidance = buildOpenCodeGuidance(
|
|
47
|
+
const guidance = buildOpenCodeGuidance(
|
|
48
|
+
policy.enabledCapabilities.map((name) => ({ name })),
|
|
49
|
+
{ secretGuardrails: policy.secrets.guardrails },
|
|
50
|
+
);
|
|
48
51
|
return { guidance: guidance.errors.length > 0 ? null : guidance, errors: guidance.errors };
|
|
49
52
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Cleanup, Context } from "@opencode/plugin/promise/plugin";
|
|
2
2
|
|
|
3
|
+
import { errorMessage } from "../runtime/error-message";
|
|
3
4
|
import {
|
|
4
5
|
PREPARE_TIMEOUT_MS,
|
|
5
6
|
type PrepareRun,
|
|
@@ -52,7 +53,7 @@ async function report(
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
function failedRun(error: unknown): PrepareRun {
|
|
55
|
-
return { ok: false, notes: [
|
|
56
|
+
return { ok: false, notes: [errorMessage(error)] };
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/** No remote: OpenCode holds MCP servers and skills until `setup` returns. */
|
package/src/opencode/tui.tsx
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* does not accept it, so the jsx-key rules cannot be satisfied — only suppressed.
|
|
7
7
|
* Arrays render through `.map`: Solid re-runs it whole on a store change, fine for a few lines.
|
|
8
8
|
*/
|
|
9
|
+
import { BRAND_ACCENT } from "../brand/palette";
|
|
9
10
|
import { mateVersion } from "../runtime/install";
|
|
10
11
|
import { readContext, readStalenessLines } from "./companion-policy";
|
|
11
12
|
|
|
@@ -47,7 +48,8 @@ export const STALENESS_POLL_MS = 5_000;
|
|
|
47
48
|
|
|
48
49
|
type SessionNotes = { lines: string[] };
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
/** The `mate v<version>` line uses the brand accent, which reads on light and dark themes alike. */
|
|
52
|
+
export const MATE_VERSION_COLOR = BRAND_ACCENT;
|
|
51
53
|
const MATE_VERSION = process.env.MATE_VERSION ?? mateVersion();
|
|
52
54
|
|
|
53
55
|
function SessionContext({
|
|
@@ -72,7 +74,7 @@ function SessionContext({
|
|
|
72
74
|
paddingBottom={sidebar ? 1 : 0}
|
|
73
75
|
flexShrink={0}
|
|
74
76
|
>
|
|
75
|
-
<text fg={
|
|
77
|
+
<text fg={MATE_VERSION_COLOR}>mate v{MATE_VERSION}</text>
|
|
76
78
|
<text fg={text.muted}>repo: {context.repositoryPath}</text>
|
|
77
79
|
<text fg={text.muted}>mate: {context.companionPath}</text>
|
|
78
80
|
{notes.lines.map((note) => (
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** The artifact-write refusal both runtimes print; pure, so either guard can import it. */
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { FRAMEWORK_NAME } from "./framework";
|
|
5
|
+
|
|
6
|
+
/** Matched by consumers and tests; keep byte-identical. */
|
|
7
|
+
export const ARTIFACT_REFUSAL_HEADLINE = "artifact writes must go to the companion framework path.";
|
|
8
|
+
|
|
9
|
+
export interface ArtifactRefusalInput {
|
|
10
|
+
/** As the tool supplied it, relative or absolute. */
|
|
11
|
+
target: string;
|
|
12
|
+
/** The absolute path the guard judged. */
|
|
13
|
+
resolved: string;
|
|
14
|
+
companion: string;
|
|
15
|
+
/** The Working Repository root relative targets resolve against. */
|
|
16
|
+
repoRoot: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The absolute path both guards judge: a relative target resolves against the repo root, never a `cd`. */
|
|
20
|
+
export function resolveArtifactTarget(target: string, repoRoot: string): string {
|
|
21
|
+
if (path.isAbsolute(target)) return path.normalize(target);
|
|
22
|
+
return path.normalize(path.join(repoRoot, target));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function relativeInside(target: string, root: string): string | null {
|
|
26
|
+
const relative = path.relative(path.normalize(root), path.normalize(target));
|
|
27
|
+
if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) return null;
|
|
28
|
+
return relative;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The refusal text, without a trailing newline. */
|
|
32
|
+
export function artifactRefusal({
|
|
33
|
+
target,
|
|
34
|
+
resolved,
|
|
35
|
+
companion,
|
|
36
|
+
repoRoot,
|
|
37
|
+
}: ArtifactRefusalInput): string {
|
|
38
|
+
const isRelative = !path.isAbsolute(target);
|
|
39
|
+
const inside = relativeInside(resolved, repoRoot);
|
|
40
|
+
const suggested = inside ? path.join(companion, inside) : null;
|
|
41
|
+
|
|
42
|
+
const lines = [
|
|
43
|
+
`${FRAMEWORK_NAME} guardrail: ${ARTIFACT_REFUSAL_HEADLINE}`,
|
|
44
|
+
` target: ${target}`,
|
|
45
|
+
];
|
|
46
|
+
if (isRelative) lines.push(` resolved: ${resolved}`);
|
|
47
|
+
lines.push(` companion: ${companion}`);
|
|
48
|
+
if (suggested) lines.push(` suggested: ${suggested}`);
|
|
49
|
+
if (isRelative) {
|
|
50
|
+
lines.push(
|
|
51
|
+
"Relative paths resolve against the working repository; a preceding `cd` is not followed.",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
lines.push(
|
|
55
|
+
suggested
|
|
56
|
+
? "Rewrite to the suggested absolute path and retry."
|
|
57
|
+
: `Use an absolute path under ${companion}.`,
|
|
58
|
+
);
|
|
59
|
+
if (path.basename(target) === "CLAUDE.md") {
|
|
60
|
+
lines.push(`Note: CLAUDE.md already lives in the companion repo at ${companion}/CLAUDE.md.`);
|
|
61
|
+
}
|
|
62
|
+
return lines.join("\n");
|
|
63
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
1
3
|
import type { ClaudeHookGroup } from "./claude-hook-group";
|
|
4
|
+
import type { SessionKind } from "./session-kind";
|
|
2
5
|
import { NOT_WRAPPED, readWrapStateSync, type WrapState } from "./wrap-state";
|
|
3
6
|
|
|
4
7
|
/**
|
|
@@ -7,13 +10,6 @@ import { NOT_WRAPPED, readWrapStateSync, type WrapState } from "./wrap-state";
|
|
|
7
10
|
* may import it.
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
|
-
/**
|
|
11
|
-
* `managed-repo`: `mate claude` with a Working Repository; its prepare just
|
|
12
|
-
* reconciled the Claude wrap, which the launcher asserts. `managed-companion`:
|
|
13
|
-
* a companion-scoped launch. `unmanaged`: a session with no launch environment.
|
|
14
|
-
*/
|
|
15
|
-
export type SessionKind = "managed-repo" | "managed-companion" | "unmanaged";
|
|
16
|
-
|
|
17
13
|
export interface ClaudeDeliveryPlan {
|
|
18
14
|
/** Capability hook groups; `launchSettingsExclude` is what `--settings` must not repeat. */
|
|
19
15
|
readonly hooks: {
|
|
@@ -28,9 +24,95 @@ export interface ClaudeDeliveryPlan {
|
|
|
28
24
|
readonly reconcile: "attended-prepare" | "prepare-hook" | "none";
|
|
29
25
|
/** Companion Git synchronization. */
|
|
30
26
|
readonly gitSync: "launch-preflight" | "prepare-hook" | "banner";
|
|
27
|
+
/**
|
|
28
|
+
* Plugin folders loaded as Claude plugins. A managed launch passes Mate's
|
|
29
|
+
* bundled plugin and every gathered Capability folder but `launchExclude`,
|
|
30
|
+
* the folders the wrap carries; a Claude-wrapped plain session gets those
|
|
31
|
+
* from the wrap alone.
|
|
32
|
+
*/
|
|
33
|
+
readonly plugins: {
|
|
34
|
+
readonly owner: "launch-flag" | "wrap" | "none";
|
|
35
|
+
readonly launchExclude: readonly string[];
|
|
36
|
+
};
|
|
37
|
+
/** Companion Instructions, per thread kind; `dirAccess` through settings loads no memory file. */
|
|
38
|
+
readonly instructions: ClaudeInstructionOwners;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ClaudeInstructionOwners {
|
|
42
|
+
/** `wrap-session-hook`: the wrap's session-prepare hook, once it has prepared, for a launch and a plain session alike. */
|
|
43
|
+
readonly main: "native-memory" | "wrap-session-hook" | "none";
|
|
44
|
+
/** `plugin-subagent-hook`: the bundled plugin's SubagentStart copy; `wrap-subagent-hook`: the wrap's. */
|
|
45
|
+
readonly subagents: "plugin-subagent-hook" | "native-memory" | "wrap-subagent-hook" | "none";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether the session's launch passes `--plugin-dir`: every managed launch does, a plain
|
|
50
|
+
* session cannot. Not whether the session loads plugin folders: a Claude-wrapped plain
|
|
51
|
+
* session gets them from the wrap.
|
|
52
|
+
*/
|
|
53
|
+
export function launchPassesClaudePluginFolders(kind: SessionKind): boolean {
|
|
54
|
+
return kind !== "unmanaged";
|
|
31
55
|
}
|
|
32
56
|
|
|
33
57
|
export function planClaudeDelivery(kind: SessionKind, wrap: WrapState): ClaudeDeliveryPlan {
|
|
58
|
+
return {
|
|
59
|
+
...planChannels(kind, wrap),
|
|
60
|
+
plugins: planPlugins(kind, wrap),
|
|
61
|
+
instructions: planClaudeInstructions(kind, wrap.runtimes.has("claude")),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The instruction owners alone, for a hook that knows its wrap without reading
|
|
67
|
+
* one: a wrap-carried hook runs only because a Claude wrap installed it, maybe
|
|
68
|
+
* the main checkout's, so it must not gate on this Checkout's manifest.
|
|
69
|
+
*/
|
|
70
|
+
export function planClaudeInstructions(
|
|
71
|
+
kind: SessionKind,
|
|
72
|
+
wrappedForClaude: boolean,
|
|
73
|
+
): ClaudeInstructionOwners {
|
|
74
|
+
switch (kind) {
|
|
75
|
+
case "managed-repo":
|
|
76
|
+
return { main: "wrap-session-hook", subagents: "plugin-subagent-hook" };
|
|
77
|
+
case "managed-companion":
|
|
78
|
+
return { main: "native-memory", subagents: "native-memory" };
|
|
79
|
+
case "unmanaged":
|
|
80
|
+
return wrappedForClaude
|
|
81
|
+
? { main: "wrap-session-hook", subagents: "wrap-subagent-hook" }
|
|
82
|
+
: { main: "none", subagents: "none" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The folders a managed launch passes with `--plugin-dir`, in order: the
|
|
88
|
+
* bundled plugin, then each gathered folder the wrap does not carry, matched
|
|
89
|
+
* by resolved path. None when the launch flag does not own plugin folders.
|
|
90
|
+
*/
|
|
91
|
+
export function launchPluginFolders(
|
|
92
|
+
plan: ClaudeDeliveryPlan,
|
|
93
|
+
gathered: readonly string[],
|
|
94
|
+
bundledPluginRoot: string,
|
|
95
|
+
): string[] {
|
|
96
|
+
if (plan.plugins.owner !== "launch-flag") return [];
|
|
97
|
+
const carried = new Set(plan.plugins.launchExclude.map((dir) => path.resolve(dir)));
|
|
98
|
+
return [bundledPluginRoot, ...gathered.filter((dir) => !carried.has(path.resolve(dir)))];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function planPlugins(kind: SessionKind, wrap: WrapState): ClaudeDeliveryPlan["plugins"] {
|
|
102
|
+
switch (kind) {
|
|
103
|
+
case "managed-repo":
|
|
104
|
+
return { owner: "launch-flag", launchExclude: wrap.carriedClaudePluginDirectories };
|
|
105
|
+
case "managed-companion":
|
|
106
|
+
return { owner: "launch-flag", launchExclude: [] };
|
|
107
|
+
case "unmanaged":
|
|
108
|
+
return { owner: wrap.runtimes.has("claude") ? "wrap" : "none", launchExclude: [] };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function planChannels(
|
|
113
|
+
kind: SessionKind,
|
|
114
|
+
wrap: WrapState,
|
|
115
|
+
): Omit<ClaudeDeliveryPlan, "plugins" | "instructions"> {
|
|
34
116
|
switch (kind) {
|
|
35
117
|
case "managed-repo":
|
|
36
118
|
return {
|