@uniqbit/mate-core 0.18.5 → 1.0.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-plugin/hooks/companion-instructions.mjs +9 -0
- package/claude-plugin/hooks/hooks.json +12 -2
- package/package.json +10 -2
- package/src/brand/art.generated.ts +69 -0
- package/src/brand/index.ts +12 -0
- package/src/brand/palette.ts +19 -0
- package/src/brand/svg.generated.ts +8 -0
- package/src/brand/terminal.ts +83 -0
- package/src/brand/types.ts +33 -0
- package/src/cli/commands/artifact/artifact.ts +8 -0
- package/src/cli/commands/artifact/delivery/archive.ts +117 -0
- package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
- package/src/cli/commands/artifact/delivery/environment.ts +104 -0
- package/src/cli/commands/artifact/delivery/link.ts +154 -0
- package/src/cli/commands/artifact/delivery/output.ts +58 -0
- package/src/cli/commands/artifact/finish/command.ts +33 -3
- package/src/cli/commands/artifact/finish/engine.ts +47 -3
- package/src/cli/commands/artifact/finish/finisher.ts +10 -0
- package/src/cli/commands/artifact/finish/openspec.ts +177 -138
- package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
- package/src/cli/commands/artifact/pending/command.ts +31 -3
- package/src/cli/commands/artifact/pending/discovery.ts +21 -57
- package/src/cli/commands/cap/graphify.ts +52 -3
- package/src/cli/commands/cap/index-cmd.ts +8 -1
- package/src/cli/commands/cap/openspec.ts +13 -25
- package/src/cli/commands/companion/open.ts +56 -39
- package/src/cli/commands/companion/update.ts +75 -42
- package/src/cli/commands/doctor.ts +176 -3
- package/src/cli/commands/plugin/install.ts +9 -4
- package/src/cli/commands/plugin/plugin.ts +3 -2
- package/src/cli/commands/report/adapter.ts +11 -2
- package/src/cli/commands/report/contract.ts +21 -0
- package/src/cli/commands/report/highlight.ts +34 -4
- package/src/cli/commands/report/markdown.ts +110 -0
- package/src/cli/commands/report/palette.ts +118 -0
- package/src/cli/commands/report/renderer.ts +456 -153
- package/src/cli/commands/report/types.ts +16 -0
- package/src/cli/commands/setup.ts +7 -15
- package/src/cli/commands/studio/client-asset.ts +16 -0
- package/src/cli/commands/studio/openspec-cli.ts +10 -58
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +19 -1
- package/src/cli/commands/studio/server.ts +42 -1
- package/src/cli/commands/studio/views/client.ts +6 -616
- package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
- package/src/cli/commands/studio/views/document.tsx +137 -69
- package/src/cli/commands/studio/views/markdown.tsx +250 -0
- package/src/cli/commands/studio/views/palette.ts +134 -0
- package/src/cli/commands/studio/views/reports/index.tsx +2 -7
- package/src/cli/commands/studio/views/skills/index.tsx +43 -62
- package/src/cli/commands/studio/views/specs/index.tsx +7 -15
- package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
- package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
- package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
- package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
- package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
- package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
- package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
- package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
- package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
- package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
- package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
- package/src/cli/commands/studio/views/styles.ts +217 -206
- package/src/cli/commands/studio/views/vault/state.ts +3 -5
- package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
- package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
- package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
- package/src/cli/commands/wrap.ts +5 -13
- package/src/cli/main.ts +4 -1
- package/src/cli/parse-flags.ts +25 -7
- package/src/cli/setup-selector.tsx +38 -162
- package/src/cli/usage.ts +4 -0
- package/src/hooks/companion-instructions.ts +190 -0
- package/src/hooks/hook-io.ts +63 -0
- package/src/hooks/session-banner.ts +21 -29
- package/src/hooks/session-guidance.ts +9 -17
- package/src/hooks/session-prepare.ts +115 -28
- package/src/hooks/validate-artifact-path.ts +82 -24
- package/src/index.ts +4 -0
- package/src/lib/change-layout.ts +154 -0
- package/src/lib/components/wizard-header.tsx +3 -1
- package/src/lib/context-mode-package.ts +24 -11
- package/src/lib/delivery/archive-change.ts +154 -0
- package/src/lib/delivery/branches.ts +108 -0
- package/src/lib/delivery/environment.ts +29 -0
- package/src/lib/delivery/forge.ts +279 -0
- package/src/lib/delivery/overlap.ts +202 -0
- package/src/lib/delivery/record-delivery.ts +196 -0
- package/src/lib/delivery/record.ts +343 -0
- package/src/lib/delivery/refresh.ts +174 -0
- package/src/lib/delivery/repository-access.ts +97 -0
- package/src/lib/delivery/stages.ts +111 -0
- package/src/lib/delivery/token.ts +58 -0
- package/src/lib/frontmatter.ts +18 -0
- package/src/lib/opencode-plugin-package.ts +5 -5
- package/src/lib/openspec-cli.ts +116 -0
- package/src/lib/openspec-scopes.ts +86 -0
- package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
- package/src/lib/orchestrator/adapters/claude.ts +48 -40
- package/src/lib/orchestrator/adapters/opencode.ts +2 -0
- package/src/lib/orchestrator/editor.ts +9 -9
- package/src/lib/orchestrator/openspec-store.ts +14 -54
- package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
- package/src/lib/orchestrator/projection-claude-links.ts +159 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +19 -137
- package/src/lib/orchestrator/projection-entries.ts +45 -14
- package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
- package/src/lib/orchestrator/projection-types.ts +3 -0
- package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
- package/src/lib/orchestrator/session-prepare.ts +45 -5
- package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
- package/src/lib/orchestrator/types.ts +2 -2
- package/src/lib/orchestrator/wrap-pass.ts +22 -1
- package/src/lib/package-paths.ts +8 -0
- package/src/opencode/companion-hooks.ts +64 -5
- package/src/opencode/companion-policy.ts +37 -18
- package/src/opencode/projected-guidance.ts +4 -1
- package/src/opencode/session-prepare.ts +2 -1
- package/src/opencode/tui.tsx +4 -2
- package/src/runtime/artifact-refusal.ts +63 -0
- package/src/runtime/claude-delivery-plan.ts +89 -7
- package/src/runtime/commit-attribution-guard.ts +181 -0
- package/src/runtime/companion-guidance.ts +15 -3
- package/src/runtime/companion-instructions.ts +340 -0
- package/src/runtime/error-message.ts +4 -0
- package/src/runtime/index.ts +5 -0
- package/src/runtime/managed-block.ts +117 -0
- package/src/runtime/markdown-scan.ts +266 -0
- package/src/runtime/policy.ts +15 -1
- package/src/runtime/projected-flag.ts +7 -0
- package/src/runtime/projected-guidance.ts +1 -0
- package/src/runtime/runtime-document-manifest.ts +62 -11
- package/src/runtime/secret-guard.ts +148 -0
- package/src/runtime/secret-patterns.ts +164 -0
- package/src/runtime/secret-scan.ts +90 -0
- package/src/runtime/session-kind.ts +13 -0
- package/src/runtime/shell-heredoc.ts +81 -0
- package/src/runtime/wrap-state.ts +36 -2
- package/src/runtime/write-atomic.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
- package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
- package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/{claude/mate-artifact-publish → agents/mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/{agents/mate-artifact-publish → claude/mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/openspec-references.ts +140 -0
- package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
- package/src/tools/setup/capabilities/context-mode.ts +25 -5
- package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
- package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
- package/src/tools/setup/capabilities/graphify.ts +12 -0
- package/src/tools/setup/capabilities/openspec.ts +151 -270
- package/src/tools/setup/capabilities/tokensave.ts +36 -3
- package/src/tools/setup/context-services.ts +9 -17
- package/src/tools/setup/dynamic-plugins/install.ts +24 -28
- package/src/tools/setup/engine.ts +97 -21
- package/src/tools/setup/managed-output.ts +51 -0
- package/src/tools/setup/mate.ts +86 -11
- package/src/tools/setup/plugin.ts +35 -0
- package/src/tools/setup/plugins/guidance.ts +7 -4
- package/src/tools/setup/providers/agent-file-sections.ts +4 -2
- package/src/tools/setup/providers/claude-attribution.ts +65 -0
- package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
- package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
- package/src/tools/setup/providers/claude.ts +284 -8
- package/src/tools/setup/providers/opencode.ts +180 -1
- package/src/tools/setup/utils.ts +14 -4
- package/src/tools/setup.ts +126 -10
- package/wrappers/bin/graphify +52 -4
- package/wrappers/bin/openspec +33 -9
- package/wrappers/lib/graphify-gate.mjs +8 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
- package/src/tools/setup/runtime-documents.ts +0 -291
|
@@ -3,6 +3,8 @@ import crypto from "node:crypto";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
|
+
import { managedBlockMarkers, managedBlockStartPattern } from "../../runtime/managed-block";
|
|
7
|
+
import { removeEmptiedFile, writeAtomicThroughLink } from "../../runtime/write-atomic";
|
|
6
8
|
import type {
|
|
7
9
|
InstructionsService,
|
|
8
10
|
McpServerDescriptor,
|
|
@@ -63,13 +65,6 @@ async function saveManifest(
|
|
|
63
65
|
// Managed instruction blocks
|
|
64
66
|
// ---------------------------------------------------------------------------
|
|
65
67
|
|
|
66
|
-
function blockMarkers(frameworkName: string, blockKey: string): { start: string; end: string } {
|
|
67
|
-
return {
|
|
68
|
-
start: `<!-- ${frameworkName}:managed:${blockKey} start -->`,
|
|
69
|
-
end: `<!-- ${frameworkName}:managed:${blockKey} end -->`,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
68
|
export function instructionBlockKey(pluginId: string, content: string): string {
|
|
74
69
|
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
75
70
|
return `${pluginId}:${hash}`;
|
|
@@ -82,7 +77,7 @@ export async function upsertManagedBlock(
|
|
|
82
77
|
blockKey: string,
|
|
83
78
|
content: string,
|
|
84
79
|
): Promise<void> {
|
|
85
|
-
const { start, end } =
|
|
80
|
+
const { start, end } = managedBlockMarkers(frameworkName, blockKey);
|
|
86
81
|
let existing = "";
|
|
87
82
|
try {
|
|
88
83
|
existing = await fs.readFile(filePath, "utf8");
|
|
@@ -93,7 +88,8 @@ export async function upsertManagedBlock(
|
|
|
93
88
|
if (existing.includes(start)) return;
|
|
94
89
|
const separator = existing.length === 0 || existing.endsWith("\n\n") ? "" : "\n";
|
|
95
90
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
96
|
-
|
|
91
|
+
/** A session-start hook may read the file while another session's prepare rewrites it. */
|
|
92
|
+
await writeAtomicThroughLink(filePath, existing + (existing ? separator : "") + block);
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
/**
|
|
@@ -113,11 +109,7 @@ export async function removeManagedBlocksForPlugin(
|
|
|
113
109
|
} catch {
|
|
114
110
|
return;
|
|
115
111
|
}
|
|
116
|
-
const
|
|
117
|
-
const keyPattern = new RegExp(
|
|
118
|
-
`<!-- ${escape(frameworkName)}:managed:(${escape(pluginId)}:[0-9a-f]+) start -->`,
|
|
119
|
-
"g",
|
|
120
|
-
);
|
|
112
|
+
const keyPattern = managedBlockStartPattern(frameworkName, pluginId);
|
|
121
113
|
const keys = [...existing.matchAll(keyPattern)].map((match) => match[1]);
|
|
122
114
|
for (const key of keys) {
|
|
123
115
|
if (keepKeys.has(key)) continue;
|
|
@@ -131,7 +123,7 @@ async function removeManagedBlock(
|
|
|
131
123
|
frameworkName: string,
|
|
132
124
|
blockKey: string,
|
|
133
125
|
): Promise<void> {
|
|
134
|
-
const { start, end } =
|
|
126
|
+
const { start, end } = managedBlockMarkers(frameworkName, blockKey);
|
|
135
127
|
let existing: string;
|
|
136
128
|
try {
|
|
137
129
|
existing = await fs.readFile(filePath, "utf8");
|
|
@@ -146,10 +138,10 @@ async function removeManagedBlock(
|
|
|
146
138
|
const after = existing.slice(endIdx + end.length).replace(/^\n+/, "\n");
|
|
147
139
|
const next = (before + after).replace(/^\n+/, "");
|
|
148
140
|
if (!next.trim()) {
|
|
149
|
-
await
|
|
141
|
+
await removeEmptiedFile(filePath).catch(() => {});
|
|
150
142
|
return;
|
|
151
143
|
}
|
|
152
|
-
await
|
|
144
|
+
await writeAtomicThroughLink(filePath, next);
|
|
153
145
|
}
|
|
154
146
|
|
|
155
147
|
// ---------------------------------------------------------------------------
|
|
@@ -26,45 +26,40 @@ export interface PluginInstallResult {
|
|
|
26
26
|
error?: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Runs `npm <command>` in the shared plugin workspace, honoring the user's ambient
|
|
31
|
+
* registry/auth config. `--loglevel=error` keeps npm's own error in stderr for the report.
|
|
32
|
+
*/
|
|
33
|
+
function runNpm(
|
|
34
|
+
workspaceRoot: string,
|
|
35
|
+
command: string,
|
|
36
|
+
args: string[] = [],
|
|
37
|
+
): { ok: boolean; detail?: string } {
|
|
38
|
+
const result = spawnSync(
|
|
39
|
+
"npm",
|
|
40
|
+
[command, "--no-audit", "--no-fund", "--loglevel=error", ...args],
|
|
41
|
+
{ cwd: workspaceRoot, encoding: "utf8" },
|
|
42
|
+
);
|
|
35
43
|
if (result.error || result.status !== 0) {
|
|
36
44
|
return {
|
|
37
45
|
ok: false,
|
|
38
46
|
detail:
|
|
39
47
|
result.error?.message ??
|
|
40
|
-
result.stderr?.trim()
|
|
41
|
-
`npm install exited with ${result.status}`,
|
|
48
|
+
(result.stderr?.trim() || `npm ${command} exited with ${result.status}`),
|
|
42
49
|
};
|
|
43
50
|
}
|
|
44
51
|
return { ok: true };
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
/** Runs `npm install` for the shared plugin workspace. */
|
|
55
|
+
const defaultNpmInstall: NpmInstallRunner = (workspaceRoot) => runNpm(workspaceRoot, "install");
|
|
56
|
+
|
|
47
57
|
/** Version literals treated as moving targets: re-resolved via `npm update` on every run. */
|
|
48
58
|
const MOVING_VERSION_TAGS = new Set(["latest", "canary"]);
|
|
49
59
|
|
|
50
60
|
/** Runs `npm update <pkg...>` to force re-resolution of moving-tag-declared plugins every run. */
|
|
51
|
-
|
|
52
|
-
workspaceRoot
|
|
53
|
-
packages: string[],
|
|
54
|
-
): { ok: boolean; detail?: string } {
|
|
55
|
-
const result = spawnSync("npm", ["update", "--no-audit", "--no-fund", "--silent", ...packages], {
|
|
56
|
-
cwd: workspaceRoot,
|
|
57
|
-
encoding: "utf8",
|
|
58
|
-
});
|
|
59
|
-
if (result.error || result.status !== 0) {
|
|
60
|
-
return {
|
|
61
|
-
ok: false,
|
|
62
|
-
detail:
|
|
63
|
-
result.error?.message ?? result.stderr?.trim() ?? `npm update exited with ${result.status}`,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
return { ok: true };
|
|
67
|
-
}
|
|
61
|
+
const defaultNpmUpdate: NpmUpdateRunner = (workspaceRoot, packages) =>
|
|
62
|
+
runNpm(workspaceRoot, "update", packages);
|
|
68
63
|
|
|
69
64
|
async function readInstalledVersion(packageRoot: string): Promise<string | null> {
|
|
70
65
|
try {
|
|
@@ -155,14 +150,15 @@ export async function installDeclaredPlugins(
|
|
|
155
150
|
|
|
156
151
|
await writeWorkspaceManifest(workspaceRoot, desired);
|
|
157
152
|
const installOutcome = await runNpmInstall(workspaceRoot);
|
|
153
|
+
// Failure is decided from `ok`, so an empty detail can never read as success.
|
|
158
154
|
const installFailure = installOutcome.ok
|
|
159
155
|
? undefined
|
|
160
|
-
:
|
|
156
|
+
: installOutcome.detail || "npm install failed";
|
|
161
157
|
|
|
162
158
|
let updateFailure: string | undefined;
|
|
163
159
|
if (movingPackages.length > 0) {
|
|
164
160
|
const updateOutcome = await runNpmUpdate(workspaceRoot, movingPackages);
|
|
165
|
-
updateFailure = updateOutcome.ok ? undefined :
|
|
161
|
+
updateFailure = updateOutcome.ok ? undefined : updateOutcome.detail || "npm update failed";
|
|
166
162
|
}
|
|
167
163
|
|
|
168
164
|
const resolvedVersions = await Promise.all(
|
|
@@ -174,7 +170,7 @@ export async function installDeclaredPlugins(
|
|
|
174
170
|
return sorted.map((declaration, index) => {
|
|
175
171
|
const isMoving = MOVING_VERSION_TAGS.has(declaration.version);
|
|
176
172
|
const failure = installFailure ?? (isMoving ? updateFailure : undefined);
|
|
177
|
-
if (failure) {
|
|
173
|
+
if (failure !== undefined) {
|
|
178
174
|
return { package: declaration.package, status: "failed", error: failure };
|
|
179
175
|
}
|
|
180
176
|
const resolvedVersion = resolvedVersions[index];
|
|
@@ -2,19 +2,29 @@
|
|
|
2
2
|
import { getActiveDistribution } from "../../distribution";
|
|
3
3
|
import { FRAMEWORK_NAME } from "../../framework";
|
|
4
4
|
import type { FrameworkConfig } from "../../lib/orchestrator/types";
|
|
5
|
-
import
|
|
5
|
+
import { getClaudePluginRoot } from "../../lib/package-paths";
|
|
6
|
+
import type { RenderedRuntimeDocuments } from "../../runtime/runtime-document-manifest";
|
|
6
7
|
import { collectHostingProviders, ContextServiceMediator } from "./context-services";
|
|
7
8
|
import type {
|
|
8
9
|
CapabilityContributionInput,
|
|
9
10
|
CapabilityPlugin,
|
|
10
11
|
Plugin,
|
|
11
12
|
PluginRegistration,
|
|
13
|
+
RuntimeContributionsByRuntime,
|
|
12
14
|
SetupContext,
|
|
13
15
|
} from "./plugin";
|
|
14
|
-
import { reconcileClaudeContributions } from "./providers/claude";
|
|
15
|
-
import {
|
|
16
|
-
|
|
16
|
+
import { reconcileClaudeContributions, renderClaudeWorkingDocuments } from "./providers/claude";
|
|
17
|
+
import {
|
|
18
|
+
inspectClaudePluginFolders,
|
|
19
|
+
publishClaudePluginFolders,
|
|
20
|
+
type ClaudePluginFolders,
|
|
21
|
+
} from "./providers/claude-plugin-folders";
|
|
22
|
+
import {
|
|
23
|
+
reconcileOpenCodeContributions,
|
|
24
|
+
renderOpenCodeWorkingDocuments,
|
|
25
|
+
} from "./providers/opencode";
|
|
17
26
|
import { normalizeRegistration, type NormalizedRegistration } from "./registry";
|
|
27
|
+
import { projectionRepoRoot } from "./surface-target";
|
|
18
28
|
|
|
19
29
|
export interface SetupInstallationPlanAction {
|
|
20
30
|
phase: Plugin["kind"];
|
|
@@ -33,6 +43,11 @@ export interface SetupInstallationOutcome {
|
|
|
33
43
|
executedActions: SetupInstallationPlanAction[];
|
|
34
44
|
skippedActions: SetupInstallationPlanAction[];
|
|
35
45
|
warnings: string[];
|
|
46
|
+
/**
|
|
47
|
+
* Enabled Capabilities' Claude plugin folders, published outside a hub (a hub
|
|
48
|
+
* has no wrap for plain sessions to load them through); never printed here.
|
|
49
|
+
*/
|
|
50
|
+
claudePluginFolders: ClaudePluginFolders;
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
export function buildSetupInstallationPlan(
|
|
@@ -176,9 +191,15 @@ export async function executeSetupInstallationPlan(
|
|
|
176
191
|
|
|
177
192
|
await mediator.finalize();
|
|
178
193
|
|
|
179
|
-
await reconcileCapabilityContributions(ctx, plugins, plan);
|
|
194
|
+
const inputsByRuntime = await reconcileCapabilityContributions(ctx, plugins, plan);
|
|
195
|
+
const claudeInputs = inputsByRuntime.get("claude") ?? [];
|
|
196
|
+
const folderOptions = { bundledPluginRoot: getClaudePluginRoot() };
|
|
197
|
+
const claudePluginFolders =
|
|
198
|
+
ctx.scope === "hub"
|
|
199
|
+
? await inspectClaudePluginFolders(claudeInputs, folderOptions)
|
|
200
|
+
: await publishClaudePluginFolders(claudeInputs, folderOptions);
|
|
180
201
|
|
|
181
|
-
return { executedActions, skippedActions, warnings };
|
|
202
|
+
return { executedActions, skippedActions, warnings, claudePluginFolders };
|
|
182
203
|
}
|
|
183
204
|
|
|
184
205
|
// The Runtime Surface of each active Agent Runtime, by runtime id.
|
|
@@ -190,6 +211,18 @@ const RUNTIME_SURFACE_RECONCILERS: Record<
|
|
|
190
211
|
opencode: reconcileOpenCodeContributions,
|
|
191
212
|
};
|
|
192
213
|
|
|
214
|
+
/**
|
|
215
|
+
* The working-target render of each Runtime Surface, by runtime id, in the
|
|
216
|
+
* order its documents are rendered.
|
|
217
|
+
*/
|
|
218
|
+
const RUNTIME_SURFACE_WORKING_RENDERERS: Record<
|
|
219
|
+
string,
|
|
220
|
+
(ctx: SetupContext, inputs: CapabilityContributionInput[]) => Promise<RenderedRuntimeDocuments>
|
|
221
|
+
> = {
|
|
222
|
+
claude: renderClaudeWorkingDocuments,
|
|
223
|
+
opencode: renderOpenCodeWorkingDocuments,
|
|
224
|
+
};
|
|
225
|
+
|
|
193
226
|
/**
|
|
194
227
|
* The declared Capability contributions of one pass, keyed by runtime. Shared
|
|
195
228
|
* by both targets: what a companion pass writes is what a working pass renders.
|
|
@@ -198,6 +231,7 @@ async function collectContributionInputs(
|
|
|
198
231
|
ctx: SetupContext,
|
|
199
232
|
plugins: Plugin[],
|
|
200
233
|
plan: SetupInstallationPlan,
|
|
234
|
+
onContributionError?: ContributionErrorHandler,
|
|
201
235
|
): Promise<Map<string, CapabilityContributionInput[]>> {
|
|
202
236
|
// The plan already resolved enablement (policy, saved selection, package
|
|
203
237
|
// manager requirements); mirror it instead of recomputing.
|
|
@@ -213,7 +247,14 @@ async function collectContributionInputs(
|
|
|
213
247
|
const capability = plugin as CapabilityPlugin;
|
|
214
248
|
if (!capability.getRuntimeContributions) continue;
|
|
215
249
|
const enabled = enabledByPluginId.get(capability.id) ?? false;
|
|
216
|
-
|
|
250
|
+
let byRuntime: RuntimeContributionsByRuntime;
|
|
251
|
+
try {
|
|
252
|
+
byRuntime = await capability.getRuntimeContributions(ctx);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (!onContributionError) throw error;
|
|
255
|
+
onContributionError(capability.id, error);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
217
258
|
for (const [runtimeId, contributions] of Object.entries(byRuntime)) {
|
|
218
259
|
if (!contributions) continue;
|
|
219
260
|
// Inactive runtimes still reconcile — with everything disabled — so a
|
|
@@ -237,42 +278,77 @@ async function collectContributionInputs(
|
|
|
237
278
|
* Reconcile declared Capability contributions through every active runtime's
|
|
238
279
|
* Runtime Surface (spec: plugin-engine). Runs after all plugin phases, so
|
|
239
280
|
* providers are always done first. Disabled capabilities participate with
|
|
240
|
-
* `enabled: false` and drive teardown of their managed entries.
|
|
281
|
+
* `enabled: false` and drive teardown of their managed entries. Returns the
|
|
282
|
+
* inputs it reconciled, so the pass derives launch-only data from the same ones.
|
|
241
283
|
*/
|
|
242
284
|
async function reconcileCapabilityContributions(
|
|
243
285
|
ctx: SetupContext,
|
|
244
286
|
plugins: Plugin[],
|
|
245
287
|
plan: SetupInstallationPlan,
|
|
246
|
-
): Promise<
|
|
288
|
+
): Promise<Map<string, CapabilityContributionInput[]>> {
|
|
247
289
|
const inputsByRuntime = await collectContributionInputs(ctx, plugins, plan);
|
|
248
290
|
for (const [runtimeId, inputs] of inputsByRuntime) {
|
|
249
291
|
const reconcile = RUNTIME_SURFACE_RECONCILERS[runtimeId];
|
|
250
292
|
if (!reconcile || inputs.length === 0) continue;
|
|
251
293
|
await reconcile(ctx, inputs);
|
|
252
294
|
}
|
|
295
|
+
return inputsByRuntime;
|
|
253
296
|
}
|
|
254
297
|
|
|
255
|
-
/**
|
|
256
|
-
export
|
|
257
|
-
|
|
258
|
-
|
|
298
|
+
/** Receives one Capability's rejected contributions; that Capability then contributes nothing. */
|
|
299
|
+
export type ContributionErrorHandler = (capabilityId: string, error: unknown) => void;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* One pass's declared Capability contributions by runtime, collected without a
|
|
303
|
+
* reconcile. A rejecting Capability fails the collection unless
|
|
304
|
+
* `onContributionError` takes its error; only diagnostics should pass one.
|
|
305
|
+
*/
|
|
306
|
+
export async function collectRuntimeContributionInputs(
|
|
307
|
+
ctx: SetupContext,
|
|
308
|
+
registrations: PluginRegistration[],
|
|
309
|
+
plan: SetupInstallationPlan,
|
|
310
|
+
onContributionError?: ContributionErrorHandler,
|
|
311
|
+
): Promise<Map<string, CapabilityContributionInput[]>> {
|
|
312
|
+
const plugins = registrations.map((entry) => normalizeRegistration(entry).plugin);
|
|
313
|
+
return collectContributionInputs(ctx, plugins, plan, onContributionError);
|
|
259
314
|
}
|
|
260
315
|
|
|
261
316
|
/**
|
|
262
317
|
* The same declarations, rendered rather than written: a working-target pass
|
|
263
318
|
* produces the documents and the Projection Root places them, so no Runtime
|
|
264
|
-
* Surface reaches inside a Working Repository.
|
|
319
|
+
* Surface reaches inside a Working Repository. Refuses before anything renders
|
|
320
|
+
* when the context has no Working Repository: every document is addressed by it.
|
|
321
|
+
* Its warnings are data for the caller, never printed here.
|
|
265
322
|
*/
|
|
266
323
|
export async function renderRuntimeDocuments(
|
|
267
324
|
ctx: SetupContext,
|
|
268
325
|
registrations: PluginRegistration[],
|
|
269
326
|
plan: SetupInstallationPlan,
|
|
270
327
|
): Promise<RenderedRuntimeDocuments> {
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
328
|
+
projectionRepoRoot(ctx);
|
|
329
|
+
const inputsByRuntime = await collectRuntimeContributionInputs(ctx, registrations, plan);
|
|
330
|
+
const rendered: RenderedRuntimeDocuments = { documents: [], warnings: [] };
|
|
331
|
+
for (const [runtimeId, render] of Object.entries(RUNTIME_SURFACE_WORKING_RENDERERS)) {
|
|
332
|
+
if (!ctx.activeProviders.includes(runtimeId)) continue;
|
|
333
|
+
const { documents, warnings } = await render(ctx, inputsByRuntime.get(runtimeId) ?? []);
|
|
334
|
+
rendered.documents.push(...documents);
|
|
335
|
+
rendered.warnings.push(...warnings);
|
|
336
|
+
}
|
|
337
|
+
return rendered;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Publishes the enabled Claude plugin folders for a wrap pass, before it
|
|
342
|
+
* renders: writes Mate's marketplace files and removes those of disabled
|
|
343
|
+
* Capabilities. Runs no plugin phase.
|
|
344
|
+
*/
|
|
345
|
+
export async function publishRuntimeClaudePluginMarketplaces(
|
|
346
|
+
ctx: SetupContext,
|
|
347
|
+
registrations: PluginRegistration[],
|
|
348
|
+
plan: SetupInstallationPlan,
|
|
349
|
+
): Promise<ClaudePluginFolders> {
|
|
350
|
+
const inputsByRuntime = await collectRuntimeContributionInputs(ctx, registrations, plan);
|
|
351
|
+
return publishClaudePluginFolders(inputsByRuntime.get("claude") ?? [], {
|
|
352
|
+
bundledPluginRoot: getClaudePluginRoot(),
|
|
353
|
+
});
|
|
278
354
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ManagedOutputEntry,
|
|
6
|
+
ManagedOutputOwner,
|
|
7
|
+
ManagedOutputRecorder,
|
|
8
|
+
ManagedOutputReport,
|
|
9
|
+
} from "./plugin";
|
|
10
|
+
|
|
11
|
+
export interface ManagedOutputCollector {
|
|
12
|
+
recorder: ManagedOutputRecorder;
|
|
13
|
+
/** Entries inside the companion, deduplicated, as companion-relative POSIX paths. */
|
|
14
|
+
report(): ManagedOutputReport;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createManagedOutputCollector(companionPath: string): ManagedOutputCollector {
|
|
18
|
+
const entries: ManagedOutputEntry[] = [];
|
|
19
|
+
return {
|
|
20
|
+
recorder: { record: (entry) => entries.push(entry) },
|
|
21
|
+
report() {
|
|
22
|
+
const seen = new Set<string>();
|
|
23
|
+
const report: ManagedOutputEntry[] = [];
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const relative = path.relative(companionPath, entry.path);
|
|
26
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) continue;
|
|
27
|
+
const posix = relative.split(path.sep).join("/");
|
|
28
|
+
const key = `${entry.change}\0${entry.owner}\0${posix}`;
|
|
29
|
+
if (seen.has(key)) continue;
|
|
30
|
+
seen.add(key);
|
|
31
|
+
report.push({ ...entry, path: posix });
|
|
32
|
+
}
|
|
33
|
+
return report;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Removes `target` and records it, only when it existed: a no-op removal claims nothing. */
|
|
39
|
+
export async function removeRecorded(
|
|
40
|
+
target: string,
|
|
41
|
+
owner: ManagedOutputOwner,
|
|
42
|
+
recorder?: ManagedOutputRecorder,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const existed = await fs.lstat(target).then(
|
|
45
|
+
() => true,
|
|
46
|
+
() => false,
|
|
47
|
+
);
|
|
48
|
+
if (!existed) return;
|
|
49
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
50
|
+
recorder?.record({ path: target, change: "removed", owner });
|
|
51
|
+
}
|
package/src/tools/setup/mate.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
import { removeRecorded } from "./managed-output";
|
|
5
|
+
import type { ManagedOutputRecorder } from "./plugin";
|
|
4
6
|
import { pruneEmptyAncestors } from "./utils";
|
|
7
|
+
import { readFrontmatter } from "../../lib/frontmatter";
|
|
5
8
|
|
|
6
|
-
export const MATE_ARTIFACT_SKILLS = ["mate-artifact-publish"] as const;
|
|
7
9
|
export const MATE_SKILLS = [
|
|
8
|
-
"mate-
|
|
10
|
+
"mate-clarify-intent",
|
|
11
|
+
"mate-explore-change",
|
|
12
|
+
"mate-propose-change",
|
|
13
|
+
"mate-apply-change",
|
|
14
|
+
"mate-archive-change",
|
|
15
|
+
"mate-publish-artifact",
|
|
16
|
+
"mate-backfill-spec",
|
|
9
17
|
"mate-create-report",
|
|
10
|
-
"mate-openspec-backfill",
|
|
11
18
|
"mate-interview-me",
|
|
12
19
|
"mate-grill-me",
|
|
13
20
|
"mate-grilling",
|
|
@@ -17,8 +24,11 @@ export const MATE_SKILLS = [
|
|
|
17
24
|
"mate-simplify-code",
|
|
18
25
|
] as const;
|
|
19
26
|
const LEGACY_MATE_SKILLS = ["mate-artifact-finish", "mate-openspec-artifact-finish"] as const;
|
|
20
|
-
/**
|
|
21
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Renamed without aliases: removed only when Mate wrote them (`metadata.author: mate`),
|
|
29
|
+
* so a user-authored skill that happens to share a retired name is left alone.
|
|
30
|
+
*/
|
|
31
|
+
const RETIRED_MATE_SKILLS = ["mate-artifact-publish", "mate-openspec-backfill"] as const;
|
|
22
32
|
|
|
23
33
|
const MATE_SKILLS_SOURCE = path.join(import.meta.dirname, "../../templates/mate-skills");
|
|
24
34
|
|
|
@@ -83,13 +93,41 @@ A \`diff\` section carries a unified diff patch as produced by \`git diff\`. The
|
|
|
83
93
|
|
|
84
94
|
A report carrying a mermaid payload loads the pinned, integrity-checked diagram runtime from a CDN; a report without one does not. Offline, each diagram stays readable as its source. Otherwise the page stays a single self-contained file.
|
|
85
95
|
|
|
96
|
+
## Presentation Fields
|
|
97
|
+
|
|
98
|
+
These fields are optional. A document without them stays valid and renders with the defaults.
|
|
99
|
+
|
|
100
|
+
- \`tone\` on a status item: \`ok\`, \`warn\`, \`error\`, \`info\`, or \`neutral\`. It sets the marker's colour and shape. When absent, the tone is inferred from well-known status words such as \`passed\`, \`stale\`, or \`failed\`, and any other text is \`neutral\`.
|
|
101
|
+
|
|
102
|
+
\`\`\`json
|
|
103
|
+
{ "label": "Build", "status": "flaky", "tone": "warn" }
|
|
104
|
+
\`\`\`
|
|
105
|
+
|
|
106
|
+
- \`format\` on a \`text\` section: \`plain\` (default, shown verbatim with its line breaks) or \`markdown\`.
|
|
107
|
+
|
|
108
|
+
\`\`\`json
|
|
109
|
+
{ "id": "notes", "title": "Notes", "type": "text", "format": "markdown", "content": "- **Renderer** owns layout\\n- See \`highlight.ts\`" }
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
- \`caption\` on a \`diagram\` or \`diff\` section: a non-empty Markdown note shown beneath the visual, in the same section and without a heading of its own.
|
|
113
|
+
|
|
114
|
+
\`\`\`json
|
|
115
|
+
{ "id": "changes", "title": "Changes", "type": "diff", "patch": "...", "caption": "Moves validation into \`contract.ts\`." }
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
Markdown supports paragraphs, \`#\` headings, one-level \`-\`, \`*\`, or \`1.\` lists, \`**strong**\`, \`*emphasis*\`, \`\` \`code\` \`\`, fenced code blocks with syntax highlighting, and \`[text](https://…)\` links. Raw HTML is shown as text, links other than \`http\` or \`https\` are shown as plain text, and any other syntax stays literal.
|
|
119
|
+
|
|
120
|
+
The page follows the reader's colour scheme: Mate brand colours on white in light mode, and on a dark background in dark mode, with highlighted code and mermaid diagrams following the same scheme. It always prints as dark text on white.
|
|
121
|
+
|
|
122
|
+
To mark mermaid nodes, apply the theme classes \`added\`, \`changed\`, and \`removed\` with \`class\` or \`:::\`; they follow the colour scheme. Lines that fix a colour with \`classDef\`, \`style\`, or \`linkStyle\` are dropped from the dark copy, because a fill chosen for a white page hides the labels there.
|
|
123
|
+
|
|
86
124
|
## Invocation
|
|
87
125
|
|
|
88
126
|
1. Serialize the complete document to a temporary file or pipe it to stdin.
|
|
89
127
|
2. Run \`mate report --input <file-or->\`.
|
|
90
128
|
3. Add \`--json\` when the caller needs normalized JSON instead of browser delivery.
|
|
91
129
|
|
|
92
|
-
The CLI chooses the delivery surface; deliver only through \`mate report\`, never a hand-written file, server, or browser launch. Inside a Studio terminal it hosts the report in Studio and prints a full link only when Studio has a public address, otherwise it tells the operator to open Studio → Reports; relay that output. Elsewhere, or when Studio is unreachable, it writes self-contained HTML to a unique OS temporary directory and opens it in the default browser. The report
|
|
130
|
+
The CLI chooses the delivery surface; deliver only through \`mate report\`, never a hand-written file, server, or browser launch. Inside a Studio terminal it hosts the report in Studio and prints a full link only when Studio has a public address, otherwise it tells the operator to open Studio → Reports; relay that output. Elsewhere, or when Studio is unreachable, it writes self-contained HTML to a unique OS temporary directory and opens it in the default browser. The report header carries an icon-only print control, named "Print / Save as PDF", that calls the browser's native print dialog. If HTML delivery fails, the CLI warns on stderr and emits the complete report document as JSON on stdout.
|
|
93
131
|
|
|
94
132
|
The built-in \`mate report\` path collects Mate usage data and adapts it to the same contract and renderer.
|
|
95
133
|
`;
|
|
@@ -133,10 +171,41 @@ async function resolveMateSkillSource(skill: string, tool: string): Promise<stri
|
|
|
133
171
|
throw new Error(`Mate skill source not found: ${skill}`);
|
|
134
172
|
}
|
|
135
173
|
|
|
136
|
-
|
|
174
|
+
/** The `metadata.author` a skill directory's SKILL.md declares, if any. */
|
|
175
|
+
export async function readSkillAuthor(skillDir: string): Promise<unknown> {
|
|
176
|
+
let source: string;
|
|
177
|
+
try {
|
|
178
|
+
source = await fs.readFile(path.join(skillDir, "SKILL.md"), "utf8");
|
|
179
|
+
} catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const metadata = readFrontmatter(source)?.metadata as { author?: unknown } | null | undefined;
|
|
183
|
+
return metadata?.author;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function isMateAuthored(skillDir: string): Promise<boolean> {
|
|
187
|
+
return (await readSkillAuthor(skillDir)) === "mate";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function removeRetiredMateSkills(
|
|
191
|
+
skillsDir: string,
|
|
192
|
+
recorder?: ManagedOutputRecorder,
|
|
193
|
+
): Promise<void> {
|
|
194
|
+
for (const skill of RETIRED_MATE_SKILLS) {
|
|
195
|
+
const dir = path.join(skillsDir, skill);
|
|
196
|
+
if (await isMateAuthored(dir)) await removeRecorded(dir, "mate", recorder);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function applyMateSkills(
|
|
201
|
+
skillsDir: string,
|
|
202
|
+
tool: string,
|
|
203
|
+
recorder?: ManagedOutputRecorder,
|
|
204
|
+
): Promise<void> {
|
|
137
205
|
for (const skill of LEGACY_MATE_SKILLS) {
|
|
138
|
-
await
|
|
206
|
+
await removeRecorded(path.join(skillsDir, skill), "mate", recorder);
|
|
139
207
|
}
|
|
208
|
+
await removeRetiredMateSkills(skillsDir, recorder);
|
|
140
209
|
for (const skill of MATE_SKILLS) {
|
|
141
210
|
const destination = path.join(skillsDir, skill);
|
|
142
211
|
if (skill === "mate-create-report") {
|
|
@@ -145,13 +214,19 @@ export async function applyMateSkills(skillsDir: string, tool: string): Promise<
|
|
|
145
214
|
} else {
|
|
146
215
|
await deployMateSkillDir(await resolveMateSkillSource(skill, tool), destination);
|
|
147
216
|
}
|
|
217
|
+
recorder?.record({ path: destination, change: "written", owner: "mate" });
|
|
148
218
|
}
|
|
149
219
|
}
|
|
150
220
|
|
|
151
|
-
export async function teardownMateSkills(
|
|
152
|
-
|
|
221
|
+
export async function teardownMateSkills(
|
|
222
|
+
skillsDir: string,
|
|
223
|
+
companionPath: string,
|
|
224
|
+
recorder?: ManagedOutputRecorder,
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
await removeRetiredMateSkills(skillsDir, recorder);
|
|
227
|
+
for (const skill of [...MATE_SKILLS, ...LEGACY_MATE_SKILLS]) {
|
|
153
228
|
try {
|
|
154
|
-
await
|
|
229
|
+
await removeRecorded(path.join(skillsDir, skill), "mate", recorder);
|
|
155
230
|
} catch {
|
|
156
231
|
/* not present */
|
|
157
232
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FrameworkConfig, LinkedRepository } from "../../lib/orchestrator/types";
|
|
2
|
+
import type { SessionKind } from "../../runtime/session-kind";
|
|
2
3
|
import type { InstallRequirement, InstallRequirementContext } from "./install-contract";
|
|
3
4
|
import type { ClaudeHookGroup } from "./providers/claude-format";
|
|
4
5
|
|
|
@@ -22,6 +23,11 @@ export interface SetupContext {
|
|
|
22
23
|
repoPath?: string;
|
|
23
24
|
/** Defaults to `companion`, so a caller that names no target is unchanged. */
|
|
24
25
|
target?: ProjectionTarget;
|
|
26
|
+
/**
|
|
27
|
+
* Asks the OpenSpec capability to regenerate its CLI output in `sync` mode.
|
|
28
|
+
* Only `mate companion update` sets it; launches leave committed output alone.
|
|
29
|
+
*/
|
|
30
|
+
regenerateOpenSpec?: boolean;
|
|
25
31
|
/**
|
|
26
32
|
* Provider-mediated context services. Always populated when the engine
|
|
27
33
|
* executes a plugin; optional so hand-built contexts (tests, direct calls)
|
|
@@ -30,13 +36,34 @@ export interface SetupContext {
|
|
|
30
36
|
mcp?: McpService;
|
|
31
37
|
instructions?: InstructionsService;
|
|
32
38
|
templates?: TemplatesService;
|
|
39
|
+
/** Collects the managed output a sync pass touched; absent outside one. */
|
|
40
|
+
managedOutput?: ManagedOutputRecorder;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ManagedOutputOwner = "mate" | "openspec";
|
|
44
|
+
|
|
45
|
+
/** One path a pass wrote or removed, with the side that owns it. */
|
|
46
|
+
export interface ManagedOutputEntry {
|
|
47
|
+
/** Absolute when recorded; companion-relative POSIX in a {@link ManagedOutputReport}. */
|
|
48
|
+
path: string;
|
|
49
|
+
change: "written" | "removed";
|
|
50
|
+
owner: ManagedOutputOwner;
|
|
33
51
|
}
|
|
34
52
|
|
|
53
|
+
export interface ManagedOutputRecorder {
|
|
54
|
+
record(entry: ManagedOutputEntry): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What a sync pass reports it touched, as companion-relative POSIX paths. */
|
|
58
|
+
export type ManagedOutputReport = readonly ManagedOutputEntry[];
|
|
59
|
+
|
|
35
60
|
export interface LaunchPreflightContext {
|
|
36
61
|
companionPath: string;
|
|
37
62
|
config: FrameworkConfig;
|
|
38
63
|
repository?: LinkedRepository;
|
|
39
64
|
providerId: string;
|
|
65
|
+
/** The session the prepare answers for, so a check can skip what that session does not load. */
|
|
66
|
+
sessionKind: SessionKind;
|
|
40
67
|
}
|
|
41
68
|
|
|
42
69
|
/** Provider-agnostic description of an MCP server to register. */
|
|
@@ -188,10 +215,17 @@ export interface AgentDefinitionContribution {
|
|
|
188
215
|
content: string;
|
|
189
216
|
}
|
|
190
217
|
|
|
218
|
+
/** A Claude Code plugin folder, declared under the `claude` key. Launch-only: never written to a file. */
|
|
219
|
+
export interface PluginDirectoryContribution {
|
|
220
|
+
/** Absolute path of the folder holding `.claude-plugin/plugin.json`. */
|
|
221
|
+
path: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
191
224
|
/**
|
|
192
225
|
* Declarative Agent Runtime contributions of one Capability for one runtime.
|
|
193
226
|
* The runtime's Runtime Surface reconciles these symmetrically: applied while
|
|
194
227
|
* the Capability is enabled, removed when it is not, idempotent across runs.
|
|
228
|
+
* `pluginDirectories` is the exception: gathered for the launch, written nowhere.
|
|
195
229
|
*/
|
|
196
230
|
export interface RuntimeContributions {
|
|
197
231
|
mcpServers?: McpServerDescriptor[];
|
|
@@ -201,6 +235,7 @@ export interface RuntimeContributions {
|
|
|
201
235
|
skillTrees?: SkillTreeContribution[];
|
|
202
236
|
pluginReferences?: PluginReferenceContribution[];
|
|
203
237
|
agentDefinitions?: AgentDefinitionContribution[];
|
|
238
|
+
pluginDirectories?: PluginDirectoryContribution[];
|
|
204
239
|
}
|
|
205
240
|
|
|
206
241
|
/**
|