@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/messages.js
CHANGED
|
@@ -1,37 +1,46 @@
|
|
|
1
|
-
export const messages = {
|
|
2
|
-
en: {
|
|
3
|
-
title: "Evidence-driven workflow with Prim and Arch orchestration",
|
|
4
|
-
recommended: "Recommended setup", apply: "Apply", customize: "Customize", cancel: "Cancel",
|
|
1
|
+
export const messages = {
|
|
2
|
+
en: {
|
|
3
|
+
title: "Evidence-driven workflow with Prim and Arch orchestration",
|
|
4
|
+
recommended: "Recommended setup", apply: "Apply", customize: "Customize", cancel: "Cancel",
|
|
5
5
|
project: "Project (current directory)", global: "Global Matrix Skills",
|
|
6
|
-
copy: "Copy (recommended)", symlink: "Symlink (advanced)",
|
|
7
|
-
matrix: "Matrix", matt: "Matt Pocock skills", installed: "installed", ready: "Matrix is ready.",
|
|
8
|
-
noPlatform: "No verified platform was detected. Select Claude Code, Codex, or both.",
|
|
9
|
-
preview: "Planned changes", confirm: "Apply these changes?", doctor: "Installation diagnosis",
|
|
10
|
-
languageQuestion: "Language", scopeQuestion: "Installation scope", platformsQuestion: "Platforms", orchestrationQuestion: "Default orchestration",
|
|
6
|
+
copy: "Copy (recommended)", symlink: "Symlink (advanced)",
|
|
7
|
+
matrix: "Matrix", matt: "Matt Pocock skills", installed: "installed", ready: "Matrix is ready.",
|
|
8
|
+
noPlatform: "No verified platform was detected. Select Claude Code, Codex, or both.",
|
|
9
|
+
preview: "Planned changes", confirm: "Apply these changes?", doctor: "Installation diagnosis",
|
|
10
|
+
languageQuestion: "Language", scopeQuestion: "Installation scope", platformsQuestion: "Platforms", orchestrationQuestion: "Default orchestration",
|
|
11
|
+
orchestrationArchDetail: "Orchestrates the installed Matt Skills cohort (requires Matt Skills).", orchestrationPrimDetail: "Uses Matrix's own capabilities only (no Matt dependency).",
|
|
12
|
+
readyHint: "Type $matrix followed by your request in {platform} to start the evidence-driven workflow.",
|
|
11
13
|
mattQuestion: "Install or reconcile the supported Matt Pocock release in this project?", replaceQuestion: "Existing Matrix files are not proven managed. Back up and replace?",
|
|
12
14
|
mattReplaceQuestion: "Matt Skills contain local or unreadable content. Back them up and replace them with the supported release?",
|
|
15
|
+
mattRepairQuestion: "Matt Skills are {state}. Re-pull the supported release ({release}) now?",
|
|
16
|
+
updateScopeQuestion: "Update scope", updateScopeCurrent: "Current project only", updateScopeAll: "All indexed projects",
|
|
13
17
|
yes: "Yes", no: "No", stateMissing: "missing", stateMatching: "up to date", stateComplete: "complete", stateUserModified: "user-modified", statePartial: "partial", stateOutdated: "outdated", stateSafeUpdate: "safe update", stateAdoptable: "adoptable", stateUnreadable: "unreadable", stateCompatible: "compatible", stateUpdateRequired: "update required", stateUnsupportedNewer: "unsupported newer", stateUnverified: "unverified", stateUnknown: "unknown",
|
|
14
|
-
actionInstall: "install", actionKeep: "keep", actionBackupReplace: "back up and replace", actionRepair: "repair", actionBlocked: "requires approval",
|
|
18
|
+
actionInstall: "install", actionKeep: "keep", actionBackupReplace: "back up and replace", actionRepair: "repair", actionBlocked: "requires approval",
|
|
15
19
|
dryRunComplete: "Dry run complete.", defaultOrchestration: "Default orchestration", mattCompatibilityStatus: "Matt {release}: {status}",
|
|
16
20
|
manifestMissing: "No Matrix installation manifest was found for this scope.", transactionPending: "An interrupted Matrix transaction is awaiting recovery. Run matrix init again before applying a new plan.", runtimeMissing: "Bundled Matrix runtime is missing.", brokenLink: "Broken Matrix link detected.", mattGlobalIgnored: "global Matt Skills are present but ignored by the project compatibility contract.", mattSetupHint: "Matt {release} is installed for this project. Optionally run $setup-matt-pocock-skills once to configure its user-owned integrations.",
|
|
17
21
|
matrixModifiedDiagnostic: "{platform}: existing Matrix assets are not proven managed.", mattModifiedDiagnostic: "{platform}: Matt Skills contain local or unreadable content that requires separate replacement approval.", archIncompleteDiagnostic: "Arch requires the exact reviewed cohort and trusted project receipt. Retry with --with-mattpocock.", matrixStateDiagnostic: "{platform}: Matrix is {state}.", mattStateDiagnostic: "{platform}: Matt Pocock skills are {state}.", codexLegacyDiagnostic: "Codex legacy .codex/skills was detected; Matrix will not write there.", unmanagedExtraDiagnostic: "{platform}: unmanaged extra Skills are preserved and will not be invoked: {skills}."
|
|
18
|
-
},
|
|
19
|
-
"zh-CN": {
|
|
22
|
+
},
|
|
23
|
+
"zh-CN": {
|
|
20
24
|
manifestMissing: "当前安装范围没有 Matrix 安装清单。", transactionPending: "发现中断的 Matrix 安装事务。请再次运行 matrix init 后再应用新计划。", runtimeMissing: "缺少随附的 Matrix runtime。", brokenLink: "检测到断开的 Matrix 链接。", mattGlobalIgnored: "检测到全局 Matt Skills,但项目兼容性契约不会采用它们。", mattSetupHint: "已为当前项目安装 Matt {release}。如需配置由用户拥有的集成,可显式运行一次 $setup-matt-pocock-skills。",
|
|
21
|
-
languageQuestion: "语言", scopeQuestion: "安装范围", platformsQuestion: "要配置的平台", orchestrationQuestion: "默认编排模式",
|
|
25
|
+
languageQuestion: "语言", scopeQuestion: "安装范围", platformsQuestion: "要配置的平台", orchestrationQuestion: "默认编排模式",
|
|
26
|
+
orchestrationArchDetail: "编排项目内已安装的 Matt Skills 集合(需先安装 Matt)。",
|
|
27
|
+
orchestrationPrimDetail: "仅使用 Matrix 自带能力,不依赖 Matt Skills。",
|
|
28
|
+
readyHint: "在 {platform} 中输入 `$matrix` 加你的请求,即可启动证据驱动工作流。",
|
|
22
29
|
mattQuestion: "是否在当前项目安装或核对 Matrix 支持的 Matt Pocock Release?", replaceQuestion: "现有 Matrix 文件无法确认由 Matrix 管理。是否备份并替换?", mattReplaceQuestion: "Matt Skills 存在本地修改或不可读内容。是否先备份,再替换为受支持的 Release?",
|
|
30
|
+
mattRepairQuestion: "Matt Skills {state}。是否立即重新拉取受支持的 release({release})?",
|
|
31
|
+
updateScopeQuestion: "更新范围", updateScopeCurrent: "仅当前项目", updateScopeAll: "全部已索引项目",
|
|
23
32
|
yes: "是", no: "否", stateMissing: "未安装", stateMatching: "已是最新", stateComplete: "完整", stateUserModified: "已被用户修改", statePartial: "不完整", stateOutdated: "可更新", stateSafeUpdate: "可安全更新", stateAdoptable: "可接管", stateUnreadable: "不可读", stateCompatible: "兼容", stateUpdateRequired: "需要更新", stateUnsupportedNewer: "版本过新且不受支持", stateUnverified: "未验证", stateUnknown: "未知",
|
|
24
|
-
actionInstall: "安装", actionKeep: "保留", actionBackupReplace: "备份并替换", actionRepair: "修复", actionBlocked: "需要确认",
|
|
33
|
+
actionInstall: "安装", actionKeep: "保留", actionBackupReplace: "备份并替换", actionRepair: "修复", actionBlocked: "需要确认",
|
|
25
34
|
dryRunComplete: "预览完成。", defaultOrchestration: "默认编排", mattCompatibilityStatus: "Matt {release}:{status}",
|
|
26
|
-
title: "提供 Prim 与 Arch 编排的证据驱动工作流",
|
|
27
|
-
recommended: "推荐配置", apply: "开始安装", customize: "自定义", cancel: "取消",
|
|
35
|
+
title: "提供 Prim 与 Arch 编排的证据驱动工作流",
|
|
36
|
+
recommended: "推荐配置", apply: "开始安装", customize: "自定义", cancel: "取消",
|
|
28
37
|
project: "项目(当前目录)", global: "全局 Matrix Skills",
|
|
29
|
-
copy: "复制(推荐)", symlink: "符号链接(高级)",
|
|
30
|
-
matrix: "Matrix", matt: "Matt Pocock skills", installed: "已安装", ready: "Matrix 已就绪。",
|
|
31
|
-
noPlatform: "未检测到已验证的平台。请选择 Claude Code、Codex,或两者。",
|
|
38
|
+
copy: "复制(推荐)", symlink: "符号链接(高级)",
|
|
39
|
+
matrix: "Matrix", matt: "Matt Pocock skills", installed: "已安装", ready: "Matrix 已就绪。",
|
|
40
|
+
noPlatform: "未检测到已验证的平台。请选择 Claude Code、Codex,或两者。",
|
|
32
41
|
preview: "计划变更", confirm: "确认应用这些变更?", doctor: "安装诊断",
|
|
33
42
|
matrixModifiedDiagnostic: "{platform}:现有 Matrix 资产无法确认由 Matrix 管理。", mattModifiedDiagnostic: "{platform}:Matt Skills 存在本地修改或不可读内容,需要单独授权替换。", archIncompleteDiagnostic: "Arch 需要精确匹配已审查集合和可信项目 receipt。请使用 --with-mattpocock 重试。", matrixStateDiagnostic: "{platform}:Matrix 状态为 {state}。", mattStateDiagnostic: "{platform}:Matt Pocock Skills 状态为 {state}。", codexLegacyDiagnostic: "检测到 Codex 旧版 .codex/skills;Matrix 不会写入该目录。", unmanagedExtraDiagnostic: "{platform}:将保留但不会调用这些非托管附加 Skills:{skills}。"
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export function text(language, key) { return messages[language]?.[key] ?? messages.en[key] ?? key; }
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function text(language, key) { return messages[language]?.[key] ?? messages.en[key] ?? key; }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
// Windows CreateProcess cannot launch npm.cmd, so npm/npx must run as
|
|
5
|
+
// `node <npm-cli.js> ...`. Resolution order: explicit path, npm_execpath,
|
|
6
|
+
// the npm bundled beside the running Node. Non-Windows returns the command
|
|
7
|
+
// unchanged so every caller keeps its POSIX behavior.
|
|
8
|
+
export function resolveNpmCli({ platform = process.platform, nodeExecutable = process.execPath, npmCliPath } = {}) {
|
|
9
|
+
if (platform !== "win32") return null;
|
|
10
|
+
const candidates = [npmCliPath, process.env.npm_execpath, path.join(path.dirname(nodeExecutable), "node_modules", "npm", "bin", "npm-cli.js")].filter(Boolean);
|
|
11
|
+
const cli = candidates.find((candidate) => npmCliPath || fs.existsSync(candidate));
|
|
12
|
+
if (!cli) throw new Error("npm CLI was not found beside the Node runtime. Install the package manually with `npm i -g <package>` or reinstall Node.js so its bundled npm is present.");
|
|
13
|
+
return cli;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function buildNpmProcessInvocation({ command, args, platform = process.platform, nodeExecutable = process.execPath, npmCliPath }) {
|
|
17
|
+
if (platform !== "win32" || (command !== "npm" && command !== "npx")) return { executable: command, args, shell: false };
|
|
18
|
+
const cli = resolveNpmCli({ platform, nodeExecutable, npmCliPath });
|
|
19
|
+
if (command === "npm") return { executable: nodeExecutable, args: [cli, ...args], shell: false };
|
|
20
|
+
const [yes, packageSpec, ...commandArgs] = args;
|
|
21
|
+
if (yes !== "--yes" || !packageSpec) throw new Error("Windows npx invocation must use fixed npm exec arguments.");
|
|
22
|
+
return { executable: nodeExecutable, args: [cli, "exec", yes, packageSpec, "--", ...commandArgs], shell: false };
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export const projectsIndexPath = (home = os.homedir()) => path.join(home, ".matrix", "projects.json");
|
|
6
|
+
|
|
7
|
+
function writeAtomically(file, value) {
|
|
8
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
9
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
10
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`);
|
|
11
|
+
fs.renameSync(temporary, file);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readProjectsIndex({ home = os.homedir() } = {}) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(fs.readFileSync(projectsIndexPath(home), "utf8"));
|
|
17
|
+
if (!Array.isArray(parsed)) return { ok: false, code: "PROJECTS_INDEX_CORRUPT", projects: [] };
|
|
18
|
+
const projects = parsed.filter((entry) => entry && typeof entry.path === "string");
|
|
19
|
+
return { ok: true, projects };
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code === "ENOENT") return { ok: true, projects: [] };
|
|
22
|
+
return { ok: false, code: "PROJECTS_INDEX_CORRUPT", projects: [] };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function registerProject({ projectRoot, scope, platforms, language, home = os.homedir() } = {}) {
|
|
27
|
+
const resolved = path.resolve(projectRoot);
|
|
28
|
+
const { ok, projects } = readProjectsIndex({ home });
|
|
29
|
+
const next = (ok ? projects : []).filter((entry) => entry.path !== resolved);
|
|
30
|
+
next.unshift({ path: resolved, scope, platforms, language, updatedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z") });
|
|
31
|
+
writeAtomically(projectsIndexPath(home), next);
|
|
32
|
+
return { ok: true, projects: next };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function pruneProject({ projectRoot, home = os.homedir() } = {}) {
|
|
36
|
+
const resolved = path.resolve(projectRoot);
|
|
37
|
+
const { ok, projects } = readProjectsIndex({ home });
|
|
38
|
+
if (!ok) return { ok: false, removed: null };
|
|
39
|
+
const removed = projects.find((entry) => entry.path === resolved) ?? null;
|
|
40
|
+
if (!removed) return { ok: true, removed: null };
|
|
41
|
+
writeAtomically(projectsIndexPath(home), projects.filter((entry) => entry.path !== resolved));
|
|
42
|
+
return { ok: true, removed };
|
|
43
|
+
}
|
package/src/runtime-resolver.js
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { MATRIX_VERSION, PLATFORMS, hashDirectory, releaseCatalog } from "./catalog.js";
|
|
4
|
-
|
|
5
|
-
function readJson(file) {
|
|
6
|
-
try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function installationScope(projectRoot) {
|
|
10
|
-
try {
|
|
11
|
-
const line = fs.readFileSync(path.join(projectRoot, ".matrix", "config.yaml"), "utf8").split(/\r?\n/).find((value) => value.startsWith("installation_scope:"));
|
|
12
|
-
return line?.split(":").slice(1).join(":").trim() === "global" ? "global" : "project";
|
|
13
|
-
} catch { return "project"; }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function runtimeCommand(runtime) {
|
|
17
|
-
const quoted = process.platform === "win32" ? `"${runtime}"` : `'${runtime.replaceAll("'", "'\\''")}'`;
|
|
18
|
-
return `node ${quoted}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function resolveProjectRuntime({ projectRoot = process.cwd() } = {}) {
|
|
22
|
-
const root = path.resolve(projectRoot);
|
|
23
|
-
const manifest = readJson(path.join(root, ".matrix", "installation.json"));
|
|
24
|
-
if (!manifest) return installationScope(root) === "global"
|
|
25
|
-
? { ok: true, code: "GLOBAL_INSTALLATION", runtime: null }
|
|
26
|
-
: { ok: true, code: "PROJECT_INSTALLATION_ABSENT", runtime: null };
|
|
27
|
-
const candidates = [];
|
|
28
|
-
let currentCatalog = null;
|
|
29
|
-
let catalogUnavailable = false;
|
|
30
|
-
try { currentCatalog = releaseCatalog(manifest.language ?? "en"); } catch { catalogUnavailable = true; }
|
|
31
|
-
for (const platform of ["codex", "claude-code"]) {
|
|
32
|
-
const expected = manifest.platforms?.[platform]?.skills?.matrix?.hash;
|
|
33
|
-
if (!expected) continue;
|
|
34
|
-
const matrixRoot = path.join(PLATFORMS[platform].skillRoot(root), "matrix");
|
|
35
|
-
const runtime = path.join(matrixRoot, "scripts", "matrix-runtime.mjs");
|
|
36
|
-
if (!fs.existsSync(runtime)) { candidates.push({ platform, matrixRoot, runtime, reason: "missing" }); continue; }
|
|
37
|
-
try {
|
|
38
|
-
const actual = hashDirectory(matrixRoot);
|
|
39
|
-
if (actual === expected) {
|
|
40
|
-
if (manifest.catalogVersion !== MATRIX_VERSION) {
|
|
41
|
-
const catalogVersion = manifest.catalogVersion ?? null;
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
code: catalogVersion ? "PROJECT_RUNTIME_VERSION_MISMATCH" : "PROJECT_RUNTIME_VERSION_UNKNOWN",
|
|
45
|
-
message: catalogVersion
|
|
46
|
-
? `The project Matrix Runtime is ${catalogVersion}, while this launcher is ${MATRIX_VERSION}. The project remains authoritative, but a different-release Runtime requires explicit invocation.`
|
|
47
|
-
: `The project Matrix Runtime has no release identity. The project remains authoritative, but an unidentified Runtime requires explicit invocation.`,
|
|
48
|
-
platform,
|
|
49
|
-
runtime,
|
|
50
|
-
catalogVersion,
|
|
51
|
-
launcherVersion: MATRIX_VERSION,
|
|
52
|
-
recovery_command: runtimeCommand(runtime)
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
if (catalogUnavailable
|
|
56
|
-
|| manifest.catalogDigest !== currentCatalog.digest || expected !== currentCatalog.skills.matrix) {
|
|
57
|
-
return {
|
|
58
|
-
ok: false,
|
|
59
|
-
code: "PROJECT_RUNTIME_RELEASE_MISMATCH",
|
|
60
|
-
message: `The project Matrix Runtime claims ${MATRIX_VERSION} but does not match that release catalog. Run matrix update.`,
|
|
61
|
-
platform,
|
|
62
|
-
runtime,
|
|
63
|
-
catalogVersion: manifest.catalogVersion,
|
|
64
|
-
expected: currentCatalog?.skills.matrix ?? null,
|
|
65
|
-
actual
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
return { ok: true, code: "PROJECT_RUNTIME", platform, runtime, catalogVersion: manifest.catalogVersion };
|
|
69
|
-
}
|
|
70
|
-
candidates.push({ platform, matrixRoot, runtime, reason: "modified" });
|
|
71
|
-
} catch { candidates.push({ platform, matrixRoot, runtime, reason: "unreadable" }); }
|
|
72
|
-
}
|
|
73
|
-
return candidates.length
|
|
74
|
-
? { ok: false, code: "PROJECT_RUNTIME_UNTRUSTED", message: "The project Matrix Runtime does not match its installation manifest.", findings: candidates }
|
|
75
|
-
: { ok: true, code: "PROJECT_RUNTIME_ABSENT", runtime: null };
|
|
76
|
-
}
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { MATRIX_VERSION, PLATFORMS, hashDirectory, releaseCatalog } from "./catalog.js";
|
|
4
|
+
|
|
5
|
+
function readJson(file) {
|
|
6
|
+
try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function installationScope(projectRoot) {
|
|
10
|
+
try {
|
|
11
|
+
const line = fs.readFileSync(path.join(projectRoot, ".matrix", "config.yaml"), "utf8").split(/\r?\n/).find((value) => value.startsWith("installation_scope:"));
|
|
12
|
+
return line?.split(":").slice(1).join(":").trim() === "global" ? "global" : "project";
|
|
13
|
+
} catch { return "project"; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function runtimeCommand(runtime) {
|
|
17
|
+
const quoted = process.platform === "win32" ? `"${runtime}"` : `'${runtime.replaceAll("'", "'\\''")}'`;
|
|
18
|
+
return `node ${quoted}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveProjectRuntime({ projectRoot = process.cwd() } = {}) {
|
|
22
|
+
const root = path.resolve(projectRoot);
|
|
23
|
+
const manifest = readJson(path.join(root, ".matrix", "installation.json"));
|
|
24
|
+
if (!manifest) return installationScope(root) === "global"
|
|
25
|
+
? { ok: true, code: "GLOBAL_INSTALLATION", runtime: null }
|
|
26
|
+
: { ok: true, code: "PROJECT_INSTALLATION_ABSENT", runtime: null };
|
|
27
|
+
const candidates = [];
|
|
28
|
+
let currentCatalog = null;
|
|
29
|
+
let catalogUnavailable = false;
|
|
30
|
+
try { currentCatalog = releaseCatalog(manifest.language ?? "en"); } catch { catalogUnavailable = true; }
|
|
31
|
+
for (const platform of ["codex", "claude-code"]) {
|
|
32
|
+
const expected = manifest.platforms?.[platform]?.skills?.matrix?.hash;
|
|
33
|
+
if (!expected) continue;
|
|
34
|
+
const matrixRoot = path.join(PLATFORMS[platform].skillRoot(root), "matrix");
|
|
35
|
+
const runtime = path.join(matrixRoot, "scripts", "matrix-runtime.mjs");
|
|
36
|
+
if (!fs.existsSync(runtime)) { candidates.push({ platform, matrixRoot, runtime, reason: "missing" }); continue; }
|
|
37
|
+
try {
|
|
38
|
+
const actual = hashDirectory(matrixRoot);
|
|
39
|
+
if (actual === expected) {
|
|
40
|
+
if (manifest.catalogVersion !== MATRIX_VERSION) {
|
|
41
|
+
const catalogVersion = manifest.catalogVersion ?? null;
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
code: catalogVersion ? "PROJECT_RUNTIME_VERSION_MISMATCH" : "PROJECT_RUNTIME_VERSION_UNKNOWN",
|
|
45
|
+
message: catalogVersion
|
|
46
|
+
? `The project Matrix Runtime is ${catalogVersion}, while this launcher is ${MATRIX_VERSION}. The project remains authoritative, but a different-release Runtime requires explicit invocation.`
|
|
47
|
+
: `The project Matrix Runtime has no release identity. The project remains authoritative, but an unidentified Runtime requires explicit invocation.`,
|
|
48
|
+
platform,
|
|
49
|
+
runtime,
|
|
50
|
+
catalogVersion,
|
|
51
|
+
launcherVersion: MATRIX_VERSION,
|
|
52
|
+
recovery_command: runtimeCommand(runtime)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (catalogUnavailable
|
|
56
|
+
|| manifest.catalogDigest !== currentCatalog.digest || expected !== currentCatalog.skills.matrix) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
code: "PROJECT_RUNTIME_RELEASE_MISMATCH",
|
|
60
|
+
message: `The project Matrix Runtime claims ${MATRIX_VERSION} but does not match that release catalog. Run matrix update.`,
|
|
61
|
+
platform,
|
|
62
|
+
runtime,
|
|
63
|
+
catalogVersion: manifest.catalogVersion,
|
|
64
|
+
expected: currentCatalog?.skills.matrix ?? null,
|
|
65
|
+
actual
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, code: "PROJECT_RUNTIME", platform, runtime, catalogVersion: manifest.catalogVersion };
|
|
69
|
+
}
|
|
70
|
+
candidates.push({ platform, matrixRoot, runtime, reason: "modified" });
|
|
71
|
+
} catch { candidates.push({ platform, matrixRoot, runtime, reason: "unreadable" }); }
|
|
72
|
+
}
|
|
73
|
+
return candidates.length
|
|
74
|
+
? { ok: false, code: "PROJECT_RUNTIME_UNTRUSTED", message: "The project Matrix Runtime does not match its installation manifest.", findings: candidates }
|
|
75
|
+
: { ok: true, code: "PROJECT_RUNTIME_ABSENT", runtime: null };
|
|
76
|
+
}
|
package/src/self-update.js
CHANGED
|
@@ -1,81 +1,88 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (a.
|
|
19
|
-
if (a.prerelease ===
|
|
20
|
-
if (
|
|
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
|
-
return { ok:
|
|
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
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { buildNpmProcessInvocation } from "./npm-invocation.js";
|
|
6
|
+
|
|
7
|
+
export const PACKAGE_NAME = "@rosenfan/matrix";
|
|
8
|
+
const REGISTRY = "https://registry.npmjs.org/@rosenfan%2fmatrix/latest";
|
|
9
|
+
|
|
10
|
+
function parse(version) {
|
|
11
|
+
const match = String(version ?? "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
12
|
+
return match ? { numeric: match.slice(1, 4).map(Number), prerelease: match[4] ?? null } : null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function compareVersions(left, right) {
|
|
16
|
+
const a = parse(left); const b = parse(right);
|
|
17
|
+
if (!a || !b) return null;
|
|
18
|
+
for (let index = 0; index < 3; index += 1) if (a.numeric[index] !== b.numeric[index]) return a.numeric[index] - b.numeric[index];
|
|
19
|
+
if (a.prerelease === b.prerelease) return 0;
|
|
20
|
+
if (a.prerelease === null) return 1;
|
|
21
|
+
if (b.prerelease === null) return -1;
|
|
22
|
+
return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function latestVersion(fetchImpl = fetch) {
|
|
26
|
+
const response = await fetchImpl(REGISTRY, { headers: { accept: "application/json" } });
|
|
27
|
+
if (!response.ok) throw new Error(`registry returned ${response.status}`);
|
|
28
|
+
const body = await response.json();
|
|
29
|
+
if (!parse(body.version)) throw new Error("registry returned an invalid version");
|
|
30
|
+
return body.version;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function run(command, args, options = {}) {
|
|
34
|
+
let invocation;
|
|
35
|
+
try {
|
|
36
|
+
invocation = buildNpmProcessInvocation({ command, args });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return { ok: false, status: null, error: error.message, stdout: "", stderr: "" };
|
|
39
|
+
}
|
|
40
|
+
const result = spawnSync(invocation.executable, invocation.args, { encoding: "utf8", ...options });
|
|
41
|
+
return { ok: !result.error && result.status === 0, status: result.status, error: result.error?.message, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function validateCandidate(packageRoot, expectedVersion) {
|
|
45
|
+
const manifestFile = path.join(packageRoot, "package.json");
|
|
46
|
+
try {
|
|
47
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
48
|
+
if (manifest.name !== PACKAGE_NAME || manifest.version !== expectedVersion) return { ok: false, reason: "candidate package identity mismatch" };
|
|
49
|
+
for (const directory of ["assets/skills", "assets/skills-zh-CN", "src", "bin"]) if (!fs.existsSync(path.join(packageRoot, directory))) return { ok: false, reason: `candidate missing ${directory}` };
|
|
50
|
+
for (const languageRoot of [path.join(packageRoot, "assets", "skills"), path.join(packageRoot, "assets", "skills-zh-CN")]) if (!fs.existsSync(path.join(languageRoot, "matrix", "SKILL.md"))) return { ok: false, reason: "candidate Matrix assets are incomplete" };
|
|
51
|
+
for (const args of [["--version"], ["--help"], ["workflow", "doctor", "--json"]]) {
|
|
52
|
+
const result = run(process.execPath, [path.join(packageRoot, "bin", "matrix.js"), ...args], { cwd: os.tmpdir() });
|
|
53
|
+
if (!result.ok) return { ok: false, reason: `candidate command failed: matrix ${args.join(" ")}` };
|
|
54
|
+
}
|
|
55
|
+
return { ok: true };
|
|
56
|
+
} catch (error) { return { ok: false, reason: error.message }; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function npmGlobalRoot() {
|
|
60
|
+
const result = run("npm", ["root", "-g"]);
|
|
61
|
+
if (!result.ok) throw new Error(result.error ?? result.stderr.trim() ?? "npm root -g failed");
|
|
62
|
+
return result.stdout.trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function validateRegistryCandidate(version) {
|
|
66
|
+
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "matrix-update-"));
|
|
67
|
+
try {
|
|
68
|
+
const install = run("npm", ["install", "--prefix", temporary, "--ignore-scripts", "--no-save", "--package-lock=false", `${PACKAGE_NAME}@${version}`], { cwd: temporary });
|
|
69
|
+
if (!install.ok) return { ok: false, reason: install.error ?? install.stderr.trim() ?? "candidate installation failed" };
|
|
70
|
+
return validateCandidate(path.join(temporary, "node_modules", "@rosenfan", "matrix"), version);
|
|
71
|
+
} finally { fs.rmSync(temporary, { recursive: true, force: true }); }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function installGlobal(version) {
|
|
75
|
+
const result = run("npm", ["install", "--global", `${PACKAGE_NAME}@${version}`]);
|
|
76
|
+
return result.ok ? { ok: true } : { ok: false, reason: result.error ?? result.stderr.trim() ?? "npm install failed" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function selfUpdate(currentVersion, targetVersion) {
|
|
80
|
+
const candidate = validateRegistryCandidate(targetVersion);
|
|
81
|
+
if (!candidate.ok) return candidate;
|
|
82
|
+
const install = installGlobal(targetVersion);
|
|
83
|
+
if (install.ok) return install;
|
|
84
|
+
const rollback = installGlobal(currentVersion);
|
|
85
|
+
return { ok: false, reason: `${install.reason}; ${rollback.ok ? `restored ${currentVersion}` : `rollback failed: ${rollback.reason}`}` };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function globalBinPath() { return path.join(npmGlobalRoot(), "@rosenfan", "matrix", "bin", "matrix.js"); }
|