@rosenfan/matrix 0.1.4 → 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 (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +29 -29
  3. package/README.zh-CN.md +27 -27
  4. package/{.claude → assets}/skills/matrix/SKILL.md +78 -78
  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 +49 -49
  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 +36 -36
  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 -41
  13. package/{.claude/skills/matrix-archive → assets/skills/matrix-build}/agents/openai.yaml +4 -4
  14. package/{.claude → assets}/skills/matrix-claude/SKILL.md +98 -98
  15. package/{.claude → assets}/skills/matrix-claude/agents/openai.yaml +4 -4
  16. package/{.claude → assets}/skills/matrix-design/SKILL.md +65 -65
  17. package/{.claude → assets}/skills/matrix-design/agents/openai.yaml +4 -4
  18. package/{.claude → assets}/skills/matrix-hotfix/SKILL.md +8 -8
  19. package/{.claude/skills/matrix-build → assets/skills/matrix-hotfix}/agents/openai.yaml +4 -4
  20. package/{.claude → assets}/skills/matrix-open/SKILL.md +39 -39
  21. package/assets/skills/matrix-open/agents/openai.yaml +4 -0
  22. package/{.claude → assets}/skills/matrix-status/SKILL.md +15 -15
  23. package/{.claude → assets}/skills/matrix-status/agents/openai.yaml +4 -4
  24. package/{.claude → assets}/skills/matrix-tweak/SKILL.md +8 -8
  25. package/assets/skills/matrix-tweak/agents/openai.yaml +4 -0
  26. package/{.claude → assets}/skills/matrix-verify/SKILL.md +62 -62
  27. package/assets/skills/matrix-verify/agents/openai.yaml +4 -0
  28. package/assets/skills-zh-CN/matrix/SKILL.md +14 -14
  29. package/assets/skills-zh-CN/matrix-build/SKILL.md +4 -4
  30. package/assets/skills-zh-CN/matrix-design/SKILL.md +4 -4
  31. package/assets/skills-zh-CN/matrix-open/SKILL.md +3 -3
  32. package/assets/skills-zh-CN/matrix-verify/SKILL.md +19 -19
  33. package/bin/matrix.js +15 -15
  34. package/package.json +4 -13
  35. package/src/catalog.js +79 -72
  36. package/src/cli.js +100 -100
  37. package/src/distribution.js +181 -178
  38. package/src/installer.js +10 -10
  39. package/src/matt-catalog.mjs +1 -1
  40. package/src/matt.js +115 -115
  41. package/src/messages.js +14 -14
  42. package/src/runtime-resolver.js +76 -76
  43. package/src/self-update.js +81 -81
  44. package/src/workflow-transaction.js +13 -13
  45. package/src/workflow.js +711 -705
  46. package/.claude/skills/matrix-open/agents/openai.yaml +0 -4
  47. package/.claude/skills/matrix-tweak/agents/openai.yaml +0 -4
  48. package/.claude/skills/matrix-verify/agents/openai.yaml +0 -4
@@ -5,28 +5,28 @@ description: 依据计划验证 Matrix 变更并审查差异。
5
5
 
6
6
  # Matrix Verify
7
7
 
8
- 运行已批准 Contract 的验证命令,在 `verification.md` 写入 `## Test evidence`。hotfix 另写 `## Regression evidence`;tweak 另写 `## Scope review evidence`。所有模式都必须写入 `## Review evidence`,并在其中保留内容充分的 `### Standards` 与 `### Spec` 两个子节。读取冻结 `orchestration`:
8
+ 运行已批准 Contract 的验证命令,在 `verification.md` 写入 `## Test evidence`。hotfix 另写 `## Regression evidence`;tweak 另写 `## Scope review evidence`。所有模式都必须写入 `## Review evidence`,并在其中保留内容充分的 `### Standards` 与 `### Spec` 两个子节。读取冻结 `orchestration`:
9
9
 
10
10
  验证正文使用 `artifact_language`;上述 Runtime 所需英文证据标题保持不变。
11
11
 
12
12
  - **Prim**:Matrix 自行完成 Standards 与 Spec 双轴审查。
13
13
  - **Arch**:调用 `code-review` 产生双轴证据,并说明调用原因。
14
14
 
15
- 审查结果只是 Matrix Verify 的输入;companion 不得决定阶段结果、扩大已批准架构、推进或归档。不得调用事后架构改进 wrapper。正确安装的审查能力失败时最多执行一次同能力 Matrix fallback;安装完整性问题必须停止。
16
-
17
- Arch 正常执行 `code-review` 后,使用项目 Runtime 把 fixed point、HEAD/diff、Contract、审查正文和工作区绑定为当前 revision 的回执:
18
-
19
- ```powershell
20
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs review --source code-review --fixed-point <ref> --standards <passed|failed> --spec <passed|failed>
21
- ```
22
-
23
- 只有在无 Git 基线、已提交的 HEAD diff 为空、仍存在 staged/unstaged/untracked 候选,或 capability 真实执行失败时,才记录限制并使用一次有界 fallback:
24
-
25
- ```powershell
26
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs review --source matrix-fallback --reason <git-baseline-unavailable|empty-head-diff|uncommitted-worktree|capability-execution-failed> [--fixed-point <ref-for-empty-head-diff-or-uncommitted-worktree>] --standards <passed|failed> --spec <passed|failed>
27
- ```
28
-
29
- 不得把 fallback 写成 `code-review` 成功。`empty-head-diff` 必须提供 fixed point,由 Runtime 核实三点 diff 确实为空。`uncommitted-worktree` 也必须提供 fixed point,Runtime 会把 staged、unstaged 与 untracked 候选内容绑定为 `candidate_hash`,之后任意变化都会使回执过期。当前 revision 已有回执或任一审查轴失败时,必须先受控 Return,不能覆盖回执后继续。
15
+ 审查结果只是 Matrix Verify 的输入;companion 不得决定阶段结果、扩大已批准架构、推进或归档。不得调用事后架构改进 wrapper。正确安装的审查能力失败时最多执行一次同能力 Matrix fallback;安装完整性问题必须停止。
16
+
17
+ Arch 正常执行 `code-review` 后,使用项目 Runtime 把 fixed point、HEAD/diff、Contract、审查正文和工作区绑定为当前 revision 的回执:
18
+
19
+ ```powershell
20
+ node <matrix-skill-directory>/scripts/matrix-runtime.mjs review --source code-review --fixed-point <ref> --standards <passed|failed> --spec <passed|failed>
21
+ ```
22
+
23
+ 只有在无 Git 基线、已提交的 HEAD diff 为空、仍存在 staged/unstaged/untracked 候选,或 capability 真实执行失败时,才记录限制并使用一次有界 fallback:
24
+
25
+ ```powershell
26
+ node <matrix-skill-directory>/scripts/matrix-runtime.mjs review --source matrix-fallback --reason <git-baseline-unavailable|empty-head-diff|uncommitted-worktree|capability-execution-failed> [--fixed-point <ref-for-empty-head-diff-or-uncommitted-worktree>] --standards <passed|failed> --spec <passed|failed>
27
+ ```
28
+
29
+ 不得把 fallback 写成 `code-review` 成功。`empty-head-diff` 必须提供 fixed point,由 Runtime 核实三点 diff 确实为空。`uncommitted-worktree` 也必须提供 fixed point,Runtime 会把 staged、unstaged 与 untracked 候选内容绑定为 `candidate_hash`,之后任意变化都会使回执过期。当前 revision 已有回执或任一审查轴失败时,必须先受控 Return,不能覆盖回执后继续。
30
30
 
31
31
  失败时使用受控 Return:
32
32
 
@@ -35,9 +35,9 @@ node <matrix-skill-directory>/scripts/matrix-runtime.mjs return build --reason v
35
35
  node <matrix-skill-directory>/scripts/matrix-runtime.mjs return design --reason acceptance-or-design-gap
36
36
  ```
37
37
 
38
- Return 会轮换旧 `verification.md`;不得把旧结论直接复制为新的当前证据。
39
-
40
- Arch 的 Return 也会轮换 `review-receipt.json`;新 revision 必须重新审查并生成新回执。仅有 Markdown 标题、旧回执或与当前工作区不匹配的回执都不能通过 guard。
38
+ Return 会轮换旧 `verification.md`;不得把旧结论直接复制为新的当前证据。
39
+
40
+ Arch 的 Return 也会轮换 `review-receipt.json`;新 revision 必须重新审查并生成新回执。仅有 Markdown 标题、旧回执或与当前工作区不匹配的回执都不能通过 guard。
41
41
 
42
42
  ```powershell
43
43
  node <matrix-skill-directory>/scripts/matrix-runtime.mjs guard verify
package/bin/matrix.js CHANGED
@@ -1,15 +1,15 @@
1
- #!/usr/bin/env node
2
-
3
- import { main } from "../src/cli.js";
4
-
5
- main(process.argv.slice(2)).then((exitCode) => {
6
- if (Number.isInteger(exitCode) && exitCode !== 0) process.exitCode = exitCode;
7
- }).catch((error) => {
8
- if (error.code === "CANCELLED") {
9
- console.log("\nCancelled.");
10
- process.exitCode = 0;
11
- } else {
12
- console.error(`\nMatrix error: ${error.message}`);
13
- process.exitCode = 1;
14
- }
15
- });
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/cli.js";
4
+
5
+ main(process.argv.slice(2)).then((exitCode) => {
6
+ if (Number.isInteger(exitCode) && exitCode !== 0) process.exitCode = exitCode;
7
+ }).catch((error) => {
8
+ if (error.code === "CANCELLED") {
9
+ console.log("\nCancelled.");
10
+ process.exitCode = 0;
11
+ } else {
12
+ console.error(`\nMatrix error: ${error.message}`);
13
+ process.exitCode = 1;
14
+ }
15
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rosenfan/matrix",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Evidence-driven development workflow with Prim and Arch agent-skill orchestration.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,19 +10,10 @@
10
10
  "files": [
11
11
  "bin/",
12
12
  "src/",
13
+ "assets/skills",
13
14
  "assets/skills-zh-CN",
14
- ".claude/skills/matrix",
15
- ".claude/skills/matrix-open",
16
- ".claude/skills/matrix-design",
17
- ".claude/skills/matrix-build",
18
- ".claude/skills/matrix-verify",
19
- ".claude/skills/matrix-archive",
20
- ".claude/skills/matrix-hotfix",
21
- ".claude/skills/matrix-tweak",
22
- ".claude/skills/matrix-status",
23
- ".claude/skills/matrix-claude",
24
- "!.claude/skills/**/__pycache__",
25
- "!.claude/skills/**/scripts/test_*.py",
15
+ "!assets/skills/**/__pycache__",
16
+ "!assets/skills-zh-CN/**/__pycache__",
26
17
  "README.md",
27
18
  "README.zh-CN.md",
28
19
  "LICENSE"
package/src/catalog.js CHANGED
@@ -1,87 +1,94 @@
1
- import crypto from "node:crypto";
2
- import fs from "node:fs";
3
- import os from "node:os";
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
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
- ];
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
14
  export const MATT_SKILLS = MATT_INSTALLABLE_SKILLS;
15
15
  export const UNMANAGED_MATT_SKILLS = MATT_ROLES.incompatible;
16
-
16
+
17
17
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
18
18
  const packageManifest = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
19
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, ".claude", "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
- export function hashDirectory(directory, extraFiles = []) {
55
- const hash = crypto.createHash("sha256");
56
- const files = new Map();
57
- const visit = (current, relative = "") => {
58
- for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
59
- const child = path.join(current, entry.name);
60
- const childRelative = path.join(relative, entry.name).replaceAll("\\", "/");
61
- if (entry.isDirectory()) visit(child, childRelative);
62
- else if (entry.isFile()) {
63
- files.set(childRelative, fs.readFileSync(child));
64
- }
65
- }
66
- };
67
- visit(directory);
68
- for (const extra of extraFiles) files.set(extra.path, Buffer.from(extra.contents));
69
- for (const [relative, contents] of [...files.entries()].sort(([left], [right]) => left.localeCompare(right))) {
70
- hash.update(`${relative}\0`);
71
- hash.update(contents);
72
- }
73
- return hash.digest("hex");
74
- }
75
-
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
+
76
83
  export function workflowRuntimeSource() { return path.join(packageRoot, "src", "workflow.js"); }
77
84
  export function workflowTransactionSource() { return path.join(packageRoot, "src", "workflow-transaction.js"); }
78
85
  export function mattCatalogSource() { return path.join(packageRoot, "src", "matt-catalog.mjs"); }
79
-
86
+
80
87
  export function releaseCatalog(language = "en") {
81
- if (!LANGUAGE_IDS.includes(language)) throw new Error(`Unsupported language: ${language}`);
82
- const root = sourceSkillsRoot(language);
83
- const missing = MATRIX_SKILLS.filter((skill) => !fs.existsSync(path.join(root, skill, "SKILL.md")));
84
- if (missing.length) throw new Error(`Matrix package is incomplete. Missing: ${missing.join(", ")}`);
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(", ")}`);
85
92
  const runtime = fs.readFileSync(workflowRuntimeSource());
86
93
  const transaction = fs.readFileSync(workflowTransactionSource());
87
94
  const mattCatalog = fs.readFileSync(mattCatalogSource());
package/src/cli.js CHANGED
@@ -2,22 +2,22 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
5
- import { PLATFORMS } from "./catalog.js";
6
- import { createDistribution, installedIntent } from "./distribution.js";
7
- import { createMattAdapter, inspectMattInstallation } from "./matt.js";
5
+ import { PLATFORMS } from "./catalog.js";
6
+ import { createDistribution, installedIntent } from "./distribution.js";
7
+ import { createMattAdapter, inspectMattInstallation } from "./matt.js";
8
8
  import { text } from "./messages.js";
9
9
  import { createUi } from "./ui.js";
10
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
+ import { compareVersions, globalBinPath, latestVersion, selfUpdate } from "./self-update.js";
12
+ import { resolveProjectRuntime } from "./runtime-resolver.js";
13
+ import { MATT_COMPATIBILITY } from "./matt-catalog.mjs";
14
14
 
15
15
  const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
16
- const version = JSON.parse(fs.readFileSync(packagePath, "utf8")).version;
17
- const HELP = `Matrix ${version}\n\nUsage: matrix init [directory] [options]\n matrix update [directory] [options]\n matrix doctor [directory] [options]\n\nOptions:\n --language <en|zh-CN>\n --scope <project|global>\n --platform <claude-code|codex> (repeatable)\n --mode <copy|symlink>\n --with-mattpocock | --without-mattpocock\n --force-matt\n --default-orchestration <prim|arch>\n --skip-self-update --force --yes, -y --dry-run --json --no-color`;
16
+ const version = JSON.parse(fs.readFileSync(packagePath, "utf8")).version;
17
+ const HELP = `Matrix ${version}\n\nUsage: matrix init [directory] [options]\n matrix update [directory] [options]\n matrix doctor [directory] [options]\n\nOptions:\n --language <en|zh-CN>\n --scope <project|global>\n --platform <claude-code|codex> (repeatable)\n --mode <copy|symlink>\n --with-mattpocock | --without-mattpocock\n --force-matt\n --default-orchestration <prim|arch>\n --skip-self-update --force --yes, -y --dry-run --json --no-color`;
18
18
 
19
- function parse(argv) {
20
- const result = { command: null, directory: null, scope: null, language: null, platforms: [], mode: "copy", matt: null, mattPolicy: "preserve", defaultOrchestration: null, matrixPolicy: "safe", yes: false, dryRun: false, json: false, color: true, skipSelfUpdate: false, reexec: false };
19
+ function parse(argv) {
20
+ const result = { command: null, directory: null, scope: null, language: null, platforms: [], mode: "copy", matt: null, mattPolicy: "preserve", defaultOrchestration: null, matrixPolicy: "safe", yes: false, dryRun: false, json: false, color: true, skipSelfUpdate: false, reexec: false };
21
21
  const values = [...argv];
22
22
  if (values[0] && !values[0].startsWith("-")) result.command = values.shift();
23
23
  while (values.length) {
@@ -27,9 +27,9 @@ function parse(argv) {
27
27
  else if (token === "--language") result.language = values.shift();
28
28
  else if (token === "--platform") result.platforms.push(values.shift());
29
29
  else if (token === "--mode") result.mode = values.shift();
30
- else if (token === "--with-mattpocock") result.matt = "missing";
31
- else if (token === "--without-mattpocock") result.matt = "none";
32
- else if (token === "--force-matt") result.mattPolicy = "replace";
30
+ else if (token === "--with-mattpocock") result.matt = "missing";
31
+ else if (token === "--without-mattpocock") result.matt = "none";
32
+ else if (token === "--force-matt") result.mattPolicy = "replace";
33
33
  else if (token === "--default-orchestration") result.defaultOrchestration = values.shift();
34
34
  else if (token === "--force") result.matrixPolicy = "replace";
35
35
  else if (["--yes", "-y"].includes(token)) result.yes = true;
@@ -53,8 +53,8 @@ function projectRoot(directory) {
53
53
 
54
54
  function labelsFor(language) { return { yes: text(language, "yes"), no: text(language, "no") }; }
55
55
 
56
- function localizedState(language, 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);
56
+ function localizedState(language, 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);
58
58
  }
59
59
 
60
60
  function localizedAction(language, action) {
@@ -71,20 +71,20 @@ async function intentFrom(options, ui) {
71
71
  const defaultOrchestration = options.defaultOrchestration ?? (interactive && !options.yes && matt === "missing"
72
72
  ? await ui.select(text(language, "orchestrationQuestion"), [{ label: "Arch", value: "arch" }, { label: "Prim", value: "prim" }])
73
73
  : null);
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 };
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 };
75
75
  }
76
76
 
77
77
  function render(ui, evaluation) {
78
78
  const language = evaluation.intent?.language ?? "en";
79
79
  const actions = (evaluation.actions ?? []).map((item) => `${item.platform}: ${localizedAction(language, item.matrix)}`).join(", ");
80
80
  ui.info(actions ? `${text(language, "preview")}: ${actions}` : evaluation.code);
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
- }
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
+ }
86
86
  for (const item of evaluation.diagnostics ?? []) ui.warn(item.code === "USER_MODIFIED_BLOCKED" ? text(language, "replaceQuestion") : item.message ?? item.code);
87
- for (const item of evaluation.diagnosis ?? []) (item.code === "MATT_GLOBAL_IGNORED" ? ui.muted : ui.warn)(item.message ?? item.code);
87
+ for (const item of evaluation.diagnosis ?? []) (item.code === "MATT_GLOBAL_IGNORED" ? ui.muted : ui.warn)(item.message ?? item.code);
88
88
  }
89
89
 
90
90
  async function init(options) {
@@ -94,42 +94,42 @@ async function init(options) {
94
94
  return refresh(intent, options, ui);
95
95
  }
96
96
 
97
- async function refresh(intent, options, ui) {
98
- const distribution = createDistribution({ mattAdapter: createMattAdapter({ stdio: options.json ? "pipe" : "inherit" }) });
99
- let evaluation = distribution.evaluate(intent);
100
- const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
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
+ async function refresh(intent, options, ui) {
98
+ const distribution = createDistribution({ mattAdapter: createMattAdapter({ stdio: options.json ? "pipe" : "inherit" }) });
99
+ let evaluation = distribution.evaluate(intent);
100
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
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);
122
122
  if (!evaluation.ok) return 2;
123
123
  if (!options.yes && interactive && !options.dryRun && !await ui.confirm(text(intent.language, "confirm"), true, labelsFor(intent.language))) return 0;
124
- const result = distribution.commit(evaluation.plan);
124
+ const result = distribution.commit(evaluation.plan);
125
125
  if (!result.ok) { ui.warn(result.error ?? result.code); return 2; }
126
126
  if (result.code === "PARTIAL") { ui.warn(result.recovery); return 1; }
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
- }
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
+ }
133
133
  ui.muted(evaluation.intent.platforms.map((id) => `${PLATFORMS[id].name}: $matrix <request>`).join("\n"));
134
134
  return 0;
135
135
  }
@@ -143,7 +143,7 @@ async function update(options) {
143
143
  if (options.json) console.log(JSON.stringify(result)); else ui.warn(result.message);
144
144
  return 2;
145
145
  }
146
- const forbidden = options.language || options.scope || options.platforms.length || options.matt || options.mattPolicy === "replace" || options.defaultOrchestration || options.mode !== "copy";
146
+ const forbidden = options.language || options.scope || options.platforms.length || options.matt || options.mattPolicy === "replace" || options.defaultOrchestration || options.mode !== "copy";
147
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
148
  const intent = { ...installed.intent, dryRun: options.dryRun };
149
149
  let npm = { status: "skipped", current: version, target: version, reason: options.skipSelfUpdate ? "disabled by --skip-self-update" : null };
@@ -158,7 +158,7 @@ async function update(options) {
158
158
  return 2;
159
159
  }
160
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; }
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
162
  if (!options.json) {
163
163
  await ui.banner(); ui.info(`Matrix update: CLI ${npm.current}${npm.status === "available" ? ` → ${npm.target}` : ""}`);
164
164
  ui.muted(`Assets: ${intent.scope}, ${intent.language}, ${intent.platforms.join(", ")}`);
@@ -166,7 +166,7 @@ async function update(options) {
166
166
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
167
167
  if (npm.status === "available") {
168
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; }
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
170
  const updated = selfUpdate(version, npm.target);
171
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
172
  const args = [globalBinPath(), "update", root, "--skip-self-update", "--_reexec", "--yes"];
@@ -174,51 +174,51 @@ async function update(options) {
174
174
  const rerun = spawnSync(process.execPath, args, { stdio: "inherit" });
175
175
  return Number.isInteger(rerun.status) ? rerun.status : 2;
176
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
- }
177
+ if (options.json) return refresh(intent, { ...options, yes: true, updateMode: true }, ui);
178
+ return refresh(intent, { ...options, updateMode: true }, ui);
179
+ }
192
180
 
193
- async function doctor(options) {
194
- const ui = createUi({ color: options.color && !options.json });
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);
200
- const evaluation = createDistribution().diagnose(intent);
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}` : ""}`); }
204
- return evaluation.ok ? 0 : 2;
205
- }
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
+ }
206
187
 
207
- export async function main(argv) {
208
- if (argv[0] === "workflow") {
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
- }
221
- const result = invoke(argv.slice(1).filter((value) => value !== "--json"));
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
+ }
192
+
193
+ async function doctor(options) {
194
+ const ui = createUi({ color: options.color && !options.json });
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);
200
+ const evaluation = createDistribution().diagnose(intent);
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}` : ""}`); }
204
+ return evaluation.ok ? 0 : 2;
205
+ }
206
+
207
+ export async function main(argv) {
208
+ if (argv[0] === "workflow") {
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
+ }
221
+ const result = invoke(argv.slice(1).filter((value) => value !== "--json"));
222
222
  if (json) console.log(JSON.stringify(result));
223
223
  else console.log(result.ok ? JSON.stringify(result, null, 2) : `MATRIX ERROR [${result.code}]: ${result.message}`);
224
224
  return result.ok ? 0 : 2;