@rosenfan/matrix 0.1.3 → 0.1.4
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 +29 -34
- package/.claude/skills/matrix/scripts/matrix_state.py +11 -11
- package/.claude/skills/matrix-archive/SKILL.md +12 -12
- package/.claude/skills/matrix-build/SKILL.md +16 -13
- package/.claude/skills/matrix-claude/SKILL.md +10 -10
- package/.claude/skills/matrix-design/SKILL.md +19 -17
- package/.claude/skills/matrix-hotfix/SKILL.md +1 -1
- package/.claude/skills/matrix-open/SKILL.md +23 -21
- package/.claude/skills/matrix-status/SKILL.md +3 -3
- package/.claude/skills/matrix-tweak/SKILL.md +1 -1
- package/.claude/skills/matrix-verify/SKILL.md +39 -23
- package/README.md +78 -69
- package/README.zh-CN.md +77 -68
- package/assets/skills-zh-CN/matrix/SKILL.md +33 -33
- package/assets/skills-zh-CN/matrix-archive/SKILL.md +10 -10
- package/assets/skills-zh-CN/matrix-build/SKILL.md +13 -11
- package/assets/skills-zh-CN/matrix-claude/SKILL.md +10 -10
- package/assets/skills-zh-CN/matrix-design/SKILL.md +13 -11
- package/assets/skills-zh-CN/matrix-hotfix/SKILL.md +1 -1
- package/assets/skills-zh-CN/matrix-open/SKILL.md +19 -17
- package/assets/skills-zh-CN/matrix-status/SKILL.md +3 -3
- package/assets/skills-zh-CN/matrix-tweak/SKILL.md +1 -1
- package/assets/skills-zh-CN/matrix-verify/SKILL.md +27 -11
- package/package.json +1 -1
- package/src/catalog.js +31 -20
- package/src/cli.js +149 -91
- package/src/distribution.js +259 -136
- package/src/installer.js +10 -10
- package/src/matt-catalog.mjs +67 -0
- package/src/matt.js +115 -33
- package/src/messages.js +14 -10
- package/src/runtime-resolver.js +76 -0
- package/src/self-update.js +81 -81
- package/src/workflow-transaction.js +13 -9
- package/src/workflow.js +641 -388
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const MATT_COMPATIBILITY = Object.freeze({
|
|
4
|
+
matrixVersion: "0.1.4",
|
|
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
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
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,33 +65,63 @@ 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 }) {
|
|
17
|
-
const invocation = buildProcessInvocation({ command, args });
|
|
18
|
-
return execFileSync(invocation.executable, invocation.args, { cwd, stdio
|
|
19
|
-
}
|
|
68
|
+
export function defaultProcessRunner({ command, args, cwd, timeout, stdio = "inherit" }) {
|
|
69
|
+
const invocation = buildProcessInvocation({ command, args });
|
|
70
|
+
return execFileSync(invocation.executable, invocation.args, { cwd, stdio, timeout, shell: invocation.shell });
|
|
71
|
+
}
|
|
20
72
|
|
|
21
|
-
export function createMattAdapter({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
+
} = {}) {
|
|
85
|
+
return {
|
|
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();
|
|
96
|
+
const attempted = [];
|
|
97
|
+
try {
|
|
98
|
+
for (const platform of platforms) {
|
|
99
|
+
const args = ["--yes", MATT_COMPATIBILITY.skillsCli, "add", MATT_COMPATIBILITY.archive, "--yes", "--copy", "--agent", PLATFORMS[platform].skillsCliAgent];
|
|
100
|
+
for (const skill of skills) args.push("--skill", skill);
|
|
101
|
+
run({ command: "npx", args, cwd: root, timeout, stdio });
|
|
102
|
+
attempted.push(platform);
|
|
103
|
+
}
|
|
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
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
remove(root);
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
code: "MATT_INSTALL_FAILED",
|
|
117
|
+
attempted,
|
|
118
|
+
message: error.message,
|
|
119
|
+
recovery: "Run matrix init --with-mattpocock after resolving the skills CLI error."
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
discardCandidate(candidate) {
|
|
124
|
+
if (candidate?.root) remove(candidate.root);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
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
|
|
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
|
|
12
|
-
|
|
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.",
|
|
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 链接。",
|
|
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: "
|
|
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
|
+
}
|
package/src/self-update.js
CHANGED
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
|
|
6
|
-
export const PACKAGE_NAME = "@rosenfan/matrix";
|
|
7
|
-
const REGISTRY = "https://registry.npmjs.org/@rosenfan%2fmatrix/latest";
|
|
8
|
-
|
|
9
|
-
function parse(version) {
|
|
10
|
-
const match = String(version ?? "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
11
|
-
return match ? { numeric: match.slice(1, 4).map(Number), prerelease: match[4] ?? null } : null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function compareVersions(left, right) {
|
|
15
|
-
const a = parse(left); const b = parse(right);
|
|
16
|
-
if (!a || !b) return null;
|
|
17
|
-
for (let index = 0; index < 3; index += 1) if (a.numeric[index] !== b.numeric[index]) return a.numeric[index] - b.numeric[index];
|
|
18
|
-
if (a.prerelease === b.prerelease) return 0;
|
|
19
|
-
if (a.prerelease === null) return 1;
|
|
20
|
-
if (b.prerelease === null) return -1;
|
|
21
|
-
return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function latestVersion(fetchImpl = fetch) {
|
|
25
|
-
const response = await fetchImpl(REGISTRY, { headers: { accept: "application/json" } });
|
|
26
|
-
if (!response.ok) throw new Error(`registry returned ${response.status}`);
|
|
27
|
-
const body = await response.json();
|
|
28
|
-
if (!parse(body.version)) throw new Error("registry returned an invalid version");
|
|
29
|
-
return body.version;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function run(command, args, options = {}) {
|
|
33
|
-
const result = spawnSync(command, args, { encoding: "utf8", ...options });
|
|
34
|
-
return { ok: !result.error && result.status === 0, status: result.status, error: result.error?.message, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function validateCandidate(packageRoot, expectedVersion) {
|
|
38
|
-
const manifestFile = path.join(packageRoot, "package.json");
|
|
39
|
-
try {
|
|
40
|
-
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
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" };
|
|
44
|
-
for (const args of [["--version"], ["--help"], ["workflow", "doctor", "--json"]]) {
|
|
45
|
-
const result = run(process.execPath, [path.join(packageRoot, "bin", "matrix.js"), ...args], { cwd: os.tmpdir() });
|
|
46
|
-
if (!result.ok) return { ok: false, reason: `candidate command failed: matrix ${args.join(" ")}` };
|
|
47
|
-
}
|
|
48
|
-
return { ok: true };
|
|
49
|
-
} catch (error) { return { ok: false, reason: error.message }; }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function npmGlobalRoot() {
|
|
53
|
-
const result = run("npm", ["root", "-g"]);
|
|
54
|
-
if (!result.ok) throw new Error(result.error ?? result.stderr.trim() ?? "npm root -g failed");
|
|
55
|
-
return result.stdout.trim();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function validateRegistryCandidate(version) {
|
|
59
|
-
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "matrix-update-"));
|
|
60
|
-
try {
|
|
61
|
-
const install = run("npm", ["install", "--prefix", temporary, "--ignore-scripts", "--no-save", "--package-lock=false", `${PACKAGE_NAME}@${version}`], { cwd: temporary });
|
|
62
|
-
if (!install.ok) return { ok: false, reason: install.error ?? install.stderr.trim() ?? "candidate installation failed" };
|
|
63
|
-
return validateCandidate(path.join(temporary, "node_modules", "@rosenfan", "matrix"), version);
|
|
64
|
-
} finally { fs.rmSync(temporary, { recursive: true, force: true }); }
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function installGlobal(version) {
|
|
68
|
-
const result = run("npm", ["install", "--global", `${PACKAGE_NAME}@${version}`]);
|
|
69
|
-
return result.ok ? { ok: true } : { ok: false, reason: result.error ?? result.stderr.trim() ?? "npm install failed" };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function selfUpdate(currentVersion, targetVersion) {
|
|
73
|
-
const candidate = validateRegistryCandidate(targetVersion);
|
|
74
|
-
if (!candidate.ok) return candidate;
|
|
75
|
-
const install = installGlobal(targetVersion);
|
|
76
|
-
if (install.ok) return install;
|
|
77
|
-
const rollback = installGlobal(currentVersion);
|
|
78
|
-
return { ok: false, reason: `${install.reason}; ${rollback.ok ? `restored ${currentVersion}` : `rollback failed: ${rollback.reason}`}` };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function globalBinPath() { return path.join(npmGlobalRoot(), "@rosenfan", "matrix", "bin", "matrix.js"); }
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
export const PACKAGE_NAME = "@rosenfan/matrix";
|
|
7
|
+
const REGISTRY = "https://registry.npmjs.org/@rosenfan%2fmatrix/latest";
|
|
8
|
+
|
|
9
|
+
function parse(version) {
|
|
10
|
+
const match = String(version ?? "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
11
|
+
return match ? { numeric: match.slice(1, 4).map(Number), prerelease: match[4] ?? null } : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function compareVersions(left, right) {
|
|
15
|
+
const a = parse(left); const b = parse(right);
|
|
16
|
+
if (!a || !b) return null;
|
|
17
|
+
for (let index = 0; index < 3; index += 1) if (a.numeric[index] !== b.numeric[index]) return a.numeric[index] - b.numeric[index];
|
|
18
|
+
if (a.prerelease === b.prerelease) return 0;
|
|
19
|
+
if (a.prerelease === null) return 1;
|
|
20
|
+
if (b.prerelease === null) return -1;
|
|
21
|
+
return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function latestVersion(fetchImpl = fetch) {
|
|
25
|
+
const response = await fetchImpl(REGISTRY, { headers: { accept: "application/json" } });
|
|
26
|
+
if (!response.ok) throw new Error(`registry returned ${response.status}`);
|
|
27
|
+
const body = await response.json();
|
|
28
|
+
if (!parse(body.version)) throw new Error("registry returned an invalid version");
|
|
29
|
+
return body.version;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function run(command, args, options = {}) {
|
|
33
|
+
const result = spawnSync(command, args, { encoding: "utf8", ...options });
|
|
34
|
+
return { ok: !result.error && result.status === 0, status: result.status, error: result.error?.message, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validateCandidate(packageRoot, expectedVersion) {
|
|
38
|
+
const manifestFile = path.join(packageRoot, "package.json");
|
|
39
|
+
try {
|
|
40
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
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" };
|
|
44
|
+
for (const args of [["--version"], ["--help"], ["workflow", "doctor", "--json"]]) {
|
|
45
|
+
const result = run(process.execPath, [path.join(packageRoot, "bin", "matrix.js"), ...args], { cwd: os.tmpdir() });
|
|
46
|
+
if (!result.ok) return { ok: false, reason: `candidate command failed: matrix ${args.join(" ")}` };
|
|
47
|
+
}
|
|
48
|
+
return { ok: true };
|
|
49
|
+
} catch (error) { return { ok: false, reason: error.message }; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function npmGlobalRoot() {
|
|
53
|
+
const result = run("npm", ["root", "-g"]);
|
|
54
|
+
if (!result.ok) throw new Error(result.error ?? result.stderr.trim() ?? "npm root -g failed");
|
|
55
|
+
return result.stdout.trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function validateRegistryCandidate(version) {
|
|
59
|
+
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "matrix-update-"));
|
|
60
|
+
try {
|
|
61
|
+
const install = run("npm", ["install", "--prefix", temporary, "--ignore-scripts", "--no-save", "--package-lock=false", `${PACKAGE_NAME}@${version}`], { cwd: temporary });
|
|
62
|
+
if (!install.ok) return { ok: false, reason: install.error ?? install.stderr.trim() ?? "candidate installation failed" };
|
|
63
|
+
return validateCandidate(path.join(temporary, "node_modules", "@rosenfan", "matrix"), version);
|
|
64
|
+
} finally { fs.rmSync(temporary, { recursive: true, force: true }); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function installGlobal(version) {
|
|
68
|
+
const result = run("npm", ["install", "--global", `${PACKAGE_NAME}@${version}`]);
|
|
69
|
+
return result.ok ? { ok: true } : { ok: false, reason: result.error ?? result.stderr.trim() ?? "npm install failed" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function selfUpdate(currentVersion, targetVersion) {
|
|
73
|
+
const candidate = validateRegistryCandidate(targetVersion);
|
|
74
|
+
if (!candidate.ok) return candidate;
|
|
75
|
+
const install = installGlobal(targetVersion);
|
|
76
|
+
if (install.ok) return install;
|
|
77
|
+
const rollback = installGlobal(currentVersion);
|
|
78
|
+
return { ok: false, reason: `${install.reason}; ${rollback.ok ? `restored ${currentVersion}` : `rollback failed: ${rollback.reason}`}` };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function globalBinPath() { return path.join(npmGlobalRoot(), "@rosenfan", "matrix", "bin", "matrix.js"); }
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import crypto from "node:crypto";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
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";
|
|
8
9
|
const LOCK_SCHEMA = "matrix/workflow-lock/v1";
|
|
9
10
|
const MAX_JOURNAL_BYTES = 1024 * 1024;
|
|
10
11
|
const MAX_TRANSACTIONS = 128;
|
|
11
|
-
const RECEIPT_LIMIT = 32;
|
|
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: "
|
|
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: "
|
|
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: `
|
|
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) => `
|
|
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
|
-
? [`
|
|
592
|
+
? [workflowCommand(`doctor --repair --lock ${currentLock.id}`)]
|
|
589
593
|
: []
|
|
590
594
|
}] : [])
|
|
591
595
|
];
|