@rosenfan/matrix 0.1.3 → 0.1.5
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 +72 -63
- package/README.zh-CN.md +70 -61
- package/{.claude → assets}/skills/matrix/SKILL.md +81 -86
- 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 +60 -60
- 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 +27 -27
- package/{.claude/skills/matrix-hotfix → assets/skills/matrix-archive}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-build/SKILL.md +41 -38
- package/{.claude/skills/matrix-archive → assets/skills/matrix-build}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-claude/SKILL.md +93 -93
- package/{.claude → assets}/skills/matrix-claude/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-design/SKILL.md +69 -67
- package/{.claude → assets}/skills/matrix-design/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-hotfix/SKILL.md +7 -7
- package/{.claude/skills/matrix-build → assets/skills/matrix-hotfix}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-open/SKILL.md +26 -24
- package/assets/skills/matrix-open/agents/openai.yaml +4 -0
- package/{.claude → assets}/skills/matrix-status/SKILL.md +14 -14
- package/{.claude → assets}/skills/matrix-status/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-tweak/SKILL.md +7 -7
- package/assets/skills/matrix-tweak/agents/openai.yaml +4 -0
- package/assets/skills/matrix-verify/SKILL.md +89 -0
- package/assets/skills/matrix-verify/agents/openai.yaml +4 -0
- package/assets/skills-zh-CN/matrix/SKILL.md +27 -27
- package/assets/skills-zh-CN/matrix-archive/SKILL.md +10 -10
- package/assets/skills-zh-CN/matrix-build/SKILL.md +13 -11
- package/assets/skills-zh-CN/matrix-claude/SKILL.md +10 -10
- package/assets/skills-zh-CN/matrix-design/SKILL.md +12 -10
- package/assets/skills-zh-CN/matrix-hotfix/SKILL.md +1 -1
- package/assets/skills-zh-CN/matrix-open/SKILL.md +18 -16
- package/assets/skills-zh-CN/matrix-status/SKILL.md +3 -3
- package/assets/skills-zh-CN/matrix-tweak/SKILL.md +1 -1
- package/assets/skills-zh-CN/matrix-verify/SKILL.md +25 -9
- package/bin/matrix.js +15 -15
- package/package.json +4 -13
- package/src/catalog.js +110 -92
- package/src/cli.js +138 -80
- package/src/distribution.js +379 -253
- package/src/installer.js +4 -4
- package/src/matt-catalog.mjs +67 -0
- package/src/matt.js +91 -9
- package/src/messages.js +14 -10
- package/src/runtime-resolver.js +76 -0
- package/src/self-update.js +2 -2
- package/src/workflow-transaction.js +9 -5
- package/src/workflow.js +688 -429
- 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/SKILL.md +0 -73
- package/.claude/skills/matrix-verify/agents/openai.yaml +0 -4
package/src/catalog.js
CHANGED
|
@@ -1,92 +1,110 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
"matrix
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { MATT_INSTALLABLE_SKILLS, MATT_ROLES } from "./matt-catalog.mjs";
|
|
7
|
+
|
|
8
|
+
export const LANGUAGE_IDS = ["en", "zh-CN"];
|
|
9
|
+
export const PLATFORM_IDS = ["claude-code", "codex"];
|
|
10
|
+
export const MATRIX_SKILLS = [
|
|
11
|
+
"matrix", "matrix-open", "matrix-design", "matrix-build", "matrix-verify",
|
|
12
|
+
"matrix-archive", "matrix-hotfix", "matrix-tweak", "matrix-status", "matrix-claude"
|
|
13
|
+
];
|
|
14
|
+
export const MATT_SKILLS = MATT_INSTALLABLE_SKILLS;
|
|
15
|
+
export const UNMANAGED_MATT_SKILLS = MATT_ROLES.incompatible;
|
|
16
|
+
|
|
17
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
18
|
+
const packageManifest = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
19
|
+
export const MATRIX_VERSION = packageManifest.version;
|
|
20
|
+
|
|
21
|
+
export const PLATFORMS = {
|
|
22
|
+
"claude-code": {
|
|
23
|
+
id: "claude-code", name: "Claude Code", skillsCliAgent: "claude-code",
|
|
24
|
+
skillRoot(base) { return path.join(base, ".claude", "skills"); },
|
|
25
|
+
legacyRoots() { return []; }
|
|
26
|
+
},
|
|
27
|
+
codex: {
|
|
28
|
+
id: "codex", name: "Codex", skillsCliAgent: "codex",
|
|
29
|
+
skillRoot(base) { return path.join(base, ".agents", "skills"); },
|
|
30
|
+
legacyRoots(base) { return [path.join(base, ".codex", "skills")]; }
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function sourceSkillsRoot(language = "en") {
|
|
35
|
+
const localized = language === "zh-CN"
|
|
36
|
+
? path.join(packageRoot, "assets", "skills-zh-CN")
|
|
37
|
+
: path.join(packageRoot, "assets", "skills");
|
|
38
|
+
if (!fs.existsSync(localized)) throw new Error(`Matrix package is incomplete: ${language} assets are missing.`);
|
|
39
|
+
return localized;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function scopeBase(scope, projectRoot, home = os.homedir()) {
|
|
43
|
+
return scope === "global" ? home : projectRoot;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function installationRoot(scope, projectRoot, home) {
|
|
47
|
+
return path.join(scopeBase(scope, projectRoot, home), ".matrix");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function manifestPath(scope, projectRoot, home) {
|
|
51
|
+
return path.join(installationRoot(scope, projectRoot, home), "installation.json");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Content digests must identify logical file content, not checkout line endings:
|
|
55
|
+
// a git working tree (autocrlf CRLF), the npm tarball and a local tgz must agree.
|
|
56
|
+
// Generated caches (__pycache__) are skipped so dirty working trees cannot poison digests.
|
|
57
|
+
const normalizeEol = (contents) => Buffer.from(contents.toString("utf8").replaceAll("\r\n", "\n").replaceAll("\r", "\n"), "utf8");
|
|
58
|
+
const GENERATED_DIRS = new Set(["__pycache__"]);
|
|
59
|
+
|
|
60
|
+
export function hashDirectory(directory, extraFiles = []) {
|
|
61
|
+
const hash = crypto.createHash("sha256");
|
|
62
|
+
const files = new Map();
|
|
63
|
+
const visit = (current, relative = "") => {
|
|
64
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
65
|
+
if (entry.isDirectory() && GENERATED_DIRS.has(entry.name)) continue;
|
|
66
|
+
const child = path.join(current, entry.name);
|
|
67
|
+
const childRelative = path.join(relative, entry.name).replaceAll("\\", "/");
|
|
68
|
+
if (entry.isDirectory()) visit(child, childRelative);
|
|
69
|
+
else if (entry.isFile()) {
|
|
70
|
+
files.set(childRelative, fs.readFileSync(child));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
visit(directory);
|
|
75
|
+
for (const extra of extraFiles) files.set(extra.path, Buffer.from(extra.contents));
|
|
76
|
+
for (const [relative, contents] of [...files.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
77
|
+
hash.update(`${relative}\0`);
|
|
78
|
+
hash.update(normalizeEol(contents));
|
|
79
|
+
}
|
|
80
|
+
return hash.digest("hex");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function workflowRuntimeSource() { return path.join(packageRoot, "src", "workflow.js"); }
|
|
84
|
+
export function workflowTransactionSource() { return path.join(packageRoot, "src", "workflow-transaction.js"); }
|
|
85
|
+
export function mattCatalogSource() { return path.join(packageRoot, "src", "matt-catalog.mjs"); }
|
|
86
|
+
|
|
87
|
+
export function releaseCatalog(language = "en") {
|
|
88
|
+
if (!LANGUAGE_IDS.includes(language)) throw new Error(`Unsupported language: ${language}`);
|
|
89
|
+
const root = sourceSkillsRoot(language);
|
|
90
|
+
const missing = MATRIX_SKILLS.filter((skill) => !fs.existsSync(path.join(root, skill, "SKILL.md")));
|
|
91
|
+
if (missing.length) throw new Error(`Matrix package is incomplete. Missing: ${missing.join(", ")}`);
|
|
92
|
+
const runtime = fs.readFileSync(workflowRuntimeSource());
|
|
93
|
+
const transaction = fs.readFileSync(workflowTransactionSource());
|
|
94
|
+
const mattCatalog = fs.readFileSync(mattCatalogSource());
|
|
95
|
+
const sharedRuntimeFiles = [
|
|
96
|
+
{ path: "scripts/package.json", contents: Buffer.from('{"type":"module"}\n') },
|
|
97
|
+
{ path: "scripts/matrix-runtime.mjs", contents: runtime },
|
|
98
|
+
{ path: "scripts/workflow-transaction.js", contents: transaction },
|
|
99
|
+
{ path: "scripts/matt-catalog.mjs", contents: mattCatalog }
|
|
100
|
+
];
|
|
101
|
+
const compatibilityFiles = ["matrix_state.py", "matrix_claude.py"].map((filename) => ({
|
|
102
|
+
path: `scripts/${filename}`,
|
|
103
|
+
contents: fs.readFileSync(path.join(sourceSkillsRoot("en"), "matrix", "scripts", filename))
|
|
104
|
+
}));
|
|
105
|
+
const skills = Object.fromEntries(MATRIX_SKILLS.map((skill) => [skill, hashDirectory(
|
|
106
|
+
path.join(root, skill),
|
|
107
|
+
skill === "matrix" ? [...sharedRuntimeFiles, ...compatibilityFiles] : sharedRuntimeFiles
|
|
108
|
+
)]));
|
|
109
|
+
return { language, root, version: MATRIX_VERSION, skills, digest: crypto.createHash("sha256").update(JSON.stringify(skills)).digest("hex") };
|
|
110
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { PLATFORMS } from "./catalog.js";
|
|
6
|
-
import { createDistribution, installedIntent } from "./distribution.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
6
|
+
import { createDistribution, installedIntent } from "./distribution.js";
|
|
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";
|
|
12
|
+
import { resolveProjectRuntime } from "./runtime-resolver.js";
|
|
13
|
+
import { MATT_COMPATIBILITY } from "./matt-catalog.mjs";
|
|
11
14
|
|
|
12
15
|
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
13
16
|
const version = JSON.parse(fs.readFileSync(packagePath, "utf8")).version;
|
|
14
|
-
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 --default-orchestration <prim|arch>\n --skip-self-update --force --yes, -y --dry-run --json --no-color`;
|
|
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`;
|
|
15
18
|
|
|
16
19
|
function parse(argv) {
|
|
17
|
-
const result = { command: null, directory: null, scope: null, language: null, platforms: [], mode: "copy", matt: null, defaultOrchestration: null, matrixPolicy: "safe", yes: false, dryRun: false, json: false, color: true, skipSelfUpdate: false, reexec: false };
|
|
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 };
|
|
18
21
|
const values = [...argv];
|
|
19
22
|
if (values[0] && !values[0].startsWith("-")) result.command = values.shift();
|
|
20
23
|
while (values.length) {
|
|
@@ -26,12 +29,13 @@ function parse(argv) {
|
|
|
26
29
|
else if (token === "--mode") result.mode = values.shift();
|
|
27
30
|
else if (token === "--with-mattpocock") result.matt = "missing";
|
|
28
31
|
else if (token === "--without-mattpocock") result.matt = "none";
|
|
32
|
+
else if (token === "--force-matt") result.mattPolicy = "replace";
|
|
29
33
|
else if (token === "--default-orchestration") result.defaultOrchestration = values.shift();
|
|
30
34
|
else if (token === "--force") result.matrixPolicy = "replace";
|
|
31
35
|
else if (["--yes", "-y"].includes(token)) result.yes = true;
|
|
32
|
-
else if (token === "--dry-run") result.dryRun = true;
|
|
33
|
-
else if (token === "--skip-self-update") result.skipSelfUpdate = true;
|
|
34
|
-
else if (token === "--_reexec") result.reexec = true;
|
|
36
|
+
else if (token === "--dry-run") result.dryRun = true;
|
|
37
|
+
else if (token === "--skip-self-update") result.skipSelfUpdate = true;
|
|
38
|
+
else if (token === "--_reexec") result.reexec = true;
|
|
35
39
|
else if (token === "--json") result.json = true;
|
|
36
40
|
else if (token === "--no-color") result.color = false;
|
|
37
41
|
else if (["--help", "-h"].includes(token)) result.help = true;
|
|
@@ -50,7 +54,7 @@ function projectRoot(directory) {
|
|
|
50
54
|
function labelsFor(language) { return { yes: text(language, "yes"), no: text(language, "no") }; }
|
|
51
55
|
|
|
52
56
|
function localizedState(language, state) {
|
|
53
|
-
return text(language, ({ missing: "stateMissing", matching: "stateMatching", complete: "stateComplete", "user-modified": "stateUserModified", partial: "statePartial", damaged: "statePartial", newer: "stateMatching", "outdated-unchanged": "stateOutdated" })[state] ?? state);
|
|
57
|
+
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);
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
function localizedAction(language, action) {
|
|
@@ -67,99 +71,153 @@ async function intentFrom(options, ui) {
|
|
|
67
71
|
const defaultOrchestration = options.defaultOrchestration ?? (interactive && !options.yes && matt === "missing"
|
|
68
72
|
? await ui.select(text(language, "orchestrationQuestion"), [{ label: "Arch", value: "arch" }, { label: "Prim", value: "prim" }])
|
|
69
73
|
: null);
|
|
70
|
-
return { projectRoot: projectRoot(options.directory), language, scope, platforms, mode: options.mode, matt, defaultOrchestration, nonInteractive: options.yes || !interactive, matrixPolicy: options.matrixPolicy, dryRun: options.dryRun };
|
|
74
|
+
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 };
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
function render(ui, evaluation) {
|
|
74
78
|
const language = evaluation.intent?.language ?? "en";
|
|
75
79
|
const actions = (evaluation.actions ?? []).map((item) => `${item.platform}: ${localizedAction(language, item.matrix)}`).join(", ");
|
|
76
80
|
ui.info(actions ? `${text(language, "preview")}: ${actions}` : evaluation.code);
|
|
77
|
-
if (evaluation.intent?.defaultOrchestration) ui.muted(
|
|
78
|
-
for (const item of evaluation.observations ?? [])
|
|
81
|
+
if (evaluation.intent?.defaultOrchestration) ui.muted(`${text(language, "defaultOrchestration")}: ${evaluation.intent.defaultOrchestration}`);
|
|
82
|
+
for (const item of evaluation.observations ?? []) {
|
|
83
|
+
const mattStates = Object.entries(item.matt.classificationCounts ?? {}).map(([state, count]) => `${localizedState(language, state)}=${count}`).join(", ");
|
|
84
|
+
ui.muted(`${item.name}: ${text(language, "matrix")} ${localizedState(language, item.matrix)};${text(language, "matt")} ${localizedState(language, item.matt.state)}${mattStates ? ` (${mattStates})` : ""}`);
|
|
85
|
+
}
|
|
79
86
|
for (const item of evaluation.diagnostics ?? []) ui.warn(item.code === "USER_MODIFIED_BLOCKED" ? text(language, "replaceQuestion") : item.message ?? item.code);
|
|
80
|
-
for (const item of evaluation.diagnosis ?? []) (item.code === "
|
|
87
|
+
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);
|
|
81
95
|
}
|
|
82
96
|
|
|
83
|
-
async function
|
|
84
|
-
const
|
|
85
|
-
if (!options.json) await ui.banner();
|
|
86
|
-
const intent = await intentFrom(options, ui);
|
|
87
|
-
return refresh(intent, options, ui);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function refresh(intent, options, ui) {
|
|
91
|
-
const distribution = createDistribution();
|
|
97
|
+
async function refresh(intent, options, ui) {
|
|
98
|
+
const distribution = createDistribution({ mattAdapter: createMattAdapter({ stdio: options.json ? "pipe" : "inherit" }) });
|
|
92
99
|
let evaluation = distribution.evaluate(intent);
|
|
93
100
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
|
|
94
|
-
|
|
95
|
-
if (options.json)
|
|
96
|
-
|
|
101
|
+
let reevaluated = false;
|
|
102
|
+
if (!options.json) render(ui, evaluation);
|
|
103
|
+
if (!evaluation.ok && evaluation.code === "USER_MODIFIED_BLOCKED" && interactive && !options.yes && await ui.confirm(text(intent.language, "replaceQuestion"), false, labelsFor(intent.language))) {
|
|
104
|
+
evaluation = distribution.evaluate({ ...intent, matrixPolicy: "replace" }); reevaluated = true;
|
|
105
|
+
}
|
|
106
|
+
if (!evaluation.ok && evaluation.code === "MATT_USER_MODIFIED" && interactive && !options.yes
|
|
107
|
+
&& await ui.confirm(text(intent.language, "mattReplaceQuestion"), false, labelsFor(intent.language))) {
|
|
108
|
+
evaluation = distribution.evaluate({ ...intent, mattPolicy: "replace" }); reevaluated = true;
|
|
109
|
+
}
|
|
110
|
+
if (options.json) {
|
|
111
|
+
if (!evaluation.ok || options.dryRun) {
|
|
112
|
+
const output = options.updateMode ? { ...evaluation, matt: inspectMattInstallation({ projectRoot: intent.projectRoot, home: intent.home, platforms: intent.platforms }) } : evaluation;
|
|
113
|
+
console.log(JSON.stringify(output)); return evaluation.ok ? 0 : 2;
|
|
114
|
+
}
|
|
115
|
+
const result = distribution.commit(evaluation.plan);
|
|
116
|
+
const output = { ...evaluation, ...result, intent: evaluation.intent, observations: evaluation.observations, actions: evaluation.actions,
|
|
117
|
+
...(options.updateMode ? { matt: inspectMattInstallation({ projectRoot: intent.projectRoot, home: intent.home, platforms: intent.platforms }) } : {}) };
|
|
118
|
+
console.log(JSON.stringify(output));
|
|
119
|
+
return !result.ok ? 2 : result.code === "PARTIAL" ? 1 : 0;
|
|
120
|
+
}
|
|
121
|
+
if (reevaluated) render(ui, evaluation);
|
|
97
122
|
if (!evaluation.ok) return 2;
|
|
98
123
|
if (!options.yes && interactive && !options.dryRun && !await ui.confirm(text(intent.language, "confirm"), true, labelsFor(intent.language))) return 0;
|
|
99
124
|
const result = distribution.commit(evaluation.plan);
|
|
100
125
|
if (!result.ok) { ui.warn(result.error ?? result.code); return 2; }
|
|
101
126
|
if (result.code === "PARTIAL") { ui.warn(result.recovery); return 1; }
|
|
102
127
|
ui.success(options.dryRun ? text(intent.language, "dryRunComplete") : text(intent.language, "ready"));
|
|
128
|
+
if (!options.updateMode && intent.matt === "missing" && !options.dryRun) ui.muted(text(intent.language, "mattSetupHint").replace("{release}", MATT_COMPATIBILITY.release));
|
|
129
|
+
if (options.updateMode) {
|
|
130
|
+
const matt = inspectMattInstallation({ projectRoot: intent.projectRoot, home: intent.home, platforms: intent.platforms });
|
|
131
|
+
ui.muted(`${text(intent.language, "mattCompatibilityStatus").replace("{release}", matt.supported.release).replace("{status}", localizedState(intent.language, matt.status))}${matt.action ? `; ${matt.action}` : ""}`);
|
|
132
|
+
}
|
|
103
133
|
ui.muted(evaluation.intent.platforms.map((id) => `${PLATFORMS[id].name}: $matrix <request>`).join("\n"));
|
|
104
|
-
return 0;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function update(options) {
|
|
108
|
-
const ui = createUi({ color: options.color && !options.json });
|
|
109
|
-
const root = projectRoot(options.directory);
|
|
110
|
-
const installed = installedIntent({ projectRoot: root });
|
|
111
|
-
if (!installed.ok) {
|
|
112
|
-
const result = { ok: false, code: installed.code, message: "No trusted Matrix installation manifest was found. Run matrix init first." };
|
|
113
|
-
if (options.json) console.log(JSON.stringify(result)); else ui.warn(result.message);
|
|
114
|
-
return 2;
|
|
115
|
-
}
|
|
116
|
-
const forbidden = options.language || options.scope || options.platforms.length || options.matt || options.defaultOrchestration || options.mode !== "copy";
|
|
117
|
-
if (forbidden) throw new Error("matrix update inherits installation settings. Use matrix init to change language, scope, platform, mode, Matt skills, or orchestration.");
|
|
118
|
-
const intent = { ...installed.intent, dryRun: options.dryRun };
|
|
119
|
-
let npm = { status: "skipped", current: version, target: version, reason: options.skipSelfUpdate ? "disabled by --skip-self-update" : null };
|
|
120
|
-
if (!options.skipSelfUpdate && !options.reexec) {
|
|
121
|
-
try {
|
|
122
|
-
const target = await latestVersion(); const comparison = compareVersions(target, version);
|
|
123
|
-
if (comparison === null) throw new Error("unable to compare registry version");
|
|
124
|
-
npm = comparison > 0 ? { status: "available", current: version, target } : { status: "skipped", current: version, target, reason: comparison === 0 ? "already latest" : "registry version is older" };
|
|
125
|
-
} catch (error) {
|
|
126
|
-
const result = { ok: false, code: "SELF_UPDATE_CHECK_FAILED", npm: { status: "failed", current: version, reason: error.message }, recovery_command: "matrix update --skip-self-update" };
|
|
127
|
-
if (options.json) console.log(JSON.stringify(result)); else ui.warn(`${result.code}: ${error.message}\nRun ${result.recovery_command} to refresh assets without npm.`);
|
|
128
|
-
return 2;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (options.json && options.dryRun) { console.log(JSON.stringify({ ok: true, code: "DRY_RUN", npm, intent })); return 0; }
|
|
132
|
-
if (!options.json) {
|
|
133
|
-
await ui.banner(); ui.info(`Matrix update: CLI ${npm.current}${npm.status === "available" ? ` → ${npm.target}` : ""}`);
|
|
134
|
-
ui.muted(`Assets: ${intent.scope}, ${intent.language}, ${intent.platforms.join(", ")}`);
|
|
135
|
-
}
|
|
136
|
-
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
|
|
137
|
-
if (npm.status === "available") {
|
|
138
|
-
if (!options.yes && interactive && !await ui.confirm(`Upgrade Matrix CLI ${npm.current} → ${npm.target} and then refresh these assets?`, true, labelsFor(intent.language))) return 0;
|
|
139
|
-
if (options.dryRun) { if (options.json) console.log(JSON.stringify({ ok: true, code: "DRY_RUN", npm, intent })); else ui.success(text(intent.language, "dryRunComplete")); return 0; }
|
|
140
|
-
const updated = selfUpdate(version, npm.target);
|
|
141
|
-
if (!updated.ok) { const result = { ok: false, code: "SELF_UPDATE_FAILED", npm: { ...npm, status: "failed", reason: updated.reason }, recovery_command: "matrix update --skip-self-update" }; if (options.json) console.log(JSON.stringify(result)); else ui.warn(`${result.code}: ${updated.reason}`); return 2; }
|
|
142
|
-
const args = [globalBinPath(), "update", root, "--skip-self-update", "--_reexec", "--yes"];
|
|
143
|
-
if (options.matrixPolicy === "replace") args.push("--force"); if (options.json) args.push("--json"); if (!options.color) args.push("--no-color");
|
|
144
|
-
const rerun = spawnSync(process.execPath, args, { stdio: "inherit" });
|
|
145
|
-
return Number.isInteger(rerun.status) ? rerun.status : 2;
|
|
146
|
-
}
|
|
147
|
-
if (options.json) return refresh(intent, { ...options, yes: true }, ui);
|
|
148
|
-
return refresh(intent, options, ui);
|
|
149
|
-
}
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function update(options) {
|
|
138
|
+
const ui = createUi({ color: options.color && !options.json });
|
|
139
|
+
const root = projectRoot(options.directory);
|
|
140
|
+
const installed = installedIntent({ projectRoot: root });
|
|
141
|
+
if (!installed.ok) {
|
|
142
|
+
const result = { ok: false, code: installed.code, message: "No trusted Matrix installation manifest was found. Run matrix init first." };
|
|
143
|
+
if (options.json) console.log(JSON.stringify(result)); else ui.warn(result.message);
|
|
144
|
+
return 2;
|
|
145
|
+
}
|
|
146
|
+
const forbidden = options.language || options.scope || options.platforms.length || options.matt || options.mattPolicy === "replace" || options.defaultOrchestration || options.mode !== "copy";
|
|
147
|
+
if (forbidden) throw new Error("matrix update inherits installation settings. Use matrix init to change language, scope, platform, mode, Matt skills, or orchestration.");
|
|
148
|
+
const intent = { ...installed.intent, dryRun: options.dryRun };
|
|
149
|
+
let npm = { status: "skipped", current: version, target: version, reason: options.skipSelfUpdate ? "disabled by --skip-self-update" : null };
|
|
150
|
+
if (!options.skipSelfUpdate && !options.reexec) {
|
|
151
|
+
try {
|
|
152
|
+
const target = await latestVersion(); const comparison = compareVersions(target, version);
|
|
153
|
+
if (comparison === null) throw new Error("unable to compare registry version");
|
|
154
|
+
npm = comparison > 0 ? { status: "available", current: version, target } : { status: "skipped", current: version, target, reason: comparison === 0 ? "already latest" : "registry version is older" };
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const result = { ok: false, code: "SELF_UPDATE_CHECK_FAILED", npm: { status: "failed", current: version, reason: error.message }, recovery_command: "matrix update --skip-self-update" };
|
|
157
|
+
if (options.json) console.log(JSON.stringify(result)); else ui.warn(`${result.code}: ${error.message}\nRun ${result.recovery_command} to refresh assets without npm.`);
|
|
158
|
+
return 2;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (options.json && options.dryRun) { console.log(JSON.stringify({ ok: true, code: "DRY_RUN", npm, intent, matt: inspectMattInstallation({ projectRoot: root, home: intent.home, platforms: intent.platforms }) })); return 0; }
|
|
162
|
+
if (!options.json) {
|
|
163
|
+
await ui.banner(); ui.info(`Matrix update: CLI ${npm.current}${npm.status === "available" ? ` → ${npm.target}` : ""}`);
|
|
164
|
+
ui.muted(`Assets: ${intent.scope}, ${intent.language}, ${intent.platforms.join(", ")}`);
|
|
165
|
+
}
|
|
166
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
|
|
167
|
+
if (npm.status === "available") {
|
|
168
|
+
if (!options.yes && interactive && !await ui.confirm(`Upgrade Matrix CLI ${npm.current} → ${npm.target} and then refresh these assets?`, true, labelsFor(intent.language))) return 0;
|
|
169
|
+
if (options.dryRun) { if (options.json) console.log(JSON.stringify({ ok: true, code: "DRY_RUN", npm, intent, matt: inspectMattInstallation({ projectRoot: root, home: intent.home, platforms: intent.platforms }) })); else ui.success(text(intent.language, "dryRunComplete")); return 0; }
|
|
170
|
+
const updated = selfUpdate(version, npm.target);
|
|
171
|
+
if (!updated.ok) { const result = { ok: false, code: "SELF_UPDATE_FAILED", npm: { ...npm, status: "failed", reason: updated.reason }, recovery_command: "matrix update --skip-self-update" }; if (options.json) console.log(JSON.stringify(result)); else ui.warn(`${result.code}: ${updated.reason}`); return 2; }
|
|
172
|
+
const args = [globalBinPath(), "update", root, "--skip-self-update", "--_reexec", "--yes"];
|
|
173
|
+
if (options.matrixPolicy === "replace") args.push("--force"); if (options.json) args.push("--json"); if (!options.color) args.push("--no-color");
|
|
174
|
+
const rerun = spawnSync(process.execPath, args, { stdio: "inherit" });
|
|
175
|
+
return Number.isInteger(rerun.status) ? rerun.status : 2;
|
|
176
|
+
}
|
|
177
|
+
if (options.json) return refresh(intent, { ...options, yes: true, updateMode: true }, ui);
|
|
178
|
+
return refresh(intent, { ...options, updateMode: true }, ui);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function recoveryArgument(value) {
|
|
182
|
+
const argument = String(value);
|
|
183
|
+
if (/^[A-Za-z0-9_./:@=-]+$/.test(argument)) return argument;
|
|
184
|
+
if (process.platform === "win32") return `"${argument.replaceAll('"', '\\"')}"`;
|
|
185
|
+
return `'${argument.replaceAll("'", "'\\''")}'`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function runtimeRecovery(projectRuntime, argv) {
|
|
189
|
+
const argumentsWithoutJson = argv.slice(1).filter((value) => value !== "--json");
|
|
190
|
+
return { ...projectRuntime, recovery_command: [projectRuntime.recovery_command, ...argumentsWithoutJson.map(recoveryArgument)].join(" ") };
|
|
191
|
+
}
|
|
150
192
|
|
|
151
193
|
async function doctor(options) {
|
|
152
194
|
const ui = createUi({ color: options.color && !options.json });
|
|
153
|
-
const
|
|
195
|
+
const root = projectRoot(options.directory);
|
|
196
|
+
const installed = installedIntent({ projectRoot: root });
|
|
197
|
+
const intent = installed.ok
|
|
198
|
+
? { ...installed.intent, dryRun: true }
|
|
199
|
+
: await intentFrom({ ...options, directory: root, yes: true }, ui);
|
|
154
200
|
const evaluation = createDistribution().diagnose(intent);
|
|
155
|
-
|
|
156
|
-
|
|
201
|
+
const matt = inspectMattInstallation({ projectRoot: intent.projectRoot, home: intent.home, platforms: intent.platforms });
|
|
202
|
+
if (options.json) console.log(JSON.stringify({ ...evaluation, matt }));
|
|
203
|
+
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}` : ""}`); }
|
|
157
204
|
return evaluation.ok ? 0 : 2;
|
|
158
205
|
}
|
|
159
206
|
|
|
160
207
|
export async function main(argv) {
|
|
161
208
|
if (argv[0] === "workflow") {
|
|
162
209
|
const json = argv.includes("--json");
|
|
210
|
+
const projectRuntime = resolveProjectRuntime({ projectRoot: process.cwd() });
|
|
211
|
+
if (!projectRuntime.ok) {
|
|
212
|
+
const failure = projectRuntime.recovery_command ? runtimeRecovery(projectRuntime, argv) : projectRuntime;
|
|
213
|
+
if (json) console.log(JSON.stringify(failure));
|
|
214
|
+
else console.log(`MATRIX ERROR [${failure.code}]: ${failure.message}${failure.recovery_command ? `\nRun ${failure.recovery_command}` : ""}`);
|
|
215
|
+
return 2;
|
|
216
|
+
}
|
|
217
|
+
if (projectRuntime.runtime) {
|
|
218
|
+
const delegated = spawnSync(process.execPath, [projectRuntime.runtime, ...argv.slice(1).filter((value) => value !== "--json")], { stdio: "inherit" });
|
|
219
|
+
return Number.isInteger(delegated.status) ? delegated.status : 2;
|
|
220
|
+
}
|
|
163
221
|
const result = invoke(argv.slice(1).filter((value) => value !== "--json"));
|
|
164
222
|
if (json) console.log(JSON.stringify(result));
|
|
165
223
|
else console.log(result.ok ? JSON.stringify(result, null, 2) : `MATRIX ERROR [${result.code}]: ${result.message}`);
|
|
@@ -168,8 +226,8 @@ export async function main(argv) {
|
|
|
168
226
|
const options = parse(argv);
|
|
169
227
|
if (options.version) { console.log(version); return 0; }
|
|
170
228
|
if (options.help || !options.command) { console.log(HELP); return 0; }
|
|
171
|
-
if (options.command === "init") return init(options);
|
|
172
|
-
if (options.command === "update") return update(options);
|
|
229
|
+
if (options.command === "init") return init(options);
|
|
230
|
+
if (options.command === "update") return update(options);
|
|
173
231
|
if (options.command === "doctor") return doctor(options);
|
|
174
232
|
throw new Error(`Unknown command: ${options.command}. Run matrix --help.`);
|
|
175
233
|
}
|