takt-marp 0.1.0 → 0.2.1
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/package.json +1 -1
- package/scripts/lib/takt-marp-cli.mjs +68 -2
- package/scripts/takt-marp-validate-slide-workflow-foundation.mjs +68 -0
- package/templates/project/facets/instructions/takt-marp-visual-generate.md +8 -6
- package/templates/project/facets/policies/takt-marp-svg-first-visual.md +4 -2
package/package.json
CHANGED
|
@@ -6,11 +6,16 @@ import path from "node:path";
|
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { initializeProject } from "./takt-marp-project-init.mjs";
|
|
8
8
|
import { packageScriptPath } from "./takt-marp-runtime-context.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
APPROVAL_COMMANDS,
|
|
11
|
+
formatError,
|
|
12
|
+
SlideWorkflowError,
|
|
13
|
+
} from "./takt-marp-slide-workflow.mjs";
|
|
10
14
|
|
|
11
15
|
const WORKFLOW_COMMANDS = ["plan", "compose", "polish", "deliver"];
|
|
12
|
-
const VALID_COMMANDS = ["init", ...WORKFLOW_COMMANDS, "smoke"];
|
|
16
|
+
const VALID_COMMANDS = ["init", ...WORKFLOW_COMMANDS, "approve", "smoke"];
|
|
13
17
|
const RUNNER_SCRIPT = "scripts/takt-marp-run-slide-workflow.mjs";
|
|
18
|
+
const APPROVE_SCRIPT = "scripts/takt-marp-approve-slide-workflow-state.mjs";
|
|
14
19
|
const SMOKE_SCRIPT = "scripts/takt-marp-validate-slide-workflow-smoke.mjs";
|
|
15
20
|
const REQUIRED_PROJECT_DIRS = [".takt/workflows", ".takt/facets"];
|
|
16
21
|
|
|
@@ -24,6 +29,8 @@ function usage() {
|
|
|
24
29
|
" compose <slides/deck> [options] Run the compose workflow for a deck in the current project",
|
|
25
30
|
" polish <slides/deck> [options] Run the polish workflow for a deck in the current project",
|
|
26
31
|
" deliver <slides/deck> [options] Run the deliver workflow for a deck in the current project",
|
|
32
|
+
" approve <slides/deck> <command> --by <name> [--force]",
|
|
33
|
+
" Approve a workflow state (command: plan or compose)",
|
|
27
34
|
" smoke [--provider <name>] Run smoke validation in a temporary project (default provider: mock)",
|
|
28
35
|
"",
|
|
29
36
|
"Workflow options (passed through to the workflow runner unchanged):",
|
|
@@ -171,6 +178,62 @@ async function runSmoke(args) {
|
|
|
171
178
|
return exitCode;
|
|
172
179
|
}
|
|
173
180
|
|
|
181
|
+
function approveUsage() {
|
|
182
|
+
return [
|
|
183
|
+
"Usage: takt-marp approve <slides/deck> <command> --by <name> [--force]",
|
|
184
|
+
"",
|
|
185
|
+
`Commands: ${APPROVAL_COMMANDS.join(", ")}`,
|
|
186
|
+
"",
|
|
187
|
+
"Options:",
|
|
188
|
+
" --by <name> Approver identifier (required)",
|
|
189
|
+
" --force Overwrite an existing approval",
|
|
190
|
+
" --help, -h Show this message",
|
|
191
|
+
].join("\n");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function runApprove(args) {
|
|
195
|
+
let parsed;
|
|
196
|
+
try {
|
|
197
|
+
parsed = parseArgs({
|
|
198
|
+
args,
|
|
199
|
+
allowPositionals: true,
|
|
200
|
+
options: {
|
|
201
|
+
by: { type: "string" },
|
|
202
|
+
force: { type: "boolean", default: false },
|
|
203
|
+
help: { type: "boolean", short: "h", default: false },
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
throw new SlideWorkflowError(
|
|
208
|
+
`Invalid approve arguments: ${error.message}. Run 'takt-marp approve --help' for usage.`,
|
|
209
|
+
"INVALID_ARGS",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (parsed.values.help) {
|
|
213
|
+
console.log(approveUsage());
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
assertProjectInitialized();
|
|
217
|
+
if (parsed.positionals.length !== 2) {
|
|
218
|
+
throw new SlideWorkflowError(
|
|
219
|
+
`Expected <slides/deck> and <command>. Run 'takt-marp approve --help' for usage.`,
|
|
220
|
+
"INVALID_ARGS",
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const [target, command] = parsed.positionals;
|
|
224
|
+
if (!parsed.values.by) {
|
|
225
|
+
throw new SlideWorkflowError(
|
|
226
|
+
`Missing required --by <name>. Run 'takt-marp approve --help' for usage.`,
|
|
227
|
+
"INVALID_ARGS",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const approveArgs = [target, command, "--by", parsed.values.by];
|
|
231
|
+
if (parsed.values.force) {
|
|
232
|
+
approveArgs.push("--force");
|
|
233
|
+
}
|
|
234
|
+
return runPackageScript(APPROVE_SCRIPT, approveArgs);
|
|
235
|
+
}
|
|
236
|
+
|
|
174
237
|
export async function runCli(argv) {
|
|
175
238
|
const [command, ...rest] = argv;
|
|
176
239
|
if (command === undefined || command === "--help" || command === "-h" || command === "help") {
|
|
@@ -188,6 +251,9 @@ export async function runCli(argv) {
|
|
|
188
251
|
if (command === "init") {
|
|
189
252
|
return await runInit(rest);
|
|
190
253
|
}
|
|
254
|
+
if (command === "approve") {
|
|
255
|
+
return await runApprove(rest);
|
|
256
|
+
}
|
|
191
257
|
if (command === "smoke") {
|
|
192
258
|
return await runSmoke(rest);
|
|
193
259
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
supervisionPath,
|
|
21
21
|
writeApproval,
|
|
22
22
|
} from "./lib/takt-marp-slide-workflow.mjs";
|
|
23
|
+
import { runCli } from "./lib/takt-marp-cli.mjs";
|
|
23
24
|
|
|
24
25
|
const checks = [];
|
|
25
26
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -306,6 +307,47 @@ async function main() {
|
|
|
306
307
|
assert(verifyResult.stdout.includes("skipped for non-successful verify report"), `verify verifier did not report skip: ${verifyResult.stdout}`);
|
|
307
308
|
});
|
|
308
309
|
|
|
310
|
+
await check("approve command shows help without initialized project", async () => {
|
|
311
|
+
const root = await fixtureRoot();
|
|
312
|
+
const originalCwd = process.cwd();
|
|
313
|
+
let output;
|
|
314
|
+
try {
|
|
315
|
+
process.chdir(root);
|
|
316
|
+
output = await captureStdout(() => runCli(["approve", "--help"]));
|
|
317
|
+
} finally {
|
|
318
|
+
process.chdir(originalCwd);
|
|
319
|
+
}
|
|
320
|
+
assert(output.includes("Usage: takt-marp approve"), `approve help missing usage: ${output}`);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await check("approve command requires initialized project", async () => {
|
|
324
|
+
const root = await fixtureRoot();
|
|
325
|
+
const originalCwd = process.cwd();
|
|
326
|
+
try {
|
|
327
|
+
process.chdir(root);
|
|
328
|
+
const code = await runCli(["approve", "slides/demo", "plan", "--by", "foundation-test"]);
|
|
329
|
+
assert(code !== 0, "approve unexpectedly succeeded in uninitialized project");
|
|
330
|
+
} finally {
|
|
331
|
+
process.chdir(originalCwd);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
await check("approve command writes approval file", async () => {
|
|
336
|
+
const root = await initializedFixtureRoot();
|
|
337
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
338
|
+
await writeSupervision(targetInfo, "plan", "planned", "passed", "run-plan-1");
|
|
339
|
+
const originalCwd = process.cwd();
|
|
340
|
+
try {
|
|
341
|
+
process.chdir(root);
|
|
342
|
+
const code = await runCli(["approve", targetInfo.target, "plan", "--by", "foundation-test"]);
|
|
343
|
+
assert(code === 0, `approve command failed: check stderr`);
|
|
344
|
+
} finally {
|
|
345
|
+
process.chdir(originalCwd);
|
|
346
|
+
}
|
|
347
|
+
const approval = await readFile(path.join(targetInfo.reviewPath, "plan-approval.md"), "utf8");
|
|
348
|
+
assert(approval.includes("approved_by: foundation-test"), `approval file missing approver: ${approval}`);
|
|
349
|
+
});
|
|
350
|
+
|
|
309
351
|
await check("package scripts expose canonical entrypoints only", async () => {
|
|
310
352
|
const pkg = JSON.parse(await readFile(path.join(process.cwd(), "package.json"), "utf8"));
|
|
311
353
|
const scripts = pkg.scripts ?? {};
|
|
@@ -348,6 +390,32 @@ async function fixtureRoot() {
|
|
|
348
390
|
return root;
|
|
349
391
|
}
|
|
350
392
|
|
|
393
|
+
async function initializedFixtureRoot() {
|
|
394
|
+
const root = await fixtureRoot();
|
|
395
|
+
await mkdir(path.join(root, ".takt", "facets"), { recursive: true });
|
|
396
|
+
return root;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function captureStdout(fn) {
|
|
400
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
401
|
+
const chunks = [];
|
|
402
|
+
process.stdout.write = (chunk, encoding, callback) => {
|
|
403
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString());
|
|
404
|
+
if (typeof encoding === "function") {
|
|
405
|
+
encoding();
|
|
406
|
+
} else if (typeof callback === "function") {
|
|
407
|
+
callback();
|
|
408
|
+
}
|
|
409
|
+
return true;
|
|
410
|
+
};
|
|
411
|
+
try {
|
|
412
|
+
await fn();
|
|
413
|
+
} finally {
|
|
414
|
+
process.stdout.write = originalWrite;
|
|
415
|
+
}
|
|
416
|
+
return chunks.join("");
|
|
417
|
+
}
|
|
418
|
+
|
|
351
419
|
async function makeDeck(root, deckName) {
|
|
352
420
|
const deckPath = path.join(root, "slides", deckName);
|
|
353
421
|
await mkdir(path.join(deckPath, "review"), { recursive: true });
|
|
@@ -9,17 +9,19 @@ planとSLIDES.mdに必要なSVG sourceを生成してください。
|
|
|
9
9
|
- inline SVG: `SLIDES.md` の該当スライドの図版領域(placeholder 部分)だけを編集し、SVG markup を直接記述します。front matter に `html: true` が未設定の場合は追加してください。
|
|
10
10
|
- `Visual: existing: ...`: 明示された既存画像への参照を `SLIDES.md` の該当スライドの placeholder 部分に挿入します。
|
|
11
11
|
3. SVGはSVGファーストVisualポリシーに従ってください(外部ファイル・inline 両形式に適用)。
|
|
12
|
-
4.
|
|
13
|
-
5.
|
|
14
|
-
6. SVG
|
|
15
|
-
7.
|
|
16
|
-
8.
|
|
17
|
-
9.
|
|
12
|
+
4. SVGキャンバスの約90%を図形・矢印・ラベルなどの意味のある要素で使い、余白は約10%以下に抑えてください。`viewBox 0 0 1100 540` では外周の安全域を左右20-30px、上下16-24px程度にし、6:4程度に余白が目立つ構図を作らないでください。
|
|
13
|
+
5. 図形内テキストが図形からはみ出していないか確認してください。長いファイル名や英字ラベルは `tspan` で分割し、左右20px以上の余白を残してください。
|
|
14
|
+
6. Marp配置時にスライド枠からSVGがはみ出しにくいよう、`SLIDES.md` の画像指定は個別サイズではなく `visual`、`visual-dense`、`visual-full` classのCSSで制御してください。inline SVGの場合も同様に、親classのcontainment(`--visual-max-height`系token)でサイズを制御してください。
|
|
15
|
+
7. SVGをもう少し大きくしたい場合は、個別スライドの `![h:...]` を増やすのではなく、class側の `--visual-max-height` を調整してください。render evidence による確認は polish command に委譲してください。
|
|
16
|
+
8. 外部ファイル形式の場合は `SLIDES.md`の参照パスが実在するSVGを指すようにしてください。
|
|
17
|
+
9. 既存画像は明示されたものだけ使ってください。
|
|
18
|
+
10. render output の生成や表示品質の最終判定は行わず、source artifact の作成に集中してください。
|
|
18
19
|
|
|
19
20
|
**必須出力**
|
|
20
21
|
## Visual Result
|
|
21
22
|
- Status: generated / needs_input
|
|
22
23
|
- SVG files: (外部ファイル形式・inline形式の両方を記載)
|
|
24
|
+
- Canvas usage checks:
|
|
23
25
|
- Text fit checks:
|
|
24
26
|
- Source reference checks:
|
|
25
27
|
- Files changed:
|
|
@@ -11,6 +11,7 @@ Marpスライド内の図解は、レビュー可能で修正しやすいSVGを
|
|
|
11
11
|
| SVG-first | 図解、比較、フロー、概念絵は原則SVGで作る |
|
|
12
12
|
| 別ファイル管理(既定) | SVGは原則`images/*.svg`として保存しMarpから参照する。スライド固有の図版は使い分け基準に従いinlineも可 |
|
|
13
13
|
| 1 SVG 1 message | 1枚のSVGに複数の主張を詰め込まない |
|
|
14
|
+
| 9:1 の面積配分 | SVGキャンバスは図版が約9割、余白が約1割になるように使う |
|
|
14
15
|
| 可読性優先 | 900-1080px幅で読める文字量とサイズにする |
|
|
15
16
|
| テキスト収まり優先 | 文字列は必ず図形内の余白に収め、長いラベルは折り返す |
|
|
16
17
|
| 差分レビュー可能 | 意味のある要素名、整った構造、過度なminifyを避ける |
|
|
@@ -20,7 +21,8 @@ Marpスライド内の図解は、レビュー可能で修正しやすいSVGを
|
|
|
20
21
|
- `viewBox` は原則 `0 0 1100 540`。
|
|
21
22
|
- font-family は `'Noto Sans JP','Hiragino Sans','Yu Gothic',sans-serif`(knowledge の日本語優先フォールバックスタックと同順)。
|
|
22
23
|
- 背景は白または透明。
|
|
23
|
-
-
|
|
24
|
+
- SVG全体の面積配分は「図版9:余白1」を目安にし、図形・矢印・ラベルなどの意味のある要素がキャンバスの約90%を使うように配置する。
|
|
25
|
+
- 外周余白は安全域として最小限にし、`viewBox 0 0 1100 540` では左右20-30px、上下16-24pxを目安にする。余白を広く取って図版が6:4程度に小さく見える構図は禁止する。
|
|
24
26
|
- 図形内テキストは左右20px以上、上下12px以上の内側余白を残す。
|
|
25
27
|
- ファイル名、URL、長い英字ラベルは1行で入れず、`tspan`で2行以上に分割する。
|
|
26
28
|
- 箱の幅が160px未満の場合、中央ラベルは短語だけにする。`plan.md` のような短い名前でも幅200px以上を優先する。
|
|
@@ -37,7 +39,7 @@ Marpスライド内の図解は、レビュー可能で修正しやすいSVGを
|
|
|
37
39
|
- 標準classは `visual`、本文が多い場合は `visual-dense`、図だけを大きく見せる場合は `visual-full` を使う。
|
|
38
40
|
- `visual` の目安は `--visual-max-height: 280px` から `300px`。`visual-dense` は `240px` から `260px`、`visual-full` は `360px` から `410px` を目安にする。
|
|
39
41
|
- `![w:900]` 以上の幅指定は、本文がほぼないスライドに限定する。
|
|
40
|
-
- SVG
|
|
42
|
+
- SVG内の重要要素は、端で切れない範囲で外周近くまで広げる。`viewBox 0 0 1100 540` では左右20-30px、上下16-24px程度の安全域を残し、意味のある図版領域を約90%まで拡大する。
|
|
41
43
|
- SVG単体の `viewBox` 内に収まっていても、Marpに配置した結果スライド枠からはみ出す場合は失敗として扱う。
|
|
42
44
|
|
|
43
45
|
## 外部SVGとinline SVGの使い分け基準
|