@rosenfan/matrix 0.1.2 → 0.1.3
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/.claude/skills/matrix/SKILL.md +58 -56
- package/.claude/skills/matrix/scripts/matrix_claude.py +11 -11
- package/.claude/skills/matrix/scripts/matrix_state.py +60 -60
- package/.claude/skills/matrix-archive/SKILL.md +27 -24
- package/.claude/skills/matrix-build/SKILL.md +26 -24
- package/.claude/skills/matrix-claude/SKILL.md +11 -9
- package/.claude/skills/matrix-design/SKILL.md +31 -27
- package/.claude/skills/matrix-hotfix/SKILL.md +1 -1
- package/.claude/skills/matrix-open/SKILL.md +29 -19
- package/.claude/skills/matrix-status/SKILL.md +5 -5
- package/.claude/skills/matrix-tweak/SKILL.md +1 -1
- package/.claude/skills/matrix-verify/SKILL.md +23 -21
- package/README.md +123 -97
- package/README.zh-CN.md +122 -96
- package/assets/skills-zh-CN/matrix/SKILL.md +78 -75
- package/assets/skills-zh-CN/matrix-archive/SKILL.md +31 -28
- package/assets/skills-zh-CN/matrix-build/SKILL.md +37 -35
- package/assets/skills-zh-CN/matrix-claude/SKILL.md +31 -29
- package/assets/skills-zh-CN/matrix-design/SKILL.md +10 -6
- package/assets/skills-zh-CN/matrix-hotfix/SKILL.md +1 -1
- package/assets/skills-zh-CN/matrix-open/SKILL.md +19 -9
- package/assets/skills-zh-CN/matrix-status/SKILL.md +5 -5
- package/assets/skills-zh-CN/matrix-tweak/SKILL.md +1 -1
- package/assets/skills-zh-CN/matrix-verify/SKILL.md +17 -15
- package/package.json +2 -2
- package/src/catalog.js +16 -16
- package/src/cli.js +81 -28
- package/src/distribution.js +154 -123
- package/src/messages.js +4 -4
- package/src/self-update.js +81 -0
- package/src/ui.js +1 -1
- package/src/workflow-transaction.js +622 -622
- package/src/workflow.js +652 -420
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: matrix-claude
|
|
3
|
-
description: 将已批准的 Matrix Contract 导出为受边界约束的 Claude Code 任务包,不改变 Matrix 工作流状态。
|
|
4
|
-
---
|
|
5
|
-
|
|
1
|
+
---
|
|
2
|
+
name: matrix-claude
|
|
3
|
+
description: 将已批准的 Matrix Contract 导出为受边界约束的 Claude Code 任务包,不改变 Matrix 工作流状态。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
6
|
# Matrix Claude Handoff
|
|
7
7
|
|
|
8
|
-
这是可选旁路,不是 Matrix phase。它不得执行 `transition`、`archive`,也不得写入 `matrix.yaml` 或 `events.jsonl
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
8
|
+
这是可选旁路,不是 Matrix phase。它不得执行 `transition`、`archive`,也不得写入 `matrix.yaml` 或 `events.jsonl`。它只支持匹配的 full 或 legacy-full 三产物 Contract;新的 lightweight hotfix/tweak 不支持导出。
|
|
9
|
+
|
|
10
|
+
人类可读 handoff 内容使用 inspect 的 `artifact_language`;稳定的机器可读标题和 metadata key 保持不变。
|
|
11
|
+
|
|
12
|
+
仅当变更已进入 Build、`design -> build` 已批准精确 Contract,并且用户明确选择 Claude Code 实现时使用。先检查:
|
|
13
|
+
|
|
14
|
+
```powershell
|
|
15
|
+
matrix workflow guard build
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
若结果为 `CONTRACT_CHANGED`、`CONTRACT_UNAPPROVED` 或 `CONTRACT_READ_FAILED`,回到 `$matrix-design`,修订后重新进入 Build 取得新批准。导出只读取批准快照中的 `proposal.md`、`design.md` 与 `plan.md`。
|
|
19
|
+
|
|
20
|
+
```powershell
|
|
21
|
+
# 通用项目:只导出 Matrix 产物
|
|
22
|
+
matrix workflow export --task-id <ID> --target generic
|
|
23
|
+
|
|
24
|
+
# FnSec:仅在 active.md 含有 NO_ACTIVE_OBJECTIVE 时更新看板
|
|
25
|
+
matrix workflow export --task-id <ID> --target fnsec --apply-fnsec-board
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
任务包包含批准的 Contract hash 与 revision,以及同一快照的 proposal、design、plan。导出本身不改变 Build 阶段;Claude 完成后,补齐 `## Build evidence`,然后继续:
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
matrix workflow guard build
|
|
32
|
+
matrix workflow transition verify
|
|
33
|
+
```
|
|
@@ -3,14 +3,18 @@ name: matrix-design
|
|
|
3
3
|
description: 在实现前设计并冻结活动 Matrix 变更。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Matrix Design
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# Matrix Design
|
|
7
|
+
|
|
8
|
+
Design 只用于 full 和可恢复的旧 shortcut change。新的 lightweight hotfix/tweak 从 Open 直接进入 Build,不得为满足 full guard 补写 Design 产物。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- **Arch**:仅按事实触发 `domain-modeling`、`research`、`wayfinder`、`prototype`、`codebase-design`,不得机械执行固定序列。每次调用前说明能力与原因。
|
|
10
|
+
design 和 plan 正文使用 `matrix workflow inspect` 的 `artifact_language`;Runtime 所需英文标题 token 保持不变。
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
读取 `matrix workflow inspect`、proposal 与冻结 `orchestration`,写入 `design.md`(`## Decisions`、`## Boundaries`、`## Test seams`、`## Risks`)与 `plan.md`(`## Steps`、`## Validation`、`## Stop conditions`)。
|
|
13
|
+
|
|
14
|
+
- **Prim**:使用 Matrix 自身能力调查并设计。
|
|
15
|
+
- **Arch**:仅按事实触发 `domain-modeling`、`research`、`wayfinder`、`prototype`、`codebase-design`,不得机械执行固定序列。每次调用前说明能力与原因。
|
|
16
|
+
|
|
17
|
+
长期有效的领域、ADR 或研究产物遵循仓库约定,关键结论必须同步进入 Matrix design/plan。prototype 默认写入系统临时目录。companion 不得拥有 Matrix 状态或创建竞争计划。重大范围或架构决策须先由用户确认。
|
|
14
18
|
|
|
15
19
|
```powershell
|
|
16
20
|
matrix workflow guard design
|
|
@@ -5,4 +5,4 @@ description: 为可复现的小型缺陷运行受约束的 Matrix 工作流。
|
|
|
5
5
|
|
|
6
6
|
# Matrix Hotfix
|
|
7
7
|
|
|
8
|
-
从 `$matrix` 以 `hotfix`
|
|
8
|
+
从 `$matrix` 以 `hotfix` 启动,共用 `open → build → verify → archive` lightweight 生命周期,不维护独立 hotfix 状态机。Build 前记录预期/实际行为和可复现失败信号,展示紧凑 Contract 并等待确认;Build 记录复现与根因,Verify 记录回归证据。若影响架构、公开 API、数据模式或多模块协调,受控 Return 后升级为 full。
|
|
@@ -3,16 +3,26 @@ name: matrix-open
|
|
|
3
3
|
description: 将开发请求澄清为有证据的 Matrix 提案。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Matrix Open
|
|
7
|
-
|
|
8
|
-
读取 `matrix workflow inspect`
|
|
6
|
+
# Matrix Open
|
|
7
|
+
|
|
8
|
+
读取 `matrix workflow inspect` 返回的 `profile` 与 `evidence_policy`。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- **Arch**:仅在存在重大歧义或用户要求压力测试时调用 `grilling`;仅在领域词汇、不变量或架构决定不清晰时调用 `domain-modeling`。调用前说明能力与原因。
|
|
10
|
+
proposal 正文使用其中的 `artifact_language`;Runtime 所需的英文标题 token 必须原样保留,仅标题下内容本地化。
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
- full proposal 包含 `## Goal`、`## Scope`、`## Non-goals`、`## Acceptance`、`## Risks`,随后进入 Design。
|
|
13
|
+
- lightweight proposal 在修改项目实现前完整定义 `## Goal`、`## Scope`、`## Non-goals`、`## Approach`、`## Acceptance`、`## Validation`、`## Risks`、`## Upgrade conditions`。
|
|
14
|
+
- hotfix 额外包含 `## Expected behavior`、`## Actual behavior`、`## Reproduction`。
|
|
15
|
+
- tweak 额外包含 `## Current behavior`、`## Preserved behavior`、`## Diff boundary`。
|
|
16
|
+
|
|
17
|
+
- **Prim**:使用 Matrix 自身能力澄清并编写提案。
|
|
18
|
+
- **Arch**:仅在存在重大歧义或用户要求压力测试时调用 `grilling`;仅在领域词汇、不变量或架构决定不清晰时调用 `domain-modeling`。调用前说明能力与原因。
|
|
19
|
+
|
|
20
|
+
companion 输出只能作为当前 proposal 的证据,不得创建第二套工作流、提案、确认状态或阶段转换。正确安装的能力失败时最多执行一次同能力 Matrix fallback;安装完整性问题必须修复。
|
|
14
21
|
|
|
15
22
|
```powershell
|
|
16
|
-
matrix workflow guard open
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
matrix workflow guard open
|
|
24
|
+
# full
|
|
25
|
+
matrix workflow transition design
|
|
26
|
+
# hotfix/tweak,仅在用户明确确认后
|
|
27
|
+
matrix workflow transition build --confirmed
|
|
28
|
+
```
|
|
@@ -8,8 +8,8 @@ description: 检查活动 Matrix 工作流的磁盘状态、守卫与恢复命
|
|
|
8
8
|
执行:
|
|
9
9
|
|
|
10
10
|
```powershell
|
|
11
|
-
matrix workflow inspect
|
|
12
|
-
matrix workflow doctor
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
报告变更 ID
|
|
11
|
+
matrix workflow inspect
|
|
12
|
+
matrix workflow doctor
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
报告变更 ID、工作流、`profile`、`evidence_policy`、冻结 `orchestration`、阶段、守卫结果、`next_skill`、Workflow health、未完成 transaction、可用策略和精确恢复命令。Arch 完整性失败时列出平台/Skill 事实并报告 `matrix init --with-mattpocock`,不得把 change 解释为 Prim。两条命令都只读;不得推断恢复策略、运行 `--repair` 或修改状态。
|
|
@@ -5,4 +5,4 @@ description: 为边界清晰的小改动运行轻量 Matrix 工作流。
|
|
|
5
5
|
|
|
6
6
|
# Matrix Tweak
|
|
7
7
|
|
|
8
|
-
从 `$matrix` 以 `tweak`
|
|
8
|
+
从 `$matrix` 以 `tweak` 启动,共用 `open → build → verify → archive` lightweight 生命周期,不维护独立 tweak 状态机或 Design 阶段。修改实现前,在紧凑 proposal 中定义当前/保留行为、diff 边界、验收与验证,展示后等待确认;Build 与 Verify 必须证明范围和 diff 一致。若涉及 API、模式、架构或跨模块协调,受控 Return 后升级为 full。
|
|
@@ -3,23 +3,25 @@ name: matrix-verify
|
|
|
3
3
|
description: 依据计划验证 Matrix 变更并审查差异。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Matrix Verify
|
|
7
|
-
|
|
8
|
-
运行计划的验证命令,在 `verification.md` 写入 `## Test evidence`。读取冻结 `orchestration`:
|
|
9
|
-
|
|
10
|
-
- **Prim**:Matrix 自行完成 Standards 与 Spec 双轴审查。
|
|
11
|
-
- **Arch**:调用 `code-review` 产生双轴证据,并说明调用原因。
|
|
12
|
-
|
|
13
|
-
审查结果只是 Matrix Verify 的输入;companion 不得决定阶段结果、扩大已批准架构、推进或归档。不得调用事后架构改进 wrapper。正确安装的审查能力失败时最多执行一次同能力 Matrix fallback;安装完整性问题必须停止。
|
|
6
|
+
# Matrix Verify
|
|
14
7
|
|
|
15
|
-
|
|
8
|
+
运行已批准 Contract 的验证命令,在 `verification.md` 写入 `## Test evidence`。hotfix 另写 `## Regression evidence`;tweak 另写 `## Scope review evidence`。读取冻结 `orchestration`:
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
验证正文使用 `artifact_language`;上述 Runtime 所需英文证据标题保持不变。
|
|
11
|
+
|
|
12
|
+
- **Prim**:Matrix 自行完成 Standards 与 Spec 双轴审查。
|
|
13
|
+
- **Arch**:调用 `code-review` 产生双轴证据,并说明调用原因。
|
|
14
|
+
|
|
15
|
+
审查结果只是 Matrix Verify 的输入;companion 不得决定阶段结果、扩大已批准架构、推进或归档。不得调用事后架构改进 wrapper。正确安装的审查能力失败时最多执行一次同能力 Matrix fallback;安装完整性问题必须停止。
|
|
16
|
+
|
|
17
|
+
失败时使用受控 Return:
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
matrix workflow return build --reason verification-failed
|
|
21
|
+
matrix workflow return design --reason acceptance-or-design-gap
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Return 会轮换旧 `verification.md`;不得把旧结论直接复制为新的当前证据。
|
|
23
25
|
|
|
24
26
|
```powershell
|
|
25
27
|
matrix workflow guard verify
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rosenfan/matrix",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Evidence-driven development workflow with Prim and Arch agent-skill orchestration.",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Evidence-driven development workflow with Prim and Arch agent-skill orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
package/src/catalog.js
CHANGED
|
@@ -10,11 +10,11 @@ export const MATRIX_SKILLS = [
|
|
|
10
10
|
"matrix", "matrix-open", "matrix-design", "matrix-build", "matrix-verify",
|
|
11
11
|
"matrix-archive", "matrix-hotfix", "matrix-tweak", "matrix-status", "matrix-claude"
|
|
12
12
|
];
|
|
13
|
-
export const MATT_SKILLS = [
|
|
14
|
-
"grilling", "domain-modeling", "research", "wayfinder", "prototype",
|
|
15
|
-
"codebase-design", "tdd", "diagnosing-bugs", "resolving-merge-conflicts", "code-review"
|
|
16
|
-
];
|
|
17
|
-
export const UNMANAGED_MATT_SKILLS = ["grill-with-docs", "implement", "improve-codebase-architecture"];
|
|
13
|
+
export const MATT_SKILLS = [
|
|
14
|
+
"grilling", "domain-modeling", "research", "wayfinder", "prototype",
|
|
15
|
+
"codebase-design", "tdd", "diagnosing-bugs", "resolving-merge-conflicts", "code-review"
|
|
16
|
+
];
|
|
17
|
+
export const UNMANAGED_MATT_SKILLS = ["grill-with-docs", "implement", "improve-codebase-architecture"];
|
|
18
18
|
|
|
19
19
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
20
20
|
const packageManifest = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
@@ -74,19 +74,19 @@ export function hashDirectory(directory, extraFiles = []) {
|
|
|
74
74
|
return hash.digest("hex");
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export function workflowRuntimeSource() { return path.join(packageRoot, "src", "workflow.js"); }
|
|
78
|
-
export function workflowTransactionSource() { return path.join(packageRoot, "src", "workflow-transaction.js"); }
|
|
77
|
+
export function workflowRuntimeSource() { return path.join(packageRoot, "src", "workflow.js"); }
|
|
78
|
+
export function workflowTransactionSource() { return path.join(packageRoot, "src", "workflow-transaction.js"); }
|
|
79
79
|
|
|
80
80
|
export function releaseCatalog(language = "en") {
|
|
81
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(", ")}`);
|
|
85
|
-
const runtime = fs.readFileSync(workflowRuntimeSource());
|
|
86
|
-
const transaction = fs.readFileSync(workflowTransactionSource());
|
|
87
|
-
const skills = Object.fromEntries(MATRIX_SKILLS.map((skill) => [skill, hashDirectory(path.join(root, skill), [
|
|
88
|
-
{ path: "scripts/matrix-runtime.mjs", contents: runtime },
|
|
89
|
-
{ path: "scripts/workflow-transaction.js", contents: transaction }
|
|
90
|
-
])]));
|
|
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(", ")}`);
|
|
85
|
+
const runtime = fs.readFileSync(workflowRuntimeSource());
|
|
86
|
+
const transaction = fs.readFileSync(workflowTransactionSource());
|
|
87
|
+
const skills = Object.fromEntries(MATRIX_SKILLS.map((skill) => [skill, hashDirectory(path.join(root, skill), [
|
|
88
|
+
{ path: "scripts/matrix-runtime.mjs", contents: runtime },
|
|
89
|
+
{ path: "scripts/workflow-transaction.js", contents: transaction }
|
|
90
|
+
])]));
|
|
91
91
|
return { language, root, version: packageManifest.version, skills, digest: crypto.createHash("sha256").update(JSON.stringify(skills)).digest("hex") };
|
|
92
92
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { PLATFORMS } from "./catalog.js";
|
|
5
|
-
import { createDistribution } from "./distribution.js";
|
|
6
|
-
import { text } from "./messages.js";
|
|
7
|
-
import { createUi } from "./ui.js";
|
|
8
|
-
import { invoke } from "./workflow.js";
|
|
6
|
+
import { createDistribution, installedIntent } from "./distribution.js";
|
|
7
|
+
import { text } from "./messages.js";
|
|
8
|
+
import { createUi } from "./ui.js";
|
|
9
|
+
import { invoke } from "./workflow.js";
|
|
10
|
+
import { compareVersions, globalBinPath, latestVersion, selfUpdate } from "./self-update.js";
|
|
9
11
|
|
|
10
12
|
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
11
13
|
const version = JSON.parse(fs.readFileSync(packagePath, "utf8")).version;
|
|
12
|
-
const HELP = `Matrix ${version}\n\nUsage: matrix init [directory] [options]\n matrix doctor [directory] [options]\n\nOptions:\n --language <en|zh-CN>\n --scope <project|global>\n --platform <claude-code|codex> (repeatable)\n --mode <copy|symlink>\n --with-mattpocock | --without-mattpocock\n --default-orchestration <prim|arch>\n --force --yes, -y --dry-run --json --no-color`;
|
|
14
|
+
const HELP = `Matrix ${version}\n\nUsage: matrix init [directory] [options]\n matrix update [directory] [options]\n matrix doctor [directory] [options]\n\nOptions:\n --language <en|zh-CN>\n --scope <project|global>\n --platform <claude-code|codex> (repeatable)\n --mode <copy|symlink>\n --with-mattpocock | --without-mattpocock\n --default-orchestration <prim|arch>\n --skip-self-update --force --yes, -y --dry-run --json --no-color`;
|
|
13
15
|
|
|
14
16
|
function parse(argv) {
|
|
15
|
-
const result = { command: null, directory: null, scope: null, language: null, platforms: [], mode: "copy", matt: null, defaultOrchestration: null, matrixPolicy: "safe", yes: false, dryRun: false, json: false, color: true };
|
|
17
|
+
const result = { command: null, directory: null, scope: null, language: null, platforms: [], mode: "copy", matt: null, defaultOrchestration: null, matrixPolicy: "safe", yes: false, dryRun: false, json: false, color: true, skipSelfUpdate: false, reexec: false };
|
|
16
18
|
const values = [...argv];
|
|
17
19
|
if (values[0] && !values[0].startsWith("-")) result.command = values.shift();
|
|
18
20
|
while (values.length) {
|
|
@@ -22,12 +24,14 @@ function parse(argv) {
|
|
|
22
24
|
else if (token === "--language") result.language = values.shift();
|
|
23
25
|
else if (token === "--platform") result.platforms.push(values.shift());
|
|
24
26
|
else if (token === "--mode") result.mode = values.shift();
|
|
25
|
-
else if (token === "--with-mattpocock") result.matt = "missing";
|
|
26
|
-
else if (token === "--without-mattpocock") result.matt = "none";
|
|
27
|
-
else if (token === "--default-orchestration") result.defaultOrchestration = values.shift();
|
|
27
|
+
else if (token === "--with-mattpocock") result.matt = "missing";
|
|
28
|
+
else if (token === "--without-mattpocock") result.matt = "none";
|
|
29
|
+
else if (token === "--default-orchestration") result.defaultOrchestration = values.shift();
|
|
28
30
|
else if (token === "--force") result.matrixPolicy = "replace";
|
|
29
31
|
else if (["--yes", "-y"].includes(token)) result.yes = true;
|
|
30
|
-
else if (token === "--dry-run") result.dryRun = true;
|
|
32
|
+
else if (token === "--dry-run") result.dryRun = true;
|
|
33
|
+
else if (token === "--skip-self-update") result.skipSelfUpdate = true;
|
|
34
|
+
else if (token === "--_reexec") result.reexec = true;
|
|
31
35
|
else if (token === "--json") result.json = true;
|
|
32
36
|
else if (token === "--no-color") result.color = false;
|
|
33
37
|
else if (["--help", "-h"].includes(token)) result.help = true;
|
|
@@ -58,29 +62,33 @@ async function intentFrom(options, ui) {
|
|
|
58
62
|
const language = options.language ?? (options.yes ? "en" : await ui.select("Language / 语言", [{ label: "English", value: "en" }, { label: "中文", value: "zh-CN" }]));
|
|
59
63
|
const scope = options.scope ?? (options.yes ? "project" : await ui.select(text(language, "scopeQuestion"), [{ label: text(language, "project"), value: "project" }, { label: text(language, "global"), value: "global" }]));
|
|
60
64
|
let platforms = options.platforms;
|
|
61
|
-
if (!platforms.length && interactive && !options.yes) platforms = await ui.selectMany(text(language, "platformsQuestion"), [{ label: "Claude Code", value: "claude-code" }, { label: "Codex", value: "codex" }]);
|
|
62
|
-
const matt = options.matt ?? (interactive && !options.yes ? (await ui.confirm(text(language, "mattQuestion"), true, labelsFor(language)) ? "missing" : "none") : "missing");
|
|
63
|
-
const defaultOrchestration = options.defaultOrchestration ?? (interactive && !options.yes && matt === "missing"
|
|
64
|
-
? await ui.select(text(language, "orchestrationQuestion"), [{ label: "Arch", value: "arch" }, { label: "Prim", value: "prim" }])
|
|
65
|
-
: null);
|
|
66
|
-
return { projectRoot: projectRoot(options.directory), language, scope, platforms, mode: options.mode, matt, defaultOrchestration, nonInteractive: options.yes || !interactive, matrixPolicy: options.matrixPolicy, dryRun: options.dryRun };
|
|
65
|
+
if (!platforms.length && interactive && !options.yes) platforms = await ui.selectMany(text(language, "platformsQuestion"), [{ label: "Claude Code", value: "claude-code" }, { label: "Codex", value: "codex" }]);
|
|
66
|
+
const matt = options.matt ?? (interactive && !options.yes ? (await ui.confirm(text(language, "mattQuestion"), true, labelsFor(language)) ? "missing" : "none") : "missing");
|
|
67
|
+
const defaultOrchestration = options.defaultOrchestration ?? (interactive && !options.yes && matt === "missing"
|
|
68
|
+
? await ui.select(text(language, "orchestrationQuestion"), [{ label: "Arch", value: "arch" }, { label: "Prim", value: "prim" }])
|
|
69
|
+
: null);
|
|
70
|
+
return { projectRoot: projectRoot(options.directory), language, scope, platforms, mode: options.mode, matt, defaultOrchestration, nonInteractive: options.yes || !interactive, matrixPolicy: options.matrixPolicy, dryRun: options.dryRun };
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
function render(ui, evaluation) {
|
|
70
74
|
const language = evaluation.intent?.language ?? "en";
|
|
71
|
-
const actions = (evaluation.actions ?? []).map((item) => `${item.platform}: ${localizedAction(language, item.matrix)}`).join(", ");
|
|
72
|
-
ui.info(actions ? `${text(language, "preview")}: ${actions}` : evaluation.code);
|
|
73
|
-
if (evaluation.intent?.defaultOrchestration) ui.muted(`Default orchestration: ${evaluation.intent.defaultOrchestration}`);
|
|
75
|
+
const actions = (evaluation.actions ?? []).map((item) => `${item.platform}: ${localizedAction(language, item.matrix)}`).join(", ");
|
|
76
|
+
ui.info(actions ? `${text(language, "preview")}: ${actions}` : evaluation.code);
|
|
77
|
+
if (evaluation.intent?.defaultOrchestration) ui.muted(`Default orchestration: ${evaluation.intent.defaultOrchestration}`);
|
|
74
78
|
for (const item of evaluation.observations ?? []) ui.muted(`${item.name}: ${text(language, "matrix")} ${localizedState(language, item.matrix)};${text(language, "matt")} ${localizedState(language, item.matt.state)}${item.matt.inherited ? ` (${text(language, "global")})` : ""}`);
|
|
75
79
|
for (const item of evaluation.diagnostics ?? []) ui.warn(item.code === "USER_MODIFIED_BLOCKED" ? text(language, "replaceQuestion") : item.message ?? item.code);
|
|
76
80
|
for (const item of evaluation.diagnosis ?? []) (item.code === "MATT_INHERITED" ? ui.muted : ui.warn)(item.message ?? item.code);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
async function init(options) {
|
|
80
|
-
const ui = createUi({ color: options.color && !options.json });
|
|
81
|
-
if (!options.json) await ui.banner();
|
|
82
|
-
const intent = await intentFrom(options, ui);
|
|
83
|
-
|
|
83
|
+
async function init(options) {
|
|
84
|
+
const ui = createUi({ color: options.color && !options.json });
|
|
85
|
+
if (!options.json) await ui.banner();
|
|
86
|
+
const intent = await intentFrom(options, ui);
|
|
87
|
+
return refresh(intent, options, ui);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function refresh(intent, options, ui) {
|
|
91
|
+
const distribution = createDistribution();
|
|
84
92
|
let evaluation = distribution.evaluate(intent);
|
|
85
93
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
|
|
86
94
|
if (!evaluation.ok && evaluation.code === "USER_MODIFIED_BLOCKED" && interactive && !options.yes && await ui.confirm(text(intent.language, "replaceQuestion"), false, labelsFor(intent.language))) evaluation = distribution.evaluate({ ...intent, matrixPolicy: "replace" });
|
|
@@ -93,8 +101,52 @@ async function init(options) {
|
|
|
93
101
|
if (result.code === "PARTIAL") { ui.warn(result.recovery); return 1; }
|
|
94
102
|
ui.success(options.dryRun ? text(intent.language, "dryRunComplete") : text(intent.language, "ready"));
|
|
95
103
|
ui.muted(evaluation.intent.platforms.map((id) => `${PLATFORMS[id].name}: $matrix <request>`).join("\n"));
|
|
96
|
-
return 0;
|
|
97
|
-
}
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function update(options) {
|
|
108
|
+
const ui = createUi({ color: options.color && !options.json });
|
|
109
|
+
const root = projectRoot(options.directory);
|
|
110
|
+
const installed = installedIntent({ projectRoot: root });
|
|
111
|
+
if (!installed.ok) {
|
|
112
|
+
const result = { ok: false, code: installed.code, message: "No trusted Matrix installation manifest was found. Run matrix init first." };
|
|
113
|
+
if (options.json) console.log(JSON.stringify(result)); else ui.warn(result.message);
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
116
|
+
const forbidden = options.language || options.scope || options.platforms.length || options.matt || options.defaultOrchestration || options.mode !== "copy";
|
|
117
|
+
if (forbidden) throw new Error("matrix update inherits installation settings. Use matrix init to change language, scope, platform, mode, Matt skills, or orchestration.");
|
|
118
|
+
const intent = { ...installed.intent, dryRun: options.dryRun };
|
|
119
|
+
let npm = { status: "skipped", current: version, target: version, reason: options.skipSelfUpdate ? "disabled by --skip-self-update" : null };
|
|
120
|
+
if (!options.skipSelfUpdate && !options.reexec) {
|
|
121
|
+
try {
|
|
122
|
+
const target = await latestVersion(); const comparison = compareVersions(target, version);
|
|
123
|
+
if (comparison === null) throw new Error("unable to compare registry version");
|
|
124
|
+
npm = comparison > 0 ? { status: "available", current: version, target } : { status: "skipped", current: version, target, reason: comparison === 0 ? "already latest" : "registry version is older" };
|
|
125
|
+
} catch (error) {
|
|
126
|
+
const result = { ok: false, code: "SELF_UPDATE_CHECK_FAILED", npm: { status: "failed", current: version, reason: error.message }, recovery_command: "matrix update --skip-self-update" };
|
|
127
|
+
if (options.json) console.log(JSON.stringify(result)); else ui.warn(`${result.code}: ${error.message}\nRun ${result.recovery_command} to refresh assets without npm.`);
|
|
128
|
+
return 2;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (options.json && options.dryRun) { console.log(JSON.stringify({ ok: true, code: "DRY_RUN", npm, intent })); return 0; }
|
|
132
|
+
if (!options.json) {
|
|
133
|
+
await ui.banner(); ui.info(`Matrix update: CLI ${npm.current}${npm.status === "available" ? ` → ${npm.target}` : ""}`);
|
|
134
|
+
ui.muted(`Assets: ${intent.scope}, ${intent.language}, ${intent.platforms.join(", ")}`);
|
|
135
|
+
}
|
|
136
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json);
|
|
137
|
+
if (npm.status === "available") {
|
|
138
|
+
if (!options.yes && interactive && !await ui.confirm(`Upgrade Matrix CLI ${npm.current} → ${npm.target} and then refresh these assets?`, true, labelsFor(intent.language))) return 0;
|
|
139
|
+
if (options.dryRun) { if (options.json) console.log(JSON.stringify({ ok: true, code: "DRY_RUN", npm, intent })); else ui.success(text(intent.language, "dryRunComplete")); return 0; }
|
|
140
|
+
const updated = selfUpdate(version, npm.target);
|
|
141
|
+
if (!updated.ok) { const result = { ok: false, code: "SELF_UPDATE_FAILED", npm: { ...npm, status: "failed", reason: updated.reason }, recovery_command: "matrix update --skip-self-update" }; if (options.json) console.log(JSON.stringify(result)); else ui.warn(`${result.code}: ${updated.reason}`); return 2; }
|
|
142
|
+
const args = [globalBinPath(), "update", root, "--skip-self-update", "--_reexec", "--yes"];
|
|
143
|
+
if (options.matrixPolicy === "replace") args.push("--force"); if (options.json) args.push("--json"); if (!options.color) args.push("--no-color");
|
|
144
|
+
const rerun = spawnSync(process.execPath, args, { stdio: "inherit" });
|
|
145
|
+
return Number.isInteger(rerun.status) ? rerun.status : 2;
|
|
146
|
+
}
|
|
147
|
+
if (options.json) return refresh(intent, { ...options, yes: true }, ui);
|
|
148
|
+
return refresh(intent, options, ui);
|
|
149
|
+
}
|
|
98
150
|
|
|
99
151
|
async function doctor(options) {
|
|
100
152
|
const ui = createUi({ color: options.color && !options.json });
|
|
@@ -116,7 +168,8 @@ export async function main(argv) {
|
|
|
116
168
|
const options = parse(argv);
|
|
117
169
|
if (options.version) { console.log(version); return 0; }
|
|
118
170
|
if (options.help || !options.command) { console.log(HELP); return 0; }
|
|
119
|
-
if (options.command === "init") return init(options);
|
|
171
|
+
if (options.command === "init") return init(options);
|
|
172
|
+
if (options.command === "update") return update(options);
|
|
120
173
|
if (options.command === "doctor") return doctor(options);
|
|
121
174
|
throw new Error(`Unknown command: ${options.command}. Run matrix --help.`);
|
|
122
175
|
}
|