@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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { FRAMEWORK_NAME } from "../../framework";
|
|
4
|
+
import { ConfigStore, mergeWithDefaults } from "../../lib/orchestrator/config-store";
|
|
5
|
+
import { findRepoLocalLinkedRepository } from "../../lib/orchestrator/repo-local-registry";
|
|
6
|
+
import {
|
|
7
|
+
projectWorkingRepository,
|
|
8
|
+
projectWorkingRuntimeDocuments,
|
|
9
|
+
} from "../../lib/orchestrator/working-repo-projection";
|
|
10
|
+
import { renderWorkingRuntimeDocuments } from "../../tools/setup";
|
|
11
|
+
import { launchAmbiguityDeps } from "./shared/companion-selection";
|
|
12
|
+
|
|
13
|
+
export interface WrapCommandDependencies {
|
|
14
|
+
findRepoLocalLinkedRepository?: typeof findRepoLocalLinkedRepository;
|
|
15
|
+
projectWorkingRepository?: typeof projectWorkingRepository;
|
|
16
|
+
projectWorkingRuntimeDocuments?: typeof projectWorkingRuntimeDocuments;
|
|
17
|
+
renderWorkingRuntimeDocuments?: typeof renderWorkingRuntimeDocuments;
|
|
18
|
+
resolveLinkedCompanions?: typeof launchAmbiguityDeps.resolveLinkedCompanions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const wrapCommandDeps: Required<WrapCommandDependencies> = {
|
|
22
|
+
findRepoLocalLinkedRepository,
|
|
23
|
+
projectWorkingRepository,
|
|
24
|
+
projectWorkingRuntimeDocuments,
|
|
25
|
+
renderWorkingRuntimeDocuments,
|
|
26
|
+
resolveLinkedCompanions: (cwd: string) => launchAmbiguityDeps.resolveLinkedCompanions(cwd),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** The companion's own selection, read from files: wrapping runs no setup pass. */
|
|
30
|
+
async function readCompanionConfig(companionPath: string) {
|
|
31
|
+
return mergeWithDefaults(
|
|
32
|
+
await new ConfigStore(
|
|
33
|
+
path.join(companionPath, `.${FRAMEWORK_NAME}`, "config", "framework.yaml"),
|
|
34
|
+
).load(),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `--companion` is consumed by the dispatch gate before the command runs, so it
|
|
40
|
+
* is parsed there too; this only rejects anything else.
|
|
41
|
+
*/
|
|
42
|
+
export function parseWrapArgs(argv: string[]): { companion?: string } | { error: string } {
|
|
43
|
+
let companion: string | undefined;
|
|
44
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
45
|
+
const arg = argv[index]!;
|
|
46
|
+
if (arg === "--companion") {
|
|
47
|
+
const value = argv[index + 1];
|
|
48
|
+
if (!value || value.startsWith("--")) return { error: "--companion requires a path" };
|
|
49
|
+
companion = value;
|
|
50
|
+
index += 1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (arg.startsWith("--companion=")) {
|
|
54
|
+
const value = arg.slice("--companion=".length);
|
|
55
|
+
if (!value) return { error: "--companion requires a path" };
|
|
56
|
+
companion = value;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
return { error: `unknown wrap option: ${arg}` };
|
|
60
|
+
}
|
|
61
|
+
return companion === undefined ? {} : { companion };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Writes the Working Repository's Projection Root and the documents the Agent
|
|
66
|
+
* Runtimes discover by themselves, so an Unmanaged Session both resolves its
|
|
67
|
+
* Repository Link and loads the readers that consult it. The companion has
|
|
68
|
+
* already been settled by the dispatch gate, which honors `--companion`, the
|
|
69
|
+
* launch environment, and the picker in that order; wrapping only makes the
|
|
70
|
+
* answer durable.
|
|
71
|
+
*/
|
|
72
|
+
export async function runWrapCommand(
|
|
73
|
+
argv: string[] = [],
|
|
74
|
+
cwd: string = process.cwd(),
|
|
75
|
+
deps: WrapCommandDependencies = {},
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const findRepository =
|
|
78
|
+
deps.findRepoLocalLinkedRepository ?? wrapCommandDeps.findRepoLocalLinkedRepository;
|
|
79
|
+
const project = deps.projectWorkingRepository ?? wrapCommandDeps.projectWorkingRepository;
|
|
80
|
+
const projectDocuments =
|
|
81
|
+
deps.projectWorkingRuntimeDocuments ?? wrapCommandDeps.projectWorkingRuntimeDocuments;
|
|
82
|
+
const renderDocuments =
|
|
83
|
+
deps.renderWorkingRuntimeDocuments ?? wrapCommandDeps.renderWorkingRuntimeDocuments;
|
|
84
|
+
const resolveLinked = deps.resolveLinkedCompanions ?? wrapCommandDeps.resolveLinkedCompanions;
|
|
85
|
+
|
|
86
|
+
const parsed = parseWrapArgs(argv);
|
|
87
|
+
if ("error" in parsed) {
|
|
88
|
+
console.error(`${FRAMEWORK_NAME}: ${parsed.error}`);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const repository = await findRepository(cwd);
|
|
94
|
+
if (!repository) {
|
|
95
|
+
console.error(
|
|
96
|
+
`${FRAMEWORK_NAME}: this directory is not a working repository linked to a companion.`,
|
|
97
|
+
);
|
|
98
|
+
console.error(`Run \`${FRAMEWORK_NAME} companion link\` first.`);
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The gate pins only on ambiguity, so the unambiguous case still resolves
|
|
105
|
+
* here — a single Linked Companion needs no answer to have been recorded.
|
|
106
|
+
*/
|
|
107
|
+
const companionPath =
|
|
108
|
+
(parsed.companion && path.resolve(parsed.companion)) ??
|
|
109
|
+
process.env.MATE_ARTIFACT_PATH ??
|
|
110
|
+
(await resolveLinked(cwd))[0]?.companionPath;
|
|
111
|
+
if (!companionPath) {
|
|
112
|
+
console.error(`${FRAMEWORK_NAME}: no registered companion links ${repository.id}.`);
|
|
113
|
+
console.error(`Run \`${FRAMEWORK_NAME} companion link\` first.`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const result = await project(companionPath, repository);
|
|
119
|
+
if (result.kind === "failed") {
|
|
120
|
+
console.error(
|
|
121
|
+
`${FRAMEWORK_NAME}: failed to write the projection for ${repository.id}: ${result.error.message}`,
|
|
122
|
+
);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* After the projection, never instead of it: a runtime document that cannot
|
|
129
|
+
* be written fails the command while the projection already written stays.
|
|
130
|
+
*/
|
|
131
|
+
const config = await readCompanionConfig(result.companionPath);
|
|
132
|
+
const documents = await projectDocuments(
|
|
133
|
+
result.companionPath,
|
|
134
|
+
repository,
|
|
135
|
+
config,
|
|
136
|
+
await renderDocuments(result.companionPath, config, repository.path),
|
|
137
|
+
);
|
|
138
|
+
if (documents.kind === "failed") {
|
|
139
|
+
console.error(
|
|
140
|
+
`${FRAMEWORK_NAME}: failed to reconcile ${documents.document} for ${repository.id}: ${documents.error.message}`,
|
|
141
|
+
);
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const changed = result.kind === "written" || documents.kind === "written";
|
|
147
|
+
console.log(
|
|
148
|
+
changed
|
|
149
|
+
? `Wrapped ${repository.id} at ${result.projectionRoot}`
|
|
150
|
+
: `Already wrapped: ${result.projectionRoot}`,
|
|
151
|
+
);
|
|
152
|
+
console.log(` Companion: ${result.companionPath}`);
|
|
153
|
+
for (const document of documents.documents) {
|
|
154
|
+
console.log(` Runtime: ${document}`);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Said at the moment the mode is switched, not left to be discovered at the
|
|
158
|
+
* next launch: wrapping makes `mate claude` and `mate opencode` refuse here,
|
|
159
|
+
* and the operator has no other way to learn that from a successful command.
|
|
160
|
+
*/
|
|
161
|
+
console.log(
|
|
162
|
+
`Start sessions yourself now: \`claude\` or \`opencode\`. \`${FRAMEWORK_NAME} claude\` and \`${FRAMEWORK_NAME} opencode\` do not run in a wrapped repository — \`${FRAMEWORK_NAME} unwrap\` reverses this.`,
|
|
163
|
+
);
|
|
164
|
+
}
|
package/src/cli/main.ts
CHANGED
|
@@ -16,13 +16,22 @@ import { runLaunchClaudeCommand } from "./commands/launch/claude";
|
|
|
16
16
|
import { runLaunchOpenCodeCommand } from "./commands/launch/opencode";
|
|
17
17
|
import { runPluginCommand } from "./commands/plugin/plugin";
|
|
18
18
|
import { runReportCommand } from "./commands/report";
|
|
19
|
+
import { runStudioCommand } from "./commands/studio";
|
|
19
20
|
import { runUpdateCommand } from "./commands/update";
|
|
20
|
-
import {
|
|
21
|
+
import { runUnwrapCommand } from "./commands/unwrap";
|
|
22
|
+
import { parseWrapArgs, runWrapCommand } from "./commands/wrap";
|
|
21
23
|
import { runWorkingCommand } from "./commands/working/working";
|
|
22
24
|
import { runInstallCommand } from "./commands/install";
|
|
23
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
inspectInstallPreflight,
|
|
27
|
+
isRepairableInstallPreflight,
|
|
28
|
+
repairInstallState,
|
|
29
|
+
} from "../lib/install";
|
|
24
30
|
import { resolveRootContext } from "../lib/orchestrator/root-context";
|
|
25
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
ensureUnambiguousCompanion,
|
|
33
|
+
type CompanionSelectionOptions,
|
|
34
|
+
} from "./commands/shared/companion-selection";
|
|
26
35
|
import { hydrateDynamicPlugins } from "../tools/setup/dynamic-plugins/hydrate";
|
|
27
36
|
import { findPluginCliCommand } from "./plugin-commands";
|
|
28
37
|
import { usage } from "./usage";
|
|
@@ -34,8 +43,12 @@ export interface GateNeeds {
|
|
|
34
43
|
notHubRoot?: boolean;
|
|
35
44
|
/** Block the command when the current directory resolves to a companion root. */
|
|
36
45
|
notCompanionRoot?: boolean;
|
|
37
|
-
/**
|
|
38
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Require an unambiguous companion (selection wizard on ambiguity) before
|
|
48
|
+
* dispatch. Options select a companion explicitly or force the wizard past a
|
|
49
|
+
* recorded answer.
|
|
50
|
+
*/
|
|
51
|
+
companion?: boolean | CompanionSelectionOptions;
|
|
39
52
|
/** Require a complete installation (install preflight) before dispatch. */
|
|
40
53
|
install?: boolean;
|
|
41
54
|
}
|
|
@@ -43,6 +56,7 @@ export interface GateNeeds {
|
|
|
43
56
|
export interface MainDeps {
|
|
44
57
|
ensureUnambiguousCompanion: typeof ensureUnambiguousCompanion;
|
|
45
58
|
inspectInstallPreflight: typeof inspectInstallPreflight;
|
|
59
|
+
repairInstallState?: typeof repairInstallState;
|
|
46
60
|
hydrateDynamicPlugins: typeof hydrateDynamicPlugins;
|
|
47
61
|
resolveRootContext: typeof resolveRootContext;
|
|
48
62
|
}
|
|
@@ -50,6 +64,7 @@ export interface MainDeps {
|
|
|
50
64
|
const mainDeps: MainDeps = {
|
|
51
65
|
ensureUnambiguousCompanion,
|
|
52
66
|
inspectInstallPreflight,
|
|
67
|
+
repairInstallState,
|
|
53
68
|
hydrateDynamicPlugins,
|
|
54
69
|
resolveRootContext,
|
|
55
70
|
};
|
|
@@ -80,17 +95,16 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
80
95
|
const isPluginCommand =
|
|
81
96
|
command === "cap" && findPluginCliCommand(subcommand, rest[0]) !== undefined;
|
|
82
97
|
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
// reasons put them in the same stdout-owning bucket as plugin commands.
|
|
88
|
-
const ownsStdout = isPluginCommand || command === "workspace";
|
|
98
|
+
// Plugin commands are the only stdout owners: the un-awaited
|
|
99
|
+
// scheduleBackgroundCheck() has been observed to race a large final stdout
|
|
100
|
+
// write against process exit, truncating it.
|
|
101
|
+
const ownsStdout = isPluginCommand;
|
|
89
102
|
|
|
90
103
|
// Every dispatch case opens with a gate() call declaring what the command
|
|
91
104
|
// needs before it may run. Declared gates run in fixed order — update
|
|
92
105
|
// enforcement, companion selection, install preflight — and a blocked gate
|
|
93
106
|
// sets the exit code, so callers only need `if (!(await gate(...))) return`.
|
|
107
|
+
let installRepairAttempted = false;
|
|
94
108
|
const gate = async (needs: GateNeeds): Promise<boolean> => {
|
|
95
109
|
if (!ownsStdout) {
|
|
96
110
|
const updateStore = new UpdateStateStore();
|
|
@@ -120,7 +134,11 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
120
134
|
}
|
|
121
135
|
}
|
|
122
136
|
|
|
123
|
-
|
|
137
|
+
const companionOptions = typeof needs.companion === "object" ? needs.companion : {};
|
|
138
|
+
if (
|
|
139
|
+
needs.companion &&
|
|
140
|
+
!(await deps.ensureUnambiguousCompanion(process.cwd(), companionOptions))
|
|
141
|
+
) {
|
|
124
142
|
process.exitCode = 1;
|
|
125
143
|
return false;
|
|
126
144
|
}
|
|
@@ -128,6 +146,14 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
128
146
|
if (needs.install) {
|
|
129
147
|
const preflight = await deps.inspectInstallPreflight();
|
|
130
148
|
if (!preflight.ok) {
|
|
149
|
+
if (!installRepairAttempted && isRepairableInstallPreflight(preflight)) {
|
|
150
|
+
installRepairAttempted = true;
|
|
151
|
+
try {
|
|
152
|
+
await (deps.repairInstallState ?? repairInstallState)(preflight.plan!);
|
|
153
|
+
process.stderr.write(`${FRAMEWORK_NAME}: repaired installation state; continuing.\n`);
|
|
154
|
+
if ((await deps.inspectInstallPreflight()).ok) return true;
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
131
157
|
console.error(`${FRAMEWORK_NAME}: ${preflight.reason ?? "installation is incomplete"}`);
|
|
132
158
|
console.error(`Run \`${FRAMEWORK_NAME} install\` before continuing.`);
|
|
133
159
|
process.exitCode = 1;
|
|
@@ -171,9 +197,10 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
171
197
|
case "hub":
|
|
172
198
|
if (!(await gate({ updateGuard: true, notCompanionRoot: true }))) return;
|
|
173
199
|
break;
|
|
174
|
-
// open/tui consume a companion context.
|
|
200
|
+
// open/tui/sync consume a companion context.
|
|
175
201
|
case "open":
|
|
176
202
|
case "tui":
|
|
203
|
+
case "sync":
|
|
177
204
|
if (!(await gate({ updateGuard: true, notHubRoot: true, companion: true }))) return;
|
|
178
205
|
break;
|
|
179
206
|
// list and unknown subcommands (which fail inside the command).
|
|
@@ -199,6 +226,14 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
199
226
|
if (!(await gate({ updateGuard: true, install: true }))) return;
|
|
200
227
|
await runReportCommand(argv.slice(3));
|
|
201
228
|
return;
|
|
229
|
+
case "studio":
|
|
230
|
+
/**
|
|
231
|
+
* Serves the machine-wide inventory read-only, so it resolves no
|
|
232
|
+
* companion context and stays runnable from any directory.
|
|
233
|
+
*/
|
|
234
|
+
if (!(await gate({ updateGuard: true }))) return;
|
|
235
|
+
await runStudioCommand(argv.slice(3));
|
|
236
|
+
return;
|
|
202
237
|
case "config":
|
|
203
238
|
if (!(await gate({ updateGuard: true }))) return;
|
|
204
239
|
await runConfigCommand(argv.slice(3));
|
|
@@ -215,16 +250,30 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
|
|
|
215
250
|
if (!(await gate({}))) return;
|
|
216
251
|
await runUpdateCommand(argv.slice(3));
|
|
217
252
|
return;
|
|
218
|
-
case "workspace":
|
|
219
|
-
// list/materialize are context-independent: no companion, install, or
|
|
220
|
-
// update gating — an editor extension may call these frequently.
|
|
221
|
-
if (!(await gate({}))) return;
|
|
222
|
-
await runWorkspaceCommand(subcommand, rest);
|
|
223
|
-
return;
|
|
224
253
|
case "working":
|
|
225
254
|
if (!(await gate({}))) return;
|
|
226
255
|
await runWorkingCommand(subcommand, rest);
|
|
227
256
|
return;
|
|
257
|
+
case "wrap": {
|
|
258
|
+
// Recording the answer is the point, so a recorded one must not suppress
|
|
259
|
+
// the wizard; a bad flag is reported without prompting first.
|
|
260
|
+
const wrapArgs = argv.slice(3);
|
|
261
|
+
const parsedWrap = parseWrapArgs(wrapArgs);
|
|
262
|
+
if ("error" in parsedWrap) {
|
|
263
|
+
await runWrapCommand(wrapArgs);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const needs = { ignoreProjection: true, companion: parsedWrap.companion };
|
|
267
|
+
if (!(await gate({ updateGuard: true, companion: needs, install: true }))) return;
|
|
268
|
+
await runWrapCommand(wrapArgs);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
case "unwrap":
|
|
272
|
+
// A recovery path: it withdraws what a wrap placed, so it must never be
|
|
273
|
+
// gated on a companion or an installation it does not consult.
|
|
274
|
+
if (!(await gate({}))) return;
|
|
275
|
+
await runUnwrapCommand(argv.slice(3));
|
|
276
|
+
return;
|
|
228
277
|
default:
|
|
229
278
|
// Unknown commands fail fast: no case matched, so no gate ever ran.
|
|
230
279
|
console.error(`Unknown command: ${command}`);
|
package/src/cli/parse-flags.ts
CHANGED
|
@@ -7,31 +7,56 @@ import type { CapabilityConfig } from "../lib/orchestrator/types";
|
|
|
7
7
|
|
|
8
8
|
export type CliFlags = Record<string, string | string[] | boolean>;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Flags whose presence is the whole value. A flag not listed here consumes the next
|
|
12
|
+
* non-`--` token, so an unlisted boolean silently swallows a positional and then reads
|
|
13
|
+
* as absent — e.g. `--no-push my-change` yielding `{"no-push": "my-change"}`.
|
|
14
|
+
*/
|
|
15
|
+
export type BooleanFlagSet = ReadonlySet<string>;
|
|
16
|
+
|
|
17
|
+
const NO_BOOLEAN_FLAGS: BooleanFlagSet = new Set<string>();
|
|
18
|
+
|
|
19
|
+
export function parseFlags(
|
|
20
|
+
argv: string[],
|
|
21
|
+
booleanFlags: BooleanFlagSet = NO_BOOLEAN_FLAGS,
|
|
22
|
+
): CliFlags {
|
|
11
23
|
const flags: CliFlags = {};
|
|
12
24
|
|
|
25
|
+
const assign = (key: string, value: string): void => {
|
|
26
|
+
const current = flags[key];
|
|
27
|
+
if (current === undefined) flags[key] = value;
|
|
28
|
+
else if (Array.isArray(current)) current.push(value);
|
|
29
|
+
else flags[key] = [current as string, value];
|
|
30
|
+
};
|
|
31
|
+
|
|
13
32
|
for (let index = 0; index < argv.length; index += 1) {
|
|
14
33
|
const token = argv[index];
|
|
15
34
|
if (!token.startsWith("--")) {
|
|
16
35
|
continue;
|
|
17
36
|
}
|
|
18
37
|
|
|
19
|
-
const
|
|
20
|
-
const
|
|
38
|
+
const body = token.slice(2);
|
|
39
|
+
const equals = body.indexOf("=");
|
|
40
|
+
if (equals !== -1) {
|
|
41
|
+
const key = body.slice(0, equals);
|
|
42
|
+
const value = body.slice(equals + 1);
|
|
43
|
+
if (booleanFlags.has(key)) flags[key] = value !== "false";
|
|
44
|
+
else assign(key, value);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
21
47
|
|
|
22
|
-
if (
|
|
23
|
-
flags[
|
|
48
|
+
if (booleanFlags.has(body)) {
|
|
49
|
+
flags[body] = true;
|
|
24
50
|
continue;
|
|
25
51
|
}
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} else {
|
|
32
|
-
flags[key] = [flags[key] as string, next];
|
|
53
|
+
const next = argv[index + 1];
|
|
54
|
+
if (!next || next.startsWith("--")) {
|
|
55
|
+
flags[body] = true;
|
|
56
|
+
continue;
|
|
33
57
|
}
|
|
34
58
|
|
|
59
|
+
assign(body, next);
|
|
35
60
|
index += 1;
|
|
36
61
|
}
|
|
37
62
|
|
package/src/cli/usage.ts
CHANGED
|
@@ -14,17 +14,21 @@ export function usage(): string {
|
|
|
14
14
|
` ${n} companion link`,
|
|
15
15
|
` ${n} companion setup`,
|
|
16
16
|
` ${n} companion list`,
|
|
17
|
+
` ${n} companion sync`,
|
|
17
18
|
` ${n} companion open`,
|
|
18
19
|
` ${n} companion tui`,
|
|
19
|
-
` ${n} workspace list --json`,
|
|
20
|
-
` ${n} workspace materialize --repository ID --companion PATH --json`,
|
|
21
20
|
` ${n} working cleanup`,
|
|
21
|
+
` ${n} wrap [--companion PATH]`,
|
|
22
|
+
` ${n} unwrap`,
|
|
22
23
|
` ${n} hub init [folder]`,
|
|
23
24
|
` ${n} hub add [source] [--id ID] [--path PATH]`,
|
|
24
25
|
` ${n} hub sync [--json] (companions + hub plugins)`,
|
|
25
|
-
` ${n} artifact
|
|
26
|
+
` ${n} artifact publish <anchor|change-name> [--type openspec] [--no-push] [--json]`,
|
|
27
|
+
" target: a dated archive anchor, or a change name matching exactly one archive",
|
|
28
|
+
` ${n} artifact pending [--json]`,
|
|
26
29
|
` ${n} doctor`,
|
|
27
30
|
` ${n} report [--days N] [--input FILE|-] [--json]`,
|
|
31
|
+
` ${n} studio`,
|
|
28
32
|
` ${n} config`,
|
|
29
33
|
` ${n} claude [args...] (use -- --no-git to bypass companion Git sync)`,
|
|
30
34
|
` ${n} opencode [args...] (use -- --no-git to bypass companion Git sync)`,
|
|
@@ -41,6 +45,11 @@ export function usage(): string {
|
|
|
41
45
|
` ${n} working cleanup removes Mate-owned local integration without resetting product work`,
|
|
42
46
|
" tracked files and capability data remain untouched; link or launch recreates required state",
|
|
43
47
|
"",
|
|
48
|
+
"Wrapped repositories:",
|
|
49
|
+
` ${n} wrap configures the repository so sessions you start yourself load the companion`,
|
|
50
|
+
` ${n} claude and ${n} opencode do not run while a repository is wrapped`,
|
|
51
|
+
` ${n} unwrap withdraws the wrap and restores managed launches; the link is kept`,
|
|
52
|
+
"",
|
|
44
53
|
"Doctor states:",
|
|
45
54
|
" linked-working-repository — cwd is inside a registered working repository",
|
|
46
55
|
" companion-repository — cwd is the companion repository itself",
|
package/src/framework.ts
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Framework identity: names everything identity-shaped — state directories
|
|
3
|
-
* (`~/.mate`, `.mate/`), managed-block markers, `MATE_*` env values, usage
|
|
4
|
-
* output, command hints, error prefixes, agent guidance, and permission
|
|
5
|
-
* entries. The distribution is always invoked as `mate`.
|
|
6
|
-
*/
|
|
7
|
-
export const FRAMEWORK_NAME = "mate";
|
|
1
|
+
export { FRAMEWORK_NAME } from "./runtime/framework";
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
// Show the active Mate companion paths in Claude Code
|
|
1
|
+
// Show the active Mate companion paths in Claude Code, and repair the
|
|
2
|
+
// companion's Git state before the session reads it.
|
|
3
|
+
import {
|
|
4
|
+
syncCompanionUnattended,
|
|
5
|
+
unattendedSyncStalenessLines,
|
|
6
|
+
type UnattendedSyncOptions,
|
|
7
|
+
} from "../runtime/companion-sync";
|
|
8
|
+
import { hasLaunchEnvironment, resolveCompanionRuntime } from "../runtime/env";
|
|
9
|
+
import { projectionFreshness, projectionStalenessLines } from "../runtime/freshness";
|
|
10
|
+
import { mateVersion } from "../runtime/install";
|
|
2
11
|
import type { HookEnv } from "./validate-artifact-path";
|
|
3
12
|
|
|
4
13
|
export interface BannerOutcome {
|
|
@@ -6,20 +15,64 @@ export interface BannerOutcome {
|
|
|
6
15
|
stdout: string;
|
|
7
16
|
}
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Marks the copy a wrap inlines into the Working Repository's settings. A
|
|
20
|
+
* managed launch loads the plugin's own copy *and* that document, and both
|
|
21
|
+
* registrations resolve to the same command against the same environment, so
|
|
22
|
+
* nothing inside the process tells them apart. The flag is what keeps the
|
|
23
|
+
* banner from printing twice in exactly the session that is configured
|
|
24
|
+
* correctly — the projected copy defers, the launch's own copy prints.
|
|
25
|
+
*/
|
|
26
|
+
export const PROJECTED_BANNER_FLAG = "--projected";
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Resolves in-process from either source — no subprocess in either branch.
|
|
30
|
+
* With `mate doctor` out of scope this is one of only two surfaces where a
|
|
31
|
+
* drifted wrap becomes visible, so staleness is reported, never suppressed.
|
|
32
|
+
*/
|
|
33
|
+
export function buildBanner(
|
|
34
|
+
env: HookEnv,
|
|
35
|
+
cwd: string = process.cwd(),
|
|
36
|
+
argv: readonly string[] = [],
|
|
37
|
+
syncOptions: UnattendedSyncOptions = {},
|
|
38
|
+
): BannerOutcome {
|
|
39
|
+
if (argv.includes(PROJECTED_BANNER_FLAG) && hasLaunchEnvironment(env)) {
|
|
40
|
+
return { exitCode: 0, stdout: "" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { context, projection } = resolveCompanionRuntime(env, cwd);
|
|
44
|
+
if (!context.repositoryPath || !context.companionPath) return { exitCode: 0, stdout: "" };
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Session start is the only moment at which the companion may move without
|
|
48
|
+
* invalidating a read the session has already taken, so the repair happens
|
|
49
|
+
* here and nowhere later. The launch-environment check comes first, so a
|
|
50
|
+
* Managed Session pays nothing at all — not the freshness read, not the
|
|
51
|
+
* policy read; and the consent gate is the operation's own.
|
|
52
|
+
*/
|
|
53
|
+
const gitNotes = hasLaunchEnvironment(env)
|
|
54
|
+
? []
|
|
55
|
+
: unattendedSyncStalenessLines(syncCompanionUnattended(context.companionPath, syncOptions));
|
|
56
|
+
|
|
57
|
+
const lines = [
|
|
58
|
+
`mate v${env.MATE_VERSION || mateVersion()}`,
|
|
59
|
+
` repo: ${context.repositoryPath}`,
|
|
60
|
+
` mate: ${context.companionPath}`,
|
|
61
|
+
];
|
|
62
|
+
if (projection) {
|
|
63
|
+
for (const note of projectionStalenessLines(projection, projectionFreshness(projection))) {
|
|
64
|
+
lines.push(` ${note}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** `systemMessage` only: the model must never be told to run the command. */
|
|
68
|
+
for (const note of gitNotes) lines.push(` ${note}`);
|
|
69
|
+
|
|
70
|
+
return { exitCode: 0, stdout: JSON.stringify({ systemMessage: lines.join("\n") }) + "\n" };
|
|
18
71
|
}
|
|
19
72
|
|
|
20
73
|
// Plugin-shim entry.
|
|
21
|
-
export function run(): number {
|
|
22
|
-
const outcome = buildBanner(process.env);
|
|
74
|
+
export function run(argv: readonly string[] = process.argv.slice(2)): number {
|
|
75
|
+
const outcome = buildBanner(process.env, process.cwd(), argv);
|
|
23
76
|
if (outcome.stdout) process.stdout.write(outcome.stdout);
|
|
24
77
|
return outcome.exitCode;
|
|
25
78
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Deliver the companion guidance to a Claude session Mate did not launch.
|
|
2
|
+
import { buildProjectedGuidance } from "../runtime/projected-guidance";
|
|
3
|
+
import type { HookEnv } from "./validate-artifact-path";
|
|
4
|
+
|
|
5
|
+
export interface GuidanceOutcome {
|
|
6
|
+
exitCode: number;
|
|
7
|
+
stdout: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `SessionStart.additionalContext` is the file-based form of the managed
|
|
12
|
+
* launch's `--append-system-prompt`: a settings document can declare a hook,
|
|
13
|
+
* and there is no settings key for the flag. The two never both fire —
|
|
14
|
+
* `buildProjectedGuidance` yields nothing when a launch environment is present
|
|
15
|
+
* — so the plugin may carry this group alongside the projected copy.
|
|
16
|
+
*
|
|
17
|
+
* Emits nothing rather than a note when no companion resolves: an unwrapped
|
|
18
|
+
* repository must read exactly as it did before the hook existed.
|
|
19
|
+
*/
|
|
20
|
+
export function buildSessionGuidance(env: HookEnv, cwd: string = process.cwd()): GuidanceOutcome {
|
|
21
|
+
const guidance = buildProjectedGuidance(env, cwd);
|
|
22
|
+
if (!guidance) return { exitCode: 0, stdout: "" };
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
exitCode: 0,
|
|
26
|
+
stdout: `${JSON.stringify({
|
|
27
|
+
hookSpecificOutput: {
|
|
28
|
+
hookEventName: "SessionStart",
|
|
29
|
+
additionalContext: guidance,
|
|
30
|
+
},
|
|
31
|
+
})}\n`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Plugin-shim entry.
|
|
36
|
+
export function run(): number {
|
|
37
|
+
const outcome = buildSessionGuidance(process.env);
|
|
38
|
+
if (outcome.stdout) process.stdout.write(outcome.stdout);
|
|
39
|
+
return outcome.exitCode;
|
|
40
|
+
}
|