@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.
Files changed (54) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +72 -63
  3. package/README.zh-CN.md +70 -61
  4. package/{.claude → assets}/skills/matrix/SKILL.md +81 -86
  5. package/{.claude → assets}/skills/matrix/agents/openai.yaml +4 -4
  6. package/{.claude → assets}/skills/matrix/scripts/matrix_claude.py +11 -11
  7. package/{.claude → assets}/skills/matrix/scripts/matrix_state.py +60 -60
  8. package/assets/skills/matrix/scripts/test_matrix_claude.py +69 -0
  9. package/assets/skills/matrix/scripts/test_matrix_state.py +69 -0
  10. package/{.claude → assets}/skills/matrix-archive/SKILL.md +27 -27
  11. package/{.claude/skills/matrix-hotfix → assets/skills/matrix-archive}/agents/openai.yaml +4 -4
  12. package/{.claude → assets}/skills/matrix-build/SKILL.md +41 -38
  13. package/{.claude/skills/matrix-archive → assets/skills/matrix-build}/agents/openai.yaml +4 -4
  14. package/{.claude → assets}/skills/matrix-claude/SKILL.md +93 -93
  15. package/{.claude → assets}/skills/matrix-claude/agents/openai.yaml +4 -4
  16. package/{.claude → assets}/skills/matrix-design/SKILL.md +69 -67
  17. package/{.claude → assets}/skills/matrix-design/agents/openai.yaml +4 -4
  18. package/{.claude → assets}/skills/matrix-hotfix/SKILL.md +7 -7
  19. package/{.claude/skills/matrix-build → assets/skills/matrix-hotfix}/agents/openai.yaml +4 -4
  20. package/{.claude → assets}/skills/matrix-open/SKILL.md +26 -24
  21. package/assets/skills/matrix-open/agents/openai.yaml +4 -0
  22. package/{.claude → assets}/skills/matrix-status/SKILL.md +14 -14
  23. package/{.claude → assets}/skills/matrix-status/agents/openai.yaml +4 -4
  24. package/{.claude → assets}/skills/matrix-tweak/SKILL.md +7 -7
  25. package/assets/skills/matrix-tweak/agents/openai.yaml +4 -0
  26. package/assets/skills/matrix-verify/SKILL.md +89 -0
  27. package/assets/skills/matrix-verify/agents/openai.yaml +4 -0
  28. package/assets/skills-zh-CN/matrix/SKILL.md +27 -27
  29. package/assets/skills-zh-CN/matrix-archive/SKILL.md +10 -10
  30. package/assets/skills-zh-CN/matrix-build/SKILL.md +13 -11
  31. package/assets/skills-zh-CN/matrix-claude/SKILL.md +10 -10
  32. package/assets/skills-zh-CN/matrix-design/SKILL.md +12 -10
  33. package/assets/skills-zh-CN/matrix-hotfix/SKILL.md +1 -1
  34. package/assets/skills-zh-CN/matrix-open/SKILL.md +18 -16
  35. package/assets/skills-zh-CN/matrix-status/SKILL.md +3 -3
  36. package/assets/skills-zh-CN/matrix-tweak/SKILL.md +1 -1
  37. package/assets/skills-zh-CN/matrix-verify/SKILL.md +25 -9
  38. package/bin/matrix.js +15 -15
  39. package/package.json +4 -13
  40. package/src/catalog.js +110 -92
  41. package/src/cli.js +138 -80
  42. package/src/distribution.js +379 -253
  43. package/src/installer.js +4 -4
  44. package/src/matt-catalog.mjs +67 -0
  45. package/src/matt.js +91 -9
  46. package/src/messages.js +14 -10
  47. package/src/runtime-resolver.js +76 -0
  48. package/src/self-update.js +2 -2
  49. package/src/workflow-transaction.js +9 -5
  50. package/src/workflow.js +688 -429
  51. package/.claude/skills/matrix-open/agents/openai.yaml +0 -4
  52. package/.claude/skills/matrix-tweak/agents/openai.yaml +0 -4
  53. package/.claude/skills/matrix-verify/SKILL.md +0 -73
  54. package/.claude/skills/matrix-verify/agents/openai.yaml +0 -4
package/src/installer.js CHANGED
@@ -2,7 +2,6 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { createDistribution } from "./distribution.js";
4
4
  import { MATRIX_SKILLS, MATT_SKILLS, PLATFORMS, sourceSkillsRoot } from "./catalog.js";
5
- import { createMattAdapter } from "./matt.js";
6
5
 
7
6
  export { MATRIX_SKILLS, MATT_SKILLS, sourceSkillsRoot };
8
7
 
@@ -37,9 +36,10 @@ export function installMatrix({ destination, mode = "copy", conflict = "error",
37
36
  }
38
37
 
39
38
  export function installMattSkills({ projectRoot, platforms = ["claude-code"], skills = MATT_SKILLS, global = false, timeout = 300_000 }) {
40
- const result = createMattAdapter().installMissing({ projectRoot, platforms, skills, global, timeout });
41
- if (!result.ok) throw new Error(`${result.code}: ${result.message}`);
42
- return result;
39
+ void projectRoot; void platforms; void skills; void global; void timeout;
40
+ const error = new Error("Direct Matt installation was retired; run matrix init --with-mattpocock so Distribution owns the complete Matrix/Matt transaction.");
41
+ error.code = "MATT_DIRECT_INSTALL_UNSUPPORTED";
42
+ throw error;
43
43
  }
44
44
 
45
45
  export function findPython() { return null; }
@@ -0,0 +1,67 @@
1
+ import crypto from "node:crypto";
2
+
3
+ export const MATT_COMPATIBILITY = Object.freeze({
4
+ matrixVersion: "0.1.5",
5
+ release: "v1.2.3",
6
+ commit: "6acc160e4e0cd062dbbbd7a1b26ae92855edf07e",
7
+ archive: "https://github.com/mattpocock/skills/archive/6acc160e4e0cd062dbbbd7a1b26ae92855edf07e.tar.gz",
8
+ skillsCli: "skills@1.5.22"
9
+ });
10
+
11
+ export const MATT_ROLES = Object.freeze({
12
+ automatic: Object.freeze([
13
+ "grilling", "domain-modeling", "research", "prototype", "codebase-design",
14
+ "tdd", "diagnosing-bugs", "code-review", "wizard", "writing-for-agents"
15
+ ]),
16
+ handoff: Object.freeze([
17
+ "setup-matt-pocock-skills", "improve-codebase-architecture", "wayfinder", "handoff", "to-questionnaire"
18
+ ]),
19
+ standalone: Object.freeze([
20
+ "ask-matt", "grill-with-docs", "triage", "to-spec", "to-tickets", "grill-me", "teach", "wait-what"
21
+ ]),
22
+ incompatible: Object.freeze(["implement", "resolving-merge-conflicts"])
23
+ });
24
+
25
+ export const MATT_AUTOMATIC_SKILLS = MATT_ROLES.automatic;
26
+ export const MATT_INSTALLABLE_SKILLS = Object.freeze([
27
+ ...MATT_ROLES.automatic,
28
+ ...MATT_ROLES.handoff,
29
+ ...MATT_ROLES.standalone
30
+ ]);
31
+ export const MATT_OFFICIAL_SKILLS = Object.freeze(Object.values(MATT_ROLES).flat());
32
+ // Content identities produced by skills@1.5.22 --copy from the exact v1.2.3 archive.
33
+ // They let init classify existing project files before any target mutation without
34
+ // bundling or redistributing Matt's Skill contents.
35
+ export const MATT_CONTENT_HASHES = Object.freeze({
36
+ "grilling": "f6b0b6a07f6f51a7b39dfed4c737bd5990149e6ac227265dc457eb962e40fde6",
37
+ "domain-modeling": "8d774870ffd93ad77b07f56bbe59fb76c8f9e246fe49e0181ed65a519fc28929",
38
+ "research": "3bbc342f7484484d31118fd7cb148690aba2600b318d6b680edc6ce2c2399948",
39
+ "prototype": "08f5e9d003dfcbc274f4a31864c9fcbc2962ad8574f40e7bba7d78193f5d349f",
40
+ "codebase-design": "0050828acbec9af9711ac1376749acaf358fb1bbfd98c80fc20df7a26658ccd7",
41
+ "tdd": "775d2b0eb45fdf7a102f0ec0cfca8448c5d441c49373e0a89bc7a22dbe892c3c",
42
+ "diagnosing-bugs": "9fc5749a1671a7c894cc538fbbedda19890bd20f297eb6095ba0969a933ed2dc",
43
+ "code-review": "e4c202dae670331dab5aef98a05594a45ec3b8210a8f9c2ab8a8bba848da4c8e",
44
+ "wizard": "991cb02af3f4584b07f5b6ce30176d05bf2622152a8776db9832825f80601e3d",
45
+ "writing-for-agents": "f58ac3a5e4034da89bd31ff86ffdc74df9e986ef818055d9a399f05fc3b37a1b",
46
+ "setup-matt-pocock-skills": "cb2d75f72fb70ba283a5d76f2722d090a00cb4d3e227e32b05a547193e561800",
47
+ "improve-codebase-architecture": "986e4245d52053692fc866c4c3065a3b944fc2f27a1ea99785a2b116d785e964",
48
+ "wayfinder": "a99de1eab6ae2950b5d4146abfd58257a3e0cfdcbd5e9611f27b2a485fd52242",
49
+ "handoff": "9f8a832aafee3eec6dae6902d758b2438fefb116a6afb9914777238538b14d2b",
50
+ "to-questionnaire": "9a27c55ce8d45b8a7f1a7309bece202bebcd7cab359f551aeeeb3d5c320a3b55",
51
+ "ask-matt": "55c6191c39ad1727d8285225db5a97e622026f6c861f1c924dbc92d8c433e344",
52
+ "grill-with-docs": "05c1b216a482ea0fd5a5281e96b0f1b5fd5e1c1e9edd572709c71944ef6fd7d6",
53
+ "triage": "7009d742e1cf0e40b0d8de8fc47ece25161795da595bd7017a170426dbb51d9a",
54
+ "to-spec": "969b7955a044939809487b416d1e907021c6ad4b07bd5979f46e2f1c629ffd01",
55
+ "to-tickets": "b7c6499e10c567abdbfca7f5c730f2de91584a517816ace5aaaff49f4790e947",
56
+ "grill-me": "cec05582f13830a7bd63984474b999c806a7b93935ed638a27f32eac1b7f976b",
57
+ "teach": "209d2ead53b2c6eba2425198f6f09eb51fd2bde490c34da2d93b187b4b749414",
58
+ "wait-what": "345b8e9aba9f134944f37ae7807cadd19c1b6866d77e3414f2e8f3835061aac0",
59
+ "implement": "3180dbe245245f99dceb669e597cf633935cc87be0ca3c39a86dde54e62a4db9",
60
+ "resolving-merge-conflicts": "cf66bf26fb8a943d426271120f033825f7ed591349812ff4f636d3db56c976dc"
61
+ });
62
+ export const MATT_CATALOG_DIGEST = crypto.createHash("sha256").update(JSON.stringify({
63
+ compatibility: MATT_COMPATIBILITY,
64
+ roles: MATT_ROLES,
65
+ installableSkills: MATT_INSTALLABLE_SKILLS,
66
+ contentHashes: MATT_CONTENT_HASHES
67
+ })).digest("hex");
package/src/matt.js CHANGED
@@ -1,7 +1,59 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import fs from "node:fs";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
- import { PLATFORMS, MATT_SKILLS } from "./catalog.js";
5
+ import { PLATFORMS, hashDirectory } from "./catalog.js";
6
+ import { MATT_CATALOG_DIGEST, MATT_COMPATIBILITY, MATT_CONTENT_HASHES, MATT_INSTALLABLE_SKILLS } from "./matt-catalog.mjs";
7
+
8
+ export const mattReceiptPath = (projectRoot) => path.join(path.resolve(projectRoot), ".matrix", "matt-installation.json");
9
+
10
+ function readJson(file) {
11
+ try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; }
12
+ }
13
+
14
+ function releaseOrder(left, right) {
15
+ const parse = (value) => String(value ?? "").replace(/^v/, "").split(".").map((part) => Number(part));
16
+ const a = parse(left); const b = parse(right);
17
+ if (a.some(Number.isNaN) || b.some(Number.isNaN)) return null;
18
+ for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
19
+ if ((a[index] ?? 0) !== (b[index] ?? 0)) return (a[index] ?? 0) > (b[index] ?? 0) ? 1 : -1;
20
+ }
21
+ return 0;
22
+ }
23
+
24
+ export function inspectMattInstallation({ projectRoot, platforms, home = os.homedir(), contentHashes = MATT_CONTENT_HASHES }) {
25
+ const receipt = readJson(mattReceiptPath(projectRoot));
26
+ const selected = [...new Set(platforms ?? Object.keys(receipt?.platforms ?? {}))];
27
+ const localPresence = selected.reduce((count, platform) => {
28
+ const root = PLATFORMS[platform]?.skillRoot(projectRoot);
29
+ return count + MATT_INSTALLABLE_SKILLS.filter((skill) => root && fs.existsSync(path.join(root, skill, "SKILL.md"))).length;
30
+ }, 0);
31
+ const base = { supported: MATT_COMPATIBILITY, installed: receipt?.compatibility?.release ?? "unknown", action: "matrix init . --with-mattpocock" };
32
+ if (!receipt) return { ...base, status: localPresence ? "unverified" : "incomplete", platforms: [] };
33
+ const order = releaseOrder(receipt.compatibility?.release, MATT_COMPATIBILITY.release);
34
+ if (order !== 0 || receipt.compatibility?.commit !== MATT_COMPATIBILITY.commit || receipt.catalogDigest !== MATT_CATALOG_DIGEST) {
35
+ return { ...base, status: order != null && order > 0 ? "unsupported-newer" : "update-required", platforms: [] };
36
+ }
37
+ try {
38
+ const findings = selected.map((platform) => {
39
+ const root = PLATFORMS[platform].skillRoot(projectRoot);
40
+ const records = receipt.platforms?.[platform]?.skills ?? {};
41
+ const missing = MATT_INSTALLABLE_SKILLS.filter((skill) => !fs.existsSync(path.join(root, skill, "SKILL.md")) || !records[skill]?.hash);
42
+ const modified = MATT_INSTALLABLE_SKILLS.filter((skill) => !missing.includes(skill) && (() => {
43
+ const actual = hashDirectory(path.join(root, skill));
44
+ return actual !== records[skill].hash || actual !== contentHashes[skill];
45
+ })());
46
+ const globalRoot = PLATFORMS[platform].skillRoot(home);
47
+ const ignoredGlobal = MATT_INSTALLABLE_SKILLS.filter((skill) => !fs.existsSync(path.join(root, skill, "SKILL.md")) && fs.existsSync(path.join(globalRoot, skill, "SKILL.md"))).length;
48
+ return { platform, root, missing, modified, ignoredGlobal };
49
+ });
50
+ const status = findings.some((item) => item.missing.length) ? "incomplete"
51
+ : findings.some((item) => item.modified.length) ? "user-modified" : "compatible";
52
+ return { ...base, status, action: status === "compatible" ? null : base.action, platforms: findings };
53
+ } catch (error) {
54
+ return { ...base, status: "unknown", message: error.message, platforms: [] };
55
+ }
56
+ }
5
57
 
6
58
  export function buildProcessInvocation({ command, args, platform = process.platform, nodeExecutable = process.execPath, npmCliPath }) {
7
59
  if (platform !== "win32" || command !== "npx") return { executable: command, args, shell: false };
@@ -13,25 +65,52 @@ export function buildProcessInvocation({ command, args, platform = process.platf
13
65
  return { executable: nodeExecutable, args: [cli, "exec", yes, packageSpec, "--", ...commandArgs], shell: false };
14
66
  }
15
67
 
16
- export function defaultProcessRunner({ command, args, cwd, timeout }) {
68
+ export function defaultProcessRunner({ command, args, cwd, timeout, stdio = "inherit" }) {
17
69
  const invocation = buildProcessInvocation({ command, args });
18
- return execFileSync(invocation.executable, invocation.args, { cwd, stdio: "inherit", timeout, shell: invocation.shell });
70
+ return execFileSync(invocation.executable, invocation.args, { cwd, stdio, timeout, shell: invocation.shell });
19
71
  }
20
72
 
21
- export function createMattAdapter({ run = defaultProcessRunner } = {}) {
73
+ export function createMattAdapter({
74
+ run = defaultProcessRunner,
75
+ stdio = "inherit",
76
+ makeTemporary = () => fs.mkdtempSync(path.join(os.tmpdir(), "matrix-matt-")),
77
+ remove = (target) => fs.rmSync(target, { recursive: true, force: true }),
78
+ verifyCandidate = ({ root, platforms, skills }) => {
79
+ for (const platform of platforms) for (const skill of skills) {
80
+ const installed = path.join(PLATFORMS[platform].skillRoot(root), skill);
81
+ if (hashDirectory(installed) !== MATT_CONTENT_HASHES[skill]) throw new Error(`Matt candidate content did not match the reviewed release: ${platform}/${skill}`);
82
+ }
83
+ }
84
+ } = {}) {
22
85
  return {
23
- installMissing({ projectRoot, platforms, skills = MATT_SKILLS, global = false, timeout = 300_000 }) {
86
+ installMissing() {
87
+ return {
88
+ ok: false,
89
+ code: "MATT_DIRECT_INSTALL_UNSUPPORTED",
90
+ message: "Direct Matt installation was retired; use matrix init --with-mattpocock so Distribution owns staging, backup, and recovery.",
91
+ recovery: "Run matrix init --with-mattpocock."
92
+ };
93
+ },
94
+ prepareCandidate({ platforms, skills = MATT_INSTALLABLE_SKILLS, timeout = 300_000 }) {
95
+ const root = makeTemporary();
24
96
  const attempted = [];
25
97
  try {
26
98
  for (const platform of platforms) {
27
- const args = ["--yes", "skills@latest", "add", "mattpocock/skills", "--yes", "--agent", PLATFORMS[platform].skillsCliAgent];
99
+ const args = ["--yes", MATT_COMPATIBILITY.skillsCli, "add", MATT_COMPATIBILITY.archive, "--yes", "--copy", "--agent", PLATFORMS[platform].skillsCliAgent];
28
100
  for (const skill of skills) args.push("--skill", skill);
29
- if (global) args.push("--global");
30
- run({ command: "npx", args, cwd: projectRoot, timeout });
101
+ run({ command: "npx", args, cwd: root, timeout, stdio });
31
102
  attempted.push(platform);
32
103
  }
33
- return { ok: true, code: "OK", attempted };
104
+ verifyCandidate({ root, platforms, skills });
105
+ return {
106
+ ok: true,
107
+ code: "OK",
108
+ root,
109
+ attempted,
110
+ platforms: Object.fromEntries(platforms.map((platform) => [platform, PLATFORMS[platform].skillRoot(root)]))
111
+ };
34
112
  } catch (error) {
113
+ remove(root);
35
114
  return {
36
115
  ok: false,
37
116
  code: "MATT_INSTALL_FAILED",
@@ -40,6 +119,9 @@ export function createMattAdapter({ run = defaultProcessRunner } = {}) {
40
119
  recovery: "Run matrix init --with-mattpocock after resolving the skills CLI error."
41
120
  };
42
121
  }
122
+ },
123
+ discardCandidate(candidate) {
124
+ if (candidate?.root) remove(candidate.root);
43
125
  }
44
126
  };
45
127
  }
package/src/messages.js CHANGED
@@ -2,31 +2,35 @@ export const messages = {
2
2
  en: {
3
3
  title: "Evidence-driven workflow with Prim and Arch orchestration",
4
4
  recommended: "Recommended setup", apply: "Apply", customize: "Customize", cancel: "Cancel",
5
- project: "Project (current directory)", global: "Global (all projects)",
5
+ project: "Project (current directory)", global: "Global Matrix Skills",
6
6
  copy: "Copy (recommended)", symlink: "Symlink (advanced)",
7
7
  matrix: "Matrix", matt: "Matt Pocock skills", installed: "installed", ready: "Matrix is ready.",
8
8
  noPlatform: "No verified platform was detected. Select Claude Code, Codex, or both.",
9
9
  preview: "Planned changes", confirm: "Apply these changes?", doctor: "Installation diagnosis",
10
10
  languageQuestion: "Language", scopeQuestion: "Installation scope", platformsQuestion: "Platforms", orchestrationQuestion: "Default orchestration",
11
- mattQuestion: "Install missing Matt Pocock skills?", replaceQuestion: "Existing Matrix files are not proven managed. Back up and replace?",
12
- yes: "Yes", no: "No", stateMissing: "missing", stateMatching: "up to date", stateComplete: "complete", stateUserModified: "user-modified", statePartial: "partial", stateOutdated: "outdated",
11
+ 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
+ mattReplaceQuestion: "Matt Skills contain local or unreadable content. Back them up and replace them with the supported release?",
13
+ 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",
13
14
  actionInstall: "install", actionKeep: "keep", actionBackupReplace: "back up and replace", actionRepair: "repair", actionBlocked: "requires approval",
14
- dryRunComplete: "Dry run complete.", 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.", mattInherited: "Matt skills are satisfied by the global installation."
15
+ dryRunComplete: "Dry run complete.", defaultOrchestration: "Default orchestration", mattCompatibilityStatus: "Matt {release}: {status}",
16
+ 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
+ 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}."
15
18
  },
16
19
  "zh-CN": {
17
- manifestMissing: "当前安装范围没有 Matrix 安装清单。", transactionPending: "发现中断的 Matrix 安装事务。请再次运行 matrix init 后再应用新计划。", runtimeMissing: "缺少随附的 Matrix runtime。", brokenLink: "检测到断开的 Matrix 链接。", mattInherited: "Matt skills 由全局安装提供。",
20
+ manifestMissing: "当前安装范围没有 Matrix 安装清单。", transactionPending: "发现中断的 Matrix 安装事务。请再次运行 matrix init 后再应用新计划。", runtimeMissing: "缺少随附的 Matrix runtime。", brokenLink: "检测到断开的 Matrix 链接。", mattGlobalIgnored: "检测到全局 Matt Skills,但项目兼容性契约不会采用它们。", mattSetupHint: "已为当前项目安装 Matt {release}。如需配置由用户拥有的集成,可显式运行一次 $setup-matt-pocock-skills。",
18
21
  languageQuestion: "语言", scopeQuestion: "安装范围", platformsQuestion: "要配置的平台", orchestrationQuestion: "默认编排模式",
19
- mattQuestion: "是否安装缺失的 Matt Pocock skills?", replaceQuestion: "现有 Matrix 文件无法确认由 Matrix 管理。是否备份并替换?",
20
- yes: "是", no: "否", stateMissing: "未安装", stateMatching: "已是最新", stateComplete: "完整", stateUserModified: "已被用户修改", statePartial: "不完整", stateOutdated: "可更新",
22
+ mattQuestion: "是否在当前项目安装或核对 Matrix 支持的 Matt Pocock Release?", replaceQuestion: "现有 Matrix 文件无法确认由 Matrix 管理。是否备份并替换?", mattReplaceQuestion: "Matt Skills 存在本地修改或不可读内容。是否先备份,再替换为受支持的 Release?",
23
+ yes: "是", no: "否", stateMissing: "未安装", stateMatching: "已是最新", stateComplete: "完整", stateUserModified: "已被用户修改", statePartial: "不完整", stateOutdated: "可更新", stateSafeUpdate: "可安全更新", stateAdoptable: "可接管", stateUnreadable: "不可读", stateCompatible: "兼容", stateUpdateRequired: "需要更新", stateUnsupportedNewer: "版本过新且不受支持", stateUnverified: "未验证", stateUnknown: "未知",
21
24
  actionInstall: "安装", actionKeep: "保留", actionBackupReplace: "备份并替换", actionRepair: "修复", actionBlocked: "需要确认",
22
- dryRunComplete: "预览完成。",
25
+ dryRunComplete: "预览完成。", defaultOrchestration: "默认编排", mattCompatibilityStatus: "Matt {release}:{status}",
23
26
  title: "提供 Prim 与 Arch 编排的证据驱动工作流",
24
27
  recommended: "推荐配置", apply: "开始安装", customize: "自定义", cancel: "取消",
25
- project: "项目(当前目录)", global: "全局(所有项目)",
28
+ project: "项目(当前目录)", global: "全局 Matrix Skills",
26
29
  copy: "复制(推荐)", symlink: "符号链接(高级)",
27
30
  matrix: "Matrix", matt: "Matt Pocock skills", installed: "已安装", ready: "Matrix 已就绪。",
28
31
  noPlatform: "未检测到已验证的平台。请选择 Claude Code、Codex,或两者。",
29
- preview: "计划变更", confirm: "确认应用这些变更?", doctor: "安装诊断"
32
+ preview: "计划变更", confirm: "确认应用这些变更?", doctor: "安装诊断",
33
+ 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}。"
30
34
  }
31
35
  };
32
36
 
@@ -0,0 +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
+ }
@@ -39,8 +39,8 @@ export function validateCandidate(packageRoot, expectedVersion) {
39
39
  try {
40
40
  const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
41
41
  if (manifest.name !== PACKAGE_NAME || manifest.version !== expectedVersion) return { ok: false, reason: "candidate package identity mismatch" };
42
- for (const directory of [".claude/skills", "assets/skills-zh-CN", "src", "bin"]) if (!fs.existsSync(path.join(packageRoot, directory))) return { ok: false, reason: `candidate missing ${directory}` };
43
- for (const languageRoot of [path.join(packageRoot, ".claude", "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" };
42
+ 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}` };
43
+ 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" };
44
44
  for (const args of [["--version"], ["--help"], ["workflow", "doctor", "--json"]]) {
45
45
  const result = run(process.execPath, [path.join(packageRoot, "bin", "matrix.js"), ...args], { cwd: os.tmpdir() });
46
46
  if (!result.ok) return { ok: false, reason: `candidate command failed: matrix ${args.join(" ")}` };
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import crypto from "node:crypto";
5
+ import { fileURLToPath } from "node:url";
5
6
 
6
7
  const JOURNAL_SCHEMA = "matrix/workflow-transaction/v1";
7
8
  const RECEIPT_SCHEMA = "matrix/workflow-transaction-receipt/v1";
@@ -9,6 +10,9 @@ const LOCK_SCHEMA = "matrix/workflow-lock/v1";
9
10
  const MAX_JOURNAL_BYTES = 1024 * 1024;
10
11
  const MAX_TRANSACTIONS = 128;
11
12
  const RECEIPT_LIMIT = 32;
13
+ const quoteCommandPath = (value) => process.platform === "win32" ? `"${value}"` : `'${value.replaceAll("'", "'\\''")}'`;
14
+ const workflowRuntimePath = path.join(path.dirname(fileURLToPath(import.meta.url)), "matrix-runtime.mjs");
15
+ const workflowCommand = (argumentsText) => `node ${quoteCommandPath(workflowRuntimePath)} ${argumentsText}`;
12
16
 
13
17
  const stamp = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
14
18
  const fail = (code, message, extra = {}) => ({ ok: false, code, message, ...extra });
@@ -430,11 +434,11 @@ export function workflowMutationGate(cwd) {
430
434
  let pending;
431
435
  try { pending = pendingTransactions(cwd); }
432
436
  catch (error) { return recoverError(error, null); }
433
- if (pending.length) return fail("WORKFLOW_RECOVERY_REQUIRED", "An unfinished Workflow transaction must be resolved before starting another mutation.", { transactions: pending.map(({ id }) => id), recovery_command: "matrix workflow doctor" });
437
+ if (pending.length) return fail("WORKFLOW_RECOVERY_REQUIRED", "An unfinished Workflow transaction must be resolved before starting another mutation.", { transactions: pending.map(({ id }) => id), recovery_command: workflowCommand("doctor") });
434
438
  const currentLock = readLock(cwd);
435
439
  if (!currentLock) return null;
436
440
  const owner = lockOwner(currentLock);
437
- return fail(owner === "active" ? "WORKFLOW_BUSY" : owner === "absent" ? "WORKFLOW_RECOVERY_REQUIRED" : "WORKFLOW_LOCK_CONFLICT", owner === "active" ? "An active Workflow mutation owns the project boundary." : "A Workflow lock requires explicit doctor recovery.", { lock_id: currentLock.id, transaction_id: currentLock.transaction_id, recovery_command: "matrix workflow doctor" });
441
+ return fail(owner === "active" ? "WORKFLOW_BUSY" : owner === "absent" ? "WORKFLOW_RECOVERY_REQUIRED" : "WORKFLOW_LOCK_CONFLICT", owner === "active" ? "An active Workflow mutation owns the project boundary." : "A Workflow lock requires explicit doctor recovery.", { lock_id: currentLock.id, transaction_id: currentLock.transaction_id, recovery_command: workflowCommand("doctor") });
438
442
  }
439
443
 
440
444
  function compactReceipt(cwd, journal, disposition) {
@@ -474,7 +478,7 @@ function recoverError(error, transactionId) {
474
478
  const code = error.code && String(error.code).startsWith("WORKFLOW_") ? error.code : "WORKFLOW_PERSISTENCE_FAILED";
475
479
  return fail(code, error.message, {
476
480
  transaction_id: transactionId,
477
- recovery_command: `matrix workflow doctor --repair --transaction ${transactionId} --strategy continue`
481
+ recovery_command: workflowCommand(`doctor --repair --transaction ${transactionId} --strategy continue`)
478
482
  });
479
483
  }
480
484
 
@@ -559,7 +563,7 @@ function repairTransaction(cwd, id, strategy) {
559
563
  }
560
564
 
561
565
  function exactCommands(transaction) {
562
- return transaction.strategies.map((strategy) => `matrix workflow doctor --repair --transaction ${transaction.id} --strategy ${strategy}`);
566
+ return transaction.strategies.map((strategy) => workflowCommand(`doctor --repair --transaction ${transaction.id} --strategy ${strategy}`));
563
567
  }
564
568
 
565
569
  export function workflowDoctor(cwd, { repair = false, transaction = null, strategy = null, lock = null } = {}) {
@@ -585,7 +589,7 @@ export function workflowDoctor(cwd, { repair = false, transaction = null, strate
585
589
  transaction_id: currentLock.transaction_id,
586
590
  owner,
587
591
  commands: owner === "absent" && !transactions.some((item) => item.id === currentLock.transaction_id && (item.corrupt || !["committed", "rolled-back"].includes(item.status)))
588
- ? [`matrix workflow doctor --repair --lock ${currentLock.id}`]
592
+ ? [workflowCommand(`doctor --repair --lock ${currentLock.id}`)]
589
593
  : []
590
594
  }] : [])
591
595
  ];