@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.
- package/LICENSE +21 -21
- package/README.md +29 -29
- package/README.zh-CN.md +27 -27
- package/{.claude → assets}/skills/matrix/SKILL.md +78 -78
- package/{.claude → assets}/skills/matrix/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix/scripts/matrix_claude.py +11 -11
- package/{.claude → assets}/skills/matrix/scripts/matrix_state.py +49 -49
- package/assets/skills/matrix/scripts/test_matrix_claude.py +69 -0
- package/assets/skills/matrix/scripts/test_matrix_state.py +69 -0
- package/{.claude → assets}/skills/matrix-archive/SKILL.md +36 -36
- package/{.claude/skills/matrix-hotfix → assets/skills/matrix-archive}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-build/SKILL.md +41 -41
- package/{.claude/skills/matrix-archive → assets/skills/matrix-build}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-claude/SKILL.md +98 -98
- package/{.claude → assets}/skills/matrix-claude/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-design/SKILL.md +65 -65
- package/{.claude → assets}/skills/matrix-design/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-hotfix/SKILL.md +8 -8
- package/{.claude/skills/matrix-build → assets/skills/matrix-hotfix}/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-open/SKILL.md +39 -39
- package/assets/skills/matrix-open/agents/openai.yaml +4 -0
- package/{.claude → assets}/skills/matrix-status/SKILL.md +15 -15
- package/{.claude → assets}/skills/matrix-status/agents/openai.yaml +4 -4
- package/{.claude → assets}/skills/matrix-tweak/SKILL.md +8 -8
- package/assets/skills/matrix-tweak/agents/openai.yaml +4 -0
- package/{.claude → assets}/skills/matrix-verify/SKILL.md +62 -62
- package/assets/skills/matrix-verify/agents/openai.yaml +4 -0
- package/assets/skills-zh-CN/matrix/SKILL.md +14 -14
- package/assets/skills-zh-CN/matrix-build/SKILL.md +4 -4
- package/assets/skills-zh-CN/matrix-design/SKILL.md +4 -4
- package/assets/skills-zh-CN/matrix-open/SKILL.md +3 -3
- package/assets/skills-zh-CN/matrix-verify/SKILL.md +19 -19
- package/bin/matrix.js +15 -15
- package/package.json +4 -13
- package/src/catalog.js +79 -72
- package/src/cli.js +100 -100
- package/src/distribution.js +181 -178
- package/src/installer.js +10 -10
- package/src/matt-catalog.mjs +1 -1
- package/src/matt.js +115 -115
- package/src/messages.js +14 -14
- package/src/runtime-resolver.js +76 -76
- package/src/self-update.js +81 -81
- package/src/workflow-transaction.js +13 -13
- package/src/workflow.js +711 -705
- package/.claude/skills/matrix-open/agents/openai.yaml +0 -4
- package/.claude/skills/matrix-tweak/agents/openai.yaml +0 -4
- package/.claude/skills/matrix-verify/agents/openai.yaml +0 -4
package/src/distribution.js
CHANGED
|
@@ -1,63 +1,66 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import {
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import {
|
|
6
6
|
MATRIX_SKILLS, MATT_SKILLS, UNMANAGED_MATT_SKILLS, PLATFORM_IDS, PLATFORMS, hashDirectory,
|
|
7
7
|
installationRoot, manifestPath, mattCatalogSource, releaseCatalog, scopeBase, sourceSkillsRoot, workflowRuntimeSource, workflowTransactionSource
|
|
8
|
-
} from "./catalog.js";
|
|
8
|
+
} from "./catalog.js";
|
|
9
9
|
import { createMattAdapter, mattReceiptPath } from "./matt.js";
|
|
10
10
|
import { MATT_CATALOG_DIGEST, MATT_COMPATIBILITY, MATT_CONTENT_HASHES } from "./matt-catalog.mjs";
|
|
11
11
|
import { text } from "./messages.js";
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
// Manifest v4 records content digests computed over EOL-normalized bytes and the
|
|
14
|
+
// assets/skills source layout; v3 manifests (raw-byte hashes, .claude/skills era)
|
|
15
|
+
// stay readable and are refreshed to v4 by the next update.
|
|
16
|
+
const VERSION = 4;
|
|
14
17
|
const MATT_OPERATIONS = new Set(["none", "missing", "readonly"]);
|
|
15
|
-
const digest = (value) => crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
16
|
-
const exists = (value) => fs.existsSync(value);
|
|
17
|
-
const pathExists = (value) => { try { fs.lstatSync(value); return true; } catch { return false; } };
|
|
18
|
-
const nowStamp = () => new Date().toISOString().replace(/[:.]/g, "-");
|
|
18
|
+
const digest = (value) => crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
19
|
+
const exists = (value) => fs.existsSync(value);
|
|
20
|
+
const pathExists = (value) => { try { fs.lstatSync(value); return true; } catch { return false; } };
|
|
21
|
+
const nowStamp = () => new Date().toISOString().replace(/[:.]/g, "-");
|
|
19
22
|
const transactionPath = (intent) => path.join(installationRoot(intent.scope, intent.projectRoot, intent.home), "transaction.json");
|
|
20
23
|
const configPath = (intent) => path.join(intent.projectRoot, ".matrix", "config.yaml");
|
|
21
|
-
const writeJson = (file, value) => {
|
|
22
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
23
|
-
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
24
|
-
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`);
|
|
25
|
-
fs.renameSync(temporary, file);
|
|
26
|
-
};
|
|
27
|
-
const readJson = (file) => { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; } };
|
|
28
|
-
const readConfig = (file) => {
|
|
29
|
-
try {
|
|
30
|
-
return Object.fromEntries(fs.readFileSync(file, "utf8").split(/\r?\n/).filter((line) => line.includes(":") && !line.startsWith(" ")).map((line) => {
|
|
31
|
-
const [key, ...value] = line.split(":");
|
|
32
|
-
return [key.trim(), value.join(":").trim()];
|
|
33
|
-
}));
|
|
34
|
-
} catch { return {}; }
|
|
35
|
-
};
|
|
36
|
-
const configContent = (existing, defaultOrchestration, scope, language) => {
|
|
37
|
-
const values = {
|
|
38
|
-
...existing,
|
|
39
|
-
schema: "matrix/config/v1",
|
|
40
|
-
auto_transition: existing.auto_transition === "false" ? "false" : "true",
|
|
41
|
-
default_orchestration: defaultOrchestration,
|
|
42
|
-
installation_scope: scope,
|
|
43
|
-
language
|
|
44
|
-
};
|
|
45
|
-
const preferred = ["schema", "auto_transition", "default_orchestration", "installation_scope", "language"];
|
|
46
|
-
const keys = [...preferred, ...Object.keys(values).filter((key) => !preferred.includes(key)).sort()];
|
|
47
|
-
return `${keys.map((key) => `${key}: ${values[key]}`).join("\n")}\n`;
|
|
48
|
-
};
|
|
49
|
-
const removeEntry = (value) => { if (pathExists(value)) fs.rmSync(value, { recursive: true, force: true }); };
|
|
50
|
-
const removeStaging = (stagingRoot) => {
|
|
51
|
-
removeEntry(stagingRoot);
|
|
52
|
-
const parent = path.dirname(stagingRoot);
|
|
53
|
-
if (exists(parent) && fs.readdirSync(parent).length === 0) fs.rmdirSync(parent);
|
|
54
|
-
};
|
|
24
|
+
const writeJson = (file, value) => {
|
|
25
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
26
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
27
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`);
|
|
28
|
+
fs.renameSync(temporary, file);
|
|
29
|
+
};
|
|
30
|
+
const readJson = (file) => { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; } };
|
|
31
|
+
const readConfig = (file) => {
|
|
32
|
+
try {
|
|
33
|
+
return Object.fromEntries(fs.readFileSync(file, "utf8").split(/\r?\n/).filter((line) => line.includes(":") && !line.startsWith(" ")).map((line) => {
|
|
34
|
+
const [key, ...value] = line.split(":");
|
|
35
|
+
return [key.trim(), value.join(":").trim()];
|
|
36
|
+
}));
|
|
37
|
+
} catch { return {}; }
|
|
38
|
+
};
|
|
39
|
+
const configContent = (existing, defaultOrchestration, scope, language) => {
|
|
40
|
+
const values = {
|
|
41
|
+
...existing,
|
|
42
|
+
schema: "matrix/config/v1",
|
|
43
|
+
auto_transition: existing.auto_transition === "false" ? "false" : "true",
|
|
44
|
+
default_orchestration: defaultOrchestration,
|
|
45
|
+
installation_scope: scope,
|
|
46
|
+
language
|
|
47
|
+
};
|
|
48
|
+
const preferred = ["schema", "auto_transition", "default_orchestration", "installation_scope", "language"];
|
|
49
|
+
const keys = [...preferred, ...Object.keys(values).filter((key) => !preferred.includes(key)).sort()];
|
|
50
|
+
return `${keys.map((key) => `${key}: ${values[key]}`).join("\n")}\n`;
|
|
51
|
+
};
|
|
52
|
+
const removeEntry = (value) => { if (pathExists(value)) fs.rmSync(value, { recursive: true, force: true }); };
|
|
53
|
+
const removeStaging = (stagingRoot) => {
|
|
54
|
+
removeEntry(stagingRoot);
|
|
55
|
+
const parent = path.dirname(stagingRoot);
|
|
56
|
+
if (exists(parent) && fs.readdirSync(parent).length === 0) fs.rmdirSync(parent);
|
|
57
|
+
};
|
|
55
58
|
const compareVersions = (left, right) => {
|
|
56
|
-
const parse = (value) => String(value ?? "0.0.0").split("-")[0].split(".").map((part) => Number(part) || 0);
|
|
57
|
-
const a = parse(left); const b = parse(right);
|
|
58
|
-
for (let index = 0; index < Math.max(a.length, b.length); index += 1) if ((a[index] ?? 0) !== (b[index] ?? 0)) return (a[index] ?? 0) > (b[index] ?? 0) ? 1 : -1;
|
|
59
|
-
return 0;
|
|
60
|
-
};
|
|
59
|
+
const parse = (value) => String(value ?? "0.0.0").split("-")[0].split(".").map((part) => Number(part) || 0);
|
|
60
|
+
const a = parse(left); const b = parse(right);
|
|
61
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) if ((a[index] ?? 0) !== (b[index] ?? 0)) return (a[index] ?? 0) > (b[index] ?? 0) ? 1 : -1;
|
|
62
|
+
return 0;
|
|
63
|
+
};
|
|
61
64
|
const skillPresent = (root, skill) => exists(path.join(root, skill, "SKILL.md"));
|
|
62
65
|
const isBrokenLink = (value) => { try { return fs.lstatSync(value).isSymbolicLink() && !exists(value); } catch { return false; } };
|
|
63
66
|
const mattObservationMatchesRelease = (observation, contentHashes) => MATT_SKILLS.every((skill) => observation.matt.skills?.[skill]?.hash === contentHashes[skill]);
|
|
@@ -78,52 +81,52 @@ const localizedState = (language, state) => text(language, ({
|
|
|
78
81
|
const localizedAction = (language, action) => text(language, ({
|
|
79
82
|
install: "actionInstall", keep: "actionKeep", "backup-replace": "actionBackupReplace", repair: "actionRepair", blocked: "actionBlocked"
|
|
80
83
|
})[action] ?? action);
|
|
81
|
-
|
|
82
|
-
function readManifest(intent) { return readJson(manifestPath(intent.scope, intent.projectRoot, intent.home)); }
|
|
83
|
-
|
|
84
|
+
|
|
85
|
+
function readManifest(intent) { return readJson(manifestPath(intent.scope, intent.projectRoot, intent.home)); }
|
|
86
|
+
|
|
84
87
|
export function installedIntent({ projectRoot = process.cwd(), home = os.homedir() } = {}) {
|
|
85
88
|
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
86
89
|
const config = readConfig(path.join(resolvedProjectRoot, ".matrix", "config.yaml"));
|
|
87
90
|
const projectManifest = readJson(manifestPath("project", resolvedProjectRoot, home));
|
|
88
91
|
const scope = projectManifest ? "project" : config.installation_scope === "global" ? "global" : "project";
|
|
89
92
|
const manifest = projectManifest ?? readJson(manifestPath(scope, resolvedProjectRoot, home));
|
|
90
|
-
const platforms = Object.keys(manifest?.platforms ?? {}).filter((id) => PLATFORM_IDS.includes(id));
|
|
91
|
-
if (!manifest || !platforms.length) return { ok: false, code: "MANIFEST_MISSING", scope, config };
|
|
92
|
-
const language = ["en", "zh-CN"].includes(manifest.language) ? manifest.language : (["en", "zh-CN"].includes(config.language) ? config.language : "en");
|
|
93
|
-
const defaultOrchestration = ["prim", "arch"].includes(manifest.orchestration?.default) ? manifest.orchestration.default : "prim";
|
|
93
|
+
const platforms = Object.keys(manifest?.platforms ?? {}).filter((id) => PLATFORM_IDS.includes(id));
|
|
94
|
+
if (!manifest || !platforms.length) return { ok: false, code: "MANIFEST_MISSING", scope, config };
|
|
95
|
+
const language = ["en", "zh-CN"].includes(manifest.language) ? manifest.language : (["en", "zh-CN"].includes(config.language) ? config.language : "en");
|
|
96
|
+
const defaultOrchestration = ["prim", "arch"].includes(manifest.orchestration?.default) ? manifest.orchestration.default : "prim";
|
|
94
97
|
const matt = "readonly";
|
|
95
|
-
return {
|
|
96
|
-
ok: true,
|
|
97
|
-
intent: {
|
|
98
|
-
projectRoot: resolvedProjectRoot, home, scope, platforms, language,
|
|
99
|
-
mode: ["copy", "symlink"].includes(manifest.mode) ? manifest.mode : "copy",
|
|
100
|
-
matt, defaultOrchestration, nonInteractive: true, matrixPolicy: "safe", dryRun: false
|
|
101
|
-
},
|
|
102
|
-
manifest,
|
|
103
|
-
config
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function detectPlatforms(projectRoot, scope, home) {
|
|
108
|
-
const bases = [scopeBase(scope, projectRoot, home)];
|
|
109
|
-
if (scope === "project") bases.push(home);
|
|
110
|
-
return PLATFORM_IDS.filter((id) => bases.some((base) => exists(PLATFORMS[id].skillRoot(base))));
|
|
111
|
-
}
|
|
112
|
-
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
intent: {
|
|
101
|
+
projectRoot: resolvedProjectRoot, home, scope, platforms, language,
|
|
102
|
+
mode: ["copy", "symlink"].includes(manifest.mode) ? manifest.mode : "copy",
|
|
103
|
+
matt, defaultOrchestration, nonInteractive: true, matrixPolicy: "safe", dryRun: false
|
|
104
|
+
},
|
|
105
|
+
manifest,
|
|
106
|
+
config
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function detectPlatforms(projectRoot, scope, home) {
|
|
111
|
+
const bases = [scopeBase(scope, projectRoot, home)];
|
|
112
|
+
if (scope === "project") bases.push(home);
|
|
113
|
+
return PLATFORM_IDS.filter((id) => bases.some((base) => exists(PLATFORMS[id].skillRoot(base))));
|
|
114
|
+
}
|
|
115
|
+
|
|
113
116
|
function normalizeIntent(raw) {
|
|
114
|
-
const scope = raw.scope ?? "project";
|
|
115
|
-
const platforms = [...new Set(raw.platforms ?? [])];
|
|
116
|
-
if (!["project", "global"].includes(scope)) return { error: "INVALID_INTENT", detail: "scope" };
|
|
117
|
-
if (!["copy", "symlink"].includes(raw.mode ?? "copy")) return { error: "INVALID_INTENT", detail: "mode" };
|
|
118
|
-
if (!["en", "zh-CN"].includes(raw.language ?? "en")) return { error: "INVALID_INTENT", detail: "language" };
|
|
117
|
+
const scope = raw.scope ?? "project";
|
|
118
|
+
const platforms = [...new Set(raw.platforms ?? [])];
|
|
119
|
+
if (!["project", "global"].includes(scope)) return { error: "INVALID_INTENT", detail: "scope" };
|
|
120
|
+
if (!["copy", "symlink"].includes(raw.mode ?? "copy")) return { error: "INVALID_INTENT", detail: "mode" };
|
|
121
|
+
if (!["en", "zh-CN"].includes(raw.language ?? "en")) return { error: "INVALID_INTENT", detail: "language" };
|
|
119
122
|
if (raw.defaultOrchestration != null && !["prim", "arch"].includes(raw.defaultOrchestration)) return { error: "INVALID_INTENT", detail: "default-orchestration" };
|
|
120
123
|
if (!MATT_OPERATIONS.has(raw.matt ?? "none")) return { error: "INVALID_INTENT", detail: "matt" };
|
|
121
124
|
if (raw.mattPolicy != null && !["preserve", "replace"].includes(raw.mattPolicy)) return { error: "INVALID_INTENT", detail: "matt-policy" };
|
|
122
|
-
if (platforms.some((id) => !PLATFORM_IDS.includes(id))) return { error: "UNVERIFIED_PLATFORM" };
|
|
123
|
-
return { projectRoot: path.resolve(raw.projectRoot ?? process.cwd()), home: raw.home ?? os.homedir(), scope, platforms,
|
|
125
|
+
if (platforms.some((id) => !PLATFORM_IDS.includes(id))) return { error: "UNVERIFIED_PLATFORM" };
|
|
126
|
+
return { projectRoot: path.resolve(raw.projectRoot ?? process.cwd()), home: raw.home ?? os.homedir(), scope, platforms,
|
|
124
127
|
language: raw.language ?? "en", mode: raw.mode ?? "copy", matrixPolicy: raw.matrixPolicy ?? "safe",
|
|
125
128
|
matt: raw.matt ?? "none", mattPolicy: raw.mattPolicy ?? "preserve", defaultOrchestration: raw.defaultOrchestration ?? null,
|
|
126
|
-
nonInteractive: Boolean(raw.nonInteractive), dryRun: Boolean(raw.dryRun) };
|
|
129
|
+
nonInteractive: Boolean(raw.nonInteractive), dryRun: Boolean(raw.dryRun) };
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
export function classifyMattTarget({ present, readable = true, actual, expected, recorded, receiptTrusted = false }) {
|
|
@@ -142,18 +145,18 @@ function trustedMattReceipt(receipt) {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
function observePlatform(intent, catalog, manifest, mattReceipt, platformId, contentHashes) {
|
|
145
|
-
const platform = PLATFORMS[platformId]; const base = scopeBase(intent.scope, intent.projectRoot, intent.home);
|
|
146
|
-
const root = platform.skillRoot(base); const records = manifest?.platforms?.[platformId]?.skills ?? {};
|
|
147
|
-
const skills = MATRIX_SKILLS.map((skill) => {
|
|
148
|
-
const target = path.join(root, skill);
|
|
149
|
-
if (!pathExists(target)) return { skill, target, state: "missing" };
|
|
150
|
-
if (isBrokenLink(target) || !exists(path.join(target, "SKILL.md"))) return { skill, target, state: "damaged" };
|
|
151
|
-
const actual = hashDirectory(target); const recorded = records[skill]?.hash; const expected = catalog.skills[skill];
|
|
152
|
-
if (!recorded) return { skill, target, state: "user-modified", actual };
|
|
153
|
-
if (actual === expected) return { skill, target, state: "matching", actual };
|
|
154
|
-
if (actual === recorded) return { skill, target, state: compareVersions(manifest?.catalogVersion, catalog.version) > 0 ? "newer" : "outdated-unchanged", actual };
|
|
155
|
-
return { skill, target, state: "user-modified", actual };
|
|
156
|
-
});
|
|
148
|
+
const platform = PLATFORMS[platformId]; const base = scopeBase(intent.scope, intent.projectRoot, intent.home);
|
|
149
|
+
const root = platform.skillRoot(base); const records = manifest?.platforms?.[platformId]?.skills ?? {};
|
|
150
|
+
const skills = MATRIX_SKILLS.map((skill) => {
|
|
151
|
+
const target = path.join(root, skill);
|
|
152
|
+
if (!pathExists(target)) return { skill, target, state: "missing" };
|
|
153
|
+
if (isBrokenLink(target) || !exists(path.join(target, "SKILL.md"))) return { skill, target, state: "damaged" };
|
|
154
|
+
const actual = hashDirectory(target); const recorded = records[skill]?.hash; const expected = catalog.skills[skill];
|
|
155
|
+
if (!recorded) return { skill, target, state: "user-modified", actual };
|
|
156
|
+
if (actual === expected) return { skill, target, state: "matching", actual };
|
|
157
|
+
if (actual === recorded) return { skill, target, state: compareVersions(manifest?.catalogVersion, catalog.version) > 0 ? "newer" : "outdated-unchanged", actual };
|
|
158
|
+
return { skill, target, state: "user-modified", actual };
|
|
159
|
+
});
|
|
157
160
|
const states = new Set(skills.map((item) => item.state));
|
|
158
161
|
const matrix = states.size === 1 && states.has("matching") ? "matching" : states.has("user-modified") ? "user-modified"
|
|
159
162
|
: states.has("missing") && states.size === 1 ? "missing" : states.has("newer") ? "newer"
|
|
@@ -184,63 +187,63 @@ function observePlatform(intent, catalog, manifest, mattReceipt, platformId, con
|
|
|
184
187
|
return { id: platformId, name: platform.name, root, skills, matrix, matt: { root: mattRoot, present: mattPresent.length, missing, total: MATT_SKILLS.length, inherited: 0, ignoredGlobal,
|
|
185
188
|
state: mattPresent.length === MATT_SKILLS.length ? "complete" : mattPresent.length ? "partial" : "missing", skills: managedSkills, classifications, classificationCounts, unmanaged } };
|
|
186
189
|
}
|
|
187
|
-
|
|
188
|
-
function actionFor(observation, policy) {
|
|
189
|
-
if (observation.matrix === "matching" || observation.matrix === "newer") return "keep";
|
|
190
|
-
if (observation.matrix === "missing") return "install";
|
|
191
|
-
if (observation.matrix === "outdated-unchanged") return "backup-replace";
|
|
192
|
-
if (["damaged", "user-modified"].includes(observation.matrix)) return policy === "replace" ? "backup-replace" : "blocked";
|
|
193
|
-
return "repair";
|
|
194
|
-
}
|
|
195
|
-
|
|
190
|
+
|
|
191
|
+
function actionFor(observation, policy) {
|
|
192
|
+
if (observation.matrix === "matching" || observation.matrix === "newer") return "keep";
|
|
193
|
+
if (observation.matrix === "missing") return "install";
|
|
194
|
+
if (observation.matrix === "outdated-unchanged") return "backup-replace";
|
|
195
|
+
if (["damaged", "user-modified"].includes(observation.matrix)) return policy === "replace" ? "backup-replace" : "blocked";
|
|
196
|
+
return "repair";
|
|
197
|
+
}
|
|
198
|
+
|
|
196
199
|
export function recoverPendingTransaction(intent, { allowMattWrites = true } = {}) {
|
|
197
200
|
const file = transactionPath(intent); const journal = readJson(file);
|
|
198
201
|
if (!journal) return { ok: true, recovered: false };
|
|
199
202
|
if (!allowMattWrites && ((journal.operations ?? []).some((operation) => operation.kind === "matt") || journal.mattReceipt?.written || journal.mattReceipt?.backup)) {
|
|
200
203
|
return { ok: false, code: "RECOVERY_REQUIRED", recovery: `A pending Matt init transaction must be recovered by matrix init --with-mattpocock before Matrix update can continue. Keep ${file}.` };
|
|
201
204
|
}
|
|
202
|
-
const restoreMetadata = (entry) => {
|
|
203
|
-
if (!entry) return;
|
|
204
|
-
if (entry.backup && pathExists(entry.backup)) {
|
|
205
|
-
removeEntry(entry.path);
|
|
206
|
-
fs.mkdirSync(path.dirname(entry.path), { recursive: true });
|
|
207
|
-
fs.renameSync(entry.backup, entry.path);
|
|
208
|
-
} else if (entry.written && !entry.existed) removeEntry(entry.path);
|
|
209
|
-
};
|
|
210
|
-
try {
|
|
205
|
+
const restoreMetadata = (entry) => {
|
|
206
|
+
if (!entry) return;
|
|
207
|
+
if (entry.backup && pathExists(entry.backup)) {
|
|
208
|
+
removeEntry(entry.path);
|
|
209
|
+
fs.mkdirSync(path.dirname(entry.path), { recursive: true });
|
|
210
|
+
fs.renameSync(entry.backup, entry.path);
|
|
211
|
+
} else if (entry.written && !entry.existed) removeEntry(entry.path);
|
|
212
|
+
};
|
|
213
|
+
try {
|
|
211
214
|
if (journal.state === "committed") {
|
|
212
215
|
removeStaging(journal.stagingRoot);
|
|
213
216
|
for (const operation of journal.operations ?? []) removeEntry(operation.backup);
|
|
214
217
|
removeEntry(journal.manifest?.backup); removeEntry(journal.config?.backup); removeEntry(journal.mattReceipt?.backup); removeEntry(file);
|
|
215
|
-
return { ok: true, recovered: true, receipt: `Finalized completed Matrix transaction ${journal.id}.` };
|
|
216
|
-
}
|
|
217
|
-
for (const operation of [...(journal.operations ?? [])].reverse()) {
|
|
218
|
-
if (operation.status !== "pending") removeEntry(operation.target);
|
|
219
|
-
if (operation.backup && pathExists(operation.backup)) { fs.mkdirSync(path.dirname(operation.target), { recursive: true }); fs.renameSync(operation.backup, operation.target); }
|
|
220
|
-
}
|
|
218
|
+
return { ok: true, recovered: true, receipt: `Finalized completed Matrix transaction ${journal.id}.` };
|
|
219
|
+
}
|
|
220
|
+
for (const operation of [...(journal.operations ?? [])].reverse()) {
|
|
221
|
+
if (operation.status !== "pending") removeEntry(operation.target);
|
|
222
|
+
if (operation.backup && pathExists(operation.backup)) { fs.mkdirSync(path.dirname(operation.target), { recursive: true }); fs.renameSync(operation.backup, operation.target); }
|
|
223
|
+
}
|
|
221
224
|
restoreMetadata(journal.config);
|
|
222
225
|
restoreMetadata(journal.manifest);
|
|
223
226
|
restoreMetadata(journal.mattReceipt);
|
|
224
|
-
removeStaging(journal.stagingRoot); removeEntry(file);
|
|
225
|
-
return { ok: true, recovered: true, receipt: `Recovered interrupted Matrix transaction ${journal.id}.` };
|
|
226
|
-
} catch (error) { return { ok: false, code: "RECOVERY_REQUIRED", recovery: `Keep ${file} and restore the paths recorded there before retrying.`, error: error.message }; }
|
|
227
|
-
}
|
|
228
|
-
|
|
227
|
+
removeStaging(journal.stagingRoot); removeEntry(file);
|
|
228
|
+
return { ok: true, recovered: true, receipt: `Recovered interrupted Matrix transaction ${journal.id}.` };
|
|
229
|
+
} catch (error) { return { ok: false, code: "RECOVERY_REQUIRED", recovery: `Keep ${file} and restore the paths recorded there before retrying.`, error: error.message }; }
|
|
230
|
+
}
|
|
231
|
+
|
|
229
232
|
export function createDistribution({ failAt, mattAdapter = createMattAdapter(), mattCandidateVerifier = null, mattContentHashes = MATT_CONTENT_HASHES } = {}) {
|
|
230
233
|
const candidateVerifier = mattCandidateVerifier ?? ((candidate, actions) => verifyMattCandidate(candidate, actions, mattContentHashes));
|
|
231
|
-
const evaluate = (rawIntent) => {
|
|
232
|
-
const intent = normalizeIntent(rawIntent); const language = intent.language ?? rawIntent.language ?? "en";
|
|
233
|
-
if (intent.error) return { ok: false, code: intent.error, diagnostics: [{ code: intent.error, message: text(language, "noPlatform") }] };
|
|
234
|
-
if (!intent.platforms.length) { const detected = detectPlatforms(intent.projectRoot, intent.scope, intent.home); if (!detected.length) return { ok: false, code: "INPUT_REQUIRED", diagnostics: [{ code: "INPUT_REQUIRED", message: text(language, "noPlatform") }] }; intent.platforms = detected; }
|
|
235
|
-
let catalog; try { catalog = releaseCatalog(intent.language); } catch (error) { return { ok: false, code: "PACKAGE_INCOMPLETE", intent, diagnostics: [{ code: "PACKAGE_INCOMPLETE", message: error.message }] }; }
|
|
234
|
+
const evaluate = (rawIntent) => {
|
|
235
|
+
const intent = normalizeIntent(rawIntent); const language = intent.language ?? rawIntent.language ?? "en";
|
|
236
|
+
if (intent.error) return { ok: false, code: intent.error, diagnostics: [{ code: intent.error, message: text(language, "noPlatform") }] };
|
|
237
|
+
if (!intent.platforms.length) { const detected = detectPlatforms(intent.projectRoot, intent.scope, intent.home); if (!detected.length) return { ok: false, code: "INPUT_REQUIRED", diagnostics: [{ code: "INPUT_REQUIRED", message: text(language, "noPlatform") }] }; intent.platforms = detected; }
|
|
238
|
+
let catalog; try { catalog = releaseCatalog(intent.language); } catch (error) { return { ok: false, code: "PACKAGE_INCOMPLETE", intent, diagnostics: [{ code: "PACKAGE_INCOMPLETE", message: error.message }] }; }
|
|
236
239
|
const manifest = readManifest(intent);
|
|
237
240
|
const mattReceipt = readJson(mattReceiptPath(intent.projectRoot));
|
|
238
241
|
intent.platforms = [...new Set([...Object.keys(manifest?.platforms ?? {}), ...intent.platforms])];
|
|
239
242
|
const observations = intent.platforms.map((id) => observePlatform(intent, catalog, manifest, mattReceipt, id, mattContentHashes));
|
|
240
|
-
const existingConfig = readConfig(configPath(intent));
|
|
241
|
-
const existingDefault = ["prim", "arch"].includes(existingConfig.default_orchestration) ? existingConfig.default_orchestration
|
|
242
|
-
: ["prim", "arch"].includes(manifest?.orchestration?.default) ? manifest.orchestration.default : null;
|
|
243
|
-
intent.defaultOrchestration = intent.defaultOrchestration ?? existingDefault ?? (intent.matt === "missing" && intent.nonInteractive ? "arch" : "prim");
|
|
243
|
+
const existingConfig = readConfig(configPath(intent));
|
|
244
|
+
const existingDefault = ["prim", "arch"].includes(existingConfig.default_orchestration) ? existingConfig.default_orchestration
|
|
245
|
+
: ["prim", "arch"].includes(manifest?.orchestration?.default) ? manifest.orchestration.default : null;
|
|
246
|
+
intent.defaultOrchestration = intent.defaultOrchestration ?? existingDefault ?? (intent.matt === "missing" && intent.nonInteractive ? "arch" : "prim");
|
|
244
247
|
const actions = observations.map((item) => ({ platform: item.id, matrix: actionFor(item, intent.matrixPolicy), matt: intent.matt === "missing" ? { kind: "reconcile", skills: [...MATT_SKILLS] } : { kind: "keep", skills: [] } }));
|
|
245
248
|
const blocked = actions.filter((item) => item.matrix === "blocked");
|
|
246
249
|
const blockedMatt = intent.matt === "missing" && intent.mattPolicy !== "replace"
|
|
@@ -249,41 +252,41 @@ export function createDistribution({ failAt, mattAdapter = createMattAdapter(),
|
|
|
249
252
|
const archMustRemainAvailable = manifest?.orchestration?.available?.includes("arch") || intent.defaultOrchestration === "arch";
|
|
250
253
|
const mattReady = trustedMattReceipt(mattReceipt) && observations.every(mattObservationMatchesReceipt);
|
|
251
254
|
const incompleteWithoutInstall = intent.matt !== "readonly" && intent.matt !== "missing" && archMustRemainAvailable && !mattReady;
|
|
252
|
-
const plan = { version: VERSION, intent, catalogDigest: catalog.digest, manifestPath: manifestPath(intent.scope, intent.projectRoot, intent.home), configPath: configPath(intent), actions, observations, preconditionDigest: digest(observations) };
|
|
253
|
-
plan.seal = digest({ intent, catalogDigest: plan.catalogDigest, actions, preconditionDigest: plan.preconditionDigest });
|
|
255
|
+
const plan = { version: VERSION, intent, catalogDigest: catalog.digest, manifestPath: manifestPath(intent.scope, intent.projectRoot, intent.home), configPath: configPath(intent), actions, observations, preconditionDigest: digest(observations) };
|
|
256
|
+
plan.seal = digest({ intent, catalogDigest: plan.catalogDigest, actions, preconditionDigest: plan.preconditionDigest });
|
|
254
257
|
const code = blocked.length ? "USER_MODIFIED_BLOCKED" : blockedMatt.length ? "MATT_USER_MODIFIED" : incompleteWithoutInstall ? "ARCH_INSTALLATION_INCOMPLETE" : "OK";
|
|
255
|
-
return { ok: code === "OK", code, intent, observations, actions, plan, diagnostics: [
|
|
258
|
+
return { ok: code === "OK", code, intent, observations, actions, plan, diagnostics: [
|
|
256
259
|
...blocked.map((item) => ({ code: "USER_MODIFIED_BLOCKED", platform: item.platform, message: text(intent.language, "matrixModifiedDiagnostic").replace("{platform}", item.platform) })),
|
|
257
260
|
...blockedMatt.map((item) => ({ code: "MATT_USER_MODIFIED", platform: item.id, states: item.matt.classificationCounts, message: text(intent.language, "mattModifiedDiagnostic").replace("{platform}", item.name) })),
|
|
258
261
|
...(incompleteWithoutInstall ? [{ code: "ARCH_INSTALLATION_INCOMPLETE", message: text(intent.language, "archIncompleteDiagnostic") }] : [])
|
|
259
262
|
], summary: `${text(intent.language, "preview")}: ${actions.map((item) => `${item.platform}: ${localizedAction(intent.language, item.matrix)}`).join(", ")}` };
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
return { evaluate,
|
|
263
|
-
diagnose(rawIntent) {
|
|
264
|
-
const evaluation = evaluate(rawIntent); if (!evaluation.intent) return evaluation;
|
|
265
|
-
const diagnostics = []; const pending = readJson(transactionPath(evaluation.intent)); const manifest = readManifest(evaluation.intent);
|
|
266
|
-
if (pending) diagnostics.push({ code: "RECOVERY_REQUIRED", message: text(evaluation.intent.language, "transactionPending") });
|
|
267
|
-
if (!manifest) diagnostics.push({ code: "MANIFEST_MISSING", message: text(evaluation.intent.language, "manifestMissing") });
|
|
268
|
-
for (const observation of evaluation.observations ?? []) {
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return { evaluate,
|
|
266
|
+
diagnose(rawIntent) {
|
|
267
|
+
const evaluation = evaluate(rawIntent); if (!evaluation.intent) return evaluation;
|
|
268
|
+
const diagnostics = []; const pending = readJson(transactionPath(evaluation.intent)); const manifest = readManifest(evaluation.intent);
|
|
269
|
+
if (pending) diagnostics.push({ code: "RECOVERY_REQUIRED", message: text(evaluation.intent.language, "transactionPending") });
|
|
270
|
+
if (!manifest) diagnostics.push({ code: "MANIFEST_MISSING", message: text(evaluation.intent.language, "manifestMissing") });
|
|
271
|
+
for (const observation of evaluation.observations ?? []) {
|
|
269
272
|
diagnostics.push({ code: `MATRIX_${observation.matrix.toUpperCase().replaceAll("-", "_")}`, platform: observation.id, message: text(evaluation.intent.language, "matrixStateDiagnostic").replace("{platform}", observation.name).replace("{state}", localizedState(evaluation.intent.language, observation.matrix)) });
|
|
270
|
-
const matrixRoot = path.join(observation.root, "matrix");
|
|
271
|
-
if (pathExists(matrixRoot) && (!exists(matrixRoot) || !exists(path.join(matrixRoot, "scripts", "matrix-runtime.mjs")) || !exists(path.join(matrixRoot, "scripts", "workflow-transaction.js")))) diagnostics.push({ code: "RUNTIME_MISSING", platform: observation.id, message: `${observation.name}: ${text(evaluation.intent.language, "runtimeMissing")}` });
|
|
272
|
-
for (const skill of observation.skills.filter((entry) => isBrokenLink(entry.target))) diagnostics.push({ code: "BROKEN_LINK", platform: observation.id, path: skill.target, message: `${observation.name}: ${text(evaluation.intent.language, "brokenLink")} ${skill.target}` });
|
|
273
|
+
const matrixRoot = path.join(observation.root, "matrix");
|
|
274
|
+
if (pathExists(matrixRoot) && (!exists(matrixRoot) || !exists(path.join(matrixRoot, "scripts", "matrix-runtime.mjs")) || !exists(path.join(matrixRoot, "scripts", "workflow-transaction.js")))) diagnostics.push({ code: "RUNTIME_MISSING", platform: observation.id, message: `${observation.name}: ${text(evaluation.intent.language, "runtimeMissing")}` });
|
|
275
|
+
for (const skill of observation.skills.filter((entry) => isBrokenLink(entry.target))) diagnostics.push({ code: "BROKEN_LINK", platform: observation.id, path: skill.target, message: `${observation.name}: ${text(evaluation.intent.language, "brokenLink")} ${skill.target}` });
|
|
273
276
|
if (observation.id === "codex" && PLATFORMS.codex.legacyRoots(scopeBase(evaluation.intent.scope, evaluation.intent.projectRoot, evaluation.intent.home)).some(exists)) diagnostics.push({ code: "CODEX_LEGACY_DETECTED", platform: observation.id, message: text(evaluation.intent.language, "codexLegacyDiagnostic") });
|
|
274
277
|
diagnostics.push({ code: `MATT_${observation.matt.state.toUpperCase()}`, platform: observation.id, message: text(evaluation.intent.language, "mattStateDiagnostic").replace("{platform}", observation.name).replace("{state}", localizedState(evaluation.intent.language, observation.matt.state)) });
|
|
275
278
|
if (observation.matt.ignoredGlobal) diagnostics.push({ code: "MATT_GLOBAL_IGNORED", platform: observation.id, count: observation.matt.ignoredGlobal, message: `${observation.name}: ${observation.matt.ignoredGlobal} ${text(evaluation.intent.language, "mattGlobalIgnored")}` });
|
|
276
279
|
if (observation.matt.unmanaged.length) diagnostics.push({ code: "ARCH_UNMANAGED_EXTRA", platform: observation.id, skills: observation.matt.unmanaged, message: text(evaluation.intent.language, "unmanagedExtraDiagnostic").replace("{platform}", observation.name).replace("{skills}", observation.matt.unmanaged.join(", ")) });
|
|
277
|
-
}
|
|
278
|
-
return { ...evaluation, diagnosis: diagnostics };
|
|
279
|
-
},
|
|
280
|
-
commit(plan) {
|
|
281
|
-
if (!plan?.seal || plan.seal !== digest({ intent: plan.intent, catalogDigest: plan.catalogDigest, actions: plan.actions, preconditionDigest: plan.preconditionDigest })) return { ok: false, code: "INVALID_INTENT" };
|
|
280
|
+
}
|
|
281
|
+
return { ...evaluation, diagnosis: diagnostics };
|
|
282
|
+
},
|
|
283
|
+
commit(plan) {
|
|
284
|
+
if (!plan?.seal || plan.seal !== digest({ intent: plan.intent, catalogDigest: plan.catalogDigest, actions: plan.actions, preconditionDigest: plan.preconditionDigest })) return { ok: false, code: "INVALID_INTENT" };
|
|
282
285
|
const recovery = recoverPendingTransaction(plan.intent, { allowMattWrites: plan.intent.matt !== "readonly" }); if (!recovery.ok) return recovery;
|
|
283
286
|
const refreshed = evaluate({ ...plan.intent, platforms: plan.intent.platforms });
|
|
284
|
-
if (!refreshed.plan || refreshed.plan.preconditionDigest !== plan.preconditionDigest || refreshed.plan.catalogDigest !== plan.catalogDigest) return { ok: false, code: "PLAN_STALE" };
|
|
285
|
-
if (!refreshed.ok) return { ok: false, code: refreshed.code, diagnostics: refreshed.diagnostics };
|
|
286
|
-
if (plan.intent.dryRun) return { ok: true, code: "DRY_RUN", actions: plan.actions };
|
|
287
|
+
if (!refreshed.plan || refreshed.plan.preconditionDigest !== plan.preconditionDigest || refreshed.plan.catalogDigest !== plan.catalogDigest) return { ok: false, code: "PLAN_STALE" };
|
|
288
|
+
if (!refreshed.ok) return { ok: false, code: refreshed.code, diagnostics: refreshed.diagnostics };
|
|
289
|
+
if (plan.intent.dryRun) return { ok: true, code: "DRY_RUN", actions: plan.actions };
|
|
287
290
|
const previousManifest = readManifest(plan.intent);
|
|
288
291
|
const previousMattReceipt = readJson(mattReceiptPath(plan.intent.projectRoot));
|
|
289
292
|
const root = installationRoot(plan.intent.scope, plan.intent.projectRoot, plan.intent.home); const id = nowStamp(); const stagingRoot = path.join(root, "staging", id); const journalFile = transactionPath(plan.intent);
|
|
@@ -304,9 +307,9 @@ export function createDistribution({ failAt, mattAdapter = createMattAdapter(),
|
|
|
304
307
|
let mattCandidate = null;
|
|
305
308
|
try {
|
|
306
309
|
persist(); const catalog = releaseCatalog(plan.intent.language); const stagedPlatforms = new Map();
|
|
307
|
-
for (const item of refreshed.observations) {
|
|
308
|
-
if (plan.actions.find((entry) => entry.platform === item.id).matrix === "keep") continue;
|
|
309
|
-
const platformStage = path.join(stagingRoot, item.id);
|
|
310
|
+
for (const item of refreshed.observations) {
|
|
311
|
+
if (plan.actions.find((entry) => entry.platform === item.id).matrix === "keep") continue;
|
|
312
|
+
const platformStage = path.join(stagingRoot, item.id);
|
|
310
313
|
for (const skill of MATRIX_SKILLS) { const staged = path.join(platformStage, skill); fs.mkdirSync(path.dirname(staged), { recursive: true }); fs.cpSync(path.join(catalog.root, skill), staged, { recursive: true }); fs.mkdirSync(path.join(staged, "scripts"), { recursive: true }); fs.writeFileSync(path.join(staged, "scripts", "package.json"), '{"type":"module"}\n'); fs.copyFileSync(workflowRuntimeSource(), path.join(staged, "scripts", "matrix-runtime.mjs")); fs.copyFileSync(workflowTransactionSource(), path.join(staged, "scripts", "workflow-transaction.js")); fs.copyFileSync(mattCatalogSource(), path.join(staged, "scripts", "matt-catalog.mjs")); if (skill === "matrix") for (const filename of ["matrix_state.py", "matrix_claude.py"]) fs.copyFileSync(path.join(sourceSkillsRoot("en"), "matrix", "scripts", filename), path.join(staged, "scripts", filename)); if (!exists(path.join(staged, "SKILL.md"))) throw new Error(`Stage validation failed: ${item.id}/${skill}`); }
|
|
311
314
|
stagedPlatforms.set(item.id, platformStage);
|
|
312
315
|
}
|
|
@@ -323,16 +326,16 @@ export function createDistribution({ failAt, mattAdapter = createMattAdapter(),
|
|
|
323
326
|
candidateVerifier(mattCandidate, mattActions);
|
|
324
327
|
}
|
|
325
328
|
failAt?.("after-stage"); let operationIndex = 0;
|
|
326
|
-
for (const item of refreshed.observations) {
|
|
327
|
-
if (plan.actions.find((entry) => entry.platform === item.id).matrix === "keep") continue;
|
|
328
|
-
fs.mkdirSync(item.root, { recursive: true });
|
|
329
|
-
for (const skill of MATRIX_SKILLS) {
|
|
330
|
-
const operation = journal.operations[operationIndex++]; const target = operation.target; operation.status = "backing-up";
|
|
331
|
-
if (pathExists(target)) operation.backup = path.join(root, "backups", id, item.id, skill); persist();
|
|
332
|
-
if (operation.backup) { fs.mkdirSync(path.dirname(operation.backup), { recursive: true }); fs.renameSync(target, operation.backup); }
|
|
333
|
-
operation.status = "backed-up"; persist(); failAt?.("after-backup", { platform: item.id, skill });
|
|
334
|
-
const staged = path.join(stagedPlatforms.get(item.id), skill);
|
|
335
|
-
if (plan.intent.mode === "symlink") { const store = path.join(root, "skills", catalog.digest, skill); if (!pathExists(store)) { fs.mkdirSync(path.dirname(store), { recursive: true }); fs.renameSync(staged, store); } fs.symlinkSync(store, target, process.platform === "win32" ? "junction" : "dir"); } else fs.renameSync(staged, target);
|
|
329
|
+
for (const item of refreshed.observations) {
|
|
330
|
+
if (plan.actions.find((entry) => entry.platform === item.id).matrix === "keep") continue;
|
|
331
|
+
fs.mkdirSync(item.root, { recursive: true });
|
|
332
|
+
for (const skill of MATRIX_SKILLS) {
|
|
333
|
+
const operation = journal.operations[operationIndex++]; const target = operation.target; operation.status = "backing-up";
|
|
334
|
+
if (pathExists(target)) operation.backup = path.join(root, "backups", id, item.id, skill); persist();
|
|
335
|
+
if (operation.backup) { fs.mkdirSync(path.dirname(operation.backup), { recursive: true }); fs.renameSync(target, operation.backup); }
|
|
336
|
+
operation.status = "backed-up"; persist(); failAt?.("after-backup", { platform: item.id, skill });
|
|
337
|
+
const staged = path.join(stagedPlatforms.get(item.id), skill);
|
|
338
|
+
if (plan.intent.mode === "symlink") { const store = path.join(root, "skills", catalog.digest, skill); if (!pathExists(store)) { fs.mkdirSync(path.dirname(store), { recursive: true }); fs.renameSync(staged, store); } fs.symlinkSync(store, target, process.platform === "win32" ? "junction" : "dir"); } else fs.renameSync(staged, target);
|
|
336
339
|
operation.status = "committed"; persist(); failAt?.("after-commit", { platform: item.id, skill });
|
|
337
340
|
}
|
|
338
341
|
}
|
|
@@ -364,11 +367,11 @@ export function createDistribution({ failAt, mattAdapter = createMattAdapter(),
|
|
|
364
367
|
}
|
|
365
368
|
const postObservations = plan.intent.platforms.map((platform) => observePlatform(plan.intent, catalog, readManifest(plan.intent), previousMattReceipt, platform, mattContentHashes));
|
|
366
369
|
const incomplete = mattActions.length ? postObservations.filter((item) => !mattObservationMatchesRelease(item, mattContentHashes)) : [];
|
|
367
|
-
const backups = journal.operations.filter((item) => item.backup).map((item) => ({ target: item.target, backup: item.backup }));
|
|
370
|
+
const backups = journal.operations.filter((item) => item.backup).map((item) => ({ target: item.target, backup: item.backup }));
|
|
368
371
|
if (mattResults.some((item) => !item.ok) || incomplete.length) {
|
|
369
372
|
if (mattCandidate) mattAdapter.discardCandidate?.(mattCandidate);
|
|
370
373
|
const restored = recoverPendingTransaction(plan.intent);
|
|
371
|
-
return { ok: true, code: "PARTIAL", committed: [], backups: [], recovery: "The platform was not added. Repair the Arch Skill installation and rerun matrix init --with-mattpocock.", matt: mattResults, recoveryReceipt: restored.receipt ?? recovery.receipt };
|
|
374
|
+
return { ok: true, code: "PARTIAL", committed: [], backups: [], recovery: "The platform was not added. Repair the Arch Skill installation and rerun matrix init --with-mattpocock.", matt: mattResults, recoveryReceipt: restored.receipt ?? recovery.receipt };
|
|
372
375
|
}
|
|
373
376
|
if (journal.manifest.existed) { journal.manifest.backup = path.join(root, "backups", id, "installation.json"); persist(); fs.mkdirSync(path.dirname(journal.manifest.backup), { recursive: true }); fs.renameSync(plan.manifestPath, journal.manifest.backup); }
|
|
374
377
|
if (journal.config.existed) { journal.config.backup = path.join(root, "backups", id, "config.yaml"); persist(); fs.mkdirSync(path.dirname(journal.config.backup), { recursive: true }); fs.renameSync(plan.configPath, journal.config.backup); }
|
|
@@ -397,14 +400,14 @@ export function createDistribution({ failAt, mattAdapter = createMattAdapter(),
|
|
|
397
400
|
journal.mattReceipt.written = true; persist();
|
|
398
401
|
}
|
|
399
402
|
writeJson(plan.manifestPath, {
|
|
400
|
-
version: VERSION, catalogVersion: catalog.version, language: plan.intent.language, mode: plan.intent.mode, scope: plan.intent.scope,
|
|
401
|
-
catalogDigest: catalog.digest, orchestration: { default: plan.intent.defaultOrchestration, available }, platforms: records
|
|
402
|
-
});
|
|
403
|
+
version: VERSION, catalogVersion: catalog.version, language: plan.intent.language, mode: plan.intent.mode, scope: plan.intent.scope,
|
|
404
|
+
catalogDigest: catalog.digest, orchestration: { default: plan.intent.defaultOrchestration, available }, platforms: records
|
|
405
|
+
});
|
|
403
406
|
journal.manifest.written = true; persist(); failAt?.("after-manifest"); journal.state = "committed"; persist();
|
|
404
407
|
if (mattCandidate) mattAdapter.discardCandidate?.(mattCandidate);
|
|
405
408
|
removeStaging(stagingRoot); removeEntry(journal.manifest.backup); removeEntry(journal.config.backup); removeEntry(journal.mattReceipt.backup); removeEntry(journalFile);
|
|
406
409
|
return { ok: true, code: "OK", committed: journal.operations.map((item) => item.target), backups, matt: mattResults, recoveryReceipt: recovery.receipt };
|
|
407
410
|
} catch (error) { if (mattCandidate) mattAdapter.discardCandidate?.(mattCandidate); const restored = recoverPendingTransaction(plan.intent); return { ok: false, code: error.code === "MATT_USER_MODIFIED" ? error.code : "MATRIX_COMMIT_FAILED", error: error.message, recovery: restored.ok ? "Matrix targets and installation manifest were restored." : restored.recovery }; }
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
package/src/installer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { createDistribution } from "./distribution.js";
|
|
4
|
-
import { MATRIX_SKILLS, MATT_SKILLS, PLATFORMS, sourceSkillsRoot } from "./catalog.js";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createDistribution } from "./distribution.js";
|
|
4
|
+
import { MATRIX_SKILLS, MATT_SKILLS, PLATFORMS, sourceSkillsRoot } from "./catalog.js";
|
|
5
5
|
|
|
6
6
|
export { MATRIX_SKILLS, MATT_SKILLS, sourceSkillsRoot };
|
|
7
7
|
|
|
@@ -35,11 +35,11 @@ export function installMatrix({ destination, mode = "copy", conflict = "error",
|
|
|
35
35
|
return { installed: result.committed ?? MATRIX_SKILLS, skipped: [], existing: [], backupRoot: result.backups?.[0] ? result.backups[0].backup : null };
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export function installMattSkills({ projectRoot, platforms = ["claude-code"], skills = MATT_SKILLS, global = false, timeout = 300_000 }) {
|
|
39
|
-
void projectRoot; void platforms; void skills; void global; void timeout;
|
|
40
|
-
const error = new Error("Direct Matt installation was retired; run matrix init --with-mattpocock so Distribution owns the complete Matrix/Matt transaction.");
|
|
41
|
-
error.code = "MATT_DIRECT_INSTALL_UNSUPPORTED";
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
38
|
+
export function installMattSkills({ projectRoot, platforms = ["claude-code"], skills = MATT_SKILLS, global = false, timeout = 300_000 }) {
|
|
39
|
+
void projectRoot; void platforms; void skills; void global; void timeout;
|
|
40
|
+
const error = new Error("Direct Matt installation was retired; run matrix init --with-mattpocock so Distribution owns the complete Matrix/Matt transaction.");
|
|
41
|
+
error.code = "MATT_DIRECT_INSTALL_UNSUPPORTED";
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
44
|
|
|
45
45
|
export function findPython() { return null; }
|
package/src/matt-catalog.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
|
|
3
3
|
export const MATT_COMPATIBILITY = Object.freeze({
|
|
4
|
-
matrixVersion: "0.1.
|
|
4
|
+
matrixVersion: "0.1.5",
|
|
5
5
|
release: "v1.2.3",
|
|
6
6
|
commit: "6acc160e4e0cd062dbbbd7a1b26ae92855edf07e",
|
|
7
7
|
archive: "https://github.com/mattpocock/skills/archive/6acc160e4e0cd062dbbbd7a1b26ae92855edf07e.tar.gz",
|