@uniqbit/mate-core 0.15.5 → 0.16.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/.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 +6 -4
- 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/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/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 +147 -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 +147 -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 +47 -0
- 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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLAUDE_LOCAL_CONFIG_DOCUMENT,
|
|
3
|
+
CLAUDE_MCP_DOCUMENT,
|
|
4
|
+
CLAUDE_SETTINGS_DOCUMENT,
|
|
5
|
+
OPENCODE_CONFIG_DOCUMENT,
|
|
6
|
+
} from "../../lib/orchestrator/projection-runtime-documents";
|
|
7
|
+
import type {
|
|
8
|
+
ManagedRegion,
|
|
9
|
+
RenderedRuntimeDocument,
|
|
10
|
+
} from "../../lib/orchestrator/projection-types";
|
|
11
|
+
import { getOpenCodePluginPackageReference } from "../../lib/opencode-plugin-package";
|
|
12
|
+
import type { CapabilityContributionInput, SetupContext } from "./plugin";
|
|
13
|
+
import {
|
|
14
|
+
mateBannerHookGroups,
|
|
15
|
+
mateGuardHookGroups,
|
|
16
|
+
mateGuidanceHookGroups,
|
|
17
|
+
} from "./providers/claude-plugin-hooks";
|
|
18
|
+
import {
|
|
19
|
+
renderManagedClaudeMcpServers,
|
|
20
|
+
renderManagedClaudePermissionEntries,
|
|
21
|
+
} from "./providers/claude";
|
|
22
|
+
import {
|
|
23
|
+
renderCompanionExternalDirectoryPermissions,
|
|
24
|
+
renderManagedOpenCodeMcpServers,
|
|
25
|
+
} from "./providers/opencode";
|
|
26
|
+
import { projectionRepoRoot } from "./surface-target";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The working target's documents, rendered as values. Each is a document the
|
|
30
|
+
* runtime finds by walking up from the current directory, and every path inside
|
|
31
|
+
* one still names the Companion Repository — only where the document is placed
|
|
32
|
+
* differs from the companion target.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
CLAUDE_LOCAL_CONFIG_DOCUMENT,
|
|
37
|
+
CLAUDE_MCP_DOCUMENT,
|
|
38
|
+
CLAUDE_SETTINGS_DOCUMENT,
|
|
39
|
+
OPENCODE_CONFIG_DOCUMENT,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function nonEmpty(region: ManagedRegion): boolean {
|
|
43
|
+
if (region.kind === "list") return region.values.length > 0;
|
|
44
|
+
if (region.kind === "map") return Object.keys(region.entries).length > 0;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function document(documentPath: string, regions: ManagedRegion[]): RenderedRuntimeDocument[] {
|
|
49
|
+
const kept = regions.filter(nonEmpty);
|
|
50
|
+
return kept.length > 0 ? [{ path: documentPath, regions: kept }] : [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The Mate plugin reference is what makes an Unmanaged OpenCode session load
|
|
55
|
+
* the companion guidance at all: OpenCode has no config channel for a generated
|
|
56
|
+
* system prompt, so the plugin is the only injection point, and a bare session
|
|
57
|
+
* loads no companion config to find it in.
|
|
58
|
+
*
|
|
59
|
+
* A managed launch does load the plugin twice — its `OPENCODE_CONFIG_DIR` names
|
|
60
|
+
* the companion, whose config carries the same reference. That is inert rather
|
|
61
|
+
* than doubled: the plugin skips its own emission when the companion-policy
|
|
62
|
+
* marker is already in the prompt.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Capability hook groups stay companion-only. A managed session loads this
|
|
67
|
+
* document as its `local` source *alongside* the companion's, so anything here
|
|
68
|
+
* that the launch also delivers runs twice; the guard, the guidance hook and
|
|
69
|
+
* the banner are the contributions whose absence is a correctness hole and
|
|
70
|
+
* whose double run is inert — the guard because its verdict depends only on the
|
|
71
|
+
* tool input, the guidance hook because it yields nothing under a launch
|
|
72
|
+
* environment, and the banner because the copy carried here is flagged
|
|
73
|
+
* `--projected` and defers to the one the launch's own plugin prints.
|
|
74
|
+
*
|
|
75
|
+
* `CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD` is what a managed launch sets
|
|
76
|
+
* beside `--add-dir`. Without it the companion directories this document
|
|
77
|
+
* already permits are reachable but their `CLAUDE.md` is not loaded, so the
|
|
78
|
+
* permission alone delivers no guidance.
|
|
79
|
+
*/
|
|
80
|
+
function claudeSettingsRegions(
|
|
81
|
+
ctx: SetupContext,
|
|
82
|
+
inputs: CapabilityContributionInput[],
|
|
83
|
+
): ManagedRegion[] {
|
|
84
|
+
return [
|
|
85
|
+
{ at: ["hooks", "PreToolUse"], kind: "list", values: mateGuardHookGroups() },
|
|
86
|
+
{
|
|
87
|
+
at: ["hooks", "SessionStart"],
|
|
88
|
+
kind: "list",
|
|
89
|
+
values: [...mateBannerHookGroups(), ...mateGuidanceHookGroups()],
|
|
90
|
+
},
|
|
91
|
+
/**
|
|
92
|
+
* `stripPrefix` heals a repository an older Mate wrapped. That version wrote
|
|
93
|
+
* the whole launch environment here, and Claude Code applies `env` to the
|
|
94
|
+
* session, so every hook in a bare session inherited `MATE_*` and read the
|
|
95
|
+
* repository as a Managed Session — silently, since looking managed is
|
|
96
|
+
* exactly what makes the guidance hook emit nothing. Guidance must be the
|
|
97
|
+
* projection's to give in an Unmanaged Session, so no `MATE_*` key may
|
|
98
|
+
* survive here.
|
|
99
|
+
*/
|
|
100
|
+
{
|
|
101
|
+
at: ["env"],
|
|
102
|
+
kind: "map",
|
|
103
|
+
entries: { CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: "1" },
|
|
104
|
+
stripPrefix: "MATE_",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
at: ["permissions", "allow"],
|
|
108
|
+
kind: "list",
|
|
109
|
+
values: renderManagedClaudePermissionEntries(ctx.companionPath, inputs),
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function renderDocumentsForTarget(
|
|
115
|
+
ctx: SetupContext,
|
|
116
|
+
inputsByRuntime: Map<string, CapabilityContributionInput[]>,
|
|
117
|
+
): RenderedRuntimeDocument[] {
|
|
118
|
+
const repoRoot = projectionRepoRoot(ctx);
|
|
119
|
+
|
|
120
|
+
const documents: RenderedRuntimeDocument[] = [];
|
|
121
|
+
if (ctx.activeProviders.includes("claude")) {
|
|
122
|
+
const inputs = inputsByRuntime.get("claude") ?? [];
|
|
123
|
+
documents.push(...document(CLAUDE_SETTINGS_DOCUMENT, claudeSettingsRegions(ctx, inputs)));
|
|
124
|
+
/**
|
|
125
|
+
* Local scope, not `.mcp.json`: a project-scoped server is inert until a
|
|
126
|
+
* human approves it in a session, which an Unmanaged Session is exactly the
|
|
127
|
+
* case that cannot rely on. The repo path is the key Claude Code files a
|
|
128
|
+
* project under, resolved so a symlinked working directory still matches.
|
|
129
|
+
*
|
|
130
|
+
* Confirmed against a real `~/.claude.json`: the keys there are the
|
|
131
|
+
* directories sessions started in, not repository roots — sibling
|
|
132
|
+
* directories of one checkout each hold their own entry, as do temporary
|
|
133
|
+
* directories. So this reaches exactly one session: the one started at the
|
|
134
|
+
* repository root. A session started in a subdirectory of a wrapped
|
|
135
|
+
* repository has its own key, finds no `mcpServers` under it, and sees none
|
|
136
|
+
* of these servers — the Unmanaged Session case this document exists for,
|
|
137
|
+
* missed. That limit is deliberate. The directories a session might start
|
|
138
|
+
* in are unbounded, and every one of them would be a fresh entry in the
|
|
139
|
+
* user's global configuration that nothing later reclaims; keying the
|
|
140
|
+
* repository root only keeps the projection's footprint there to a single
|
|
141
|
+
* key. A subdirectory session still gets the rest of the projection — the
|
|
142
|
+
* documents inside the repository are found by the runtime's own upward
|
|
143
|
+
* walk — it loses only the MCP servers.
|
|
144
|
+
*/
|
|
145
|
+
documents.push(
|
|
146
|
+
...document(CLAUDE_LOCAL_CONFIG_DOCUMENT, [
|
|
147
|
+
{
|
|
148
|
+
at: ["projects", repoRoot, "mcpServers"],
|
|
149
|
+
kind: "map",
|
|
150
|
+
entries: renderManagedClaudeMcpServers(inputs),
|
|
151
|
+
},
|
|
152
|
+
]),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (ctx.activeProviders.includes("opencode")) {
|
|
156
|
+
const inputs = inputsByRuntime.get("opencode") ?? [];
|
|
157
|
+
documents.push(
|
|
158
|
+
...document(OPENCODE_CONFIG_DOCUMENT, [
|
|
159
|
+
{
|
|
160
|
+
at: ["plugin"],
|
|
161
|
+
kind: "list",
|
|
162
|
+
values: [getOpenCodePluginPackageReference()],
|
|
163
|
+
},
|
|
164
|
+
{ at: ["mcp"], kind: "map", entries: renderManagedOpenCodeMcpServers(inputs) },
|
|
165
|
+
{
|
|
166
|
+
at: ["permission", "external_directory"],
|
|
167
|
+
kind: "map",
|
|
168
|
+
entries: renderCompanionExternalDirectoryPermissions(ctx.companionPath),
|
|
169
|
+
},
|
|
170
|
+
]),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return documents;
|
|
174
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import type { ProjectionTarget, SetupContext } from "./plugin";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The projection target of a reconcile pass, resolved once. A `working` pass
|
|
7
|
+
* without a Working Repository in scope refuses rather than falling back to the
|
|
8
|
+
* companion: writing the other root is the one outcome the caller cannot detect.
|
|
9
|
+
*/
|
|
10
|
+
export function projectionTarget(ctx: SetupContext): ProjectionTarget {
|
|
11
|
+
const target = ctx.target ?? "companion";
|
|
12
|
+
if (target === "working" && !ctx.repoPath) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"a working-target reconcile needs a Working Repository in scope; none was supplied",
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return target;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The Working Repository a rendered document is addressed by, resolved so a
|
|
22
|
+
* symlinked working directory still matches what the runtime reports. Every
|
|
23
|
+
* document a reconcile pass renders is for a Working Repository, so a pass with
|
|
24
|
+
* none in scope refuses here rather than resolving an absent path: `path.resolve`
|
|
25
|
+
* answers the current working directory for an empty one, and a document keyed
|
|
26
|
+
* by wherever the process happened to start would file a Runtime Surface's MCP
|
|
27
|
+
* servers under a stranger's project in the user's global Claude config — a
|
|
28
|
+
* write no caller asked for and none would notice.
|
|
29
|
+
*/
|
|
30
|
+
export function projectionRepoRoot(ctx: SetupContext): string {
|
|
31
|
+
const target = projectionTarget(ctx);
|
|
32
|
+
if (!ctx.repoPath) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`a ${target}-target render needs a Working Repository in scope to address its documents by; none was supplied`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return path.resolve(ctx.repoPath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The root a Runtime Surface writes into. Defined for the companion target
|
|
42
|
+
* only: a working-target pass renders its documents for the Projection Root to
|
|
43
|
+
* place, so no Runtime Surface ever computes a path inside a Working Repository.
|
|
44
|
+
*/
|
|
45
|
+
export function surfaceRoot(ctx: SetupContext): string {
|
|
46
|
+
if (projectionTarget(ctx) === "working") {
|
|
47
|
+
throw new Error("a working-target reconcile renders its documents; it writes no surface root");
|
|
48
|
+
}
|
|
49
|
+
return ctx.companionPath;
|
|
50
|
+
}
|
|
@@ -1,40 +1,47 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { repoLocalDirPath } from "../../lib/orchestrator/repo-local-registry";
|
|
4
|
-
import { cleanupWorkingRepoClaudeSettings } from "./providers/claude";
|
|
5
|
-
import { removeWorkingRepoLocalExcludes } from "./working-repo-local-state";
|
|
1
|
+
import { unproject } from "../../lib/orchestrator/working-repo-projection";
|
|
2
|
+
import type { ProjectionEntryId } from "../../lib/orchestrator/projection-types";
|
|
6
3
|
|
|
7
4
|
export interface WorkingRepoCleanupResult {
|
|
8
5
|
changed: boolean;
|
|
9
6
|
removed: string[];
|
|
10
7
|
updated: string[];
|
|
8
|
+
/** Entries `unproject` deliberately left in place, named rather than omitted. */
|
|
9
|
+
retained: string[];
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
12
|
+
/**
|
|
13
|
+
* How an entry is reported. Presentation only — cleanup holds no list of paths,
|
|
14
|
+
* so a newly declared entry is removed without touching this map.
|
|
15
|
+
*/
|
|
16
|
+
const REPORTED_AS: Partial<
|
|
17
|
+
Record<ProjectionEntryId, { as: "removed" | "updated"; label: string }>
|
|
18
|
+
> = {
|
|
19
|
+
"projection-root": { as: "removed", label: ".mate" },
|
|
20
|
+
"git-excludes": { as: "removed", label: "git-excludes" },
|
|
21
|
+
"claude-working-settings": { as: "updated", label: "claude-settings" },
|
|
22
|
+
"legacy-tokensave-claude-md": { as: "updated", label: "claude-settings" },
|
|
23
|
+
};
|
|
21
24
|
|
|
22
25
|
export async function cleanupWorkingRepository(
|
|
23
26
|
repoPath: string,
|
|
24
|
-
registeredCompanionPaths: string[],
|
|
27
|
+
registeredCompanionPaths: string[] = [],
|
|
25
28
|
): Promise<WorkingRepoCleanupResult> {
|
|
26
|
-
const
|
|
29
|
+
const { outcomes } = await unproject({ repoPath, registeredCompanionPaths });
|
|
27
30
|
const removed: string[] = [];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
const updated: string[] = [];
|
|
32
|
+
const retained: string[] = [];
|
|
33
|
+
|
|
34
|
+
for (const entry of outcomes) {
|
|
35
|
+
if (entry.state === "retained") {
|
|
36
|
+
retained.push(entry.id);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (entry.state !== "removed") continue;
|
|
40
|
+
const reported = REPORTED_AS[entry.id];
|
|
41
|
+
if (!reported) continue;
|
|
42
|
+
const bucket = reported.as === "removed" ? removed : updated;
|
|
43
|
+
if (!bucket.includes(reported.label)) bucket.push(reported.label);
|
|
38
44
|
}
|
|
39
|
-
|
|
45
|
+
|
|
46
|
+
return { changed: removed.length > 0 || updated.length > 0, removed, updated, retained };
|
|
40
47
|
}
|
|
@@ -5,7 +5,8 @@ import { resolveGitInfoExcludePath } from "./git-utils";
|
|
|
5
5
|
|
|
6
6
|
const MANAGED_BLOCK_START = "# mate managed: start";
|
|
7
7
|
const MANAGED_BLOCK_END = "# mate managed: end";
|
|
8
|
-
|
|
8
|
+
/** `.mcp.json` is the one working-target document no directory entry covers. */
|
|
9
|
+
const CORE_EXCLUDE_ENTRIES = ["/.mate/", "/.claude/", "/.opencode/", "/.agents/", "/.mcp.json"];
|
|
9
10
|
const LEGACY_CORE_EXCLUDE_ENTRIES = new Set([".mate/", ".claude/settings.local.json"]);
|
|
10
11
|
|
|
11
12
|
async function readExclude(excludePath: string): Promise<string> {
|
|
@@ -90,6 +91,25 @@ export async function reconcileWorkingRepoCapabilityExcludes(
|
|
|
90
91
|
return writeExclude(excludePath, existing, lines);
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
/** Whether the managed block is on disk; the block, not the exclude file. */
|
|
95
|
+
export async function managedWorkingRepoExcludesPresent(repoPath: string): Promise<boolean> {
|
|
96
|
+
const excludePath = await resolveGitInfoExcludePath(repoPath);
|
|
97
|
+
if (!excludePath) return false;
|
|
98
|
+
const lines = contentLines(await readExclude(excludePath));
|
|
99
|
+
return lines.some((line) => line.trim() === MANAGED_BLOCK_START);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Whether any Capability-owned exclusion is present outside the managed block. */
|
|
103
|
+
export async function workingRepoCapabilityExcludesPresent(
|
|
104
|
+
repoPath: string,
|
|
105
|
+
candidateEntries: string[],
|
|
106
|
+
): Promise<boolean> {
|
|
107
|
+
const excludePath = await resolveGitInfoExcludePath(repoPath);
|
|
108
|
+
if (!excludePath) return false;
|
|
109
|
+
const lines = withoutManagedBlocks(contentLines(await readExclude(excludePath)));
|
|
110
|
+
return candidateEntries.some((entry) => lines.some((line) => line.trim() === entry));
|
|
111
|
+
}
|
|
112
|
+
|
|
93
113
|
export async function removeWorkingRepoLocalExcludes(repoPath: string): Promise<boolean> {
|
|
94
114
|
const excludePath = await resolveGitInfoExcludePath(repoPath);
|
|
95
115
|
if (!excludePath) return false;
|
package/src/tools/setup.ts
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
type CapabilityConfig,
|
|
20
20
|
type FrameworkConfig,
|
|
21
21
|
} from "../lib/orchestrator/types";
|
|
22
|
-
import { syncWorkingRepoClaudeSettings } from "./setup/providers/claude";
|
|
23
22
|
import {
|
|
24
23
|
collectManagedGitignoreEntries,
|
|
25
24
|
writeManagedGitignoreBlock,
|
|
@@ -35,10 +34,10 @@ import { installDeclaredPlugins } from "./setup/dynamic-plugins/install";
|
|
|
35
34
|
import {
|
|
36
35
|
buildSetupInstallationPlan,
|
|
37
36
|
executeSetupInstallationPlan,
|
|
37
|
+
renderRuntimeDocuments,
|
|
38
38
|
type SetupInstallationOutcome,
|
|
39
39
|
} from "./setup/engine";
|
|
40
|
-
|
|
41
|
-
export { syncWorkingRepoClaudeSettings };
|
|
40
|
+
import type { RenderedRuntimeDocument } from "../lib/orchestrator/projection-types";
|
|
42
41
|
|
|
43
42
|
export interface SetupInput {
|
|
44
43
|
allowedAgents?: string[];
|
|
@@ -67,6 +66,29 @@ export async function applySetupCompatibilities(
|
|
|
67
66
|
return executeSetupInstallationPlan(ctx, plugins, plan);
|
|
68
67
|
}
|
|
69
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The active Agent Runtimes' documents for a Working Repository, rendered
|
|
71
|
+
* without a write. Runs no plugin phase: the working target reconciles
|
|
72
|
+
* declarations only, so nothing is written to the companion on its behalf.
|
|
73
|
+
*/
|
|
74
|
+
export async function renderWorkingRuntimeDocuments(
|
|
75
|
+
companionPath: string,
|
|
76
|
+
config: FrameworkConfig,
|
|
77
|
+
repoPath: string,
|
|
78
|
+
plugins: PluginRegistration[] = getActiveDistribution().registry.getAll(),
|
|
79
|
+
): Promise<RenderedRuntimeDocument[]> {
|
|
80
|
+
const plan = buildSetupInstallationPlan(config, plugins);
|
|
81
|
+
const ctx: SetupContext = {
|
|
82
|
+
companionPath,
|
|
83
|
+
config,
|
|
84
|
+
mode: "sync",
|
|
85
|
+
activeProviders: plan.activeProviders,
|
|
86
|
+
repoPath,
|
|
87
|
+
target: "working",
|
|
88
|
+
};
|
|
89
|
+
return renderRuntimeDocuments(ctx, plugins, plan);
|
|
90
|
+
}
|
|
91
|
+
|
|
70
92
|
export async function updateProjectGitignore(
|
|
71
93
|
companionPath: string,
|
|
72
94
|
config: FrameworkConfig,
|
package/wrappers/bin/graphify
CHANGED
|
@@ -1,8 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
[ -n "$MATE_ARTIFACT_PATH" ] || { echo "graphify: must be run via mate claude or mate opencode (MATE_ARTIFACT_PATH not set)" >&2; exit 1; }
|
|
3
|
-
[ -n "$MATE_REPO_PATH" ] || { echo "graphify: must be run via mate claude or mate opencode (MATE_REPO_PATH not set)" >&2; exit 1; }
|
|
4
|
-
|
|
5
2
|
_this_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
3
|
+
|
|
4
|
+
# Mate context: the launch environment first, the Working Repository's
|
|
5
|
+
# Projection Root second. Projected values stay in private shell variables and
|
|
6
|
+
# no MATE_-prefixed variable is exported: the projection carries paths and
|
|
7
|
+
# never predicates, so a partial MATE_* environment would read to any child as
|
|
8
|
+
# authoritative with every predicate disabled.
|
|
9
|
+
_mate_projection_file() {
|
|
10
|
+
local dir="$PWD"
|
|
11
|
+
while :; do
|
|
12
|
+
if [ -f "$dir/.mate/config/registry.yaml" ]; then
|
|
13
|
+
[ -f "$dir/.mate/projection.env" ] && printf '%s' "$dir/.mate/projection.env"
|
|
14
|
+
return
|
|
15
|
+
fi
|
|
16
|
+
[ "$dir" = "/" ] && return
|
|
17
|
+
dir="$(dirname "$dir")"
|
|
18
|
+
done
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_mate_projected() {
|
|
22
|
+
sed -n "s/^$2='\\(.*\\)'\$/\\1/p" "$1" | head -n 1 | sed "s/'\\\\''/'/g"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_mate_same_dir() {
|
|
26
|
+
local left="$1" right="$2"
|
|
27
|
+
[ -d "$left" ] && left="$(cd "$left" && pwd)"
|
|
28
|
+
[ -d "$right" ] && right="$(cd "$right" && pwd)"
|
|
29
|
+
[ "$left" = "$right" ]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_companion="$MATE_ARTIFACT_PATH"
|
|
33
|
+
_repo_path="$MATE_REPO_PATH"
|
|
34
|
+
_graphify_out="$GRAPHIFY_OUT"
|
|
35
|
+
if [ -z "$_companion" ]; then
|
|
36
|
+
_projection="$(_mate_projection_file)"
|
|
37
|
+
if [ -n "$_projection" ]; then
|
|
38
|
+
_companion="$(_mate_projected "$_projection" MATE_ARTIFACT_PATH)"
|
|
39
|
+
_repo_path="$(_mate_projected "$_projection" MATE_REPO_PATH)"
|
|
40
|
+
_graphify_out="$(_mate_projected "$_projection" GRAPHIFY_OUT)"
|
|
41
|
+
_projected_wrapper_dir="$(_mate_projected "$_projection" MATE_WRAPPER_BIN_PATH)"
|
|
42
|
+
if [ -n "$_companion" ] && ! _mate_same_dir "$_projected_wrapper_dir" "$_this_dir"; then
|
|
43
|
+
{
|
|
44
|
+
echo "graphify: this repository was wrapped by a different mate install."
|
|
45
|
+
echo " projected wrappers: $_projected_wrapper_dir"
|
|
46
|
+
echo " running wrapper: $_this_dir"
|
|
47
|
+
echo " re-run \`mate wrap\` with this install."
|
|
48
|
+
} >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
[ -n "$_companion" ] || { echo "graphify: no mate context here — run \`mate wrap\` in this repository" >&2; exit 1; }
|
|
54
|
+
[ -n "$_repo_path" ] || { echo "graphify: no working repository in the mate context — run \`mate wrap\` in this repository" >&2; exit 1; }
|
|
55
|
+
[ -n "$_graphify_out" ] || { echo "graphify: no graphify output path in the mate context — run \`mate wrap\` in this repository" >&2; exit 1; }
|
|
56
|
+
|
|
6
57
|
_real=""
|
|
7
58
|
IFS=:
|
|
8
59
|
for _dir in $PATH; do
|
|
@@ -14,8 +65,6 @@ done
|
|
|
14
65
|
unset IFS
|
|
15
66
|
[ -n "$_real" ] || { echo "graphify wrapper: real graphify binary not found in PATH" >&2; exit 1; }
|
|
16
67
|
|
|
17
|
-
_repo_id="${MATE_REPO_ID:-unknown}"
|
|
18
|
-
_graphify_out="$MATE_ARTIFACT_PATH/.graphify/$_repo_id/graphify-out"
|
|
19
68
|
_graphify_root="$(dirname "$_graphify_out")"
|
|
20
69
|
_graph_json="$_graphify_out/graph.json"
|
|
21
70
|
export GRAPHIFY_OUT="$_graphify_out"
|
|
@@ -32,18 +81,18 @@ case "$_cmd" in
|
|
|
32
81
|
*) _rest+=("$_a") ;;
|
|
33
82
|
esac
|
|
34
83
|
done
|
|
35
|
-
exec "$_real" extract "$
|
|
84
|
+
exec "$_real" extract "$_repo_path" --out "$_graphify_root" "${_rest[@]}"
|
|
36
85
|
;;
|
|
37
86
|
update)
|
|
38
87
|
if [ "$#" -eq 1 ] || [[ "$2" == -* ]]; then
|
|
39
|
-
exec "$_real" "$
|
|
88
|
+
exec "$_real" "$_repo_path" --update "${@:2}"
|
|
40
89
|
else
|
|
41
90
|
exec "$_real" "$2" --update "${@:3}"
|
|
42
91
|
fi
|
|
43
92
|
;;
|
|
44
93
|
extract|watch|cluster-only|label|check-update)
|
|
45
94
|
if [ "$#" -eq 1 ] || [[ "$2" == -* ]]; then
|
|
46
|
-
exec "$_real" "$_cmd" "$
|
|
95
|
+
exec "$_real" "$_cmd" "$_repo_path" "${@:2}"
|
|
47
96
|
else
|
|
48
97
|
exec "$_real" "$@"
|
|
49
98
|
fi
|
package/wrappers/bin/openspec
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
[ -n "$MATE_ARTIFACT_PATH" ] || { echo "openspec: must be run via mate claude or mate opencode" >&2; exit 1; }
|
|
3
2
|
_this_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
3
|
+
|
|
4
|
+
# Mate context: the launch environment first, the Working Repository's
|
|
5
|
+
# Projection Root second. Projected values stay in private shell variables and
|
|
6
|
+
# no MATE_-prefixed variable is exported: the projection carries paths and
|
|
7
|
+
# never predicates, so a partial MATE_* environment would read to any child as
|
|
8
|
+
# authoritative with every predicate disabled.
|
|
9
|
+
_mate_projection_file() {
|
|
10
|
+
local dir="$PWD"
|
|
11
|
+
while :; do
|
|
12
|
+
if [ -f "$dir/.mate/config/registry.yaml" ]; then
|
|
13
|
+
[ -f "$dir/.mate/projection.env" ] && printf '%s' "$dir/.mate/projection.env"
|
|
14
|
+
return
|
|
15
|
+
fi
|
|
16
|
+
[ "$dir" = "/" ] && return
|
|
17
|
+
dir="$(dirname "$dir")"
|
|
18
|
+
done
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_mate_projected() {
|
|
22
|
+
sed -n "s/^$2='\\(.*\\)'\$/\\1/p" "$1" | head -n 1 | sed "s/'\\\\''/'/g"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_mate_same_dir() {
|
|
26
|
+
local left="$1" right="$2"
|
|
27
|
+
[ -d "$left" ] && left="$(cd "$left" && pwd)"
|
|
28
|
+
[ -d "$right" ] && right="$(cd "$right" && pwd)"
|
|
29
|
+
[ "$left" = "$right" ]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_companion="$MATE_ARTIFACT_PATH"
|
|
33
|
+
if [ -z "$_companion" ]; then
|
|
34
|
+
_projection="$(_mate_projection_file)"
|
|
35
|
+
if [ -n "$_projection" ]; then
|
|
36
|
+
_companion="$(_mate_projected "$_projection" MATE_ARTIFACT_PATH)"
|
|
37
|
+
_projected_wrapper_dir="$(_mate_projected "$_projection" MATE_WRAPPER_BIN_PATH)"
|
|
38
|
+
if [ -n "$_companion" ] && ! _mate_same_dir "$_projected_wrapper_dir" "$_this_dir"; then
|
|
39
|
+
{
|
|
40
|
+
echo "openspec: this repository was wrapped by a different mate install."
|
|
41
|
+
echo " projected wrappers: $_projected_wrapper_dir"
|
|
42
|
+
echo " running wrapper: $_this_dir"
|
|
43
|
+
echo " re-run \`mate wrap\` with this install."
|
|
44
|
+
} >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
[ -n "$_companion" ] || { echo "openspec: no mate context here — run \`mate wrap\` in this repository" >&2; exit 1; }
|
|
50
|
+
|
|
4
51
|
_real=""
|
|
5
52
|
IFS=:
|
|
6
53
|
for _dir in $PATH; do
|
|
@@ -22,7 +69,7 @@ if [ "$command" = "templates" ]; then
|
|
|
22
69
|
done
|
|
23
70
|
if [ "$has_schema" -eq 0 ]; then
|
|
24
71
|
_schema=""
|
|
25
|
-
for _cfg in "$
|
|
72
|
+
for _cfg in "$_companion/openspec/config.yaml" "$_companion/openspec/config.yml"; do
|
|
26
73
|
[ -f "$_cfg" ] || continue
|
|
27
74
|
while IFS= read -r _line; do
|
|
28
75
|
case "$_line" in
|
|
@@ -45,4 +92,4 @@ if [ "$command" = "templates" ]; then
|
|
|
45
92
|
fi
|
|
46
93
|
fi
|
|
47
94
|
fi
|
|
48
|
-
exec env -C "$
|
|
95
|
+
exec env -C "$_companion" "$_real" "$@"
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Thin shim: hook logic lives in src/hooks/artifact-finish-nudge.ts, loaded
|
|
3
|
-
// through ts-loader.mjs because the installed plugin sits inside node_modules
|
|
4
|
-
// where node's native type stripping is disabled.
|
|
5
|
-
import "./ts-loader.mjs";
|
|
6
|
-
|
|
7
|
-
const { run } = await import("../../src/hooks/artifact-finish-nudge.ts");
|
|
8
|
-
process.exitCode = await run();
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
import { resolveForCapability } from "../../../lib/orchestrator/framework-context";
|
|
4
|
-
import type { CapabilityConfig } from "../../../lib/orchestrator/types";
|
|
5
|
-
import { ensureUnambiguousCompanion } from "../shared/companion-selection";
|
|
6
|
-
|
|
7
|
-
export interface HeadroomCapDeps {
|
|
8
|
-
ensureUnambiguousCompanion?: (cwd: string) => Promise<boolean>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @command mate cap headroom [...args]
|
|
13
|
-
* @description Forwards `[...args]` to the `headroom` CLI, run from the
|
|
14
|
-
* companion directory. Requires the `headroom` capability to be enabled in
|
|
15
|
-
* `.mate/config/framework.yaml`.
|
|
16
|
-
* @remarks The child process's exit code is propagated via `process.exitCode`.
|
|
17
|
-
*/
|
|
18
|
-
export async function runHeadroomCapCommand(
|
|
19
|
-
args: string[],
|
|
20
|
-
deps: HeadroomCapDeps = {},
|
|
21
|
-
): Promise<void> {
|
|
22
|
-
const ensureCompanion = deps.ensureUnambiguousCompanion ?? ensureUnambiguousCompanion;
|
|
23
|
-
if (!(await ensureCompanion(process.cwd()))) {
|
|
24
|
-
process.exitCode = 1;
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const { companionPath, configStore } = await resolveForCapability(process.cwd());
|
|
29
|
-
const config = await configStore.load();
|
|
30
|
-
|
|
31
|
-
const capability = (config.capabilities ?? []).find(
|
|
32
|
-
(entry: CapabilityConfig) => entry.name === "headroom",
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
if (!capability) {
|
|
36
|
-
process.stderr.write("headroom capability is not enabled in .mate/config/framework.yaml\n");
|
|
37
|
-
process.exitCode = 1;
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const result = spawnSync("headroom", args, { cwd: companionPath, stdio: "inherit" });
|
|
42
|
-
|
|
43
|
-
if (result.error) {
|
|
44
|
-
process.stderr.write(`Failed to run headroom: ${result.error.message}\n`);
|
|
45
|
-
process.exitCode = 1;
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (result.status !== null) {
|
|
50
|
-
process.exitCode = result.status;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { collectWorkspaceInventory } from "../../../lib/orchestrator/workspace-inventory";
|
|
2
|
-
import { writeJsonStdout } from "../../write-json-stdout";
|
|
3
|
-
|
|
4
|
-
export const workspaceListCommandDeps = {
|
|
5
|
-
collectWorkspaceInventory: () => collectWorkspaceInventory(),
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @command mate workspace list
|
|
10
|
-
* @description Prints the aggregate inventory of registered companions and
|
|
11
|
-
* their linked working-repository pairings as one JSON document. Runs
|
|
12
|
-
* without a linked working repository or any active companion context.
|
|
13
|
-
* @flags
|
|
14
|
-
* - `--json` — required; this command only supports JSON output.
|
|
15
|
-
*/
|
|
16
|
-
export async function runWorkspaceListCommand(argv: string[] = []): Promise<void> {
|
|
17
|
-
if (!argv.includes("--json")) {
|
|
18
|
-
process.stderr.write("mate: `workspace list` requires --json.\n");
|
|
19
|
-
process.exitCode = 1;
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const inventory = await workspaceListCommandDeps.collectWorkspaceInventory();
|
|
24
|
-
await writeJsonStdout(inventory);
|
|
25
|
-
}
|