@uniqbit/mate-core 0.15.4-canary.3 → 0.15.4-canary.6
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/package.json +1 -1
- package/src/cli/commands/artifact/finish/openspec.ts +1 -5
- package/src/cli/commands/companion/link.ts +0 -5
- package/src/cli/commands/companion/tui.ts +0 -1
- package/src/cli/commands/doctor.ts +253 -166
- package/src/cli/commands/setup.ts +2 -4
- package/src/cli/companion-link-wizard.tsx +22 -78
- package/src/cli/main.ts +17 -4
- package/src/cli/repo-list-table.tsx +2 -7
- package/src/lib/install.ts +30 -38
- package/src/lib/orchestrator/adapters/base.ts +3 -4
- package/src/lib/orchestrator/companion-hub.ts +8 -2
- package/src/lib/orchestrator/companion-store.ts +1 -35
- package/src/lib/orchestrator/config-store.ts +22 -7
- package/src/lib/orchestrator/framework-context.ts +0 -1
- package/src/lib/orchestrator/launcher.ts +3 -9
- package/src/lib/orchestrator/opencode-guidance.ts +1 -2
- package/src/lib/orchestrator/repo-local-registry.ts +3 -1
- package/src/lib/orchestrator/root-context.ts +117 -0
- package/src/lib/orchestrator/setup-compatibilities.ts +1 -1
- package/src/lib/orchestrator/setup-preflight.ts +1 -1
- package/src/lib/orchestrator/types.ts +1 -15
- package/src/lib/orchestrator/working-repo-store.ts +7 -1
- package/src/playbooks/companion-guidance.ts +1 -1
- package/src/runtime/env.ts +0 -3
- package/src/templates/capabilities/openspec-cap/claude/hooks/mate-artifact-finish.sh +1 -78
- package/src/tools/setup/policy.ts +4 -7
- package/src/tools/setup/providers/claude.ts +2 -18
- package/src/tools/setup/providers/opencode.ts +1 -1
- package/src/tools/setup.ts +10 -4
package/package.json
CHANGED
|
@@ -61,10 +61,7 @@ async function capSync(context: FinishContext): Promise<boolean> {
|
|
|
61
61
|
const previous = process.exitCode;
|
|
62
62
|
process.exitCode = 0;
|
|
63
63
|
const previousEnv = new Map(
|
|
64
|
-
["MATE_ARTIFACT_PATH", "MATE_REPO_PATH", "MATE_REPO_ID"
|
|
65
|
-
key,
|
|
66
|
-
process.env[key],
|
|
67
|
-
]),
|
|
64
|
+
["MATE_ARTIFACT_PATH", "MATE_REPO_PATH", "MATE_REPO_ID"].map((key) => [key, process.env[key]]),
|
|
68
65
|
);
|
|
69
66
|
|
|
70
67
|
// Pass both sides of the companion/working-repo boundary explicitly. Capability
|
|
@@ -73,7 +70,6 @@ async function capSync(context: FinishContext): Promise<boolean> {
|
|
|
73
70
|
if (context.repository) {
|
|
74
71
|
process.env.MATE_REPO_PATH = context.repository.path;
|
|
75
72
|
process.env.MATE_REPO_ID = context.repository.id;
|
|
76
|
-
process.env.MATE_REPO_PROFILE = context.repository.profile;
|
|
77
73
|
}
|
|
78
74
|
try {
|
|
79
75
|
await runIndexCapCommand([]);
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
type CompanionLinkWizardOptions,
|
|
11
11
|
} from "../../companion-link-wizard";
|
|
12
12
|
import { extractRepoName } from "../artifact/extract-repo-name";
|
|
13
|
-
import { defaultConfig } from "../../../lib/orchestrator/config-store";
|
|
14
13
|
import { GlobalConfigStore } from "../../../lib/orchestrator/global-config-store";
|
|
15
14
|
import { CompanionResolver } from "../../../lib/orchestrator/companion-resolver";
|
|
16
15
|
import { inspectSetupPreflight } from "../../../lib/orchestrator/setup-preflight";
|
|
@@ -131,7 +130,6 @@ export async function runCompanionLinkCommandWithDeps(
|
|
|
131
130
|
|
|
132
131
|
const cwd = path.resolve(process.cwd());
|
|
133
132
|
const existingMatch = await resolveCompanion(cwd);
|
|
134
|
-
const profiles = Object.keys(defaultConfig().profiles);
|
|
135
133
|
const companionsDir = companionsHomeDir();
|
|
136
134
|
const candidatePaths = new Set(await listCompanions());
|
|
137
135
|
if (existingMatch) candidatePaths.add(existingMatch.companionPath);
|
|
@@ -148,7 +146,6 @@ export async function runCompanionLinkCommandWithDeps(
|
|
|
148
146
|
).filter((candidate): candidate is string => candidate !== null);
|
|
149
147
|
|
|
150
148
|
const inputs = await selectInputs({
|
|
151
|
-
profiles,
|
|
152
149
|
existingCompanions,
|
|
153
150
|
});
|
|
154
151
|
if (!inputs) {
|
|
@@ -241,7 +238,6 @@ export async function runCompanionLinkCommandWithDeps(
|
|
|
241
238
|
const repository: LinkedRepository = {
|
|
242
239
|
id: path.basename(cwd),
|
|
243
240
|
path: cwd,
|
|
244
|
-
profile: inputs.profile,
|
|
245
241
|
};
|
|
246
242
|
|
|
247
243
|
const registerRepository =
|
|
@@ -292,6 +288,5 @@ export async function runCompanionLinkCommandWithDeps(
|
|
|
292
288
|
console.log(` Companion: ${companionPath}`);
|
|
293
289
|
}
|
|
294
290
|
console.log(` Source: ${persistedSource}`);
|
|
295
|
-
console.log(` Profile: ${repository.profile}`);
|
|
296
291
|
console.log(" Workspace: run `mate companion open` to open the workspace in your editor");
|
|
297
292
|
}
|
|
@@ -81,7 +81,6 @@ export async function runCompanionTuiCommand(): Promise<void> {
|
|
|
81
81
|
if (repository) {
|
|
82
82
|
process.env.MATE_REPO_PATH = repository.path;
|
|
83
83
|
process.env.MATE_REPO_ID = repository.id;
|
|
84
|
-
process.env.MATE_REPO_PROFILE = repository.profile;
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
console.log(renderBanner(repoPath, companionPath));
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { FRAMEWORK_NAME } from "../../framework";
|
|
5
5
|
import { getActiveDistribution } from "../../distribution";
|
|
6
|
-
import { CompanionResolver } from "../../lib/orchestrator/companion-resolver";
|
|
7
|
-
import { CompanionStore, resolvePolicyFromConfig } from "../../lib/orchestrator/companion-store";
|
|
8
|
-
import { ConfigStore } from "../../lib/orchestrator/config-store";
|
|
9
6
|
import { checkEngineRequirement } from "../../lib/orchestrator/engine-guard";
|
|
10
|
-
import { GlobalConfigStore } from "../../lib/orchestrator/global-config-store";
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
7
|
+
import type { GlobalConfigStore } from "../../lib/orchestrator/global-config-store";
|
|
8
|
+
import { pathIsDirectory } from "../../lib/orchestrator/repo-local-registry";
|
|
9
|
+
import { resolveRootContext, type RootContext } from "../../lib/orchestrator/root-context";
|
|
10
|
+
import type { CapabilityConfig, FrameworkConfig, HubMember } from "../../lib/orchestrator/types";
|
|
14
11
|
import { getRequiredPluginDrift } from "../../tools/setup/policy";
|
|
15
12
|
import { resolveCommandOnPath } from "../../tools/setup/utils";
|
|
16
13
|
import { printSection, renderKeyValueTable, renderTable } from "./status";
|
|
@@ -19,11 +16,43 @@ interface DoctorDeps {
|
|
|
19
16
|
globalConfigStore?: GlobalConfigStore;
|
|
20
17
|
cwd?: string;
|
|
21
18
|
pathValue?: string;
|
|
19
|
+
/** Returns a member checkout's HEAD commit, or null when unreadable. */
|
|
20
|
+
gitHead?: (memberPath: string) => string | null;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
export type DoctorKind = "core" | "working" | "companion" | "hub";
|
|
24
|
+
|
|
25
|
+
export interface DoctorToolInstallation {
|
|
26
|
+
tool: string;
|
|
27
|
+
source: string;
|
|
28
|
+
status: "ok" | "missing";
|
|
29
|
+
path: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DoctorHubMember {
|
|
33
|
+
id: string;
|
|
34
|
+
source: "git" | "local";
|
|
35
|
+
path: string;
|
|
36
|
+
exists: boolean;
|
|
37
|
+
commitStatus: "ok" | "drifted" | "missing" | "unknown";
|
|
38
|
+
materializedCommit: string | null;
|
|
39
|
+
actualCommit: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DoctorReport {
|
|
43
|
+
kind: DoctorKind;
|
|
44
|
+
cwd: string;
|
|
45
|
+
companionPath: string | null;
|
|
46
|
+
repositoryId: string | null;
|
|
47
|
+
multipleCompanions: Array<{ companionPath: string; repositoryId: string }>;
|
|
48
|
+
policy?: { allowedAgents: string[] };
|
|
49
|
+
policyError?: string;
|
|
50
|
+
capabilities?: string[];
|
|
51
|
+
toolInstallations?: DoctorToolInstallation[];
|
|
52
|
+
requiredPluginDrift?: Array<{ pluginId: string; kind: string; reason: string }>;
|
|
53
|
+
engineRequirement?: { range: string; ok: boolean; detail: string };
|
|
54
|
+
hub?: { members: DoctorHubMember[] };
|
|
55
|
+
resolutionFailures: Array<{ companionPath: string; message: string }>;
|
|
27
56
|
}
|
|
28
57
|
|
|
29
58
|
interface ToolCheck {
|
|
@@ -32,75 +61,26 @@ interface ToolCheck {
|
|
|
32
61
|
source: string;
|
|
33
62
|
}
|
|
34
63
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
/** Doctor's view of the shared root kind: registry/env resolution means cwd is a working repo. */
|
|
65
|
+
function doctorKind(root: RootContext): DoctorKind {
|
|
66
|
+
if (root.kind === "core") return "core";
|
|
67
|
+
if (root.kind === "hub") return "hub";
|
|
68
|
+
if (root.origin === "env" || root.origin === "registry") return "working";
|
|
69
|
+
return root.kind;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
|
|
44
|
-
const localDir = path.join(cwd, `.${FRAMEWORK_NAME}`);
|
|
45
|
-
|
|
72
|
+
function defaultGitHead(memberPath: string): string | null {
|
|
46
73
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
return execFileSync("git", ["-C", memberPath, "rev-parse", "HEAD"], {
|
|
75
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
76
|
+
})
|
|
77
|
+
.toString()
|
|
78
|
+
.trim();
|
|
49
79
|
} catch {
|
|
50
|
-
return
|
|
80
|
+
return null;
|
|
51
81
|
}
|
|
52
82
|
}
|
|
53
83
|
|
|
54
|
-
function renderList(items: string[], empty: string): string {
|
|
55
|
-
return items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : empty;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function renderEngineRequirement(
|
|
59
|
-
config: FrameworkConfig,
|
|
60
|
-
distributionName: string,
|
|
61
|
-
currentVersion: string,
|
|
62
|
-
): string {
|
|
63
|
-
const range = config.engines?.[distributionName];
|
|
64
|
-
const result = checkEngineRequirement(config, distributionName, currentVersion);
|
|
65
|
-
return result.ok
|
|
66
|
-
? `engines.${distributionName}: satisfied (requires ${range}, running ${currentVersion})`
|
|
67
|
-
: `engines.${distributionName}: ${result.reason}`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Only the engines key matching the running distribution produces a
|
|
71
|
-
// diagnostic; foreign distribution keys are ignored entirely.
|
|
72
|
-
function printEngineRequirement(config: FrameworkConfig): void {
|
|
73
|
-
const distribution = getActiveDistribution().config;
|
|
74
|
-
if (!config.engines?.[FRAMEWORK_NAME]) return;
|
|
75
|
-
printSection(
|
|
76
|
-
"Version Requirement",
|
|
77
|
-
renderEngineRequirement(config, FRAMEWORK_NAME, distribution.version),
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Companions with no drift produce no section at all: required plugins are
|
|
82
|
-
// silent when healthy.
|
|
83
|
-
function printRequiredPluginDrift(config: FrameworkConfig): void {
|
|
84
|
-
const drift = getRequiredPluginDrift(config);
|
|
85
|
-
if (drift.length === 0) return;
|
|
86
|
-
printSection(
|
|
87
|
-
"Required Plugin Drift",
|
|
88
|
-
renderTable(
|
|
89
|
-
["Plugin", "Kind", "Issue"],
|
|
90
|
-
drift.map((entry) => [entry.pluginId, entry.kind, entry.reason]),
|
|
91
|
-
),
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function renderCapabilities(capabilities: CapabilityConfig[]): string {
|
|
96
|
-
return capabilities.length > 0
|
|
97
|
-
? renderTable(
|
|
98
|
-
["Capability"],
|
|
99
|
-
capabilities.map((capability) => [capability.name]),
|
|
100
|
-
)
|
|
101
|
-
: "No capabilities configured.";
|
|
102
|
-
}
|
|
103
|
-
|
|
104
84
|
function getSelectedToolChecks(config: {
|
|
105
85
|
capabilities?: CapabilityConfig[];
|
|
106
86
|
packageManagers?: string[];
|
|
@@ -136,76 +116,183 @@ function getSelectedToolChecks(config: {
|
|
|
136
116
|
return checks;
|
|
137
117
|
}
|
|
138
118
|
|
|
139
|
-
function
|
|
140
|
-
config:
|
|
141
|
-
capabilities?: CapabilityConfig[];
|
|
142
|
-
packageManagers?: string[];
|
|
143
|
-
},
|
|
119
|
+
function collectToolInstallations(
|
|
120
|
+
config: FrameworkConfig,
|
|
144
121
|
pathValue: string,
|
|
145
|
-
):
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return
|
|
122
|
+
): DoctorToolInstallation[] {
|
|
123
|
+
return getSelectedToolChecks(config).map((check) => {
|
|
124
|
+
const resolved = resolveCommandOnPath(check.command, pathValue);
|
|
125
|
+
return {
|
|
126
|
+
tool: check.name,
|
|
127
|
+
source: check.source,
|
|
128
|
+
status: resolved ? "ok" : "missing",
|
|
129
|
+
path: resolved ?? null,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Only the engines key matching the running distribution produces a
|
|
135
|
+
// diagnostic; foreign distribution keys are ignored entirely.
|
|
136
|
+
function collectEngineRequirement(
|
|
137
|
+
config: FrameworkConfig,
|
|
138
|
+
): DoctorReport["engineRequirement"] | undefined {
|
|
139
|
+
const range = config.engines?.[FRAMEWORK_NAME];
|
|
140
|
+
if (!range) return undefined;
|
|
141
|
+
const currentVersion = getActiveDistribution().config.version;
|
|
142
|
+
const result = checkEngineRequirement(config, FRAMEWORK_NAME, currentVersion);
|
|
143
|
+
return {
|
|
144
|
+
range,
|
|
145
|
+
ok: result.ok,
|
|
146
|
+
detail: result.ok
|
|
147
|
+
? `engines.${FRAMEWORK_NAME}: satisfied (requires ${range}, running ${currentVersion})`
|
|
148
|
+
: `engines.${FRAMEWORK_NAME}: ${result.reason}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function collectHubMember(
|
|
153
|
+
hubPath: string,
|
|
154
|
+
member: HubMember,
|
|
155
|
+
gitHead: (memberPath: string) => string | null,
|
|
156
|
+
): Promise<DoctorHubMember> {
|
|
157
|
+
const memberPath = path.resolve(hubPath, member.path);
|
|
158
|
+
const base = {
|
|
159
|
+
id: member.id,
|
|
160
|
+
source: member.source.kind,
|
|
161
|
+
path: memberPath,
|
|
162
|
+
materializedCommit: member.materializedCommit ?? null,
|
|
163
|
+
};
|
|
164
|
+
if (!(await pathIsDirectory(memberPath))) {
|
|
165
|
+
return { ...base, exists: false, commitStatus: "missing", actualCommit: null };
|
|
166
|
+
}
|
|
167
|
+
if (!member.materializedCommit) {
|
|
168
|
+
return { ...base, exists: true, commitStatus: "ok", actualCommit: null };
|
|
149
169
|
}
|
|
170
|
+
const actualCommit = gitHead(memberPath);
|
|
171
|
+
return {
|
|
172
|
+
...base,
|
|
173
|
+
exists: true,
|
|
174
|
+
commitStatus:
|
|
175
|
+
actualCommit === null
|
|
176
|
+
? "unknown"
|
|
177
|
+
: actualCommit === member.materializedCommit
|
|
178
|
+
? "ok"
|
|
179
|
+
: "drifted",
|
|
180
|
+
actualCommit,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
150
183
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
184
|
+
async function collectWorkingSections(
|
|
185
|
+
report: DoctorReport,
|
|
186
|
+
root: RootContext,
|
|
187
|
+
pathValue: string,
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
if (!root.rootPath || !root.repositoryId || !root.config) {
|
|
190
|
+
report.policyError = "No repository ID was provided for the resolved companion.";
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
report.policy = { allowedAgents: root.config.allowedAgents ?? [] };
|
|
195
|
+
collectConfiguredRootSections(report, root.config, pathValue);
|
|
158
196
|
}
|
|
159
197
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
198
|
+
function collectConfiguredRootSections(
|
|
199
|
+
report: DoctorReport,
|
|
200
|
+
config: FrameworkConfig,
|
|
201
|
+
pathValue: string,
|
|
202
|
+
): void {
|
|
203
|
+
report.capabilities = (config.capabilities ?? []).map((capability) => capability.name);
|
|
204
|
+
report.toolInstallations = collectToolInstallations(config, pathValue);
|
|
205
|
+
report.requiredPluginDrift = getRequiredPluginDrift(config);
|
|
206
|
+
report.engineRequirement = collectEngineRequirement(config);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function collectDoctorReport(deps: DoctorDeps = {}): Promise<DoctorReport> {
|
|
210
|
+
const cwd = path.resolve(deps.cwd ?? process.cwd());
|
|
168
211
|
const pathValue = deps.pathValue ?? process.env.PATH ?? "";
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
212
|
+
const gitHead = deps.gitHead ?? defaultGitHead;
|
|
213
|
+
const root = await resolveRootContext(cwd, deps);
|
|
214
|
+
const kind = doctorKind(root);
|
|
172
215
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
216
|
+
const report: DoctorReport = {
|
|
217
|
+
kind,
|
|
218
|
+
cwd,
|
|
219
|
+
companionPath: root.rootPath ?? null,
|
|
220
|
+
repositoryId: root.repositoryId ?? null,
|
|
221
|
+
multipleCompanions:
|
|
222
|
+
root.resolution.ambiguousMatches.length > 1 ? root.resolution.ambiguousMatches : [],
|
|
223
|
+
resolutionFailures: root.resolution.failures,
|
|
224
|
+
};
|
|
180
225
|
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} else if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
226
|
+
if (kind === "working") {
|
|
227
|
+
await collectWorkingSections(report, root, pathValue);
|
|
228
|
+
} else if (kind === "companion" && root.config) {
|
|
229
|
+
collectConfiguredRootSections(report, root.config, pathValue);
|
|
230
|
+
} else if (kind === "hub" && root.rootPath && root.config) {
|
|
231
|
+
report.hub = {
|
|
232
|
+
members: await Promise.all(
|
|
233
|
+
(root.config.hub?.companions ?? []).map((member) =>
|
|
234
|
+
collectHubMember(root.rootPath!, member, gitHead),
|
|
235
|
+
),
|
|
236
|
+
),
|
|
237
|
+
};
|
|
238
|
+
report.requiredPluginDrift = getRequiredPluginDrift(root.config);
|
|
239
|
+
report.engineRequirement = collectEngineRequirement(root.config);
|
|
194
240
|
}
|
|
195
241
|
|
|
242
|
+
return report;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function renderList(items: string[], empty: string): string {
|
|
246
|
+
return items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : empty;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function renderCapabilities(capabilities: string[]): string {
|
|
250
|
+
return capabilities.length > 0
|
|
251
|
+
? renderTable(
|
|
252
|
+
["Capability"],
|
|
253
|
+
capabilities.map((capability) => [capability]),
|
|
254
|
+
)
|
|
255
|
+
: "No capabilities configured.";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function renderToolInstallations(installations: DoctorToolInstallation[]): string {
|
|
259
|
+
if (installations.length === 0) {
|
|
260
|
+
return "No selected capabilities or package managers require command checks.";
|
|
261
|
+
}
|
|
262
|
+
return renderTable(
|
|
263
|
+
["Tool", "Source", "Status", "Path"],
|
|
264
|
+
installations.map((item) => [item.tool, item.source, item.status, item.path ?? ""]),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function renderHubMembers(members: DoctorHubMember[]): string {
|
|
269
|
+
if (members.length === 0) return "No hub members configured.";
|
|
270
|
+
return renderTable(
|
|
271
|
+
["Member", "Source", "Path", "Exists", "Commit"],
|
|
272
|
+
members.map((member) => [
|
|
273
|
+
member.id,
|
|
274
|
+
member.source,
|
|
275
|
+
member.path,
|
|
276
|
+
member.exists ? "yes" : "no",
|
|
277
|
+
member.commitStatus,
|
|
278
|
+
]),
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function renderHumanReport(report: DoctorReport): void {
|
|
196
283
|
console.log("Mate Doctor");
|
|
197
284
|
console.log("");
|
|
198
285
|
printSection(
|
|
199
286
|
"Context",
|
|
200
287
|
renderKeyValueTable([
|
|
201
|
-
["State",
|
|
202
|
-
["Current Working Directory", cwd],
|
|
203
|
-
["Companion Path", companionPath
|
|
204
|
-
["Repository ID", repositoryId
|
|
288
|
+
["State", report.kind],
|
|
289
|
+
["Current Working Directory", report.cwd],
|
|
290
|
+
["Companion Path", report.companionPath ?? "none"],
|
|
291
|
+
["Repository ID", report.repositoryId ?? "none"],
|
|
205
292
|
]),
|
|
206
293
|
);
|
|
207
294
|
|
|
208
|
-
if (
|
|
295
|
+
if (report.multipleCompanions.length > 1) {
|
|
209
296
|
printSection(
|
|
210
297
|
"Multiple Companions",
|
|
211
298
|
[
|
|
@@ -215,63 +302,63 @@ export async function runDoctorCommand(_argv: string[] = [], deps: DoctorDeps =
|
|
|
215
302
|
"",
|
|
216
303
|
renderTable(
|
|
217
304
|
["Companion Path", "Repository ID"],
|
|
218
|
-
|
|
305
|
+
report.multipleCompanions.map((match) => [match.companionPath, match.repositoryId]),
|
|
219
306
|
),
|
|
220
307
|
].join("\n"),
|
|
221
308
|
);
|
|
222
309
|
}
|
|
223
310
|
|
|
224
|
-
if (
|
|
225
|
-
const { configStore, workingRepoStore } = storesForCompanion(companionPath);
|
|
226
|
-
const config = await configStore.load();
|
|
227
|
-
let resolvedPolicy;
|
|
228
|
-
|
|
229
|
-
if (repository) {
|
|
230
|
-
resolvedPolicy = resolvePolicyFromConfig(config, [repository], repository.id);
|
|
231
|
-
} else {
|
|
232
|
-
resolvedPolicy = await new CompanionStore(configStore, workingRepoStore).resolvePolicy(
|
|
233
|
-
repositoryId,
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (!repository) {
|
|
238
|
-
const working = await workingRepoStore.load();
|
|
239
|
-
repository = working.repos.find((repo) => repo.id === repositoryId) ?? null;
|
|
240
|
-
}
|
|
241
|
-
|
|
311
|
+
if (report.policy) {
|
|
242
312
|
printSection(
|
|
243
|
-
"
|
|
244
|
-
|
|
245
|
-
["Profile", repository?.profile ?? "unknown"],
|
|
246
|
-
["Allowed Agents", resolvedPolicy.allowedAgents.join(", ") || "none"],
|
|
247
|
-
]),
|
|
313
|
+
"Allowed Agents",
|
|
314
|
+
renderList(report.policy.allowedAgents, "No allowed agents configured."),
|
|
248
315
|
);
|
|
316
|
+
}
|
|
317
|
+
if (report.policyError) printSection("Policy", report.policyError);
|
|
318
|
+
if (report.capabilities) printSection("Capabilities", renderCapabilities(report.capabilities));
|
|
319
|
+
if (report.toolInstallations) {
|
|
320
|
+
printSection("Tool Installations", renderToolInstallations(report.toolInstallations));
|
|
321
|
+
}
|
|
322
|
+
if (report.hub) printSection("Hub Members", renderHubMembers(report.hub.members));
|
|
323
|
+
// Companions with no drift produce no section at all: required plugins are
|
|
324
|
+
// silent when healthy.
|
|
325
|
+
if (report.requiredPluginDrift && report.requiredPluginDrift.length > 0) {
|
|
249
326
|
printSection(
|
|
250
|
-
"
|
|
251
|
-
|
|
327
|
+
"Required Plugin Drift",
|
|
328
|
+
renderTable(
|
|
329
|
+
["Plugin", "Kind", "Issue"],
|
|
330
|
+
report.requiredPluginDrift.map((entry) => [entry.pluginId, entry.kind, entry.reason]),
|
|
331
|
+
),
|
|
252
332
|
);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
printEngineRequirement(config);
|
|
257
|
-
} else if (state === "linked-working-repository") {
|
|
258
|
-
printSection("Policy", "No repository ID was provided for the resolved companion.");
|
|
259
|
-
} else if (companionPath) {
|
|
260
|
-
const { configStore } = storesForCompanion(companionPath);
|
|
261
|
-
const config = await configStore.load();
|
|
262
|
-
printSection("Capabilities", renderCapabilities(config.capabilities ?? []));
|
|
263
|
-
printSection("Tool Installations", renderToolInstallations(config, pathValue));
|
|
264
|
-
printRequiredPluginDrift(config);
|
|
265
|
-
printEngineRequirement(config);
|
|
333
|
+
}
|
|
334
|
+
if (report.engineRequirement) {
|
|
335
|
+
printSection("Version Requirement", report.engineRequirement.detail);
|
|
266
336
|
}
|
|
267
337
|
|
|
268
338
|
printSection(
|
|
269
339
|
"Resolution Failures",
|
|
270
|
-
|
|
340
|
+
report.resolutionFailures.length > 0
|
|
271
341
|
? renderTable(
|
|
272
342
|
["Companion Path", "Error"],
|
|
273
|
-
|
|
343
|
+
report.resolutionFailures.map((failure) => [failure.companionPath, failure.message]),
|
|
274
344
|
)
|
|
275
345
|
: "No companion resolution failures encountered.",
|
|
276
346
|
);
|
|
277
347
|
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @command mate doctor
|
|
351
|
+
* @description Reports the current directory's root kind — `working` (linked
|
|
352
|
+
* working repository), `companion`, `hub`, or `core` (not linked) — with
|
|
353
|
+
* kind-specific diagnostics: policy and capabilities for working repos,
|
|
354
|
+
* capabilities and tool checks for companions, member health for hubs.
|
|
355
|
+
* `--json` emits the same report as one JSON document.
|
|
356
|
+
*/
|
|
357
|
+
export async function runDoctorCommand(argv: string[] = [], deps: DoctorDeps = {}): Promise<void> {
|
|
358
|
+
const report = await collectDoctorReport(deps);
|
|
359
|
+
if (argv.includes("--json")) {
|
|
360
|
+
console.log(JSON.stringify(report, null, 2));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
renderHumanReport(report);
|
|
364
|
+
}
|
|
@@ -56,7 +56,7 @@ export async function runSetupCommand(argv: string[]): Promise<void> {
|
|
|
56
56
|
* Resolves agent/package-manager/capability selections either from CLI flags
|
|
57
57
|
* (non-interactive) or from the interactive `selectSetupCompatibilities` picker,
|
|
58
58
|
* seeded from the existing config when one exists. Writes the resulting config
|
|
59
|
-
* via `setup.execute` and prints a summary table of
|
|
59
|
+
* via `setup.execute` and prints a summary table of allowed agents, package managers,
|
|
60
60
|
* and capabilities.
|
|
61
61
|
* @flags
|
|
62
62
|
* - `--agents <list>` — parsed via `parseAllowedAgents`.
|
|
@@ -192,9 +192,7 @@ async function runSetupFlow(
|
|
|
192
192
|
const { config } = result;
|
|
193
193
|
const rows: [string, string][] = [];
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
rows.push([`profile:${profileName}`, (profile.allowedAgents ?? []).join(", ") || "(none)"]);
|
|
197
|
-
}
|
|
195
|
+
rows.push(["allowedAgents", (config.allowedAgents ?? []).join(", ") || "(none)"]);
|
|
198
196
|
|
|
199
197
|
const capabilities = config.capabilities ?? [];
|
|
200
198
|
rows.push(["packageManagers", (config.packageManagers ?? ["bun"]).join(", ") || "(none)"]);
|