@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
|
@@ -4,10 +4,16 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import { resolveForCapability } from "../../../lib/orchestrator/framework-context";
|
|
6
6
|
import type { CapabilityConfig } from "../../../lib/orchestrator/types";
|
|
7
|
+
import { resolveSecretPolicy } from "../../../runtime/policy";
|
|
7
8
|
import {
|
|
8
9
|
GRAPHIFY_OUTPUT_SUBDIR,
|
|
9
10
|
GRAPHIFY_STORE_SEGMENT,
|
|
10
11
|
} from "../../../tools/setup/capabilities/graphify";
|
|
12
|
+
import {
|
|
13
|
+
ensureGraphifySecretExclusion,
|
|
14
|
+
graphifySecretExcludeArgs,
|
|
15
|
+
pruneGraphifySecrets,
|
|
16
|
+
} from "../../../tools/setup/capabilities/graphify-secrets";
|
|
11
17
|
import { ensureUnambiguousCompanion } from "../shared/companion-selection";
|
|
12
18
|
|
|
13
19
|
export interface GraphifyPaths {
|
|
@@ -17,6 +23,23 @@ export interface GraphifyPaths {
|
|
|
17
23
|
|
|
18
24
|
export interface GraphifyCapDeps {
|
|
19
25
|
ensureUnambiguousCompanion?: (cwd: string) => Promise<boolean>;
|
|
26
|
+
resolveForCapability?: typeof resolveForCapability;
|
|
27
|
+
spawn?: typeof spawnSync;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Subcommands that read the source tree into the graph; `update` also runs as `<path> --update`. */
|
|
31
|
+
const GRAPHIFY_EXTRACTION_COMMANDS = new Set(["extract", "update", "watch"]);
|
|
32
|
+
|
|
33
|
+
export function isGraphifyExtraction(commandArgs: string[]): boolean {
|
|
34
|
+
return GRAPHIFY_EXTRACTION_COMMANDS.has(commandArgs[0] ?? "") || commandArgs.includes("--update");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `extract` and `<path> --update` take `--exclude`; the `update` subcommand rejects it. */
|
|
38
|
+
function acceptsExcludeArgs(commandArgs: string[]): boolean {
|
|
39
|
+
return (
|
|
40
|
+
commandArgs[0] === "extract" ||
|
|
41
|
+
(commandArgs[0] !== "update" && commandArgs.includes("--update"))
|
|
42
|
+
);
|
|
20
43
|
}
|
|
21
44
|
|
|
22
45
|
const GRAPHIFY_PATH_COMMANDS = new Set([
|
|
@@ -142,8 +165,11 @@ export async function runGraphifyCapCommand(
|
|
|
142
165
|
return;
|
|
143
166
|
}
|
|
144
167
|
|
|
145
|
-
const
|
|
146
|
-
|
|
168
|
+
const resolve = deps.resolveForCapability ?? resolveForCapability;
|
|
169
|
+
const spawn = deps.spawn ?? spawnSync;
|
|
170
|
+
const { companionPath, repositoryId, repository, configStore, workingRepoStore } = await resolve(
|
|
171
|
+
process.cwd(),
|
|
172
|
+
);
|
|
147
173
|
const config = await configStore.load();
|
|
148
174
|
|
|
149
175
|
const capability = (config.capabilities ?? []).find(
|
|
@@ -166,11 +192,28 @@ export async function runGraphifyCapCommand(
|
|
|
166
192
|
const paths = deriveGraphifyPaths(companionPath, repositoryId);
|
|
167
193
|
const commandArgs = deriveGraphifyCommandArgs(args, repo.path, paths);
|
|
168
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Every extraction Mate runs leaves secrets out without stopping: the
|
|
197
|
+
* `.graphifyignore` region is rewritten, `--exclude` is passed where graphify
|
|
198
|
+
* takes it, and afterwards any secret that still reached the output is
|
|
199
|
+
* pruned from it. A step that can't run is a warning, never a refusal.
|
|
200
|
+
*/
|
|
201
|
+
const extraction = isGraphifyExtraction(commandArgs);
|
|
202
|
+
const secrets = resolveSecretPolicy(companionPath);
|
|
203
|
+
if (extraction) {
|
|
204
|
+
try {
|
|
205
|
+
await ensureGraphifySecretExclusion(repo.path, secrets);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
process.stderr.write(`mate: graphify: warning: ${(error as Error).message}\n`);
|
|
208
|
+
}
|
|
209
|
+
if (acceptsExcludeArgs(commandArgs)) commandArgs.push(...graphifySecretExcludeArgs(secrets));
|
|
210
|
+
}
|
|
211
|
+
|
|
169
212
|
if (commandArgs[0] === "extract") {
|
|
170
213
|
await fs.mkdir(paths.graphifyOut, { recursive: true });
|
|
171
214
|
}
|
|
172
215
|
|
|
173
|
-
const result =
|
|
216
|
+
const result = spawn("graphify", commandArgs, {
|
|
174
217
|
stdio: "inherit",
|
|
175
218
|
env: {
|
|
176
219
|
...process.env,
|
|
@@ -186,4 +229,10 @@ export async function runGraphifyCapCommand(
|
|
|
186
229
|
if (result.status !== null) {
|
|
187
230
|
process.exitCode = result.status;
|
|
188
231
|
}
|
|
232
|
+
|
|
233
|
+
if (extraction && commandArgs[0] !== "watch") {
|
|
234
|
+
for (const note of await pruneGraphifySecrets(paths.graphifyOut, secrets)) {
|
|
235
|
+
process.stderr.write(`mate: graphify: ${note}\n`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
189
238
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
hasTokensaveCapability,
|
|
4
4
|
} from "../../../lib/orchestrator/capabilities";
|
|
5
5
|
import { resolveForCapability } from "../../../lib/orchestrator/framework-context";
|
|
6
|
+
import { resolveSecretPolicy } from "../../../runtime/policy";
|
|
6
7
|
import type { CapabilityConfig } from "../../../lib/orchestrator/types";
|
|
7
8
|
import {
|
|
8
9
|
ensureTokensaveBranchingPosture,
|
|
@@ -29,6 +30,12 @@ export interface SyncCapDeps {
|
|
|
29
30
|
onStepDone?: (step: SyncCapStep, ok: boolean) => void;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/** The companion's live secret policy decides the secret-file excludes. */
|
|
34
|
+
async function defaultEnsureBranchingPosture(repoPath: string): Promise<void> {
|
|
35
|
+
const { companionPath } = await resolveForCapability(process.cwd());
|
|
36
|
+
await ensureTokensaveBranchingPosture(repoPath, resolveSecretPolicy(companionPath));
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
async function defaultLoadCapabilities(): Promise<CapabilityConfig[]> {
|
|
33
40
|
const { configStore } = await resolveForCapability(process.cwd());
|
|
34
41
|
const config = await configStore.load();
|
|
@@ -116,7 +123,7 @@ export async function runIndexCapCommand(args: string[], deps: SyncCapDeps = {})
|
|
|
116
123
|
const isTokensaveInitialized = deps.isTokensaveInitialized ?? defaultIsTokensaveInitialized;
|
|
117
124
|
const ensureStoreExcluded = deps.ensureTokensaveStoreExcluded ?? ensureTokensaveStoreExcluded;
|
|
118
125
|
const ensureBranchingPosture =
|
|
119
|
-
deps.ensureTokensaveBranchingPosture ??
|
|
126
|
+
deps.ensureTokensaveBranchingPosture ?? defaultEnsureBranchingPosture;
|
|
120
127
|
const runGraphify = deps.runGraphify ?? runGraphifyCapCommand;
|
|
121
128
|
const runTokensave = deps.runTokensave ?? runTokensaveCapCommand;
|
|
122
129
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { readFileSync } from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
|
|
@@ -6,11 +5,13 @@ import { parse } from "yaml";
|
|
|
6
5
|
|
|
7
6
|
import { resolveForCapability } from "../../../lib/orchestrator/framework-context";
|
|
8
7
|
import { getEffectiveCliTools } from "../../../lib/orchestrator/setup-compatibilities";
|
|
8
|
+
import { runOpenSpec, type RunOpenSpec } from "../../../lib/openspec-cli";
|
|
9
9
|
import type { CapabilityConfig } from "../../../lib/orchestrator/types";
|
|
10
10
|
import { ensureUnambiguousCompanion } from "../shared/companion-selection";
|
|
11
11
|
|
|
12
12
|
export interface OpenspecCapDeps {
|
|
13
13
|
ensureUnambiguousCompanion?: (cwd: string) => Promise<boolean>;
|
|
14
|
+
runOpenSpec?: RunOpenSpec;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function readOpenSpecProjectSchema(companionPath: string): string | undefined {
|
|
@@ -79,33 +80,20 @@ export async function runOpenspecCapCommand(
|
|
|
79
80
|
readOpenSpecProjectSchema(companionPath),
|
|
80
81
|
);
|
|
81
82
|
|
|
83
|
+
const run = deps.runOpenSpec ?? runOpenSpec;
|
|
84
|
+
const options = { cwd, stdio: "inherit" } as const;
|
|
85
|
+
|
|
82
86
|
if (forwardedArgs[0] === "init" || forwardedArgs[0] === "update") {
|
|
83
|
-
const reset =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
process.stderr.write(`Failed to reset openspec config: ${reset.error.message}\n`);
|
|
89
|
-
process.exitCode = 1;
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (reset.status !== 0) {
|
|
93
|
-
process.exitCode = reset.status ?? 1;
|
|
87
|
+
const reset = await run(["config", "reset", "--all", "-y"], options);
|
|
88
|
+
if (reset.code !== 0) {
|
|
89
|
+
// Inherited output already reached the terminal; only Mate's own message is returned.
|
|
90
|
+
if (reset.stderr) process.stderr.write(`${reset.stderr}\n`);
|
|
91
|
+
process.exitCode = reset.code ?? 1;
|
|
94
92
|
return;
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
const result =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
});
|
|
102
|
-
if (result.error) {
|
|
103
|
-
process.stderr.write(`Failed to run openspec: ${result.error.message}\n`);
|
|
104
|
-
process.exitCode = 1;
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (result.status !== null) {
|
|
109
|
-
process.exitCode = result.status;
|
|
110
|
-
}
|
|
96
|
+
const result = await run(forwardedArgs, options);
|
|
97
|
+
if (result.stderr) process.stderr.write(`${result.stderr}\n`);
|
|
98
|
+
if (result.code !== null) process.exitCode = result.code;
|
|
111
99
|
}
|
|
@@ -1,55 +1,72 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CompanionResolver,
|
|
5
|
+
type CompanionMatch,
|
|
6
|
+
findRepoLocalRegistryFile,
|
|
7
|
+
} from "../../../lib/orchestrator/repository-link";
|
|
8
|
+
import { GlobalConfigStore } from "../../../lib/orchestrator/global-config-store";
|
|
3
9
|
import { injectEditorFolder, getPreferredEditorCli } from "../../../lib/orchestrator/editor";
|
|
4
|
-
import {
|
|
10
|
+
import { WorkingRepoRequiredError } from "../../../lib/orchestrator/types";
|
|
11
|
+
import { resolveProjection } from "../../../runtime/projection";
|
|
5
12
|
|
|
6
13
|
export const workspaceOpenCommandDeps = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
cwd: () => process.cwd(),
|
|
15
|
+
/**
|
|
16
|
+
* The repo-local registry root, or the resolved cwd when there is none: the
|
|
17
|
+
* global scan only matches a Working Repository on exact path equality.
|
|
18
|
+
*/
|
|
19
|
+
findRepoRoot: async (cwd: string): Promise<string> =>
|
|
20
|
+
(await findRepoLocalRegistryFile(cwd))?.repoRoot ?? path.resolve(cwd),
|
|
21
|
+
/**
|
|
22
|
+
* Every Linked Companion. Not `launchAmbiguityDeps.resolveLinkedCompanions`:
|
|
23
|
+
* `logFailures` warns about each stale pointer the resolver drops, which
|
|
24
|
+
* `wrap` and `session prepare` must not print.
|
|
25
|
+
*/
|
|
26
|
+
resolveCompanions: async (cwd: string): Promise<CompanionMatch[]> => {
|
|
27
|
+
const { match, ambiguousMatches } = await new CompanionResolver(
|
|
28
|
+
new GlobalConfigStore(),
|
|
29
|
+
).resolveWithDiagnostics(cwd, { logFailures: true });
|
|
30
|
+
if (ambiguousMatches.length > 0) return ambiguousMatches;
|
|
31
|
+
return match ? [match] : [];
|
|
32
|
+
},
|
|
33
|
+
resolveProjection,
|
|
13
34
|
getPreferredEditorCli: () => getPreferredEditorCli(),
|
|
14
35
|
injectEditorFolder,
|
|
15
36
|
};
|
|
16
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The projected companion first when it still links the repository, then the
|
|
40
|
+
* rest by resolved path, so repeated runs write an identical workspace file.
|
|
41
|
+
*/
|
|
42
|
+
function orderCompanionPaths(cwd: string, matches: CompanionMatch[]): string[] {
|
|
43
|
+
const linked = [...new Set(matches.map((match) => path.resolve(match.companionPath)))].toSorted();
|
|
44
|
+
const projectedPath = workspaceOpenCommandDeps.resolveProjection(cwd)?.companionPath;
|
|
45
|
+
const projected = projectedPath ? path.resolve(projectedPath) : null;
|
|
46
|
+
if (!projected || !linked.includes(projected)) return linked;
|
|
47
|
+
return [projected, ...linked.filter((companionPath) => companionPath !== projected)];
|
|
48
|
+
}
|
|
49
|
+
|
|
17
50
|
/**
|
|
18
51
|
* @command mate companion open
|
|
19
|
-
* @description Updates the managed `.mate/workspace.code-workspace` file
|
|
20
|
-
* the active working repository and
|
|
21
|
-
* current editor window via `--add`.
|
|
22
|
-
* @remarks
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
52
|
+
* @description Updates the managed `.mate/workspace.code-workspace` file with
|
|
53
|
+
* the active working repository and every Linked Companion, then adds those
|
|
54
|
+
* folders to the current editor window via `--add`.
|
|
55
|
+
* @remarks Never selects a companion: no picker, no `MATE_ARTIFACT_PATH`
|
|
56
|
+
* narrowing, and the companion recorded at the Projection Root is left as it
|
|
57
|
+
* was. Stale companion pointers are skipped with a stderr warning. Throws
|
|
58
|
+
* {@link WorkingRepoRequiredError} when no existing Linked Companion remains;
|
|
59
|
+
* sets a non-zero exit code if the editor CLI is unavailable.
|
|
26
60
|
*/
|
|
27
61
|
export async function runWorkspaceOpenCommand(): Promise<void> {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const {
|
|
35
|
-
companionPath,
|
|
36
|
-
repositoryId,
|
|
37
|
-
repository: localRepository,
|
|
38
|
-
workingRepoStore,
|
|
39
|
-
} = await workspaceOpenCommandDeps.resolveLaunchContext();
|
|
40
|
-
const repository =
|
|
41
|
-
localRepository ??
|
|
42
|
-
(await workspaceOpenCommandDeps
|
|
43
|
-
.createStore(companionPath, workingRepoStore)
|
|
44
|
-
.getRepository(repositoryId));
|
|
45
|
-
|
|
46
|
-
if (!repository) {
|
|
47
|
-
throw new Error(`Linked repository not found: ${repositoryId}`);
|
|
48
|
-
}
|
|
62
|
+
const cwd = workspaceOpenCommandDeps.cwd();
|
|
63
|
+
const matches = await workspaceOpenCommandDeps.resolveCompanions(cwd);
|
|
64
|
+
if (matches.length === 0) throw new WorkingRepoRequiredError("companion open");
|
|
49
65
|
|
|
66
|
+
const repoPath = await workspaceOpenCommandDeps.findRepoRoot(cwd);
|
|
50
67
|
const injected = await workspaceOpenCommandDeps.injectEditorFolder(
|
|
51
|
-
|
|
52
|
-
|
|
68
|
+
orderCompanionPaths(cwd, matches),
|
|
69
|
+
repoPath,
|
|
53
70
|
workspaceOpenCommandDeps.getPreferredEditorCli(),
|
|
54
71
|
);
|
|
55
72
|
|
|
@@ -4,6 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import { FRAMEWORK_NAME } from "../../../framework";
|
|
6
6
|
import { ConfigStore } from "../../../lib/orchestrator/config-store";
|
|
7
|
+
import {
|
|
8
|
+
refreshWorkingClaudeLinks,
|
|
9
|
+
type LinkRefreshResult,
|
|
10
|
+
} from "../../../lib/orchestrator/refresh-working-claude-links";
|
|
7
11
|
import type { FrameworkConfig } from "../../../lib/orchestrator/types";
|
|
8
12
|
import { YamlFileStore } from "../../../lib/orchestrator/yaml-file-store";
|
|
9
13
|
import {
|
|
@@ -13,12 +17,13 @@ import {
|
|
|
13
17
|
isNewer,
|
|
14
18
|
} from "../../../lib/update-checker";
|
|
15
19
|
import { resolveCompanionRuntime } from "../../../runtime/env";
|
|
16
|
-
import { syncCompanionFiles } from "../../../tools/setup";
|
|
17
|
-
import {
|
|
20
|
+
import { syncCompanionFiles, type CompanionSyncOptions } from "../../../tools/setup";
|
|
21
|
+
import type { ManagedOutputOwner, ManagedOutputReport } from "../../../tools/setup/plugin";
|
|
18
22
|
import { runCommand } from "../../../tools/setup/utils";
|
|
19
23
|
import { confirm } from "../../confirm";
|
|
20
24
|
import { defaultGitOps, type GitOps } from "../artifact/finish/git";
|
|
21
25
|
import { resolveCompanionPath } from "./tui";
|
|
26
|
+
import { errorMessage } from "../../../runtime/error-message";
|
|
22
27
|
|
|
23
28
|
/** Snapshot marker for a changed path that no longer exists on disk. */
|
|
24
29
|
export const DELETED = "<deleted>";
|
|
@@ -26,27 +31,17 @@ export const DELETED = "<deleted>";
|
|
|
26
31
|
const SKILLS_LOCKFILE = "skills-lock.json";
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
34
|
+
* The side the sync reported owning `changedPath`: the path itself or an ancestor
|
|
35
|
+
* was reported. A collapsed `dir/` that only contains a reported entry stays
|
|
36
|
+
* unowned, since porcelain hides what else changed inside it.
|
|
31
37
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export function isOpenSpecGenerated(relativePath: string): boolean {
|
|
40
|
-
return OPENSPEC_GENERATED.some((pattern) => pattern.test(relativePath));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Mate-owned skills: `applyMateSkills` overwrites them wholesale, so a local edit there never survives a sync. */
|
|
44
|
-
const MATE_MANAGED_SKILL = new RegExp(
|
|
45
|
-
`^\\.(claude|agents|opencode)/skills/(${MANAGED_MATE_SKILL_NAMES.join("|")})(/|$)`,
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
export function isMateManagedSkill(relativePath: string): boolean {
|
|
49
|
-
return MATE_MANAGED_SKILL.test(relativePath);
|
|
38
|
+
export function ownerOf(
|
|
39
|
+
changedPath: string,
|
|
40
|
+
report: ManagedOutputReport,
|
|
41
|
+
): ManagedOutputOwner | undefined {
|
|
42
|
+
const target = changedPath.replace(/\/+$/, "");
|
|
43
|
+
return report.find((entry) => target === entry.path || target.startsWith(`${entry.path}/`))
|
|
44
|
+
?.owner;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
function frameworkConfigPath(companionPath: string): string {
|
|
@@ -96,9 +91,18 @@ export interface CompanionUpdateDeps {
|
|
|
96
91
|
getCurrentVersion: () => string;
|
|
97
92
|
fetchLatestVersion: () => Promise<string>;
|
|
98
93
|
loadConfig: (companionPath: string) => Promise<FrameworkConfig>;
|
|
99
|
-
syncCompanionFiles: (
|
|
94
|
+
syncCompanionFiles: (
|
|
95
|
+
companionPath: string,
|
|
96
|
+
config: FrameworkConfig,
|
|
97
|
+
options: CompanionSyncOptions,
|
|
98
|
+
) => Promise<ManagedOutputReport>;
|
|
100
99
|
/** Returns true when the file was rewritten. */
|
|
101
100
|
writeEngineFloor: (companionPath: string, range: string) => Promise<boolean>;
|
|
101
|
+
/** Re-links the companion's Claude skills and commands into its wrapped Working Repositories. */
|
|
102
|
+
refreshWorkingClaudeLinks: (
|
|
103
|
+
companionPath: string,
|
|
104
|
+
config: FrameworkConfig,
|
|
105
|
+
) => Promise<LinkRefreshResult[]>;
|
|
102
106
|
hasSkillsLock: (companionPath: string) => Promise<boolean>;
|
|
103
107
|
runSkillsUpdate: (companionPath: string) => Promise<void>;
|
|
104
108
|
createGitOps: (companionPath: string) => GitOps;
|
|
@@ -114,7 +118,10 @@ export const companionUpdateDeps: CompanionUpdateDeps = {
|
|
|
114
118
|
getCurrentVersion,
|
|
115
119
|
fetchLatestVersion,
|
|
116
120
|
loadConfig: (companionPath) => new ConfigStore(frameworkConfigPath(companionPath)).load(),
|
|
117
|
-
|
|
121
|
+
/** The Claude plugin folders it gathers are for a launch; an update ignores them. */
|
|
122
|
+
async syncCompanionFiles(companionPath, config, options) {
|
|
123
|
+
return (await syncCompanionFiles(companionPath, config, undefined, options)).managedOutput;
|
|
124
|
+
},
|
|
118
125
|
async writeEngineFloor(companionPath, range) {
|
|
119
126
|
const store = new RawFrameworkConfigStore(frameworkConfigPath(companionPath));
|
|
120
127
|
const raw = (await store.load()) ?? {};
|
|
@@ -123,6 +130,7 @@ export const companionUpdateDeps: CompanionUpdateDeps = {
|
|
|
123
130
|
await store.save({ ...raw, engines: { ...engines, [FRAMEWORK_NAME]: range } });
|
|
124
131
|
return true;
|
|
125
132
|
},
|
|
133
|
+
refreshWorkingClaudeLinks,
|
|
126
134
|
hasSkillsLock: (companionPath) =>
|
|
127
135
|
fs
|
|
128
136
|
.access(path.join(companionPath, SKILLS_LOCKFILE))
|
|
@@ -160,6 +168,7 @@ export async function diffChangedPaths(
|
|
|
160
168
|
companionPath: string,
|
|
161
169
|
git: GitOps,
|
|
162
170
|
before: Map<string, string>,
|
|
171
|
+
report: ManagedOutputReport,
|
|
163
172
|
): Promise<UpdateChanges> {
|
|
164
173
|
const written: string[] = [];
|
|
165
174
|
const conflict: string[] = [];
|
|
@@ -168,19 +177,17 @@ export async function diffChangedPaths(
|
|
|
168
177
|
if (!before.has(changed)) written.push(changed);
|
|
169
178
|
}
|
|
170
179
|
const preexisting = await Promise.all(
|
|
171
|
-
[...before].map(async ([changed, hash]) =>
|
|
172
|
-
changed
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
unchanged: (await companionUpdateDeps.snapshotPath(companionPath, changed)) === hash,
|
|
176
|
-
})),
|
|
180
|
+
[...before].map(async ([changed, hash]) => {
|
|
181
|
+
const after = await companionUpdateDeps.snapshotPath(companionPath, changed);
|
|
182
|
+
return { changed, owner: ownerOf(changed, report), unchanged: after === hash };
|
|
183
|
+
}),
|
|
177
184
|
);
|
|
178
|
-
for (const { changed,
|
|
179
|
-
|
|
180
|
-
if (!
|
|
185
|
+
for (const { changed, owner, unchanged } of preexisting) {
|
|
186
|
+
// Reported output is committed even when a local edit preceded the sync.
|
|
187
|
+
if (!owner && unchanged) continue;
|
|
181
188
|
written.push(changed);
|
|
182
|
-
if (openspec || (mate && unchanged)) continue;
|
|
183
|
-
(mate ? overwritten : conflict).push(changed);
|
|
189
|
+
if (owner === "openspec" || (owner === "mate" && unchanged)) continue;
|
|
190
|
+
(owner === "mate" ? overwritten : conflict).push(changed);
|
|
184
191
|
}
|
|
185
192
|
return {
|
|
186
193
|
written: written.toSorted(),
|
|
@@ -200,14 +207,27 @@ function fail(message: string): void {
|
|
|
200
207
|
process.exitCode = 1;
|
|
201
208
|
}
|
|
202
209
|
|
|
203
|
-
function errorMessage(error: unknown): string {
|
|
204
|
-
return error instanceof Error ? error.message : String(error);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
210
|
function listPaths(paths: string[]): string {
|
|
208
211
|
return paths.map((changed) => ` ${changed}`).join("\n");
|
|
209
212
|
}
|
|
210
213
|
|
|
214
|
+
function reportLinkRefresh(results: LinkRefreshResult[]): void {
|
|
215
|
+
for (const result of results) {
|
|
216
|
+
const where = result.repository?.path ?? "the linked Working Repositories";
|
|
217
|
+
if (result.outcome === "written") {
|
|
218
|
+
process.stdout.write(
|
|
219
|
+
`${FRAMEWORK_NAME}: refreshed Claude skill and command links in ${where}.\n`,
|
|
220
|
+
);
|
|
221
|
+
} else if (result.outcome === "current") {
|
|
222
|
+
process.stdout.write(`${FRAMEWORK_NAME}: Claude links already current in ${where}.\n`);
|
|
223
|
+
} else {
|
|
224
|
+
process.stderr.write(
|
|
225
|
+
`${FRAMEWORK_NAME}: warning: could not refresh Claude links in ${where}: ${result.error}\n`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
211
231
|
/** Version precondition; returns the running version when the update may proceed. */
|
|
212
232
|
async function checkVersion(): Promise<string | undefined> {
|
|
213
233
|
const current = companionUpdateDeps.getCurrentVersion();
|
|
@@ -313,9 +333,14 @@ export async function runCompanionUpdateCommand(argv: string[]): Promise<void> {
|
|
|
313
333
|
return;
|
|
314
334
|
}
|
|
315
335
|
|
|
336
|
+
let config: FrameworkConfig;
|
|
337
|
+
let report: ManagedOutputReport;
|
|
316
338
|
try {
|
|
317
|
-
|
|
318
|
-
|
|
339
|
+
config = await companionUpdateDeps.loadConfig(companionPath);
|
|
340
|
+
// Only this command and setup regenerate OpenSpec output; launches leave it alone.
|
|
341
|
+
report = await companionUpdateDeps.syncCompanionFiles(companionPath, config, {
|
|
342
|
+
regenerateOpenSpec: true,
|
|
343
|
+
});
|
|
319
344
|
} catch (error) {
|
|
320
345
|
fail(`companion file synchronization failed: ${errorMessage(error)}`);
|
|
321
346
|
return;
|
|
@@ -331,6 +356,9 @@ export async function runCompanionUpdateCommand(argv: string[]): Promise<void> {
|
|
|
331
356
|
}
|
|
332
357
|
}
|
|
333
358
|
|
|
359
|
+
// Working Repositories are outside the companion, so their links never join its commit.
|
|
360
|
+
reportLinkRefresh(await companionUpdateDeps.refreshWorkingClaudeLinks(companionPath, config));
|
|
361
|
+
|
|
334
362
|
try {
|
|
335
363
|
await companionUpdateDeps.writeEngineFloor(companionPath, `>=${version}`);
|
|
336
364
|
} catch (error) {
|
|
@@ -338,7 +366,12 @@ export async function runCompanionUpdateCommand(argv: string[]): Promise<void> {
|
|
|
338
366
|
return;
|
|
339
367
|
}
|
|
340
368
|
|
|
341
|
-
const { written, conflict, overwritten } = await diffChangedPaths(
|
|
369
|
+
const { written, conflict, overwritten } = await diffChangedPaths(
|
|
370
|
+
companionPath,
|
|
371
|
+
git,
|
|
372
|
+
before,
|
|
373
|
+
report,
|
|
374
|
+
);
|
|
342
375
|
if (written.length === 0) {
|
|
343
376
|
process.stdout.write(
|
|
344
377
|
`${FRAMEWORK_NAME}: ${companionPath} is already up to date with ${FRAMEWORK_NAME} ${version}.\n`,
|