takt-marp 0.1.0
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/README.ja.md +108 -0
- package/README.md +108 -0
- package/bin/takt-marp.mjs +24 -0
- package/fixtures/marp-slide-workflow/_workflow-smoke/README.md +23 -0
- package/fixtures/marp-slide-workflow/_workflow-smoke/brief.md +44 -0
- package/marp.config.mjs +3 -0
- package/package.json +56 -0
- package/scripts/lib/takt-marp-cli.mjs +199 -0
- package/scripts/lib/takt-marp-project-init.mjs +81 -0
- package/scripts/lib/takt-marp-project-templates.mjs +93 -0
- package/scripts/lib/takt-marp-runtime-context.mjs +24 -0
- package/scripts/lib/takt-marp-slide-workflow.mjs +453 -0
- package/scripts/takt-marp-approve-slide-workflow-state.mjs +37 -0
- package/scripts/takt-marp-build-slide-artifact.mjs +151 -0
- package/scripts/takt-marp-check-slide-workflow-state.mjs +41 -0
- package/scripts/takt-marp-render-slide-workflow-evidence.mjs +70 -0
- package/scripts/takt-marp-run-slide-workflow.mjs +435 -0
- package/scripts/takt-marp-sync-project-templates.mjs +125 -0
- package/scripts/takt-marp-validate-global-install.mjs +391 -0
- package/scripts/takt-marp-validate-package-boundary.mjs +276 -0
- package/scripts/takt-marp-validate-slide-workflow-foundation.mjs +571 -0
- package/scripts/takt-marp-validate-slide-workflow-smoke.mjs +1935 -0
- package/scripts/takt-marp-verify-delivery-artifacts.mjs +181 -0
- package/scripts/takt-marp-verify-render-evidence-metadata.mjs +133 -0
- package/templates/project/facets/instructions/takt-marp-ai-antipattern-fix.md +47 -0
- package/templates/project/facets/instructions/takt-marp-ai-antipattern-review.md +37 -0
- package/templates/project/facets/instructions/takt-marp-compose-fix.md +25 -0
- package/templates/project/facets/instructions/takt-marp-compose-review.md +30 -0
- package/templates/project/facets/instructions/takt-marp-compose-slides.md +35 -0
- package/templates/project/facets/instructions/takt-marp-compose-work-summary.md +23 -0
- package/templates/project/facets/instructions/takt-marp-deliver-build.md +30 -0
- package/templates/project/facets/instructions/takt-marp-deliver-fix.md +25 -0
- package/templates/project/facets/instructions/takt-marp-deliver-verify.md +25 -0
- package/templates/project/facets/instructions/takt-marp-design-system.md +37 -0
- package/templates/project/facets/instructions/takt-marp-intake.md +15 -0
- package/templates/project/facets/instructions/takt-marp-normalize-brief.md +24 -0
- package/templates/project/facets/instructions/takt-marp-plan-fix.md +26 -0
- package/templates/project/facets/instructions/takt-marp-plan-review.md +24 -0
- package/templates/project/facets/instructions/takt-marp-plan-work-summary.md +24 -0
- package/templates/project/facets/instructions/takt-marp-plan.md +26 -0
- package/templates/project/facets/instructions/takt-marp-polish-fix.md +25 -0
- package/templates/project/facets/instructions/takt-marp-polish-inspect.md +25 -0
- package/templates/project/facets/instructions/takt-marp-render-evidence.md +35 -0
- package/templates/project/facets/instructions/takt-marp-supervise-command.md +58 -0
- package/templates/project/facets/instructions/takt-marp-visual-generate.md +26 -0
- package/templates/project/facets/knowledge/takt-marp-repo-conventions.md +119 -0
- package/templates/project/facets/output-contracts/takt-marp-ai-antipattern-fix.md +48 -0
- package/templates/project/facets/output-contracts/takt-marp-ai-antipattern-review.md +43 -0
- package/templates/project/facets/output-contracts/takt-marp-command-fix.md +32 -0
- package/templates/project/facets/output-contracts/takt-marp-command-review.md +32 -0
- package/templates/project/facets/output-contracts/takt-marp-command-work.md +42 -0
- package/templates/project/facets/output-contracts/takt-marp-normalized-brief.md +31 -0
- package/templates/project/facets/output-contracts/takt-marp-slide-plan.md +30 -0
- package/templates/project/facets/output-contracts/takt-marp-supervision.md +45 -0
- package/templates/project/facets/personas/takt-marp-slide-planner.md +24 -0
- package/templates/project/facets/personas/takt-marp-slide-qa.md +23 -0
- package/templates/project/facets/personas/takt-marp-slide-reviewer.md +22 -0
- package/templates/project/facets/personas/takt-marp-slide-reviser.md +22 -0
- package/templates/project/facets/personas/takt-marp-slide-supervisor.md +24 -0
- package/templates/project/facets/personas/takt-marp-slide-writer.md +22 -0
- package/templates/project/facets/policies/takt-marp-general-slide-quality.md +91 -0
- package/templates/project/facets/policies/takt-marp-slide-quality.md +73 -0
- package/templates/project/facets/policies/takt-marp-svg-first-visual.md +66 -0
- package/templates/project/facets/policies/takt-marp-worker-boundary.md +32 -0
- package/templates/project/workflows/takt-marp-slide-ai-quality-gate.yaml +125 -0
- package/templates/project/workflows/takt-marp-slide-compose.yaml +209 -0
- package/templates/project/workflows/takt-marp-slide-deliver.yaml +164 -0
- package/templates/project/workflows/takt-marp-slide-plan.yaml +213 -0
- package/templates/project/workflows/takt-marp-slide-polish.yaml +158 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cp, mkdtemp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import {
|
|
9
|
+
archiveCommandArtifacts,
|
|
10
|
+
assertTaktExecutableAvailable,
|
|
11
|
+
assertWorkflowAvailable,
|
|
12
|
+
checkRequiredState,
|
|
13
|
+
cleanGeneratedOutputs,
|
|
14
|
+
commandSupervisionResult,
|
|
15
|
+
formatError,
|
|
16
|
+
isSuccessfulCommandState,
|
|
17
|
+
parseFrontMatter,
|
|
18
|
+
parseRequiredState,
|
|
19
|
+
resolveDeckTarget,
|
|
20
|
+
supervisionPath,
|
|
21
|
+
writeApproval,
|
|
22
|
+
} from "./lib/takt-marp-slide-workflow.mjs";
|
|
23
|
+
|
|
24
|
+
const checks = [];
|
|
25
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const RUNNER_SCRIPT = path.join(SCRIPT_DIR, "takt-marp-run-slide-workflow.mjs");
|
|
27
|
+
const VERIFY_DELIVERY_SCRIPT = path.join(SCRIPT_DIR, "takt-marp-verify-delivery-artifacts.mjs");
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
await check("front matter parser supports documented subset", async () => {
|
|
31
|
+
const parsed = parseFrontMatter(["---", "status: approved", "count: 1", "enabled: true", "items: [a, \"b\"]", "empty: []", "---", "", "body"].join("\n"));
|
|
32
|
+
assert(parsed.frontMatter.status === "approved", "scalar string parse failed");
|
|
33
|
+
assert(parsed.frontMatter.count === 1, "number parse failed");
|
|
34
|
+
assert(parsed.frontMatter.enabled === true, "boolean parse failed");
|
|
35
|
+
assert(Array.isArray(parsed.frontMatter.items), "inline array parse failed");
|
|
36
|
+
assert(parsed.frontMatter.items[0] === "a", "unquoted inline array item parse failed");
|
|
37
|
+
assert(parsed.frontMatter.items[1] === "b", "quoted inline array item parse failed");
|
|
38
|
+
assert(Array.isArray(parsed.frontMatter.empty), "empty array parse failed");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await check("invalid target rejects markdown file", async () => {
|
|
42
|
+
const root = await fixtureRoot();
|
|
43
|
+
await makeDeck(root, "demo");
|
|
44
|
+
await expectFailure(() => resolveDeckTarget("slides/demo/brief.md", { root }), "INVALID_TARGET");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await check("missing approval fails approved state check", async () => {
|
|
48
|
+
const root = await fixtureRoot();
|
|
49
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
50
|
+
await writeSupervision(targetInfo, "plan", "planned", "passed", "run-plan-1");
|
|
51
|
+
await expectFailure(() => checkRequiredState(targetInfo, parseRequiredState("plan:planned:approved")), "FILE_MISSING");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await check("invalid approval command is rejected", async () => {
|
|
55
|
+
const root = await fixtureRoot();
|
|
56
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
57
|
+
await expectFailure(() => writeApproval(targetInfo, "polish", "j5ik2o"), "APPROVAL_UNSUPPORTED");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await check("missing workflow YAML fails before TAKT", async () => {
|
|
61
|
+
const root = await fixtureRoot();
|
|
62
|
+
await expectFailure(() => assertWorkflowAvailable("compose", { root }), "WORKFLOW_NOT_IMPLEMENTED");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await check("runner checks prerequisites before workflow availability", async () => {
|
|
66
|
+
const root = await fixtureRoot();
|
|
67
|
+
await makeDeck(root, "demo");
|
|
68
|
+
const result = spawnSync(process.execPath, [RUNNER_SCRIPT, "compose", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
69
|
+
assert(result.status !== 0, "runner unexpectedly succeeded");
|
|
70
|
+
assert(result.stderr.includes("FILE_MISSING"), `expected missing approval before workflow availability, got: ${result.stderr}`);
|
|
71
|
+
assert(!result.stderr.includes("WORKFLOW_NOT_IMPLEMENTED"), `workflow availability masked prerequisite error: ${result.stderr}`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await check("missing TAKT executable fails before TAKT", async () => {
|
|
75
|
+
const root = await fixtureRoot();
|
|
76
|
+
await expectFailure(() => assertTaktExecutableAvailable({ root }), "TAKT_EXECUTABLE_MISSING");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await check("missing TAKT executable does not archive or clean force outputs", async () => {
|
|
80
|
+
const root = await fixtureRoot();
|
|
81
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
82
|
+
const fakePackage = await makeFakePackageRoot();
|
|
83
|
+
await writeSupervision(targetInfo, "plan", "planned", "passed", "run-plan-1");
|
|
84
|
+
await mkdir(path.join(root, "dist", "demo"), { recursive: true });
|
|
85
|
+
await writeFile(path.join(root, "dist", "demo", "index.html"), "<html></html>", "utf8");
|
|
86
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo", "--force"], { cwd: root, encoding: "utf8" });
|
|
87
|
+
assert(result.status !== 0, "runner unexpectedly succeeded without TAKT executable");
|
|
88
|
+
assert(result.stderr.includes("TAKT_EXECUTABLE_MISSING"), `expected missing TAKT executable, got: ${result.stderr}`);
|
|
89
|
+
assert(existsSync(supervisionPath(targetInfo, "plan")), "supervision was archived despite missing TAKT executable");
|
|
90
|
+
assert(existsSync(path.join(root, "dist", "demo", "index.html")), "generated output was cleaned despite missing TAKT executable");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await check("successful state is detected for rerun protection", async () => {
|
|
94
|
+
const root = await fixtureRoot();
|
|
95
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
96
|
+
await writeSupervision(targetInfo, "plan", "planned", "passed", "run-plan-1");
|
|
97
|
+
assert(isSuccessfulCommandState(targetInfo, "plan"), "successful supervision was not detected");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await check("successful rerun rejection is formatted without stack trace", async () => {
|
|
101
|
+
const root = await fixtureRoot();
|
|
102
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
103
|
+
const fakePackage = await makeFakePackageRoot();
|
|
104
|
+
await makeTaktExecutable(fakePackage.packageRoot);
|
|
105
|
+
await writeSupervision(targetInfo, "plan", "planned", "passed", "run-plan-1");
|
|
106
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
107
|
+
assert(result.status !== 0, "runner unexpectedly allowed successful rerun");
|
|
108
|
+
assert(result.stderr.includes("RERUN_BLOCKED:"), `expected formatted rerun error, got: ${result.stderr}`);
|
|
109
|
+
assert(!result.stderr.includes("Error:"), `rerun error included a stack trace: ${result.stderr}`);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await check("runner syncs passed TAKT report to deck", async () => {
|
|
113
|
+
const root = await fixtureRoot();
|
|
114
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
115
|
+
const fakePackage = await makeFakePackageRoot();
|
|
116
|
+
await makeTaktExecutable(fakePackage.packageRoot, fakeTaktScript(["run-current"], "passed"));
|
|
117
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
118
|
+
assert(result.status === 0, `runner failed to sync passed report: ${result.stderr}`);
|
|
119
|
+
const synced = await readFile(supervisionPath(targetInfo, "plan"), "utf8");
|
|
120
|
+
assert(synced.includes("workflow_run_id: run-current"), `runner synced wrong report: ${synced}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await check("runner syncs plan source artifacts from TAKT reports to deck", async () => {
|
|
124
|
+
const root = await fixtureRoot();
|
|
125
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
126
|
+
const fakePackage = await makeFakePackageRoot();
|
|
127
|
+
await makeTaktExecutable(fakePackage.packageRoot, fakeTaktScript(["run-current"], "passed"));
|
|
128
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
129
|
+
assert(result.status === 0, `runner failed to sync plan source artifacts: ${result.stderr}`);
|
|
130
|
+
const normalized = await readFile(path.join(targetInfo.deckPath, "brief.normalized.md"), "utf8");
|
|
131
|
+
const plan = await readFile(path.join(targetInfo.deckPath, "plan.md"), "utf8");
|
|
132
|
+
assert(normalized.includes("Mock normalized brief for run-current"), `normalized brief was not synced from reports: ${normalized}`);
|
|
133
|
+
assert(plan.includes("deliverables: [html, pdf]"), `plan deliverables were not synced from reports: ${plan}`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await check("runner syncs AI gate reports to deck", async () => {
|
|
137
|
+
const root = await fixtureRoot();
|
|
138
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
139
|
+
await writeFile(path.join(targetInfo.reviewPath, "plan-ai-antipattern-fix.md"), "stale ai fix\n", "utf8");
|
|
140
|
+
const fakePackage = await makeFakePackageRoot();
|
|
141
|
+
await makeTaktExecutable(fakePackage.packageRoot, fakeTaktScriptWithAiGateReport("run-current", { includeFix: false }));
|
|
142
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
143
|
+
assert(result.status === 0, `runner failed to sync AI gate report: ${result.stderr}`);
|
|
144
|
+
const syncedReview = await readFile(path.join(targetInfo.reviewPath, "plan-ai-antipattern-review.md"), "utf8");
|
|
145
|
+
assert(syncedReview.includes("workflow_run_id: run-current"), `runner synced wrong AI review report: ${syncedReview}`);
|
|
146
|
+
assert(!existsSync(path.join(targetInfo.reviewPath, "plan-ai-antipattern-fix.md")), "runner left stale AI fix report when current run had no fix report");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await check("runner syncs latest AI gate review cycle to deck", async () => {
|
|
150
|
+
const root = await fixtureRoot();
|
|
151
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
152
|
+
const fakePackage = await makeFakePackageRoot();
|
|
153
|
+
await makeTaktExecutable(fakePackage.packageRoot, fakeTaktScriptWithAiGateReport("run-current", { reviewCycles: [1, 2] }));
|
|
154
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
155
|
+
assert(result.status === 0, `runner failed to sync latest AI gate review report: ${result.stderr}`);
|
|
156
|
+
const syncedReview = await readFile(path.join(targetInfo.reviewPath, "plan-ai-antipattern-review.md"), "utf8");
|
|
157
|
+
assert(syncedReview.includes("cycle: 2"), `runner did not sync latest AI review cycle: ${syncedReview}`);
|
|
158
|
+
assert(syncedReview.includes("# AI Antipattern Review cycle 2"), `runner synced wrong AI review body: ${syncedReview}`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await check("runner ignores mismatched AI gate report metadata", async () => {
|
|
162
|
+
const root = await fixtureRoot();
|
|
163
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
164
|
+
await writeFile(path.join(targetInfo.reviewPath, "plan-ai-antipattern-review.md"), "stale ai review\n", "utf8");
|
|
165
|
+
const fakePackage = await makeFakePackageRoot();
|
|
166
|
+
await makeTaktExecutable(fakePackage.packageRoot, fakeTaktScriptWithAiGateReport("run-current", { aiWorkflowRunId: "stale-run" }));
|
|
167
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
168
|
+
assert(result.status === 0, `runner failed with mismatched AI gate metadata: ${result.stderr}`);
|
|
169
|
+
assert(!existsSync(path.join(targetInfo.reviewPath, "plan-ai-antipattern-review.md")), "runner treated mismatched AI gate report as current evidence");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await check("runner ignores non-passed TAKT supervision reports", async () => {
|
|
173
|
+
const root = await fixtureRoot();
|
|
174
|
+
await makeDeck(root, "demo");
|
|
175
|
+
const fakePackage = await makeFakePackageRoot();
|
|
176
|
+
await makeTaktExecutable(fakePackage.packageRoot, fakeTaktScript(["run-rejected"], "rejected"));
|
|
177
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
178
|
+
assert(result.status !== 0, "runner unexpectedly synced a non-passed report");
|
|
179
|
+
assert(result.stderr.includes("TAKT_REPORT_SYNC_MISSING"), `expected sync-missing error, got: ${result.stderr}`);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await check("runner rejects ambiguous matching TAKT report directories", async () => {
|
|
183
|
+
const root = await fixtureRoot();
|
|
184
|
+
await makeDeck(root, "demo");
|
|
185
|
+
const fakePackage = await makeFakePackageRoot();
|
|
186
|
+
await makeTaktExecutable(fakePackage.packageRoot, fakeTaktScript(["run-a", "run-b"], "passed"));
|
|
187
|
+
const result = spawnSync(process.execPath, [fakePackage.runnerScript, "plan", "slides/demo"], { cwd: root, encoding: "utf8" });
|
|
188
|
+
assert(result.status !== 0, "runner unexpectedly synced an ambiguous report");
|
|
189
|
+
assert(result.stderr.includes("TAKT_REPORT_SYNC_AMBIGUOUS"), `expected ambiguous sync error, got: ${result.stderr}`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await check("supervision validator rejects missing finding counts", async () => {
|
|
193
|
+
const root = await fixtureRoot();
|
|
194
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
195
|
+
await writeFile(
|
|
196
|
+
supervisionPath(targetInfo, "plan"),
|
|
197
|
+
[
|
|
198
|
+
"---",
|
|
199
|
+
"command: plan",
|
|
200
|
+
`target: ${targetInfo.target}`,
|
|
201
|
+
"generated_at: 2026-06-05T17:10:00+09:00",
|
|
202
|
+
"workflow_run_id: run-plan-1",
|
|
203
|
+
"step: supervision",
|
|
204
|
+
"cycle: 1",
|
|
205
|
+
"state: planned",
|
|
206
|
+
"result: passed",
|
|
207
|
+
"---",
|
|
208
|
+
"",
|
|
209
|
+
"# Supervision",
|
|
210
|
+
"",
|
|
211
|
+
].join("\n"),
|
|
212
|
+
"utf8",
|
|
213
|
+
);
|
|
214
|
+
assert(!isSuccessfulCommandState(targetInfo, "plan"), "supervision without finding counts must not be successful");
|
|
215
|
+
await expectFailure(() => checkRequiredState(targetInfo, parseRequiredState("plan:planned")), "FIELD_MISSING");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await check("rejected rerun archives command report", async () => {
|
|
219
|
+
const root = await fixtureRoot();
|
|
220
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
221
|
+
await writeSupervision(targetInfo, "plan", "none", "rejected", "run-plan-1");
|
|
222
|
+
assert((await commandSupervisionResult(targetInfo, "plan")) === "rejected", "valid rejected supervision was not detected");
|
|
223
|
+
const moved = await archiveCommandArtifacts(targetInfo, ["plan"], "rejected-rerun");
|
|
224
|
+
assert(moved.length === 1, "expected one archived report");
|
|
225
|
+
assert(!existsSync(supervisionPath(targetInfo, "plan")), "source report still exists after archive");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await check("rejected rerun requires valid canonical supervision", async () => {
|
|
229
|
+
const root = await fixtureRoot();
|
|
230
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
231
|
+
await writeFile(
|
|
232
|
+
supervisionPath(targetInfo, "plan"),
|
|
233
|
+
[
|
|
234
|
+
"---",
|
|
235
|
+
"command: plan",
|
|
236
|
+
`target: ${targetInfo.target}`,
|
|
237
|
+
"generated_at: 2026-06-05T17:10:00+09:00",
|
|
238
|
+
"workflow_run_id: run-plan-1",
|
|
239
|
+
"step: supervision",
|
|
240
|
+
"cycle: 1",
|
|
241
|
+
"state: none",
|
|
242
|
+
"result: rejected",
|
|
243
|
+
"---",
|
|
244
|
+
"",
|
|
245
|
+
"# Supervision",
|
|
246
|
+
"",
|
|
247
|
+
].join("\n"),
|
|
248
|
+
"utf8",
|
|
249
|
+
);
|
|
250
|
+
await expectFailure(() => commandSupervisionResult(targetInfo, "plan"), "FIELD_MISSING");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await check("force invalidation archives downstream approvals and cleans generated outputs", async () => {
|
|
254
|
+
const root = await fixtureRoot();
|
|
255
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
256
|
+
await writeSupervision(targetInfo, "plan", "planned", "passed", "run-plan-1");
|
|
257
|
+
await writeApproval(targetInfo, "plan", "j5ik2o");
|
|
258
|
+
await mkdir(path.join(root, "dist", "demo"), { recursive: true });
|
|
259
|
+
await writeFile(path.join(root, "dist", "demo", "deck.pdf"), "pdf");
|
|
260
|
+
await mkdir(path.join(root, ".takt", "render", "demo"), { recursive: true });
|
|
261
|
+
await writeFile(path.join(root, ".takt", "render", "demo", "metadata.json"), "{}");
|
|
262
|
+
const moved = await archiveCommandArtifacts(targetInfo, ["plan"], "force", { includeApprovals: true });
|
|
263
|
+
await cleanGeneratedOutputs(targetInfo, { root });
|
|
264
|
+
assert(moved.length === 2, "expected supervision and approval archive");
|
|
265
|
+
assert(!existsSync(path.join(root, "dist", "demo")), "dist deck directory was not cleaned");
|
|
266
|
+
assert(!existsSync(path.join(root, ".takt", "render", "demo")), "render directory was not cleaned");
|
|
267
|
+
assert(existsSync(path.join(root, "slides", "demo", "brief.md")), "source brief.md must remain");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
await check("delivery verifier skips artifact checks for non-successful reports", async () => {
|
|
271
|
+
const root = await fixtureRoot();
|
|
272
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
273
|
+
await writeFile(path.join(targetInfo.deckPath, "plan.md"), "deliverables: [html, pdf]\n", "utf8");
|
|
274
|
+
await writeFile(
|
|
275
|
+
path.join(targetInfo.reviewPath, "deliver-work.md"),
|
|
276
|
+
[
|
|
277
|
+
"---",
|
|
278
|
+
"command: deliver",
|
|
279
|
+
`target: ${targetInfo.target}`,
|
|
280
|
+
"step: work",
|
|
281
|
+
"result: needs_input",
|
|
282
|
+
"---",
|
|
283
|
+
"",
|
|
284
|
+
].join("\n"),
|
|
285
|
+
"utf8",
|
|
286
|
+
);
|
|
287
|
+
const workResult = spawnSync(process.execPath, [VERIFY_DELIVERY_SCRIPT, "work", targetInfo.target], { cwd: root, encoding: "utf8" });
|
|
288
|
+
assert(workResult.status === 0, `work verifier should skip artifact checks for needs_input, got: ${workResult.stderr}`);
|
|
289
|
+
assert(workResult.stdout.includes("skipped for non-successful work report"), `work verifier did not report skip: ${workResult.stdout}`);
|
|
290
|
+
|
|
291
|
+
await writeFile(
|
|
292
|
+
path.join(targetInfo.reviewPath, "deliver-verify.md"),
|
|
293
|
+
[
|
|
294
|
+
"---",
|
|
295
|
+
"command: deliver",
|
|
296
|
+
`target: ${targetInfo.target}`,
|
|
297
|
+
"step: verify",
|
|
298
|
+
"result: needs_fix",
|
|
299
|
+
"---",
|
|
300
|
+
"",
|
|
301
|
+
].join("\n"),
|
|
302
|
+
"utf8",
|
|
303
|
+
);
|
|
304
|
+
const verifyResult = spawnSync(process.execPath, [VERIFY_DELIVERY_SCRIPT, "verify", targetInfo.target], { cwd: root, encoding: "utf8" });
|
|
305
|
+
assert(verifyResult.status === 0, `verify verifier should skip artifact checks for needs_fix, got: ${verifyResult.stderr}`);
|
|
306
|
+
assert(verifyResult.stdout.includes("skipped for non-successful verify report"), `verify verifier did not report skip: ${verifyResult.stdout}`);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await check("package scripts expose canonical entrypoints only", async () => {
|
|
310
|
+
const pkg = JSON.parse(await readFile(path.join(process.cwd(), "package.json"), "utf8"));
|
|
311
|
+
const scripts = pkg.scripts ?? {};
|
|
312
|
+
for (const name of ["slide:plan", "slide:compose", "slide:polish", "slide:deliver", "slide:check-state", "slide:approve", "slide:validate-foundation"]) {
|
|
313
|
+
assert(scripts[name], `missing package script ${name}`);
|
|
314
|
+
}
|
|
315
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
316
|
+
if (command.includes("node scripts/")) {
|
|
317
|
+
assert(command.includes("node scripts/takt-marp-"), `package script ${name} uses unprefixed scripts path: ${command}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
for (const oldName of ["slide:draft", "slide:review-revise", "slide:build-qa"]) {
|
|
321
|
+
assert(!scripts[oldName], `old package script remains: ${oldName}`);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const failed = checks.filter((item) => item.status === "FAIL");
|
|
326
|
+
for (const item of checks) {
|
|
327
|
+
console.log(`${item.status}: ${item.name}`);
|
|
328
|
+
if (item.error) console.log(` ${item.error}`);
|
|
329
|
+
}
|
|
330
|
+
if (failed.length > 0) {
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function check(name, fn) {
|
|
336
|
+
try {
|
|
337
|
+
await fn();
|
|
338
|
+
checks.push({ name, status: "PASS" });
|
|
339
|
+
} catch (error) {
|
|
340
|
+
checks.push({ name, status: "FAIL", error: formatError(error) });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function fixtureRoot() {
|
|
345
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "slide-workflow-foundation-"));
|
|
346
|
+
await mkdir(path.join(root, ".takt", "workflows"), { recursive: true });
|
|
347
|
+
await writeFile(path.join(root, ".takt", "workflows", "takt-marp-slide-plan.yaml"), "name: takt-marp-slide-plan\n");
|
|
348
|
+
return root;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function makeDeck(root, deckName) {
|
|
352
|
+
const deckPath = path.join(root, "slides", deckName);
|
|
353
|
+
await mkdir(path.join(deckPath, "review"), { recursive: true });
|
|
354
|
+
await writeFile(path.join(deckPath, "brief.md"), "# Brief\n");
|
|
355
|
+
return resolveDeckTarget(`slides/${deckName}`, { root });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function makeFakePackageRoot() {
|
|
359
|
+
const packageRoot = await mkdtemp(path.join(os.tmpdir(), "slide-workflow-package-"));
|
|
360
|
+
// Copy (not symlink) so ESM realpath resolution derives packageRoot from the fake package, not the repo.
|
|
361
|
+
for (const relative of [
|
|
362
|
+
"takt-marp-run-slide-workflow.mjs",
|
|
363
|
+
path.join("lib", "takt-marp-slide-workflow.mjs"),
|
|
364
|
+
path.join("lib", "takt-marp-runtime-context.mjs"),
|
|
365
|
+
]) {
|
|
366
|
+
const destination = path.join(packageRoot, "scripts", relative);
|
|
367
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
368
|
+
await cp(path.join(SCRIPT_DIR, relative), destination);
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
packageRoot,
|
|
372
|
+
runnerScript: path.join(packageRoot, "scripts", "takt-marp-run-slide-workflow.mjs"),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function makeTaktExecutable(root, script = "#!/bin/sh\nexit 0\n") {
|
|
377
|
+
const executablePath = path.join(root, "node_modules", ".bin", process.platform === "win32" ? "takt.cmd" : "takt");
|
|
378
|
+
await mkdir(path.dirname(executablePath), { recursive: true });
|
|
379
|
+
await writeFile(executablePath, script, { encoding: "utf8", mode: 0o755 });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function fakeTaktScript(runNames, result) {
|
|
383
|
+
const lines = [
|
|
384
|
+
"#!/bin/sh",
|
|
385
|
+
"target=\"\"",
|
|
386
|
+
"while [ \"$#\" -gt 0 ]; do",
|
|
387
|
+
" if [ \"$1\" = \"-t\" ]; then",
|
|
388
|
+
" shift",
|
|
389
|
+
" target=\"$1\"",
|
|
390
|
+
" fi",
|
|
391
|
+
" shift",
|
|
392
|
+
"done",
|
|
393
|
+
];
|
|
394
|
+
for (const runName of runNames) {
|
|
395
|
+
lines.push(
|
|
396
|
+
`mkdir -p ".takt/runs/${runName}/reports"`,
|
|
397
|
+
`cat > ".takt/runs/${runName}/reports/brief.normalized.md" <<EOF`,
|
|
398
|
+
"# Normalized Brief",
|
|
399
|
+
"",
|
|
400
|
+
`Mock normalized brief for ${runName}.`,
|
|
401
|
+
"EOF",
|
|
402
|
+
`cat > ".takt/runs/${runName}/reports/plan.md" <<EOF`,
|
|
403
|
+
"# Slide Plan",
|
|
404
|
+
"",
|
|
405
|
+
"deliverables: [html, pdf]",
|
|
406
|
+
"",
|
|
407
|
+
`Mock plan for ${runName}.`,
|
|
408
|
+
"EOF",
|
|
409
|
+
`cat > ".takt/runs/${runName}/reports/plan-supervision.md" <<EOF`,
|
|
410
|
+
"---",
|
|
411
|
+
"command: plan",
|
|
412
|
+
"target: $target",
|
|
413
|
+
"generated_at: 2026-06-05T17:10:00+09:00",
|
|
414
|
+
`workflow_run_id: ${runName}`,
|
|
415
|
+
"step: supervision",
|
|
416
|
+
"cycle: 1",
|
|
417
|
+
"state: planned",
|
|
418
|
+
`result: ${result}`,
|
|
419
|
+
"blocking_findings: 0",
|
|
420
|
+
"major_findings: 0",
|
|
421
|
+
"minor_findings: 0",
|
|
422
|
+
"info_findings: 0",
|
|
423
|
+
"---",
|
|
424
|
+
"",
|
|
425
|
+
"# Supervision",
|
|
426
|
+
"EOF",
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
lines.push("exit 0", "");
|
|
430
|
+
return lines.join("\n");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function fakeTaktScriptWithAiGateReport(runName, options = {}) {
|
|
434
|
+
const aiCommand = options.aiCommand ?? "plan";
|
|
435
|
+
const aiTarget = options.aiTarget ?? "$target";
|
|
436
|
+
const aiWorkflowRunId = options.aiWorkflowRunId ?? runName;
|
|
437
|
+
const reviewCycles = options.reviewCycles ?? [1];
|
|
438
|
+
const reviewReportLines = reviewCycles.flatMap((cycle) => [
|
|
439
|
+
`mkdir -p ".takt/runs/${runName}/reports/subworkflows/iteration-${cycle + 1}--step-ai_quality_gate_plan--workflow-takt-marp-slide-ai-quality-gate"`,
|
|
440
|
+
`cat > ".takt/runs/${runName}/reports/subworkflows/iteration-${cycle + 1}--step-ai_quality_gate_plan--workflow-takt-marp-slide-ai-quality-gate/ai-antipattern-review.md" <<EOF`,
|
|
441
|
+
"---",
|
|
442
|
+
"command: plan",
|
|
443
|
+
"target: $target",
|
|
444
|
+
"generated_at: 2026-06-05T17:10:00+09:00",
|
|
445
|
+
`workflow_run_id: ${runName}`,
|
|
446
|
+
"step: ai_antipattern_review",
|
|
447
|
+
`cycle: ${cycle}`,
|
|
448
|
+
"reviewed_scope: command-work-report",
|
|
449
|
+
"result: approved",
|
|
450
|
+
"finding_count: 0",
|
|
451
|
+
"blocking_finding_count: 0",
|
|
452
|
+
"---",
|
|
453
|
+
"",
|
|
454
|
+
`# AI Antipattern Review cycle ${cycle}`,
|
|
455
|
+
"EOF",
|
|
456
|
+
]);
|
|
457
|
+
return [
|
|
458
|
+
"#!/bin/sh",
|
|
459
|
+
"target=\"\"",
|
|
460
|
+
"while [ \"$#\" -gt 0 ]; do",
|
|
461
|
+
" if [ \"$1\" = \"-t\" ]; then",
|
|
462
|
+
" shift",
|
|
463
|
+
" target=\"$1\"",
|
|
464
|
+
" fi",
|
|
465
|
+
" shift",
|
|
466
|
+
"done",
|
|
467
|
+
`mkdir -p ".takt/runs/${runName}/reports"`,
|
|
468
|
+
`cat > ".takt/runs/${runName}/reports/brief.normalized.md" <<EOF`,
|
|
469
|
+
"# Normalized Brief",
|
|
470
|
+
"",
|
|
471
|
+
`Mock normalized brief for ${runName}.`,
|
|
472
|
+
"EOF",
|
|
473
|
+
`cat > ".takt/runs/${runName}/reports/plan.md" <<EOF`,
|
|
474
|
+
"# Slide Plan",
|
|
475
|
+
"",
|
|
476
|
+
"deliverables: [html, pdf]",
|
|
477
|
+
"",
|
|
478
|
+
`Mock plan for ${runName}.`,
|
|
479
|
+
"EOF",
|
|
480
|
+
`cat > ".takt/runs/${runName}/reports/plan-supervision.md" <<EOF`,
|
|
481
|
+
"---",
|
|
482
|
+
`command: ${aiCommand}`,
|
|
483
|
+
`target: ${aiTarget}`,
|
|
484
|
+
"generated_at: 2026-06-05T17:10:00+09:00",
|
|
485
|
+
`workflow_run_id: ${aiWorkflowRunId}`,
|
|
486
|
+
"step: supervision",
|
|
487
|
+
"cycle: 1",
|
|
488
|
+
"state: planned",
|
|
489
|
+
"result: passed",
|
|
490
|
+
"blocking_findings: 0",
|
|
491
|
+
"major_findings: 0",
|
|
492
|
+
"minor_findings: 0",
|
|
493
|
+
"info_findings: 0",
|
|
494
|
+
"---",
|
|
495
|
+
"",
|
|
496
|
+
"# Supervision",
|
|
497
|
+
"EOF",
|
|
498
|
+
...reviewReportLines,
|
|
499
|
+
...(options.includeFix
|
|
500
|
+
? [
|
|
501
|
+
`mkdir -p ".takt/runs/${runName}/reports/subworkflows/iteration-2--step-ai_quality_gate_plan--workflow-takt-marp-slide-ai-quality-gate"`,
|
|
502
|
+
`cat > ".takt/runs/${runName}/reports/subworkflows/iteration-2--step-ai_quality_gate_plan--workflow-takt-marp-slide-ai-quality-gate/ai-antipattern-fix.md" <<EOF`,
|
|
503
|
+
"---",
|
|
504
|
+
"command: plan",
|
|
505
|
+
"target: $target",
|
|
506
|
+
"generated_at: 2026-06-05T17:10:00+09:00",
|
|
507
|
+
`workflow_run_id: ${runName}`,
|
|
508
|
+
"step: ai_antipattern_fix",
|
|
509
|
+
"cycle: 1",
|
|
510
|
+
"status: NO_FIX_NEEDED",
|
|
511
|
+
"handled_finding_count: 0",
|
|
512
|
+
"changed_file_count: 0",
|
|
513
|
+
"remaining_context_count: 0",
|
|
514
|
+
"---",
|
|
515
|
+
"",
|
|
516
|
+
"# AI Antipattern Fix",
|
|
517
|
+
"EOF",
|
|
518
|
+
]
|
|
519
|
+
: []),
|
|
520
|
+
"exit 0",
|
|
521
|
+
"",
|
|
522
|
+
].join("\n");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function writeSupervision(targetInfo, command, state, result, workflowRunId) {
|
|
526
|
+
await mkdir(targetInfo.reviewPath, { recursive: true });
|
|
527
|
+
await writeFile(
|
|
528
|
+
supervisionPath(targetInfo, command),
|
|
529
|
+
[
|
|
530
|
+
"---",
|
|
531
|
+
`command: ${command}`,
|
|
532
|
+
`target: ${targetInfo.target}`,
|
|
533
|
+
"generated_at: 2026-06-05T17:10:00+09:00",
|
|
534
|
+
`workflow_run_id: ${workflowRunId}`,
|
|
535
|
+
"step: supervision",
|
|
536
|
+
"cycle: 1",
|
|
537
|
+
`state: ${state}`,
|
|
538
|
+
`result: ${result}`,
|
|
539
|
+
"blocking_findings: 0",
|
|
540
|
+
"major_findings: 0",
|
|
541
|
+
"minor_findings: 0",
|
|
542
|
+
"info_findings: 0",
|
|
543
|
+
"---",
|
|
544
|
+
"",
|
|
545
|
+
"# Supervision",
|
|
546
|
+
"",
|
|
547
|
+
].join("\n"),
|
|
548
|
+
"utf8",
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function expectFailure(fn, code) {
|
|
553
|
+
try {
|
|
554
|
+
await fn();
|
|
555
|
+
} catch (error) {
|
|
556
|
+
if (error.code === code) return;
|
|
557
|
+
throw new Error(`Expected ${code}, got ${error.code ?? error.message}`);
|
|
558
|
+
}
|
|
559
|
+
throw new Error(`Expected failure ${code}`);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function assert(condition, message) {
|
|
563
|
+
if (!condition) {
|
|
564
|
+
throw new Error(message);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
main().catch((error) => {
|
|
569
|
+
console.error(formatError(error));
|
|
570
|
+
process.exit(1);
|
|
571
|
+
});
|