@rosenfan/matrix 0.1.4 → 0.1.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/LICENSE +21 -21
- package/README.md +41 -40
- package/README.zh-CN.md +39 -38
- package/{.claude → assets}/skills/matrix/SKILL.md +79 -78
- package/{.claude → assets}/skills/matrix/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix/scripts/matrix_claude.py +11 -11
- package/{.claude → assets}/skills/matrix/scripts/matrix_state.py +49 -49
- package/assets/skills/matrix/scripts/test_matrix_claude.py +69 -0
- package/assets/skills/matrix/scripts/test_matrix_state.py +69 -0
- package/{.claude → assets}/skills/matrix-archive/SKILL.md +36 -36
- package/{.claude/skills/matrix-hotfix → assets/skills/matrix-archive}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-build/SKILL.md +41 -41
- package/{.claude/skills/matrix-archive → assets/skills/matrix-build}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-design/SKILL.md +71 -65
- package/{.claude → assets}/skills/matrix-design/agents/openai.yaml +4 -4
- package/assets/skills/matrix-handoff/SKILL.md +89 -0
- package/{.claude/skills/matrix-claude → assets/skills/matrix-handoff}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-hotfix/SKILL.md +8 -8
- package/{.claude/skills/matrix-build → assets/skills/matrix-hotfix}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-open/SKILL.md +51 -40
- package/assets/skills/matrix-open/agents/openai.yaml +4 -0
- package/{.claude → assets}/skills/matrix-status/SKILL.md +15 -15
- package/{.claude → assets}/skills/matrix-status/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-tweak/SKILL.md +8 -8
- package/assets/skills/matrix-tweak/agents/openai.yaml +4 -0
- package/{.claude → assets}/skills/matrix-verify/SKILL.md +62 -62
- package/assets/skills/matrix-verify/agents/openai.yaml +4 -0
- package/assets/skills-zh-CN/matrix/SKILL.md +72 -71
- package/assets/skills-zh-CN/matrix-build/SKILL.md +4 -4
- package/assets/skills-zh-CN/matrix-design/SKILL.md +37 -20
- package/assets/skills-zh-CN/matrix-handoff/SKILL.md +79 -0
- package/assets/skills-zh-CN/matrix-open/SKILL.md +38 -28
- package/assets/skills-zh-CN/matrix-verify/SKILL.md +19 -19
- package/bin/matrix.js +15 -15
- package/package.json +4 -13
- package/src/catalog.js +79 -72
- package/src/cli.js +211 -141
- package/src/distribution.js +182 -179
- package/src/installer.js +10 -10
- package/src/matt-catalog.mjs +1 -1
- package/src/matt.js +9 -14
- package/src/messages.js +32 -23
- package/src/npm-invocation.js +23 -0
- package/src/project-index.js +43 -0
- package/src/runtime-resolver.js +76 -76
- package/src/self-update.js +88 -81
- package/src/ui.js +97 -89
- package/src/workflow-transaction.js +13 -13
- package/src/workflow.js +734 -705
- package/.claude/skills/matrix-claude/SKILL.md +0 -98
- package/.claude/skills/matrix-open/agents/openai.yaml +0 -4
- package/.claude/skills/matrix-tweak/agents/openai.yaml +0 -4
- package/.claude/skills/matrix-verify/agents/openai.yaml +0 -4
- package/assets/skills-zh-CN/matrix-claude/SKILL.md +0 -33
package/src/cli.js
CHANGED
|
@@ -1,99 +1,105 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { PLATFORMS } from "./catalog.js";
|
|
6
6
|
import { createDistribution, installedIntent } from "./distribution.js";
|
|
7
7
|
import { createMattAdapter, inspectMattInstallation } from "./matt.js";
|
|
8
|
-
import { text } from "./messages.js";
|
|
9
|
-
import { createUi } from "./ui.js";
|
|
10
|
-
import { invoke } from "./workflow.js";
|
|
11
|
-
import { compareVersions, globalBinPath, latestVersion, selfUpdate } from "./self-update.js";
|
|
8
|
+
import { text } from "./messages.js";
|
|
9
|
+
import { createUi } from "./ui.js";
|
|
10
|
+
import { invoke } from "./workflow.js";
|
|
11
|
+
import { compareVersions, globalBinPath, latestVersion, PACKAGE_NAME, selfUpdate } from "./self-update.js";
|
|
12
12
|
import { resolveProjectRuntime } from "./runtime-resolver.js";
|
|
13
|
+
import { readProjectsIndex, registerProject, pruneProject } from "./project-index.js";
|
|
13
14
|
import { MATT_COMPATIBILITY } from "./matt-catalog.mjs";
|
|
14
|
-
|
|
15
|
-
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
15
|
+
|
|
16
|
+
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
16
17
|
const version = JSON.parse(fs.readFileSync(packagePath, "utf8")).version;
|
|
17
|
-
const HELP = `Matrix ${version}\n\nUsage: matrix init [directory] [options]\n matrix update [directory] [options]\n matrix doctor [directory] [options]\n\nOptions:\n --language <en|zh-CN>\n --scope <project|global>\n --platform <claude-code|codex> (repeatable)\n --mode <copy|symlink>\n --with-mattpocock | --without-mattpocock\n --force-matt\n --default-orchestration <prim|arch>\n --skip-self-update --force --yes, -y --dry-run --json --no-color`;
|
|
18
|
-
|
|
18
|
+
const HELP = `Matrix ${version}\n\nUsage: matrix init [directory] [options]\n matrix update [directory] [options]\n matrix doctor [directory] [options]\n\nOptions:\n --language <en|zh-CN>\n --scope <project|global>\n --platform <claude-code|codex> (repeatable)\n --mode <copy|symlink>\n --with-mattpocock | --without-mattpocock\n --force-matt (with --with-mattpocock; replace modified Matt Skills)\n --default-orchestration <prim|arch>\n update only: --all | --project-only (update scope across indexed projects)\n --skip-self-update --force --yes, -y --dry-run --json --no-color`;
|
|
19
|
+
|
|
19
20
|
function parse(argv) {
|
|
20
|
-
const result = { command: null, directory: null, scope: null, language: null, platforms: [], mode: "copy", matt: null, mattPolicy: "preserve", defaultOrchestration: null, matrixPolicy: "safe", yes: false, dryRun: false, json: false, color: true, skipSelfUpdate: false, reexec: false };
|
|
21
|
-
const values = [...argv];
|
|
22
|
-
if (values[0] && !values[0].startsWith("-")) result.command = values.shift();
|
|
23
|
-
while (values.length) {
|
|
24
|
-
const token = values.shift();
|
|
25
|
-
if (!token.startsWith("-") && !result.directory) result.directory = token;
|
|
26
|
-
else if (token === "--scope") result.scope = values.shift();
|
|
27
|
-
else if (token === "--language") result.language = values.shift();
|
|
28
|
-
else if (token === "--platform") result.platforms.push(values.shift());
|
|
29
|
-
else if (token === "--mode") result.mode = values.shift();
|
|
21
|
+
const result = { command: null, directory: null, scope: null, language: null, platforms: [], mode: "copy", matt: null, mattPolicy: "preserve", defaultOrchestration: null, matrixPolicy: "safe", yes: false, dryRun: false, json: false, color: true, skipSelfUpdate: false, reexec: false, all: false, projectOnly: false };
|
|
22
|
+
const values = [...argv];
|
|
23
|
+
if (values[0] && !values[0].startsWith("-")) result.command = values.shift();
|
|
24
|
+
while (values.length) {
|
|
25
|
+
const token = values.shift();
|
|
26
|
+
if (!token.startsWith("-") && !result.directory) result.directory = token;
|
|
27
|
+
else if (token === "--scope") result.scope = values.shift();
|
|
28
|
+
else if (token === "--language") result.language = values.shift();
|
|
29
|
+
else if (token === "--platform") result.platforms.push(values.shift());
|
|
30
|
+
else if (token === "--mode") result.mode = values.shift();
|
|
30
31
|
else if (token === "--with-mattpocock") result.matt = "missing";
|
|
31
32
|
else if (token === "--without-mattpocock") result.matt = "none";
|
|
32
33
|
else if (token === "--force-matt") result.mattPolicy = "replace";
|
|
33
|
-
else if (token === "--default-orchestration") result.defaultOrchestration = values.shift();
|
|
34
|
-
else if (token === "--force") result.matrixPolicy = "replace";
|
|
35
|
-
else if (
|
|
36
|
-
else if (token === "--
|
|
37
|
-
else if (
|
|
38
|
-
else if (token === "--
|
|
39
|
-
else if (token === "--
|
|
40
|
-
else if (token === "--
|
|
41
|
-
else if (
|
|
42
|
-
else if (
|
|
43
|
-
else
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
34
|
+
else if (token === "--default-orchestration") result.defaultOrchestration = values.shift();
|
|
35
|
+
else if (token === "--force") result.matrixPolicy = "replace";
|
|
36
|
+
else if (token === "--all") result.all = true;
|
|
37
|
+
else if (token === "--project-only") result.projectOnly = true;
|
|
38
|
+
else if (["--yes", "-y"].includes(token)) result.yes = true;
|
|
39
|
+
else if (token === "--dry-run") result.dryRun = true;
|
|
40
|
+
else if (token === "--skip-self-update") result.skipSelfUpdate = true;
|
|
41
|
+
else if (token === "--_reexec") result.reexec = true;
|
|
42
|
+
else if (token === "--json") result.json = true;
|
|
43
|
+
else if (token === "--no-color") result.color = false;
|
|
44
|
+
else if (["--help", "-h"].includes(token)) result.help = true;
|
|
45
|
+
else if (["--version", "-v"].includes(token)) result.version = true;
|
|
46
|
+
else throw new Error(`Unknown option: ${token}`);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function projectRoot(directory) {
|
|
52
|
+
const value = path.resolve(directory ?? process.cwd());
|
|
53
|
+
if (!fs.existsSync(value) || !fs.statSync(value).isDirectory()) throw new Error(`Not a directory: ${value}`);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function labelsFor(language) { return { yes: text(language, "yes"), no: text(language, "no") }; }
|
|
58
|
+
|
|
56
59
|
function localizedState(language, state) {
|
|
57
60
|
return text(language, ({ missing: "stateMissing", matching: "stateMatching", complete: "stateComplete", "user-modified": "stateUserModified", partial: "statePartial", damaged: "statePartial", newer: "stateMatching", "outdated-unchanged": "stateOutdated", "safe-update": "stateSafeUpdate", adoptable: "stateAdoptable", unreadable: "stateUnreadable", compatible: "stateCompatible", "update-required": "stateUpdateRequired", "unsupported-newer": "stateUnsupportedNewer", unverified: "stateUnverified", unknown: "stateUnknown", incomplete: "statePartial" })[state] ?? state);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function localizedAction(language, action) {
|
|
61
|
-
return text(language, ({ install: "actionInstall", keep: "actionKeep", "backup-replace": "actionBackupReplace", repair: "actionRepair", blocked: "actionBlocked" })[action] ?? action);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function intentFrom(options, ui) {
|
|
65
|
-
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
|
|
66
|
-
const language = options.language ?? (options.yes ? "en" : await ui.select("Language / 语言", [{ label: "English", value: "en" }, { label: "中文", value: "zh-CN" }]));
|
|
67
|
-
const scope = options.scope ?? (options.yes ? "project" : await ui.select(text(language, "scopeQuestion"), [{ label: text(language, "project"), value: "project" }, { label: text(language, "global"), value: "global" }]));
|
|
68
|
-
let platforms = options.platforms;
|
|
69
|
-
if (!platforms.length && interactive && !options.yes) platforms = await ui.selectMany(text(language, "platformsQuestion"), [{ label: "Claude Code", value: "claude-code" }, { label: "Codex", value: "codex" }]);
|
|
70
|
-
const matt = options.matt ?? (interactive && !options.yes ? (await ui.confirm(text(language, "mattQuestion"), true, labelsFor(language)) ? "missing" : "none") : "missing");
|
|
71
|
-
const defaultOrchestration = options.defaultOrchestration ?? (interactive && !options.yes && matt === "missing"
|
|
72
|
-
? await ui.select(text(language, "orchestrationQuestion"), [
|
|
73
|
-
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function localizedAction(language, action) {
|
|
64
|
+
return text(language, ({ install: "actionInstall", keep: "actionKeep", "backup-replace": "actionBackupReplace", repair: "actionRepair", blocked: "actionBlocked" })[action] ?? action);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function intentFrom(options, ui) {
|
|
68
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
|
|
69
|
+
const language = options.language ?? (options.yes ? "en" : await ui.select("Language / 语言", [{ label: "English", value: "en" }, { label: "中文", value: "zh-CN" }]));
|
|
70
|
+
const scope = options.scope ?? (options.yes ? "project" : await ui.select(text(language, "scopeQuestion"), [{ label: text(language, "project"), value: "project" }, { label: text(language, "global"), value: "global" }]));
|
|
71
|
+
let platforms = options.platforms;
|
|
72
|
+
if (!platforms.length && interactive && !options.yes) platforms = await ui.selectMany(text(language, "platformsQuestion"), [{ label: "Claude Code", value: "claude-code" }, { label: "Codex", value: "codex" }]);
|
|
73
|
+
const matt = options.matt ?? (interactive && !options.yes ? (await ui.confirm(text(language, "mattQuestion"), true, labelsFor(language)) ? "missing" : "none") : "missing");
|
|
74
|
+
const defaultOrchestration = options.defaultOrchestration ?? (interactive && !options.yes && matt === "missing"
|
|
75
|
+
? await ui.select(text(language, "orchestrationQuestion"), [
|
|
76
|
+
{ label: "Arch", value: "arch", detail: text(language, "orchestrationArchDetail") },
|
|
77
|
+
{ label: "Prim", value: "prim", detail: text(language, "orchestrationPrimDetail") }
|
|
78
|
+
])
|
|
79
|
+
: null);
|
|
74
80
|
return { projectRoot: projectRoot(options.directory), language, scope, platforms, mode: options.mode, matt, mattPolicy: options.mattPolicy, defaultOrchestration, nonInteractive: options.yes || !interactive, matrixPolicy: options.matrixPolicy, dryRun: options.dryRun };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function render(ui, evaluation) {
|
|
78
|
-
const language = evaluation.intent?.language ?? "en";
|
|
79
|
-
const actions = (evaluation.actions ?? []).map((item) => `${item.platform}: ${localizedAction(language, item.matrix)}`).join(", ");
|
|
80
|
-
ui.info(actions ? `${text(language, "preview")}: ${actions}` : evaluation.code);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function render(ui, evaluation) {
|
|
84
|
+
const language = evaluation.intent?.language ?? "en";
|
|
85
|
+
const actions = (evaluation.actions ?? []).map((item) => `${item.platform}: ${localizedAction(language, item.matrix)}`).join(", ");
|
|
86
|
+
ui.info(actions ? `${text(language, "preview")}: ${actions}` : evaluation.code);
|
|
81
87
|
if (evaluation.intent?.defaultOrchestration) ui.muted(`${text(language, "defaultOrchestration")}: ${evaluation.intent.defaultOrchestration}`);
|
|
82
88
|
for (const item of evaluation.observations ?? []) {
|
|
83
89
|
const mattStates = Object.entries(item.matt.classificationCounts ?? {}).map(([state, count]) => `${localizedState(language, state)}=${count}`).join(", ");
|
|
84
90
|
ui.muted(`${item.name}: ${text(language, "matrix")} ${localizedState(language, item.matrix)};${text(language, "matt")} ${localizedState(language, item.matt.state)}${mattStates ? ` (${mattStates})` : ""}`);
|
|
85
91
|
}
|
|
86
|
-
for (const item of evaluation.diagnostics ?? []) ui.warn(item.code === "USER_MODIFIED_BLOCKED" ? text(language, "replaceQuestion") : item.message ?? item.code);
|
|
92
|
+
for (const item of evaluation.diagnostics ?? []) ui.warn(item.code === "USER_MODIFIED_BLOCKED" ? text(language, "replaceQuestion") : item.message ?? item.code);
|
|
87
93
|
for (const item of evaluation.diagnosis ?? []) (item.code === "MATT_GLOBAL_IGNORED" ? ui.muted : ui.warn)(item.message ?? item.code);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function init(options) {
|
|
91
|
-
const ui = createUi({ color: options.color && !options.json });
|
|
92
|
-
if (!options.json) await ui.banner();
|
|
93
|
-
const intent = await intentFrom(options, ui);
|
|
94
|
-
return refresh(intent, options, ui);
|
|
95
|
-
}
|
|
96
|
-
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function init(options) {
|
|
97
|
+
const ui = createUi({ color: options.color && !options.json });
|
|
98
|
+
if (!options.json) await ui.banner();
|
|
99
|
+
const intent = await intentFrom(options, ui);
|
|
100
|
+
return refresh(intent, options, ui);
|
|
101
|
+
}
|
|
102
|
+
|
|
97
103
|
async function refresh(intent, options, ui) {
|
|
98
104
|
const distribution = createDistribution({ mattAdapter: createMattAdapter({ stdio: options.json ? "pipe" : "inherit" }) });
|
|
99
105
|
let evaluation = distribution.evaluate(intent);
|
|
@@ -119,61 +125,125 @@ async function refresh(intent, options, ui) {
|
|
|
119
125
|
return !result.ok ? 2 : result.code === "PARTIAL" ? 1 : 0;
|
|
120
126
|
}
|
|
121
127
|
if (reevaluated) render(ui, evaluation);
|
|
122
|
-
if (!evaluation.ok) return 2;
|
|
123
|
-
if (!options.yes && interactive && !options.dryRun && !await ui.confirm(text(intent.language, "confirm"), true, labelsFor(intent.language))) return 0;
|
|
128
|
+
if (!evaluation.ok) return 2;
|
|
129
|
+
if (!options.yes && interactive && !options.dryRun && !await ui.confirm(text(intent.language, "confirm"), true, labelsFor(intent.language))) return 0;
|
|
124
130
|
const result = distribution.commit(evaluation.plan);
|
|
125
|
-
if (!result.ok) { ui.warn(result.error ?? result.code); return 2; }
|
|
126
|
-
if (
|
|
131
|
+
if (!result.ok) { ui.warn(result.error ?? result.code); return 2; }
|
|
132
|
+
if (intent.scope === "project" && !options.dryRun) {
|
|
133
|
+
try { registerProject({ projectRoot: intent.projectRoot, scope: intent.scope, platforms: intent.platforms, language: intent.language, home: intent.home }); }
|
|
134
|
+
catch { /* a broken project index must never fail an update */ }
|
|
135
|
+
}
|
|
136
|
+
if (result.code === "PARTIAL") { ui.warn(result.recovery); return 1; }
|
|
127
137
|
ui.success(options.dryRun ? text(intent.language, "dryRunComplete") : text(intent.language, "ready"));
|
|
128
138
|
if (!options.updateMode && intent.matt === "missing" && !options.dryRun) ui.muted(text(intent.language, "mattSetupHint").replace("{release}", MATT_COMPATIBILITY.release));
|
|
129
139
|
if (options.updateMode) {
|
|
130
|
-
const matt = inspectMattInstallation({ projectRoot: intent.projectRoot, home: intent.home, platforms: intent.platforms });
|
|
140
|
+
const matt = inspectMattInstallation({ projectRoot: intent.projectRoot, home: intent.home, platforms: intent.platforms, action: "matrix update --with-mattpocock" });
|
|
131
141
|
ui.muted(`${text(intent.language, "mattCompatibilityStatus").replace("{release}", matt.supported.release).replace("{status}", localizedState(intent.language, matt.status))}${matt.action ? `; ${matt.action}` : ""}`);
|
|
132
142
|
}
|
|
133
|
-
ui.muted(evaluation.intent.platforms.map((id) =>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
143
|
+
ui.muted(text(intent.language, "readyHint").replace("{platform}", evaluation.intent.platforms.map((id) => PLATFORMS[id].name).join(", ")));
|
|
144
|
+
ui.muted(evaluation.intent.platforms.map((id) => `${PLATFORMS[id].name}: $matrix <request>`).join("\n"));
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function updateAllProjects({ intent, options, ui, root }) {
|
|
149
|
+
const index = readProjectsIndex({ home: intent.home });
|
|
150
|
+
if (!index.ok) ui.warn("Project index is unreadable; falling back to the current project only.");
|
|
151
|
+
const targets = [...new Set([...index.projects.map((entry) => entry.path), root])];
|
|
152
|
+
const bin = path.join(path.dirname(packagePath), "bin", "matrix.js");
|
|
153
|
+
const forwarded = ["--skip-self-update", "--yes", "--json"];
|
|
154
|
+
if (options.matt === "missing") forwarded.push("--with-mattpocock");
|
|
155
|
+
if (options.mattPolicy === "replace" && options.matt === "missing") forwarded.push("--force-matt");
|
|
156
|
+
let failed = 0;
|
|
157
|
+
const pruned = [];
|
|
158
|
+
for (const target of targets) {
|
|
159
|
+
if (!fs.existsSync(target)) {
|
|
160
|
+
pruneProject({ projectRoot: target, home: intent.home });
|
|
161
|
+
pruned.push(target);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const run = spawnSync(process.execPath, [bin, "update", target, ...forwarded], { encoding: "utf8" });
|
|
165
|
+
let ok = run.status === 0;
|
|
166
|
+
let detail = run.stderr?.trim();
|
|
167
|
+
if (ok) {
|
|
168
|
+
try {
|
|
169
|
+
const output = JSON.parse(run.stdout.trim().split("\n").pop());
|
|
170
|
+
ok = output.ok !== false && output.code !== "PARTIAL";
|
|
171
|
+
detail = output.code;
|
|
172
|
+
} catch { ok = false; detail = detail || "unparseable update output"; }
|
|
173
|
+
}
|
|
174
|
+
if (!ok) failed += 1;
|
|
175
|
+
(ok ? ui.muted : ui.warn)(`${ok ? "OK" : "FAILED"} ${target}${detail ? ` (${detail})` : ""}`);
|
|
176
|
+
}
|
|
177
|
+
for (const item of pruned) ui.muted(`Pruned missing project from the index: ${item}`);
|
|
178
|
+
if (failed) { ui.warn(`${failed} project update(s) failed.`); return 2; }
|
|
179
|
+
ui.success("All indexed projects are up to date.");
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function update(options) {
|
|
184
|
+
const ui = createUi({ color: options.color && !options.json });
|
|
185
|
+
const root = projectRoot(options.directory);
|
|
186
|
+
const installed = installedIntent({ projectRoot: root });
|
|
187
|
+
if (!installed.ok) {
|
|
188
|
+
const result = { ok: false, code: installed.code, message: "No trusted Matrix installation manifest was found. Run matrix init first." };
|
|
189
|
+
if (options.json) console.log(JSON.stringify(result)); else ui.warn(result.message);
|
|
190
|
+
return 2;
|
|
191
|
+
}
|
|
192
|
+
if (options.matt === "none") throw new Error("matrix update cannot remove Matt Skills. Use matrix init --without-mattpocock.");
|
|
193
|
+
if (options.mattPolicy === "replace" && options.matt !== "missing") throw new Error("matrix update --force-matt requires --with-mattpocock.");
|
|
194
|
+
const forbidden = options.language || options.scope || options.platforms.length || options.defaultOrchestration || options.mode !== "copy";
|
|
195
|
+
if (forbidden) throw new Error("matrix update inherits installation settings. Use matrix init to change language, scope, platform, mode, or orchestration.");
|
|
196
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
|
|
197
|
+
const intent = { ...installed.intent, dryRun: options.dryRun };
|
|
198
|
+
let npm = { status: "skipped", current: version, target: version, reason: options.skipSelfUpdate ? "disabled by --skip-self-update" : null };
|
|
199
|
+
if (!options.skipSelfUpdate && !options.reexec) {
|
|
200
|
+
try {
|
|
201
|
+
const target = await latestVersion(); const comparison = compareVersions(target, version);
|
|
202
|
+
if (comparison === null) throw new Error("unable to compare registry version");
|
|
203
|
+
npm = comparison > 0 ? { status: "available", current: version, target } : { status: "skipped", current: version, target, reason: comparison === 0 ? "already latest" : "registry version is older" };
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const result = { ok: false, code: "SELF_UPDATE_CHECK_FAILED", npm: { status: "failed", current: version, reason: error.message }, recovery_command: "matrix update --skip-self-update",
|
|
206
|
+
recovery: `Run matrix update --skip-self-update to refresh assets without npm, or install the latest CLI manually with \`npm i -g ${PACKAGE_NAME}@latest\` and retry.` };
|
|
207
|
+
if (options.json) console.log(JSON.stringify(result)); else ui.warn(`${result.code}: ${error.message}\n${result.recovery}`);
|
|
208
|
+
return 2;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const mattStatus = inspectMattInstallation({ projectRoot: root, home: intent.home, platforms: intent.platforms, action: "matrix update --with-mattpocock" });
|
|
212
|
+
const mattNeedsRepair = ["incomplete", "update-required", "unverified"].includes(mattStatus.status);
|
|
213
|
+
const mattRepair = options.matt === "missing" || (mattNeedsRepair && interactive && !options.yes
|
|
214
|
+
&& await ui.confirm(text(intent.language, "mattRepairQuestion").replace("{state}", localizedState(intent.language, mattStatus.status)).replace("{release}", MATT_COMPATIBILITY.release), true, labelsFor(intent.language)));
|
|
215
|
+
if (mattRepair) intent.matt = "missing";
|
|
216
|
+
if (options.json && options.dryRun) { console.log(JSON.stringify({ ok: true, code: "DRY_RUN", npm, intent, matt: mattStatus })); return 0; }
|
|
217
|
+
const scope = options.all ? "all" : options.projectOnly ? "project"
|
|
218
|
+
: interactive && !options.yes ? await ui.select(text(intent.language, "updateScopeQuestion"), [
|
|
219
|
+
{ label: text(intent.language, "updateScopeCurrent"), value: "project" },
|
|
220
|
+
{ label: text(intent.language, "updateScopeAll"), value: "all" }
|
|
221
|
+
])
|
|
222
|
+
: "project";
|
|
223
|
+
if (!options.json) {
|
|
224
|
+
await ui.banner(); ui.info(`Matrix update: CLI ${npm.current}${npm.status === "available" ? ` → ${npm.target}` : ""}`);
|
|
225
|
+
ui.muted(`Assets: ${intent.scope}, ${intent.language}, ${intent.platforms.join(", ")}`);
|
|
226
|
+
}
|
|
227
|
+
if (npm.status === "available") {
|
|
228
|
+
const repairNotice = mattRepair ? ` Matt Skills will be repaired to ${MATT_COMPATIBILITY.release} as part of the update.` : "";
|
|
229
|
+
if (!options.yes && interactive && !await ui.confirm(`Upgrade Matrix CLI ${npm.current} → ${npm.target} and then refresh these assets?${repairNotice}`, true, labelsFor(intent.language))) return 0;
|
|
230
|
+
if (options.dryRun) { if (options.json) console.log(JSON.stringify({ ok: true, code: "DRY_RUN", npm, intent, matt: mattStatus })); else ui.success(text(intent.language, "dryRunComplete")); return 0; }
|
|
231
|
+
const updated = selfUpdate(version, npm.target);
|
|
232
|
+
if (!updated.ok) {
|
|
233
|
+
const result = { ok: false, code: "SELF_UPDATE_FAILED", npm: { ...npm, status: "failed", reason: updated.reason }, recovery_command: `npm i -g ${PACKAGE_NAME}@${npm.target} && matrix update --skip-self-update` };
|
|
234
|
+
if (options.json) console.log(JSON.stringify(result)); else ui.warn(`${result.code}: ${updated.reason}\nRun ${result.recovery_command}`);
|
|
235
|
+
return 2;
|
|
236
|
+
}
|
|
237
|
+
const args = [globalBinPath(), "update", root, "--skip-self-update", "--_reexec", "--yes"];
|
|
238
|
+
if (options.matrixPolicy === "replace") args.push("--force");
|
|
239
|
+
if (mattRepair) args.push("--with-mattpocock");
|
|
240
|
+
if (options.mattPolicy === "replace" && mattRepair) args.push("--force-matt");
|
|
241
|
+
if (scope === "all") args.push("--all");
|
|
242
|
+
if (options.json) args.push("--json"); if (!options.color) args.push("--no-color");
|
|
243
|
+
const rerun = spawnSync(process.execPath, args, { stdio: "inherit" });
|
|
244
|
+
return Number.isInteger(rerun.status) ? rerun.status : 2;
|
|
245
|
+
}
|
|
246
|
+
if (scope === "all") return updateAllProjects({ intent, options, ui, root });
|
|
177
247
|
if (options.json) return refresh(intent, { ...options, yes: true, updateMode: true }, ui);
|
|
178
248
|
return refresh(intent, { ...options, updateMode: true }, ui);
|
|
179
249
|
}
|
|
@@ -189,7 +259,7 @@ function runtimeRecovery(projectRuntime, argv) {
|
|
|
189
259
|
const argumentsWithoutJson = argv.slice(1).filter((value) => value !== "--json");
|
|
190
260
|
return { ...projectRuntime, recovery_command: [projectRuntime.recovery_command, ...argumentsWithoutJson.map(recoveryArgument)].join(" ") };
|
|
191
261
|
}
|
|
192
|
-
|
|
262
|
+
|
|
193
263
|
async function doctor(options) {
|
|
194
264
|
const ui = createUi({ color: options.color && !options.json });
|
|
195
265
|
const root = projectRoot(options.directory);
|
|
@@ -198,12 +268,12 @@ async function doctor(options) {
|
|
|
198
268
|
? { ...installed.intent, dryRun: true }
|
|
199
269
|
: await intentFrom({ ...options, directory: root, yes: true }, ui);
|
|
200
270
|
const evaluation = createDistribution().diagnose(intent);
|
|
201
|
-
const matt = inspectMattInstallation({ projectRoot: intent.projectRoot, home: intent.home, platforms: intent.platforms });
|
|
271
|
+
const matt = inspectMattInstallation({ projectRoot: intent.projectRoot, home: intent.home, platforms: intent.platforms, action: installed.ok ? "matrix update --with-mattpocock" : undefined });
|
|
202
272
|
if (options.json) console.log(JSON.stringify({ ...evaluation, matt }));
|
|
203
273
|
else { await ui.banner(); ui.info(text(intent.language, "doctor")); render(ui, evaluation); ui.muted(`${text(intent.language, "mattCompatibilityStatus").replace("{release}", matt.supported.release).replace("{status}", localizedState(intent.language, matt.status))}${matt.action ? `; ${matt.action}` : ""}`); }
|
|
204
274
|
return evaluation.ok ? 0 : 2;
|
|
205
275
|
}
|
|
206
|
-
|
|
276
|
+
|
|
207
277
|
export async function main(argv) {
|
|
208
278
|
if (argv[0] === "workflow") {
|
|
209
279
|
const json = argv.includes("--json");
|
|
@@ -219,15 +289,15 @@ export async function main(argv) {
|
|
|
219
289
|
return Number.isInteger(delegated.status) ? delegated.status : 2;
|
|
220
290
|
}
|
|
221
291
|
const result = invoke(argv.slice(1).filter((value) => value !== "--json"));
|
|
222
|
-
if (json) console.log(JSON.stringify(result));
|
|
223
|
-
else console.log(result.ok ? JSON.stringify(result, null, 2) : `MATRIX ERROR [${result.code}]: ${result.message}`);
|
|
224
|
-
return result.ok ? 0 : 2;
|
|
225
|
-
}
|
|
226
|
-
const options = parse(argv);
|
|
227
|
-
if (options.version) { console.log(version); return 0; }
|
|
228
|
-
if (options.help || !options.command) { console.log(HELP); return 0; }
|
|
229
|
-
if (options.command === "init") return init(options);
|
|
230
|
-
if (options.command === "update") return update(options);
|
|
231
|
-
if (options.command === "doctor") return doctor(options);
|
|
232
|
-
throw new Error(`Unknown command: ${options.command}. Run matrix --help.`);
|
|
233
|
-
}
|
|
292
|
+
if (json) console.log(JSON.stringify(result));
|
|
293
|
+
else console.log(result.ok ? JSON.stringify(result, null, 2) : `MATRIX ERROR [${result.code}]: ${result.message}`);
|
|
294
|
+
return result.ok ? 0 : 2;
|
|
295
|
+
}
|
|
296
|
+
const options = parse(argv);
|
|
297
|
+
if (options.version) { console.log(version); return 0; }
|
|
298
|
+
if (options.help || !options.command) { console.log(HELP); return 0; }
|
|
299
|
+
if (options.command === "init") return init(options);
|
|
300
|
+
if (options.command === "update") return update(options);
|
|
301
|
+
if (options.command === "doctor") return doctor(options);
|
|
302
|
+
throw new Error(`Unknown command: ${options.command}. Run matrix --help.`);
|
|
303
|
+
}
|