@uniqbit/mate-core 0.15.5 → 0.16.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-plugin/.claude-plugin/plugin.json +2 -2
- package/claude-plugin/hooks/hooks.json +3 -6
- package/claude-plugin/hooks/session-guidance.mjs +8 -0
- package/claude-plugin/hooks/ts-loader.mjs +19 -0
- package/package.json +7 -5
- package/src/cli/commands/artifact/artifact.ts +19 -3
- package/src/cli/commands/artifact/finish/command.ts +183 -57
- package/src/cli/commands/artifact/finish/engine.ts +80 -91
- package/src/cli/commands/artifact/finish/finisher.ts +26 -33
- package/src/cli/commands/artifact/finish/git.ts +34 -23
- package/src/cli/commands/artifact/finish/index.ts +9 -3
- package/src/cli/commands/artifact/finish/openspec.ts +225 -97
- package/src/cli/commands/artifact/pending/command.ts +175 -0
- package/src/cli/commands/artifact/pending/discovery.ts +249 -0
- package/src/cli/commands/artifact/pending/index.ts +17 -0
- package/src/cli/commands/cap/index-cmd.ts +9 -1
- package/src/cli/commands/cap/index.ts +2 -6
- package/src/cli/commands/cap/tokensave.ts +4 -4
- package/src/cli/commands/companion/companion.ts +5 -1
- package/src/cli/commands/companion/link.ts +2 -2
- package/src/cli/commands/companion/sync.ts +92 -0
- package/src/cli/commands/doctor.ts +0 -3
- package/src/cli/commands/launch/shared.ts +23 -5
- package/src/cli/commands/report/collector.ts +72 -78
- package/src/cli/commands/report/contract.ts +40 -1
- package/src/cli/commands/report/highlight.ts +27 -0
- package/src/cli/commands/report/index.ts +11 -18
- package/src/cli/commands/report/renderer.ts +199 -2
- package/src/cli/commands/report/types.ts +26 -1
- package/src/cli/commands/shared/companion-selection.ts +107 -10
- package/src/cli/commands/studio/areas.ts +68 -0
- package/src/cli/commands/studio/index.ts +69 -0
- package/src/cli/commands/studio/inventory.ts +55 -0
- package/src/cli/commands/studio/mate-inventory.ts +43 -0
- package/src/cli/commands/studio/openspec-cli.ts +198 -0
- package/src/cli/commands/studio/payload.ts +184 -0
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +61 -0
- package/src/cli/commands/studio/server.ts +201 -0
- package/src/cli/commands/studio/snapshot.ts +63 -0
- package/src/cli/commands/studio/topology.ts +199 -0
- package/src/cli/commands/studio/views/client.ts +197 -0
- package/src/cli/commands/studio/views/companion-picker.tsx +91 -0
- package/src/cli/commands/studio/views/companion-selector.tsx +90 -0
- package/src/cli/commands/studio/views/dashboard/changes.tsx +107 -0
- package/src/cli/commands/studio/views/dashboard/index.tsx +19 -0
- package/src/cli/commands/studio/views/document.tsx +256 -0
- package/src/cli/commands/studio/views/error.tsx +20 -0
- package/src/cli/commands/studio/views/model.ts +34 -0
- package/src/cli/commands/studio/views/pairings.tsx +38 -0
- package/src/cli/commands/studio/views/skills/index.tsx +87 -0
- package/src/cli/commands/studio/views/specs/index.tsx +101 -0
- package/src/cli/commands/studio/views/styles.ts +389 -0
- package/src/cli/commands/studio/views/warnings.tsx +21 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +21 -0
- package/src/cli/commands/studio/views/workflow/steps.ts +329 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +190 -0
- package/src/cli/commands/unwrap.ts +70 -0
- package/src/cli/commands/wrap.ts +164 -0
- package/src/cli/main.ts +68 -19
- package/src/cli/parse-flags.ts +36 -11
- package/src/cli/usage.ts +12 -3
- package/src/framework.ts +1 -7
- package/src/hooks/session-banner.ts +64 -11
- package/src/hooks/session-guidance.ts +40 -0
- package/src/hooks/validate-artifact-path.ts +108 -35
- package/src/lib/fs-utils.ts +9 -0
- package/src/lib/install.ts +33 -0
- package/src/lib/orchestrator/adapters/base.ts +14 -125
- package/src/lib/orchestrator/adapters/claude.ts +0 -11
- package/src/lib/orchestrator/adapters/opencode.ts +2 -32
- package/src/lib/orchestrator/companion-git-sync.ts +94 -84
- package/src/lib/orchestrator/config-store.ts +2 -21
- package/src/lib/orchestrator/editor.ts +12 -22
- package/src/lib/orchestrator/framework-context.ts +17 -6
- package/src/lib/orchestrator/global-config-store.ts +1 -1
- package/src/lib/orchestrator/launcher.ts +97 -7
- package/src/lib/orchestrator/opencode-guidance.ts +4 -56
- package/src/lib/orchestrator/projection-claude-entry.ts +198 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +120 -0
- package/src/lib/orchestrator/projection-companion-link.ts +62 -0
- package/src/lib/orchestrator/projection-entries.ts +377 -0
- package/src/lib/orchestrator/projection-record.ts +56 -0
- package/src/lib/orchestrator/projection-runtime-documents.ts +424 -0
- package/src/lib/orchestrator/projection-types.ts +169 -0
- package/src/lib/orchestrator/repo-local-registry.ts +37 -133
- package/src/lib/orchestrator/repo-local-store.ts +96 -0
- package/src/lib/orchestrator/setup-compatibilities.ts +1 -9
- package/src/lib/orchestrator/types.ts +1 -0
- package/src/lib/orchestrator/working-repo-projection.ts +366 -0
- package/src/lib/orchestrator/workspace-inventory.ts +1 -1
- package/src/lib/package-paths.ts +11 -1
- package/src/lib/public-npm.ts +2 -1
- package/src/lib/update-checker.ts +15 -9
- package/src/opencode/companion-hooks.ts +89 -245
- package/src/opencode/companion-policy.ts +35 -10
- package/src/opencode/index.ts +1 -0
- package/src/opencode/projected-guidance.ts +56 -0
- package/src/opencode/tui.tsx +13 -4
- package/src/playbooks/companion-guidance.ts +32 -116
- package/src/plugins.ts +0 -1
- package/src/runtime/companion-git-state.ts +156 -0
- package/src/runtime/companion-git.ts +203 -0
- package/src/runtime/companion-guidance.ts +222 -0
- package/src/runtime/companion-sync.ts +298 -0
- package/src/runtime/env-names.ts +30 -0
- package/src/runtime/env.ts +67 -35
- package/src/runtime/framework.ts +10 -0
- package/src/runtime/freshness.ts +58 -0
- package/src/runtime/index.ts +104 -0
- package/src/runtime/install.ts +30 -0
- package/src/runtime/policy.ts +66 -0
- package/src/runtime/projected-guidance.ts +45 -0
- package/src/runtime/projection.ts +224 -0
- package/src/runtime/repo-local.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +56 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +48 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/tasks.md +22 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +31 -90
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/design.md +3 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/explore-brief.md +6 -6
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/tasks.md +3 -0
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +30 -0
- package/src/templates/capabilities/react-doctor/claude/hooks/react-doctor.sh +2 -2
- package/src/templates/mate-skills/agents/mate-artifact-publish/SKILL.md +185 -0
- package/src/templates/mate-skills/agents/mate-artifact-publish/references/openspec.md +227 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/SKILL.md +68 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +14 -0
- package/src/templates/mate-skills/agents/mate-grill-with-docs/SKILL.md +29 -0
- package/src/templates/mate-skills/agents/mate-grilling/SKILL.md +49 -0
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +156 -0
- package/src/templates/{capabilities/openspec-cap/mate-skills → mate-skills}/agents/mate-openspec-backfill/SKILL.md +4 -2
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +139 -0
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +503 -0
- package/src/templates/mate-skills/claude/mate-artifact-publish/SKILL.md +185 -0
- package/src/templates/mate-skills/claude/mate-artifact-publish/references/openspec.md +227 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/SKILL.md +68 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +14 -0
- package/src/templates/mate-skills/claude/mate-grill-with-docs/SKILL.md +29 -0
- package/src/templates/mate-skills/claude/mate-grilling/SKILL.md +49 -0
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +156 -0
- package/src/templates/mate-skills/claude/mate-openspec-backfill/SKILL.md +67 -0
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +139 -0
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +503 -0
- package/src/templates/report-assets/README.md +32 -0
- package/src/templates/report-assets/mermaid.LICENSE +21 -0
- package/src/templates/report-assets/mermaid.min.js +4376 -0
- package/src/templates/root/TEMPLATE_CLAUDE.md +1 -9
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +1476 -197
- package/src/tools/setup/capabilities/graphify.ts +16 -7
- package/src/tools/setup/capabilities/openspec.ts +63 -56
- package/src/tools/setup/capabilities/tokensave.ts +116 -2
- package/src/tools/setup/engine.ts +34 -6
- package/src/tools/setup/mate.ts +42 -13
- package/src/tools/setup/plugin.ts +9 -0
- package/src/tools/setup/plugins/guidance.ts +11 -1
- package/src/tools/setup/providers/claude-format.ts +49 -4
- package/src/tools/setup/providers/claude-plugin-hooks.ts +117 -0
- package/src/tools/setup/providers/claude.ts +55 -220
- package/src/tools/setup/providers/opencode.ts +41 -14
- package/src/tools/setup/runtime-documents.ts +174 -0
- package/src/tools/setup/surface-target.ts +50 -0
- package/src/tools/setup/working-repo-cleanup.ts +33 -26
- package/src/tools/setup/working-repo-local-state.ts +21 -1
- package/src/tools/setup.ts +25 -3
- package/wrappers/bin/graphify +57 -8
- package/wrappers/bin/openspec +50 -3
- package/claude-plugin/hooks/artifact-finish-nudge.mjs +0 -8
- package/src/cli/commands/cap/headroom.ts +0 -52
- package/src/cli/commands/workspace/list.ts +0 -25
- package/src/cli/commands/workspace/materialize.ts +0 -46
- package/src/cli/commands/workspace/workspace.ts +0 -22
- package/src/hooks/artifact-finish-nudge.ts +0 -244
- package/src/lib/orchestrator/headroom/proxy.ts +0 -116
- package/src/lib/orchestrator/workspace-materialize.ts +0 -80
- package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/SKILL.md +0 -51
- package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/references/openspec.md +0 -134
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/SKILL.md +0 -58
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/references/openspec.md +0 -139
- package/src/tools/setup/capabilities/headroom.ts +0 -57
- /package/src/cli/commands/{workspace → companion}/open.ts +0 -0
|
@@ -31,6 +31,9 @@ import {
|
|
|
31
31
|
|
|
32
32
|
// Storage contract: <companionPath>/.graphify/<repositoryId>/graphify-out/
|
|
33
33
|
export const GRAPHIFY_STORE_SEGMENT = ".graphify";
|
|
34
|
+
|
|
35
|
+
/** Skill tree the external `graphify install` CLI writes into a runtime directory. */
|
|
36
|
+
export const GRAPHIFY_SKILL_NAME = "graphify";
|
|
34
37
|
export const GRAPHIFY_OUTPUT_SUBDIR = "graphify-out";
|
|
35
38
|
|
|
36
39
|
const GRAPHIFY_INSTALL_CMD = `uv tool install graphifyy`;
|
|
@@ -271,9 +274,12 @@ export function createGraphifyPlugin(deps: GraphifyPluginDeps = {}): CapabilityP
|
|
|
271
274
|
// every setup/sync so it survives graphifyy regenerating the skill.
|
|
272
275
|
const patchSkillTree =
|
|
273
276
|
providerId === "claude" ? patchClaudeSkillTree : patchOpenCodeSkillTree;
|
|
274
|
-
await patchSkillTree(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
+
await patchSkillTree(
|
|
278
|
+
ctx.companionPath,
|
|
279
|
+
GRAPHIFY_SKILL_NAME,
|
|
280
|
+
rewriteGraphifySkillOutputPaths,
|
|
281
|
+
{ excludeFiles: GRAPHIFY_SKILL_REWRITE_EXCLUDE },
|
|
282
|
+
);
|
|
277
283
|
},
|
|
278
284
|
|
|
279
285
|
async teardown(ctx: SetupContext) {
|
|
@@ -282,10 +288,13 @@ export function createGraphifyPlugin(deps: GraphifyPluginDeps = {}): CapabilityP
|
|
|
282
288
|
// Remove the Graphify-managed skills directory (written by the
|
|
283
289
|
// external CLI, so removed here rather than via a declaration).
|
|
284
290
|
try {
|
|
285
|
-
await fs.rm(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
291
|
+
await fs.rm(
|
|
292
|
+
path.join(ctx.companionPath, providerDir, "skills", GRAPHIFY_SKILL_NAME),
|
|
293
|
+
{
|
|
294
|
+
recursive: true,
|
|
295
|
+
force: true,
|
|
296
|
+
},
|
|
297
|
+
);
|
|
289
298
|
} catch {
|
|
290
299
|
/* not present */
|
|
291
300
|
}
|
|
@@ -32,14 +32,17 @@ export const OPENSPEC_SKILLS = [
|
|
|
32
32
|
"openspec-archive-change",
|
|
33
33
|
] as const;
|
|
34
34
|
|
|
35
|
-
// Mate-authored skills (not generated by the openspec CLI)
|
|
36
|
-
//
|
|
37
|
-
const LEGACY_CLAUDE_STATE_PATTERN = /^mate-artifact-finish\..+\.json$/;
|
|
35
|
+
// Mate-authored skills (not generated by the openspec CLI) are sourced from the
|
|
36
|
+
// shared Mate skill tree and installed per active provider while OpenSpec is enabled.
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
/** Every Mate-owned schema is installed for availability; only one is ever activated as the companion default. */
|
|
39
|
+
const MATE_SCHEMA_NAMES = ["mate-v1", "mate-minimal"] as const;
|
|
40
|
+
|
|
41
|
+
const MATE_SCHEMA_SOURCE_ROOT = path.join(
|
|
40
42
|
import.meta.dirname,
|
|
41
|
-
"../../../templates/capabilities/openspec-cap
|
|
43
|
+
"../../../templates/capabilities/openspec-cap",
|
|
42
44
|
);
|
|
45
|
+
const MATE_CONVENTIONS_SOURCE = path.join(MATE_SCHEMA_SOURCE_ROOT, "openspec-conventions.yaml");
|
|
43
46
|
|
|
44
47
|
const OPENSPEC_TOOL_DIRS = {
|
|
45
48
|
claude: ".claude",
|
|
@@ -57,6 +60,7 @@ export interface OpenSpecPluginDeps {
|
|
|
57
60
|
isCommandOnPath?: typeof isCommandOnPath;
|
|
58
61
|
confirm?: typeof confirm;
|
|
59
62
|
pathValue?: () => string;
|
|
63
|
+
wrapperBinPath?: () => string;
|
|
60
64
|
getInstalledVersion?: (pathValue: string) => Promise<string | undefined>;
|
|
61
65
|
fetchLatestVersion?: () => Promise<string | undefined>;
|
|
62
66
|
}
|
|
@@ -95,8 +99,12 @@ function getOpenSpecConfigPath(companionPath: string): string {
|
|
|
95
99
|
return path.join(companionPath, "openspec", "config.yaml");
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
function
|
|
99
|
-
return path.join(companionPath, "openspec", "schemas",
|
|
102
|
+
function getMateSchemaDir(companionPath: string, schemaName: string): string {
|
|
103
|
+
return path.join(companionPath, "openspec", "schemas", schemaName);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getMateConventionsPath(companionPath: string): string {
|
|
107
|
+
return path.join(companionPath, "openspec", "mate-conventions.yaml");
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
export async function teardownOpenspecSkills(
|
|
@@ -154,21 +162,15 @@ async function reconcileOpenSpecTools(
|
|
|
154
162
|
|
|
155
163
|
// The openspec CLI generates its own workflow skills above; layer the
|
|
156
164
|
// mate-authored skills on top for the same active providers.
|
|
157
|
-
|
|
158
|
-
await applyMateOpenspecSkills(ctx.companionPath, tools);
|
|
159
|
-
} else {
|
|
160
|
-
for (const tool of tools) {
|
|
161
|
-
await teardownMateSkills(getSkillsDir(ctx.companionPath, tool), ctx.companionPath);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
165
|
+
await applyMateOpenspecSkills(ctx.companionPath, tools);
|
|
164
166
|
}
|
|
165
167
|
|
|
166
|
-
async function
|
|
168
|
+
async function updateOpenSpecIfOutdated(
|
|
167
169
|
ctx: SetupContext,
|
|
168
170
|
deps: Required<
|
|
169
171
|
Pick<
|
|
170
172
|
OpenSpecPluginDeps,
|
|
171
|
-
"installCommand" | "
|
|
173
|
+
"installCommand" | "pathValue" | "getInstalledVersion" | "fetchLatestVersion"
|
|
172
174
|
>
|
|
173
175
|
>,
|
|
174
176
|
): Promise<void> {
|
|
@@ -178,21 +180,9 @@ async function warnIfOpenSpecOutdated(
|
|
|
178
180
|
const latest = await deps.fetchLatestVersion();
|
|
179
181
|
if (!latest || !isNewer(latest, installed)) return;
|
|
180
182
|
|
|
181
|
-
if (ctx.mode !== "setup") {
|
|
182
|
-
process.stderr.write(
|
|
183
|
-
`openspec: installed version ${installed} is outdated (latest ${latest}). Run \`${OPENSPEC_INSTALL_COMMAND}\` to upgrade.\n`,
|
|
184
|
-
);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
183
|
process.stdout.write(
|
|
189
|
-
`openspec: installed version ${installed} is outdated (latest ${latest})
|
|
184
|
+
`openspec: installed version ${installed} is outdated (latest ${latest}). Updating now:\n ${OPENSPEC_INSTALL_COMMAND}\n`,
|
|
190
185
|
);
|
|
191
|
-
const ok = await deps.confirm("Upgrade openspec now?");
|
|
192
|
-
if (!ok) {
|
|
193
|
-
process.stderr.write("openspec: upgrade skipped - continuing with the installed version\n");
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
186
|
|
|
197
187
|
try {
|
|
198
188
|
await deps.installCommand("npm", ["install", "-g", "@fission-ai/openspec@latest"], {
|
|
@@ -218,7 +208,7 @@ async function ensureOpenSpecInstalled(
|
|
|
218
208
|
>,
|
|
219
209
|
): Promise<boolean> {
|
|
220
210
|
if (deps.isCommandOnPath("openspec", deps.pathValue())) {
|
|
221
|
-
await
|
|
211
|
+
await updateOpenSpecIfOutdated(ctx, deps);
|
|
222
212
|
return true;
|
|
223
213
|
}
|
|
224
214
|
|
|
@@ -266,11 +256,8 @@ async function ensureOpenSpecInstalled(
|
|
|
266
256
|
return true;
|
|
267
257
|
}
|
|
268
258
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
await fs.rm(schemaDir, { recursive: true, force: true });
|
|
272
|
-
await pruneEmptyAncestors(schemaDir, companionPath);
|
|
273
|
-
|
|
259
|
+
/** Only the config Mate itself wrote is removed; a user-edited config.yaml is left alone. */
|
|
260
|
+
async function removeManagedOpenSpecConfig(companionPath: string): Promise<void> {
|
|
274
261
|
const configPath = getOpenSpecConfigPath(companionPath);
|
|
275
262
|
try {
|
|
276
263
|
const content = await fs.readFile(configPath, "utf8");
|
|
@@ -283,13 +270,39 @@ async function teardownOpenSpecSchema(companionPath: string): Promise<void> {
|
|
|
283
270
|
}
|
|
284
271
|
}
|
|
285
272
|
|
|
273
|
+
async function teardownOpenSpecSchema(companionPath: string): Promise<void> {
|
|
274
|
+
for (const schemaName of MATE_SCHEMA_NAMES) {
|
|
275
|
+
const schemaDir = getMateSchemaDir(companionPath, schemaName);
|
|
276
|
+
await fs.rm(schemaDir, { recursive: true, force: true });
|
|
277
|
+
await pruneEmptyAncestors(schemaDir, companionPath);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await fs.rm(getMateConventionsPath(companionPath), { force: true });
|
|
281
|
+
await removeManagedOpenSpecConfig(companionPath);
|
|
282
|
+
}
|
|
283
|
+
|
|
286
284
|
async function reconcileOpenSpecSchema(ctx: SetupContext): Promise<void> {
|
|
285
|
+
await Promise.all(
|
|
286
|
+
MATE_SCHEMA_NAMES.map((schemaName) =>
|
|
287
|
+
mergeDir(
|
|
288
|
+
path.join(MATE_SCHEMA_SOURCE_ROOT, schemaName),
|
|
289
|
+
getMateSchemaDir(ctx.companionPath, schemaName),
|
|
290
|
+
),
|
|
291
|
+
),
|
|
292
|
+
);
|
|
293
|
+
await fs.rm(
|
|
294
|
+
path.join(getMateSchemaDir(ctx.companionPath, "mate-v1"), "templates", "explore-brief.md"),
|
|
295
|
+
{ force: true },
|
|
296
|
+
);
|
|
297
|
+
await fs.copyFile(MATE_CONVENTIONS_SOURCE, getMateConventionsPath(ctx.companionPath));
|
|
298
|
+
|
|
299
|
+
// Availability is unconditional so any change may select a Mate schema;
|
|
300
|
+
// activation stays opt-in through the companion-level setup selection.
|
|
287
301
|
if (getOpenSpecSchemaSelection(ctx.config.capabilities ?? []) !== "mate-v1") {
|
|
288
|
-
await
|
|
302
|
+
await removeManagedOpenSpecConfig(ctx.companionPath);
|
|
289
303
|
return;
|
|
290
304
|
}
|
|
291
305
|
|
|
292
|
-
await mergeDir(MATE_V1_SCHEMA_SOURCE, getMateV1SchemaDir(ctx.companionPath));
|
|
293
306
|
const configPath = getOpenSpecConfigPath(ctx.companionPath);
|
|
294
307
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
295
308
|
|
|
@@ -310,6 +323,7 @@ export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityP
|
|
|
310
323
|
isCommandOnPath: deps.isCommandOnPath ?? isCommandOnPath,
|
|
311
324
|
confirm: deps.confirm ?? confirm,
|
|
312
325
|
pathValue: deps.pathValue ?? (() => process.env.PATH ?? ""),
|
|
326
|
+
wrapperBinPath: deps.wrapperBinPath ?? getWrapperBinPath,
|
|
313
327
|
getInstalledVersion: deps.getInstalledVersion ?? defaultGetInstalledVersion,
|
|
314
328
|
fetchLatestVersion: deps.fetchLatestVersion ?? defaultFetchLatestVersion,
|
|
315
329
|
};
|
|
@@ -333,11 +347,18 @@ export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityP
|
|
|
333
347
|
"Skill(openspec-propose)",
|
|
334
348
|
"Skill(openspec-apply-change)",
|
|
335
349
|
"Skill(openspec-archive-change)",
|
|
336
|
-
"Skill(mate-artifact-
|
|
350
|
+
"Skill(mate-artifact-publish)",
|
|
337
351
|
"Skill(mate-openspec-backfill)",
|
|
352
|
+
"Skill(mate-interview-me)",
|
|
353
|
+
"Skill(mate-grill-me)",
|
|
354
|
+
"Skill(mate-grilling)",
|
|
355
|
+
"Skill(mate-grill-with-docs)",
|
|
356
|
+
"Skill(mate-domain-modeling)",
|
|
357
|
+
"Skill(mate-show-me)",
|
|
358
|
+
"Skill(mate-simplify-code)",
|
|
338
359
|
"Bash(openspec:*)",
|
|
339
360
|
`Bash(${FRAMEWORK_NAME} cap graphify:*)`,
|
|
340
|
-
`Bash(${path.join(
|
|
361
|
+
`Bash(${path.join(depsWithDefaults.wrapperBinPath(), "openspec")}:*)`,
|
|
341
362
|
],
|
|
342
363
|
},
|
|
343
364
|
};
|
|
@@ -375,23 +396,9 @@ export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityP
|
|
|
375
396
|
},
|
|
376
397
|
forProvider: {
|
|
377
398
|
claude: {
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
async apply(ctx: SetupContext) {
|
|
382
|
-
const stateDir = path.join(ctx.companionPath, ".claude", "state");
|
|
383
|
-
try {
|
|
384
|
-
const entries = await fs.readdir(stateDir);
|
|
385
|
-
await Promise.all(
|
|
386
|
-
entries
|
|
387
|
-
.filter((entry) => LEGACY_CLAUDE_STATE_PATTERN.test(entry))
|
|
388
|
-
.map((entry) => fs.unlink(path.join(stateDir, entry))),
|
|
389
|
-
);
|
|
390
|
-
await pruneEmptyAncestors(stateDir, ctx.companionPath);
|
|
391
|
-
} catch {
|
|
392
|
-
/* no legacy state */
|
|
393
|
-
}
|
|
394
|
-
},
|
|
399
|
+
// Publication is explicit (`mate-artifact-publish`), so openspec
|
|
400
|
+
// contributes no archive-triggered Claude hook and no hook state.
|
|
401
|
+
async apply() {},
|
|
395
402
|
async teardown(ctx: SetupContext) {
|
|
396
403
|
await teardownToolRuntime(ctx.companionPath, "claude");
|
|
397
404
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
@@ -10,7 +11,12 @@ export { TOKENSAVE_WORKING_REPO_EXCLUDE_ENTRIES } from "./tokensave-shared";
|
|
|
10
11
|
|
|
11
12
|
export const TOKENSAVE_SUPPORTED_AGENTS = new Set(["claude", "opencode"]);
|
|
12
13
|
export const TOKENSAVE_STORE_DIR = ".tokensave";
|
|
14
|
+
export const TOKENSAVE_STORE_CONFIG_FILE = "config.json";
|
|
13
15
|
export const TOKENSAVE_MIN_RUST_VERSION = "1.91.0";
|
|
16
|
+
/** Single-graph posture Mate owns in the per-project store config; every other key is left as found. */
|
|
17
|
+
const TOKENSAVE_OWNED_POSTURE = { auto_track: false, suppress_scope_warning: true } as const;
|
|
18
|
+
const TOKENSAVE_BRANCH_META_FILE = "branch-meta.json";
|
|
19
|
+
const TOKENSAVE_DISABLED_BRANCH_META_FILE = "branch-meta.json.mate-disabled";
|
|
14
20
|
const TOKENSAVE_STORE_EXCLUDE_ENTRY = `${TOKENSAVE_STORE_DIR}/`;
|
|
15
21
|
const TOKENSAVE_BREW_INSTALL_CMD = "brew install aovestdipaperino/tap/tokensave";
|
|
16
22
|
const TOKENSAVE_CARGO_INSTALL_CMD = "cargo install --locked tokensave";
|
|
@@ -101,6 +107,21 @@ async function tokensaveInstalled(repoPath: string): Promise<boolean> {
|
|
|
101
107
|
return result.ok;
|
|
102
108
|
}
|
|
103
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Upgrades the installed binary without making setup depend on network
|
|
112
|
+
* availability. `--kill` keeps the non-interactive setup path from hanging on
|
|
113
|
+
* a running Tokensave MCP process.
|
|
114
|
+
*/
|
|
115
|
+
async function upgradeTokensave(repoPath: string): Promise<void> {
|
|
116
|
+
const result = tokensaveDeps.run(["upgrade", "--kill"], repoPath);
|
|
117
|
+
if (result.ok) return;
|
|
118
|
+
|
|
119
|
+
const detail = result.stderr.trim();
|
|
120
|
+
process.stderr.write(
|
|
121
|
+
`tokensave: automatic upgrade failed${detail ? `: ${detail}` : ""} - continuing with the installed version\n`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
104
125
|
// Global agent integration (MCP entry, session hooks, wildcard permission grant, and
|
|
105
126
|
// tokensave's own config bookkeeping) is owned by tokensave's installer — Mate never
|
|
106
127
|
// hand-edits ~/.tokensave/config.toml. Runs in setup mode only; the installer is
|
|
@@ -169,8 +190,12 @@ function getTokensaveInstallPlan(): { command: string; run: () => Promise<void>
|
|
|
169
190
|
return undefined;
|
|
170
191
|
}
|
|
171
192
|
|
|
172
|
-
export async function ensureTokensaveInstalled(
|
|
193
|
+
export async function ensureTokensaveInstalled(
|
|
194
|
+
repoPath: string,
|
|
195
|
+
options: { upgrade?: boolean } = {},
|
|
196
|
+
): Promise<boolean> {
|
|
173
197
|
if (await tokensaveInstalled(repoPath)) {
|
|
198
|
+
if (options.upgrade) await upgradeTokensave(repoPath);
|
|
174
199
|
return true;
|
|
175
200
|
}
|
|
176
201
|
|
|
@@ -230,6 +255,90 @@ export async function ensureTokensaveStoreExcluded(repoPath: string): Promise<vo
|
|
|
230
255
|
await fs.writeFile(excludePath, lines.join("\n") + "\n", "utf8");
|
|
231
256
|
}
|
|
232
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Deactivates Tokensave's multi-branch mode without deleting branch databases.
|
|
260
|
+
* The metadata is preserved in a Mate-owned backup so the migration is reversible.
|
|
261
|
+
*/
|
|
262
|
+
async function deactivateTokensaveBranchMetadata(repoPath: string): Promise<void> {
|
|
263
|
+
const storePath = path.join(repoPath, TOKENSAVE_STORE_DIR);
|
|
264
|
+
const branchMetaPath = path.join(storePath, TOKENSAVE_BRANCH_META_FILE);
|
|
265
|
+
const disabledBranchMetaPath = path.join(storePath, TOKENSAVE_DISABLED_BRANCH_META_FILE);
|
|
266
|
+
|
|
267
|
+
let branchMeta: string;
|
|
268
|
+
try {
|
|
269
|
+
branchMeta = await fs.readFile(branchMetaPath, "utf8");
|
|
270
|
+
} catch {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let removeActiveMetadata = false;
|
|
275
|
+
try {
|
|
276
|
+
await fs.copyFile(branchMetaPath, disabledBranchMetaPath, fsConstants.COPYFILE_EXCL);
|
|
277
|
+
removeActiveMetadata = true;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
removeActiveMetadata = (await fs.readFile(disabledBranchMetaPath, "utf8")) === branchMeta;
|
|
285
|
+
} catch {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (removeActiveMetadata) {
|
|
291
|
+
await fs.unlink(branchMetaPath).catch(() => {});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Reconciles Mate's single-graph branching posture into the working repo's
|
|
297
|
+
* git-excluded `.tokensave/config.json` and deactivates existing branch
|
|
298
|
+
* metadata. Best-effort by design: the file is created by `tokensave init`,
|
|
299
|
+
* so an absent, unreadable, or unparseable config is left untouched rather
|
|
300
|
+
* than treated as an error. Existing branch databases are never removed.
|
|
301
|
+
*/
|
|
302
|
+
export async function ensureTokensaveBranchingPosture(repoPath: string): Promise<void> {
|
|
303
|
+
const configPath = path.join(repoPath, TOKENSAVE_STORE_DIR, TOKENSAVE_STORE_CONFIG_FILE);
|
|
304
|
+
|
|
305
|
+
let raw: string;
|
|
306
|
+
try {
|
|
307
|
+
raw = await fs.readFile(configPath, "utf8");
|
|
308
|
+
} catch {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let parsed: unknown;
|
|
313
|
+
try {
|
|
314
|
+
parsed = JSON.parse(raw);
|
|
315
|
+
} catch {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const next = JSON.stringify(
|
|
323
|
+
{ ...(parsed as Record<string, unknown>), ...TOKENSAVE_OWNED_POSTURE },
|
|
324
|
+
null,
|
|
325
|
+
2,
|
|
326
|
+
);
|
|
327
|
+
const serialized = `${next}\n`;
|
|
328
|
+
if (serialized === raw) {
|
|
329
|
+
await deactivateTokensaveBranchMetadata(repoPath);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
await fs.writeFile(configPath, serialized, "utf8");
|
|
335
|
+
} catch {
|
|
336
|
+
/** Best-effort: the store config is not Mate's to guarantee. */
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await deactivateTokensaveBranchMetadata(repoPath);
|
|
340
|
+
}
|
|
341
|
+
|
|
233
342
|
async function teardownDriver(repoPath: string, providers: string[]) {
|
|
234
343
|
const agents = providers.filter((p) => TOKENSAVE_SUPPORTED_AGENTS.has(p));
|
|
235
344
|
if (agents.length === 0) return;
|
|
@@ -325,9 +434,14 @@ export function createTokensavePlugin(): CapabilityPlugin {
|
|
|
325
434
|
if (ctx.activeProviders.filter((p) => TOKENSAVE_SUPPORTED_AGENTS.has(p)).length === 0) return;
|
|
326
435
|
|
|
327
436
|
const targetPath = ctx.repoPath ?? ctx.companionPath;
|
|
328
|
-
if (
|
|
437
|
+
if (
|
|
438
|
+
!(await ensureTokensaveInstalled(targetPath, {
|
|
439
|
+
upgrade: ctx.mode === "setup",
|
|
440
|
+
}))
|
|
441
|
+
) {
|
|
329
442
|
return;
|
|
330
443
|
}
|
|
444
|
+
await ensureTokensaveBranchingPosture(targetPath);
|
|
331
445
|
if (ctx.mode === "setup") {
|
|
332
446
|
installTokensaveAgentIntegrations(ctx.activeProviders, targetPath);
|
|
333
447
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// oxlint-disable no-await-in-loop
|
|
2
2
|
import { getActiveDistribution } from "../../distribution";
|
|
3
3
|
import { FRAMEWORK_NAME } from "../../framework";
|
|
4
|
+
import type { RenderedRuntimeDocument } from "../../lib/orchestrator/projection-types";
|
|
4
5
|
import type { FrameworkConfig } from "../../lib/orchestrator/types";
|
|
5
6
|
import { collectHostingProviders, ContextServiceMediator } from "./context-services";
|
|
6
7
|
import type {
|
|
@@ -12,6 +13,7 @@ import type {
|
|
|
12
13
|
} from "./plugin";
|
|
13
14
|
import { reconcileClaudeContributions } from "./providers/claude";
|
|
14
15
|
import { reconcileOpenCodeContributions } from "./providers/opencode";
|
|
16
|
+
import { renderDocumentsForTarget } from "./runtime-documents";
|
|
15
17
|
import { normalizeRegistration, type NormalizedRegistration } from "./registry";
|
|
16
18
|
|
|
17
19
|
export interface SetupInstallationPlanAction {
|
|
@@ -189,16 +191,14 @@ const RUNTIME_SURFACE_RECONCILERS: Record<
|
|
|
189
191
|
};
|
|
190
192
|
|
|
191
193
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* providers are always done first. Disabled capabilities participate with
|
|
195
|
-
* `enabled: false` and drive teardown of their managed entries.
|
|
194
|
+
* The declared Capability contributions of one pass, keyed by runtime. Shared
|
|
195
|
+
* by both targets: what a companion pass writes is what a working pass renders.
|
|
196
196
|
*/
|
|
197
|
-
async function
|
|
197
|
+
async function collectContributionInputs(
|
|
198
198
|
ctx: SetupContext,
|
|
199
199
|
plugins: Plugin[],
|
|
200
200
|
plan: SetupInstallationPlan,
|
|
201
|
-
): Promise<
|
|
201
|
+
): Promise<Map<string, CapabilityContributionInput[]>> {
|
|
202
202
|
// The plan already resolved enablement (policy, saved selection, package
|
|
203
203
|
// manager requirements); mirror it instead of recomputing.
|
|
204
204
|
const enabledByPluginId = new Map(
|
|
@@ -225,10 +225,38 @@ async function reconcileCapabilityContributions(
|
|
|
225
225
|
inputsByRuntime.set(runtimeId, inputs);
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
+
return inputsByRuntime;
|
|
229
|
+
}
|
|
228
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Reconcile declared Capability contributions through every active runtime's
|
|
233
|
+
* Runtime Surface (spec: plugin-engine). Runs after all plugin phases, so
|
|
234
|
+
* providers are always done first. Disabled capabilities participate with
|
|
235
|
+
* `enabled: false` and drive teardown of their managed entries.
|
|
236
|
+
*/
|
|
237
|
+
async function reconcileCapabilityContributions(
|
|
238
|
+
ctx: SetupContext,
|
|
239
|
+
plugins: Plugin[],
|
|
240
|
+
plan: SetupInstallationPlan,
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
const inputsByRuntime = await collectContributionInputs(ctx, plugins, plan);
|
|
229
243
|
for (const [runtimeId, inputs] of inputsByRuntime) {
|
|
230
244
|
const reconcile = RUNTIME_SURFACE_RECONCILERS[runtimeId];
|
|
231
245
|
if (!reconcile || inputs.length === 0) continue;
|
|
232
246
|
await reconcile(ctx, inputs);
|
|
233
247
|
}
|
|
234
248
|
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The same declarations, rendered rather than written: a working-target pass
|
|
252
|
+
* produces the documents and the Projection Root places them, so no Runtime
|
|
253
|
+
* Surface reaches inside a Working Repository.
|
|
254
|
+
*/
|
|
255
|
+
export async function renderRuntimeDocuments(
|
|
256
|
+
ctx: SetupContext,
|
|
257
|
+
registrations: PluginRegistration[],
|
|
258
|
+
plan: SetupInstallationPlan,
|
|
259
|
+
): Promise<RenderedRuntimeDocument[]> {
|
|
260
|
+
const plugins = registrations.map((entry) => normalizeRegistration(entry).plugin);
|
|
261
|
+
return renderDocumentsForTarget(ctx, await collectContributionInputs(ctx, plugins, plan));
|
|
262
|
+
}
|
package/src/tools/setup/mate.ts
CHANGED
|
@@ -3,18 +3,22 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { pruneEmptyAncestors } from "./utils";
|
|
5
5
|
|
|
6
|
-
export const MATE_ARTIFACT_SKILLS = ["mate-artifact-
|
|
6
|
+
export const MATE_ARTIFACT_SKILLS = ["mate-artifact-publish"] as const;
|
|
7
7
|
export const MATE_SKILLS = [
|
|
8
|
-
"mate-artifact-
|
|
8
|
+
"mate-artifact-publish",
|
|
9
9
|
"mate-create-report",
|
|
10
10
|
"mate-openspec-backfill",
|
|
11
|
+
"mate-interview-me",
|
|
12
|
+
"mate-grill-me",
|
|
13
|
+
"mate-grilling",
|
|
14
|
+
"mate-grill-with-docs",
|
|
15
|
+
"mate-domain-modeling",
|
|
16
|
+
"mate-show-me",
|
|
17
|
+
"mate-simplify-code",
|
|
11
18
|
] as const;
|
|
12
|
-
const LEGACY_MATE_SKILLS = ["mate-openspec-artifact-finish"] as const;
|
|
19
|
+
const LEGACY_MATE_SKILLS = ["mate-artifact-finish", "mate-openspec-artifact-finish"] as const;
|
|
13
20
|
|
|
14
|
-
const MATE_SKILLS_SOURCE = path.join(
|
|
15
|
-
import.meta.dirname,
|
|
16
|
-
"../../templates/capabilities/openspec-cap/mate-skills",
|
|
17
|
-
);
|
|
21
|
+
const MATE_SKILLS_SOURCE = path.join(import.meta.dirname, "../../templates/mate-skills");
|
|
18
22
|
|
|
19
23
|
const MATE_CREATE_REPORT_SKILL = `---
|
|
20
24
|
name: mate-create-report
|
|
@@ -55,7 +59,27 @@ Write a JSON document with this shape:
|
|
|
55
59
|
}
|
|
56
60
|
\`\`\`
|
|
57
61
|
|
|
58
|
-
Supported section types are \`metadata\`, \`metrics\`, \`key-value\`, \`table\`, \`statuses\`, and \`
|
|
62
|
+
Supported section types are \`metadata\`, \`metrics\`, \`key-value\`, \`table\`, \`statuses\`, \`text\`, \`diagram\`, and \`diff\`. Use only strings, numbers, booleans, and null values. Section IDs must be unique. Validate required fields before invoking the CLI.
|
|
63
|
+
|
|
64
|
+
## Visual Sections
|
|
65
|
+
|
|
66
|
+
A \`diagram\` section carries exactly one payload. \`mermaid\` holds diagram source the report draws as a picture; \`text\` holds a monospace ASCII sketch or pseudocode block, kept with its line breaks and indentation. Supplying both payloads or neither fails validation.
|
|
67
|
+
|
|
68
|
+
\`\`\`json
|
|
69
|
+
{ "id": "structure", "title": "Structure", "type": "diagram", "mermaid": "classDiagram\\n Report --> Renderer" }
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
\`\`\`json
|
|
73
|
+
{ "id": "sketch", "title": "Sketch", "type": "diagram", "text": "root\\n child" }
|
|
74
|
+
\`\`\`
|
|
75
|
+
|
|
76
|
+
A \`diff\` section carries a unified diff patch as produced by \`git diff\`. The report groups it by file, marks added, removed, and context lines, and keeps those markings legible in print. An empty patch renders an explicit no-data message.
|
|
77
|
+
|
|
78
|
+
\`\`\`json
|
|
79
|
+
{ "id": "changes", "title": "Changes", "type": "diff", "patch": "diff --git a/src/acme.ts b/src/acme.ts\\n..." }
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
A report carrying a mermaid payload inlines the diagram runtime; a report without one does not. Either way the page stays a single self-contained file that makes no network request.
|
|
59
83
|
|
|
60
84
|
## Invocation
|
|
61
85
|
|
|
@@ -87,10 +111,8 @@ const DEFAULT_MATE_SKILLS_BUCKET = "agents";
|
|
|
87
111
|
/**
|
|
88
112
|
* A skill's source lives under a provider bucket so its folder name always
|
|
89
113
|
* matches its `name:` frontmatter: `<tool>/<skill>/` when that tool needs its
|
|
90
|
-
* own behavior
|
|
91
|
-
*
|
|
92
|
-
* `agents/<skill>/` — the shared default every other tool (e.g. opencode,
|
|
93
|
-
* which pushes automatically) uses.
|
|
114
|
+
* own behavior, falling back to `agents/<skill>/` — the shared default every
|
|
115
|
+
* other tool uses.
|
|
94
116
|
*/
|
|
95
117
|
async function resolveMateSkillSource(skill: string, tool: string): Promise<string> {
|
|
96
118
|
const providerDir = path.join(MATE_SKILLS_SOURCE, tool, skill);
|
|
@@ -98,8 +120,15 @@ async function resolveMateSkillSource(skill: string, tool: string): Promise<stri
|
|
|
98
120
|
await fs.access(providerDir);
|
|
99
121
|
return providerDir;
|
|
100
122
|
} catch {
|
|
101
|
-
|
|
123
|
+
const sharedDir = path.join(MATE_SKILLS_SOURCE, DEFAULT_MATE_SKILLS_BUCKET, skill);
|
|
124
|
+
try {
|
|
125
|
+
await fs.access(sharedDir);
|
|
126
|
+
return sharedDir;
|
|
127
|
+
} catch {
|
|
128
|
+
/* source is missing */
|
|
129
|
+
}
|
|
102
130
|
}
|
|
131
|
+
throw new Error(`Mate skill source not found: ${skill}`);
|
|
103
132
|
}
|
|
104
133
|
|
|
105
134
|
export async function applyMateSkills(skillsDir: string, tool: string): Promise<void> {
|
|
@@ -4,6 +4,13 @@ import type { ClaudeHookGroup } from "./providers/claude-format";
|
|
|
4
4
|
|
|
5
5
|
export type SetupScope = "companion" | "hub";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Where a reconcile pass lands its documents. `companion` writes the material's
|
|
9
|
+
* own repository; `working` renders the documents an Agent Runtime discovers by
|
|
10
|
+
* its own upward walk and leaves the placing to the Projection Root.
|
|
11
|
+
*/
|
|
12
|
+
export type ProjectionTarget = "companion" | "working";
|
|
13
|
+
|
|
7
14
|
export interface SetupContext {
|
|
8
15
|
companionPath: string;
|
|
9
16
|
config: FrameworkConfig;
|
|
@@ -13,6 +20,8 @@ export interface SetupContext {
|
|
|
13
20
|
scope?: SetupScope;
|
|
14
21
|
/** Working repository path when syncing from a linked repo (e.g. during launch). */
|
|
15
22
|
repoPath?: string;
|
|
23
|
+
/** Defaults to `companion`, so a caller that names no target is unchanged. */
|
|
24
|
+
target?: ProjectionTarget;
|
|
16
25
|
/**
|
|
17
26
|
* Provider-mediated context services. Always populated when the engine
|
|
18
27
|
* executes a plugin; optional so hand-built contexts (tests, direct calls)
|
|
@@ -104,8 +104,18 @@ export async function refreshFromTemplate(
|
|
|
104
104
|
|
|
105
105
|
remaining = removeGraphifySection(remaining).trimEnd();
|
|
106
106
|
|
|
107
|
+
const template = templateContent.trimEnd();
|
|
108
|
+
/**
|
|
109
|
+
* A template carrying no managed markers cannot be stripped by marker above,
|
|
110
|
+
* so without this it would be prepended again on every refresh. Consuming
|
|
111
|
+
* every leading copy also heals a file an earlier refresh already grew.
|
|
112
|
+
*/
|
|
113
|
+
while (template && remaining.startsWith(template)) {
|
|
114
|
+
remaining = remaining.slice(template.length).trimStart();
|
|
115
|
+
}
|
|
116
|
+
|
|
107
117
|
const sections = [
|
|
108
|
-
|
|
118
|
+
template,
|
|
109
119
|
...(normalizedOptions.appendSections ?? []).map((section) => section.trim()).filter(Boolean),
|
|
110
120
|
];
|
|
111
121
|
if (remaining) {
|