@rosenfan/matrix 0.1.5 → 0.1.6

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/src/ui.js CHANGED
@@ -1,89 +1,97 @@
1
- import readline from "node:readline";
2
- import { stdin, stdout } from "node:process";
3
-
4
- const codes = { cyan: "\u001b[96m", blue: "\u001b[94m", green: "\u001b[92m", yellow: "\u001b[93m", magenta: "\u001b[95m", dim: "\u001b[2m", reset: "\u001b[0m" };
5
- const pause = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
6
- const logo = String.raw`███╗ ███╗ █████╗ ████████╗██████╗ ██╗██╗ ██╗
7
- ████╗ ████║██╔══██╗╚══██╔══╝██╔══██╗██║╚██╗██╔╝
8
- ██╔████╔██║███████║ ██║ ██████╔╝██║ ╚███╔╝
9
- ██║╚██╔╝██║██╔══██║ ██║ ██╔══██╗██║ ██╔██╗
10
- ██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║██║██╔╝ ██╗
11
- ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝`;
12
- const bannerLines = 9;
13
-
14
- function cancelled() {
15
- const error = new Error("Cancelled.");
16
- error.code = "CANCELLED";
17
- return error;
18
- }
19
-
20
- export function createUi({ color = stdout.isTTY, animate = true } = {}) {
21
- const paint = (name, value) => color ? `${codes[name]}${value}${codes.reset}` : value;
22
- const shouldAnimate = animate && color && stdout.isTTY;
23
- const writeFrame = (stage, status, clearPrevious = false) => {
24
- if (clearPrevious) stdout.write(`\u001b[${bannerLines}A`);
25
- stdout.write("\u001b[2K" + paint("green", `[ MATRIX BOOT // ${status} ]`) + "\n");
26
- for (const line of logo.split("\n")) {
27
- const reveal = Math.floor(line.length * stage);
28
- const stable = line.slice(0, reveal);
29
- const interference = stage === 1 ? "" : "#@%/\\|~".repeat(Math.ceil((line.length - reveal) / 7)).slice(0, line.length - reveal);
30
- stdout.write("\u001b[2K" + paint(stage < 0.7 ? "green" : "cyan", stable + interference) + "\n");
31
- }
32
- stdout.write("\u001b[2K" + paint("yellow", ` ${Math.round(stage * 100)}% ${status}`) + "\n\n");
33
- };
34
- return {
35
- async banner() {
36
- if (shouldAnimate) {
37
- const sequence = [
38
- [0.04, "SIGNAL LOST", 260],
39
- [0.28, "MAGNETIC INTERFERENCE", 320],
40
- [0.62, "NEURAL LINK FORMING", 360],
41
- [1, "SIGNAL LOCKED", 420]
42
- ];
43
- stdout.write("\u001b[?25l");
44
- try {
45
- for (let index = 0; index < sequence.length; index += 1) {
46
- const [stage, status, duration] = sequence[index];
47
- writeFrame(stage, status, index > 0);
48
- await pause(duration);
49
- }
50
- } finally {
51
- stdout.write("\u001b[?25h");
52
- }
53
- stdout.write(`\u001b[${bannerLines}A`);
54
- }
55
- console.log(paint("cyan", logo));
56
- console.log(paint("yellow", "Evidence-driven workflow with Prim and Arch orchestration"));
57
- console.log();
58
- },
59
- info(message) { console.log(`${paint("blue", "> ")} ${message}`); },
60
- success(message) { console.log(`${paint("green", "OK")} ${message}`); },
61
- warn(message) { console.log(`${paint("yellow", "!")} ${message}`); },
62
- muted(message) { console.log(paint("dim", message)); },
63
- async select(question, choices, initial = 0) {
64
- if (!stdin.isTTY || !stdout.isTTY) return choices[initial].value;
65
- let selected = initial;
66
- readline.emitKeypressEvents(stdin); const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdout.write(`? ${question}\n`);
67
- const render = (clear = false) => { if (clear) stdout.write(`\u001b[${choices.length}A`); choices.forEach((choice, index) => { stdout.write("\u001b[2K"); stdout.write(`${index === selected ? paint("blue", ">") : " "} ${index === selected ? paint("blue", choice.label) : choice.label}\n`); }); };
68
- render();
69
- return new Promise((resolve, reject) => {
70
- const cleanup = () => { stdin.off("keypress", onKeypress); stdin.setRawMode(Boolean(wasRaw)); stdin.pause(); };
71
- const onKeypress = (_value, key = {}) => { if (key.ctrl && key.name === "c") { cleanup(); reject(cancelled()); } else if (["up", "k"].includes(key.name)) { selected = (selected - 1 + choices.length) % choices.length; render(true); } else if (["down", "j"].includes(key.name)) { selected = (selected + 1) % choices.length; render(true); } else if (key.name === "return") { cleanup(); resolve(choices[selected].value); } };
72
- stdin.on("keypress", onKeypress);
73
- });
74
- },
75
- async selectMany(question, choices) {
76
- if (!stdin.isTTY || !stdout.isTTY) return choices.map((choice) => choice.value);
77
- let selected = 0; const checked = new Set(choices.map((choice) => choice.value));
78
- readline.emitKeypressEvents(stdin); const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdout.write(`? ${question}\n`);
79
- const render = (clear = false) => { if (clear) stdout.write(`\u001b[${choices.length}A`); choices.forEach((choice, index) => { stdout.write("\u001b[2K"); const marker = checked.has(choice.value) ? "[x]" : "[ ]"; stdout.write(`${index === selected ? paint("blue", ">") : " "} ${marker} ${index === selected ? paint("blue", choice.label) : choice.label}\n`); }); };
80
- render();
81
- return new Promise((resolve, reject) => {
82
- const cleanup = () => { stdin.off("keypress", onKeypress); stdin.setRawMode(Boolean(wasRaw)); stdin.pause(); };
83
- const onKeypress = (_value, key = {}) => { if (key.ctrl && key.name === "c") { cleanup(); reject(cancelled()); } else if (["up", "k"].includes(key.name)) { selected = (selected - 1 + choices.length) % choices.length; render(true); } else if (["down", "j"].includes(key.name)) { selected = (selected + 1) % choices.length; render(true); } else if (key.name === "space") { const value = choices[selected].value; checked.has(value) ? checked.delete(value) : checked.add(value); render(true); } else if (key.name === "return") { cleanup(); resolve(choices.filter((choice) => checked.has(choice.value)).map((choice) => choice.value)); } };
84
- stdin.on("keypress", onKeypress);
85
- });
86
- },
87
- async confirm(question, defaultValue = true, labels = { yes: "Yes", no: "No" }) { return this.select(question, [{ label: defaultValue ? labels.yes : labels.no, value: defaultValue }, { label: defaultValue ? labels.no : labels.yes, value: !defaultValue }]); }
88
- };
89
- }
1
+ import readline from "node:readline";
2
+ import { stdin, stdout } from "node:process";
3
+
4
+ const codes = { cyan: "\u001b[96m", blue: "\u001b[94m", green: "\u001b[92m", yellow: "\u001b[93m", magenta: "\u001b[95m", dim: "\u001b[2m", reset: "\u001b[0m" };
5
+ const pause = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
6
+ const logo = String.raw`███╗ ███╗ █████╗ ████████╗██████╗ ██╗██╗ ██╗
7
+ ████╗ ████║██╔══██╗╚══██╔══╝██╔══██╗██║╚██╗██╔╝
8
+ ██╔████╔██║███████║ ██║ ██████╔╝██║ ╚███╔╝
9
+ ██║╚██╔╝██║██╔══██║ ██║ ██╔══██╗██║ ██╔██╗
10
+ ██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║██║██╔╝ ██╗
11
+ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝`;
12
+ const bannerLines = 9;
13
+
14
+ function cancelled() {
15
+ const error = new Error("Cancelled.");
16
+ error.code = "CANCELLED";
17
+ return error;
18
+ }
19
+
20
+ export function createUi({ color = stdout.isTTY, animate = true } = {}) {
21
+ const paint = (name, value) => color ? `${codes[name]}${value}${codes.reset}` : value;
22
+ const shouldAnimate = animate && color && stdout.isTTY;
23
+ const writeFrame = (stage, status, clearPrevious = false) => {
24
+ if (clearPrevious) stdout.write(`\u001b[${bannerLines}A`);
25
+ stdout.write("\u001b[2K" + paint("green", `[ MATRIX BOOT // ${status} ]`) + "\n");
26
+ for (const line of logo.split("\n")) {
27
+ const reveal = Math.floor(line.length * stage);
28
+ const stable = line.slice(0, reveal);
29
+ const interference = stage === 1 ? "" : "#@%/\\|~".repeat(Math.ceil((line.length - reveal) / 7)).slice(0, line.length - reveal);
30
+ stdout.write("\u001b[2K" + paint(stage < 0.7 ? "green" : "cyan", stable + interference) + "\n");
31
+ }
32
+ stdout.write("\u001b[2K" + paint("yellow", ` ${Math.round(stage * 100)}% ${status}`) + "\n\n");
33
+ };
34
+ return {
35
+ async banner() {
36
+ if (shouldAnimate) {
37
+ const sequence = [
38
+ [0.04, "SIGNAL LOST", 260],
39
+ [0.28, "MAGNETIC INTERFERENCE", 320],
40
+ [0.62, "NEURAL LINK FORMING", 360],
41
+ [1, "SIGNAL LOCKED", 420]
42
+ ];
43
+ stdout.write("\u001b[?25l");
44
+ try {
45
+ for (let index = 0; index < sequence.length; index += 1) {
46
+ const [stage, status, duration] = sequence[index];
47
+ writeFrame(stage, status, index > 0);
48
+ await pause(duration);
49
+ }
50
+ } finally {
51
+ stdout.write("\u001b[?25h");
52
+ }
53
+ stdout.write(`\u001b[${bannerLines}A`);
54
+ }
55
+ console.log(paint("cyan", logo));
56
+ console.log(paint("yellow", "Evidence-driven workflow with Prim and Arch orchestration"));
57
+ console.log();
58
+ },
59
+ info(message) { console.log(`${paint("blue", "> ")} ${message}`); },
60
+ success(message) { console.log(`${paint("green", "OK")} ${message}`); },
61
+ warn(message) { console.log(`${paint("yellow", "!")} ${message}`); },
62
+ muted(message) { console.log(paint("dim", message)); },
63
+ async select(question, choices, initial = 0) {
64
+ if (!stdin.isTTY || !stdout.isTTY) return choices[initial].value;
65
+ let selected = initial;
66
+ let renderedLines = 0;
67
+ readline.emitKeypressEvents(stdin); const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdout.write(`? ${question}\n`);
68
+ const render = (clear = false) => {
69
+ if (clear) stdout.write(`\u001b[${renderedLines}A`);
70
+ renderedLines = 0;
71
+ choices.forEach((choice, index) => {
72
+ stdout.write("\u001b[2K"); stdout.write(`${index === selected ? paint("blue", ">") : " "} ${index === selected ? paint("blue", choice.label) : choice.label}\n`); renderedLines += 1;
73
+ if (choice.detail) { stdout.write("\u001b[2K"); stdout.write(` ${paint("dim", choice.detail)}\n`); renderedLines += 1; }
74
+ });
75
+ };
76
+ render();
77
+ return new Promise((resolve, reject) => {
78
+ const cleanup = () => { stdin.off("keypress", onKeypress); stdin.setRawMode(Boolean(wasRaw)); stdin.pause(); };
79
+ const onKeypress = (_value, key = {}) => { if (key.ctrl && key.name === "c") { cleanup(); reject(cancelled()); } else if (["up", "k"].includes(key.name)) { selected = (selected - 1 + choices.length) % choices.length; render(true); } else if (["down", "j"].includes(key.name)) { selected = (selected + 1) % choices.length; render(true); } else if (key.name === "return") { cleanup(); resolve(choices[selected].value); } };
80
+ stdin.on("keypress", onKeypress);
81
+ });
82
+ },
83
+ async selectMany(question, choices) {
84
+ if (!stdin.isTTY || !stdout.isTTY) return choices.map((choice) => choice.value);
85
+ let selected = 0; const checked = new Set(choices.map((choice) => choice.value));
86
+ readline.emitKeypressEvents(stdin); const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdout.write(`? ${question}\n`);
87
+ const render = (clear = false) => { if (clear) stdout.write(`\u001b[${choices.length}A`); choices.forEach((choice, index) => { stdout.write("\u001b[2K"); const marker = checked.has(choice.value) ? "[x]" : "[ ]"; stdout.write(`${index === selected ? paint("blue", ">") : " "} ${marker} ${index === selected ? paint("blue", choice.label) : choice.label}\n`); }); };
88
+ render();
89
+ return new Promise((resolve, reject) => {
90
+ const cleanup = () => { stdin.off("keypress", onKeypress); stdin.setRawMode(Boolean(wasRaw)); stdin.pause(); };
91
+ const onKeypress = (_value, key = {}) => { if (key.ctrl && key.name === "c") { cleanup(); reject(cancelled()); } else if (["up", "k"].includes(key.name)) { selected = (selected - 1 + choices.length) % choices.length; render(true); } else if (["down", "j"].includes(key.name)) { selected = (selected + 1) % choices.length; render(true); } else if (key.name === "space") { const value = choices[selected].value; checked.has(value) ? checked.delete(value) : checked.add(value); render(true); } else if (key.name === "return") { cleanup(); resolve(choices.filter((choice) => checked.has(choice.value)).map((choice) => choice.value)); } };
92
+ stdin.on("keypress", onKeypress);
93
+ });
94
+ },
95
+ async confirm(question, defaultValue = true, labels = { yes: "Yes", no: "No" }) { return this.select(question, [{ label: defaultValue ? labels.yes : labels.no, value: defaultValue }, { label: defaultValue ? labels.no : labels.yes, value: !defaultValue }]); }
96
+ };
97
+ }
package/src/workflow.js CHANGED
@@ -272,8 +272,12 @@ function archIntegrity(cwd, flow = null, contentHashes = MATT_CONTENT_HASHES) {
272
272
  manifest: legacy ? manifestFile : mattReceiptFile, findings, recovery_command: "matrix init --with-mattpocock"
273
273
  } : { ok: true, code: "OK", manifest: legacy ? manifestFile : mattReceiptFile, platforms: platforms.map(([platform]) => platform), cohort: legacy ? "legacy-v0.1.3" : MATT_CATALOG_DIGEST, matt_release: legacy ? null : MATT_COMPATIBILITY.release };
274
274
  }
275
- function taskContent(change, taskId, proposal, design, plan, resultPath, contractHash, approvalRevision) {
276
- return `# Claude Code task: ${taskId}\n\n## Metadata\n\n| Field | Value |\n| --- | --- |\n| Status | CLAUDE_QUEUED |\n| Matrix change | ${change} |\n| Decision owner | Codex |\n| Implementation actor | Claude Code |\n| Approved contract | ${contractHash} |\n| Approval revision | ${approvalRevision} |\n| Result | ${resultPath} |\n\n## Objective\n\nThe frozen Matrix artifacts below are authoritative. Do not expand their scope.\n\n## Proposal\n\n${proposal}\n\n## Frozen design\n\n${design}\n\n## Ordered plan\n\n${plan}\n\n## Allowed work\n\n- Read repository instructions and existing tests before implementing.\n- Change only files required by the frozen design and plan.\n\n## Prohibited work\n\n- Do not edit Matrix state, transition its phase, archive it, or alter the frozen design.\n- Do not add unrelated features, public APIs, dependencies, or external side effects.\n- Do not claim completion without recording verifiable evidence.\n\n## Stop and report\n\nStop with \`NEEDS_DECISION\` if the scope must expand, the design conflicts with the repository, validation cannot pass within scope, or an authorization/safety issue arises.\n\n## Required result\n\nCreate \`${resultPath}\` with metadata, changed files and reasons, commands and outcomes, test results, boundary confirmation, residual risks, and any \`NEEDS_DECISION\` items. Codex reviews the result and the actual diff.\n`;
275
+ function taskContent(change, taskId, proposal, design, plan, resultPath, contractHash, approvalRevision, actor = {}) {
276
+ const agent = actor.agent ?? "Claude Code";
277
+ const owner = actor.owner ?? "Codex";
278
+ const statusToken = actor.statusToken ?? "CLAUDE_QUEUED";
279
+ const reviewer = actor.reviewer ?? "Codex";
280
+ return `# ${agent} task: ${taskId}\n\n## Metadata\n\n| Field | Value |\n| --- | --- |\n| Status | ${statusToken} |\n| Matrix change | ${change} |\n| Decision owner | ${owner} |\n| Implementation actor | ${agent} |\n| Approved contract | ${contractHash} |\n| Approval revision | ${approvalRevision} |\n| Result | ${resultPath} |\n\n## Objective\n\nThe frozen Matrix artifacts below are authoritative. Do not expand their scope.\n\n## Proposal\n\n${proposal}\n\n## Frozen design\n\n${design}\n\n## Ordered plan\n\n${plan}\n\n## Allowed work\n\n- Read repository instructions and existing tests before implementing.\n- Change only files required by the frozen design and plan.\n\n## Prohibited work\n\n- Do not edit Matrix state, transition its phase, archive it, or alter the frozen design.\n- Do not add unrelated features, public APIs, dependencies, or external side effects.\n- Do not claim completion without recording verifiable evidence.\n\n## Stop and report\n\nStop with \`NEEDS_DECISION\` if the scope must expand, the design conflicts with the repository, validation cannot pass within scope, or an authorization/safety issue arises.\n\n## Required result\n\nCreate \`${resultPath}\` with metadata, changed files and reasons, commands and outcomes, test results, boundary confirmation, residual risks, and any \`NEEDS_DECISION\` items. ${reviewer} reviews the result and the actual diff.\n`;
277
281
  }
278
282
  function activeBoard(taskId, taskPath, resultPath, change) {
279
283
  return `# Active task board\n\n## Current objective\n\n| Field | Value |\n| --- | --- |\n| ID | ${taskId} |\n| Status | CLAUDE_QUEUED |\n| Matrix change | ${change} |\n| Task | ${taskPath} |\n| Result | ${resultPath} |\n`;
@@ -1030,10 +1034,29 @@ export function invoke(argv, { cwd = process.cwd(), failAfterOperation = null, m
1030
1034
  if (workflowProfile(p, flow).id === "lightweight") return fail("EXPORT_UNSUPPORTED_WORKFLOW", "Claude export requires the full three-artifact workflow contract.");
1031
1035
  const contract = contractState(p, flow); const failure = contractFailure(flow, contract); if (failure) return failure;
1032
1036
  const taskId = args.includes("--task-id") ? args[args.indexOf("--task-id") + 1] : args[0];
1037
+ const agent = args.includes("--agent") ? args[args.indexOf("--agent") + 1] : null;
1038
+ const from = args.includes("--from") ? args[args.indexOf("--from") + 1] : null;
1039
+ if (agent && args.includes("--target")) return fail("INVALID_INTENT", "export --agent and --target are mutually exclusive.");
1040
+ if (agent && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agent)) return fail("INVALID_INTENT", "export --agent requires a kebab-case agent identifier.");
1041
+ if (from && !agent) return fail("INVALID_INTENT", "export --from requires --agent.");
1042
+ if (from && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(from)) return fail("INVALID_INTENT", "export --from requires a kebab-case agent identifier.");
1043
+ if (agent && (!args.includes("--task-id") || !/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(taskId ?? ""))) return fail("INVALID_INTENT", "export --agent requires --task-id.");
1033
1044
  const target = args.includes("--target") ? args[args.indexOf("--target") + 1] : "generic";
1034
1045
  const applyBoard = args.includes("--apply-fnsec-board");
1035
1046
  if (!/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(taskId ?? "") || !["generic", "fnsec"].includes(target)) return fail("INVALID_INTENT", "export requires a task id and target generic or fnsec.");
1036
1047
  const { proposal, design, plan } = contract.snapshot.contents;
1048
+ if (agent) {
1049
+ const destination = path.join(p.artifacts, `handoff-task-${taskId}.md`);
1050
+ const result = path.join(p.artifacts, `handoff-result-${taskId}.md`);
1051
+ const content = taskContent(id, taskId, proposal, design, plan, path.relative(cwd, result).replaceAll("\\", "/"), flow.approved_contract_hash, flow.contract_approved_revision, {
1052
+ agent,
1053
+ owner: from ?? "Matrix change owner",
1054
+ statusToken: `${agent.replaceAll("-", "_").toUpperCase()}_QUEUED`,
1055
+ reviewer: from ?? "Matrix change owner"
1056
+ });
1057
+ fs.writeFileSync(destination, content);
1058
+ return { ok: true, code: "OK", path: destination };
1059
+ }
1037
1060
  if (target === "generic") {
1038
1061
  const destination = path.join(p.artifacts, "claude-task.md"); const result = path.join(p.artifacts, "claude-result.md");
1039
1062
  fs.writeFileSync(destination, taskContent(id, taskId, proposal, design, plan, path.relative(cwd, result).replaceAll("\\", "/"), flow.approved_contract_hash, flow.contract_approved_revision));
@@ -1,98 +0,0 @@
1
- ---
2
- name: matrix-claude
3
- description: Export a frozen Matrix design as a bounded Claude Code task package without changing Matrix workflow state. Use when Codex should design and Claude Code should implement.
4
- ---
5
-
6
- # Matrix Claude Handoff
7
-
8
- This is an optional Matrix sidecar, not a Matrix phase. It must not call `transition`, `archive`, or write `matrix.yaml` or `events.jsonl`. It supports only a matching full or legacy-full three-artifact Contract; new lightweight hotfix/tweak changes are intentionally unsupported.
9
-
10
- Use `artifact_language` from inspect for human-readable handoff text while retaining the stable machine-readable headings and metadata keys.
11
-
12
- ## When to Use
13
-
14
- Use `$matrix-claude` when:
15
- - The active change is in Build and the Design -> Build transition has approved the contract
16
- - You want Claude Code (instead of Codex) to implement the design
17
- - You need a bounded task package with all implementation details
18
-
19
- ## Preconditions
20
-
21
- The exporter performs this read-only check before writing anything:
22
-
23
- ```powershell
24
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs guard build
25
- ```
26
-
27
- If `matrix` is genuinely absent from PATH, use the bundled runtime from the installed root Matrix Skill. Do not fall back when the primary command starts and returns an error.
28
-
29
- If the build guard reports `CONTRACT_CHANGED` or `CONTRACT_UNAPPROVED`, return to `$matrix-design` and approve a new contract through Build. The exporter reads the approved bytes of `proposal.md`, `design.md`, and `plan.md`; it does not invent missing facts.
30
-
31
- ## Export
32
-
33
- Treat explicit invocation of `$matrix-claude` as the user's choice of Claude Code as the implementation actor. Generate the package with the deterministic exporter; it refuses a non-matching approved contract and does not alter Matrix state.
34
-
35
- ```powershell
36
- # Generic project: only exports a Matrix artifact.
37
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs export --task-id <ID> --target generic
38
-
39
- # FnSec: exports its formal task package. Add --apply-fnsec-board only when
40
- # active.md is NO_ACTIVE_OBJECTIVE; an existing task is never overwritten.
41
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs export --task-id <ID> --target fnsec --apply-fnsec-board
42
- ```
43
-
44
- ## Task Package Contents
45
-
46
- The exported package (`artifacts/claude-task.md`) contains everything Claude Code needs to implement and produce evidence for `$matrix-verify`:
47
-
48
- 1. **Task ID, execution actor, and approved contract hash**: Claude Code and the exact approved snapshot
49
- 2. **Objective and acceptance criteria** — copied from `proposal.md`
50
- 3. **In-scope and out-of-scope boundaries** — from `proposal.md`
51
- 4. **Frozen design decisions** — from `design.md` (Decisions, Boundaries, Test seams)
52
- 5. **Ordered implementation plan** — from `plan.md` (Steps, Validation)
53
- 6. **Allowed and prohibited files/actions** — to prevent scope creep
54
- 7. **Required commands** — test, lint, typecheck, build commands to run
55
- 8. **Stop conditions** — ambiguity, scope expansion, failing acceptance, design conflict
56
- 9. **Required evidence for verify**:
57
- - Changed files list
58
- - Commands and their outputs
59
- - Test results (pass/fail)
60
- - Build evidence
61
- - Residual risks
62
- - `NEEDS_DECISION` items (if any)
63
-
64
- ## After Claude Code Completes
65
-
66
- When Claude Code returns results, verify that the following evidence is present before proceeding to `$matrix-verify`:
67
-
68
- - [ ] `artifacts/verification.md` exists with `## Build evidence` section
69
- - [ ] Build evidence includes concrete commands and outcomes
70
- - [ ] All tests in the plan's validation section have been run
71
- - [ ] Any `NEEDS_DECISION` items have been resolved or documented
72
-
73
- If evidence is missing, ask Claude Code to补充 before proceeding.
74
-
75
- ## Resuming Matrix Workflow
76
-
77
- After Claude Code completes and evidence is present:
78
-
79
- 1. Run build guard to verify evidence:
80
- ```powershell
81
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs guard build
82
- ```
83
-
84
- 2. If guard passes, transition to verify:
85
- ```powershell
86
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs transition verify
87
- ```
88
-
89
- 3. Enter `$matrix-verify` to继续验证流程
90
-
91
- If the build guard fails, return to Claude Code to补充 missing evidence.
92
-
93
- ## Important Notes
94
-
95
- - This sidecar does NOT change Matrix phase, workflow, or guard result
96
- - The Matrix state remains in `build` phase; export itself does not advance it
97
- - Claude Code must produce evidence that satisfies the build guard conditions
98
- - After successful transition to verify, the normal verify流程 continues
@@ -1,33 +0,0 @@
1
- ---
2
- name: matrix-claude
3
- description: 将已批准的 Matrix Contract 导出为受边界约束的 Claude Code 任务包,不改变 Matrix 工作流状态。
4
- ---
5
-
6
- # Matrix Claude Handoff
7
-
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
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs 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
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs export --task-id <ID> --target generic
23
-
24
- # FnSec:仅在 active.md 含有 NO_ACTIVE_OBJECTIVE 时更新看板
25
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs 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
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs guard build
32
- node <matrix-skill-directory>/scripts/matrix-runtime.mjs transition verify
33
- ```