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.
Files changed (69) hide show
  1. package/README.ja.md +108 -0
  2. package/README.md +108 -0
  3. package/bin/takt-marp.mjs +24 -0
  4. package/fixtures/marp-slide-workflow/_workflow-smoke/README.md +23 -0
  5. package/fixtures/marp-slide-workflow/_workflow-smoke/brief.md +44 -0
  6. package/marp.config.mjs +3 -0
  7. package/package.json +56 -0
  8. package/scripts/lib/takt-marp-cli.mjs +199 -0
  9. package/scripts/lib/takt-marp-project-init.mjs +81 -0
  10. package/scripts/lib/takt-marp-project-templates.mjs +93 -0
  11. package/scripts/lib/takt-marp-runtime-context.mjs +24 -0
  12. package/scripts/lib/takt-marp-slide-workflow.mjs +453 -0
  13. package/scripts/takt-marp-approve-slide-workflow-state.mjs +37 -0
  14. package/scripts/takt-marp-build-slide-artifact.mjs +151 -0
  15. package/scripts/takt-marp-check-slide-workflow-state.mjs +41 -0
  16. package/scripts/takt-marp-render-slide-workflow-evidence.mjs +70 -0
  17. package/scripts/takt-marp-run-slide-workflow.mjs +435 -0
  18. package/scripts/takt-marp-sync-project-templates.mjs +125 -0
  19. package/scripts/takt-marp-validate-global-install.mjs +391 -0
  20. package/scripts/takt-marp-validate-package-boundary.mjs +276 -0
  21. package/scripts/takt-marp-validate-slide-workflow-foundation.mjs +571 -0
  22. package/scripts/takt-marp-validate-slide-workflow-smoke.mjs +1935 -0
  23. package/scripts/takt-marp-verify-delivery-artifacts.mjs +181 -0
  24. package/scripts/takt-marp-verify-render-evidence-metadata.mjs +133 -0
  25. package/templates/project/facets/instructions/takt-marp-ai-antipattern-fix.md +47 -0
  26. package/templates/project/facets/instructions/takt-marp-ai-antipattern-review.md +37 -0
  27. package/templates/project/facets/instructions/takt-marp-compose-fix.md +25 -0
  28. package/templates/project/facets/instructions/takt-marp-compose-review.md +30 -0
  29. package/templates/project/facets/instructions/takt-marp-compose-slides.md +35 -0
  30. package/templates/project/facets/instructions/takt-marp-compose-work-summary.md +23 -0
  31. package/templates/project/facets/instructions/takt-marp-deliver-build.md +30 -0
  32. package/templates/project/facets/instructions/takt-marp-deliver-fix.md +25 -0
  33. package/templates/project/facets/instructions/takt-marp-deliver-verify.md +25 -0
  34. package/templates/project/facets/instructions/takt-marp-design-system.md +37 -0
  35. package/templates/project/facets/instructions/takt-marp-intake.md +15 -0
  36. package/templates/project/facets/instructions/takt-marp-normalize-brief.md +24 -0
  37. package/templates/project/facets/instructions/takt-marp-plan-fix.md +26 -0
  38. package/templates/project/facets/instructions/takt-marp-plan-review.md +24 -0
  39. package/templates/project/facets/instructions/takt-marp-plan-work-summary.md +24 -0
  40. package/templates/project/facets/instructions/takt-marp-plan.md +26 -0
  41. package/templates/project/facets/instructions/takt-marp-polish-fix.md +25 -0
  42. package/templates/project/facets/instructions/takt-marp-polish-inspect.md +25 -0
  43. package/templates/project/facets/instructions/takt-marp-render-evidence.md +35 -0
  44. package/templates/project/facets/instructions/takt-marp-supervise-command.md +58 -0
  45. package/templates/project/facets/instructions/takt-marp-visual-generate.md +26 -0
  46. package/templates/project/facets/knowledge/takt-marp-repo-conventions.md +119 -0
  47. package/templates/project/facets/output-contracts/takt-marp-ai-antipattern-fix.md +48 -0
  48. package/templates/project/facets/output-contracts/takt-marp-ai-antipattern-review.md +43 -0
  49. package/templates/project/facets/output-contracts/takt-marp-command-fix.md +32 -0
  50. package/templates/project/facets/output-contracts/takt-marp-command-review.md +32 -0
  51. package/templates/project/facets/output-contracts/takt-marp-command-work.md +42 -0
  52. package/templates/project/facets/output-contracts/takt-marp-normalized-brief.md +31 -0
  53. package/templates/project/facets/output-contracts/takt-marp-slide-plan.md +30 -0
  54. package/templates/project/facets/output-contracts/takt-marp-supervision.md +45 -0
  55. package/templates/project/facets/personas/takt-marp-slide-planner.md +24 -0
  56. package/templates/project/facets/personas/takt-marp-slide-qa.md +23 -0
  57. package/templates/project/facets/personas/takt-marp-slide-reviewer.md +22 -0
  58. package/templates/project/facets/personas/takt-marp-slide-reviser.md +22 -0
  59. package/templates/project/facets/personas/takt-marp-slide-supervisor.md +24 -0
  60. package/templates/project/facets/personas/takt-marp-slide-writer.md +22 -0
  61. package/templates/project/facets/policies/takt-marp-general-slide-quality.md +91 -0
  62. package/templates/project/facets/policies/takt-marp-slide-quality.md +73 -0
  63. package/templates/project/facets/policies/takt-marp-svg-first-visual.md +66 -0
  64. package/templates/project/facets/policies/takt-marp-worker-boundary.md +32 -0
  65. package/templates/project/workflows/takt-marp-slide-ai-quality-gate.yaml +125 -0
  66. package/templates/project/workflows/takt-marp-slide-compose.yaml +209 -0
  67. package/templates/project/workflows/takt-marp-slide-deliver.yaml +164 -0
  68. package/templates/project/workflows/takt-marp-slide-plan.yaml +213 -0
  69. package/templates/project/workflows/takt-marp-slide-polish.yaml +158 -0
package/README.ja.md ADDED
@@ -0,0 +1,108 @@
1
+ # takt-marp
2
+
3
+ [English](README.md)
4
+
5
+ Marpスライドデッキと、半自動でデッキを生成するためのTAKT workflowを管理するリポジトリです。
6
+
7
+ ## TAKT Marp workflow
8
+
9
+ このworkflowは `slides/<deck>/brief.md` を起点に、`plan`、`compose`、`polish`、`deliver` の状態へ進めます。
10
+
11
+ 詳細なworkflow contract: [docs/marp-slide-workflow.md](docs/marp-slide-workflow.md)
12
+
13
+ ### 1. briefを作成する
14
+
15
+ `slides/<deck>/brief.md` を作成します。コマンドに渡すtargetは `brief.md` ではなく、deck directory の `slides/<deck>` です。
16
+
17
+ 最小構成:
18
+
19
+ - `Goal`
20
+ - `Core Message`
21
+ - `Audience Context`
22
+ - `Output Requirements`
23
+
24
+ Output Requirementsの例:
25
+
26
+ ```md
27
+ ## Output Requirements
28
+ - Format: Marp
29
+ - Language: Japanese
30
+ - Target slide count: 5
31
+ - Deliverables: html, pdf
32
+ ```
33
+
34
+ ### 2. workflowを実行する
35
+
36
+ ```bash
37
+ npm run slide:plan -- "slides/<deck>"
38
+ npm run slide:approve -- "slides/<deck>" plan --by <name>
39
+ npm run slide:compose -- "slides/<deck>"
40
+ npm run slide:approve -- "slides/<deck>" compose --by <name>
41
+ npm run slide:polish -- "slides/<deck>"
42
+ npm run slide:deliver -- "slides/<deck>"
43
+ ```
44
+
45
+ targetは `slides/<deck>` を指定します。
46
+
47
+ ```bash
48
+ npm run slide:plan -- "slides/<deck>"
49
+ ```
50
+
51
+ 人間承認は `plan` と `compose` に対してのみ `slide:approve` で記録します。`review`、`revise`、`qa`、`build-qa` はworkflow内部の責務であり、トップレベルコマンドではありません。
52
+
53
+ ### 3. 生成されるファイル
54
+
55
+ ```text
56
+ slides/<deck>/
57
+ brief.normalized.md
58
+ plan.md
59
+ design-system.md
60
+ SLIDES.md
61
+ images/*.svg
62
+ review/*.md
63
+ ```
64
+
65
+ `design-system.md` はデッキ単位のtypography、spacing、layout、visual、color、QA tokenを定義します。`SLIDES.md` はスライドごとの個別style調整ではなく、Marp class経由でそれらのtokenを使う前提です。
66
+
67
+ ### 4. polishとdeliverの範囲
68
+
69
+ `polish` は見た目の検査と修正loopを担当します。
70
+
71
+ - SVG参照とXML妥当性
72
+ - スライド枠への収まり、文字の収まり、図の大きさ、ページ番号との干渉
73
+ - layout選択と段組比率
74
+ - typography consistency: 文字間、行間、サイズ階層
75
+ - spatial balance: 上寄り、左寄り、大きな意図しない余白、視覚重心
76
+ - design-system usage: token化されたCSS、スライドごとのstyle drift防止
77
+
78
+ `deliver` は要求された成果物を生成します。PDF生成は対象deckの `SLIDES.md` だけをbuildします。
79
+
80
+ ```bash
81
+ npm run build:pdf -- <deck>
82
+ ```
83
+
84
+ ### 5. 検証
85
+
86
+ 軽量なローカル確認には foundation validation を使います。
87
+
88
+ ```bash
89
+ npm test
90
+ ```
91
+
92
+ workflow routing、state gate、render evidence、delivery verification、approval handling を変更した場合は smoke validation を実行します。
93
+
94
+ ```bash
95
+ npm run slide:smoke -- --keep
96
+ ```
97
+
98
+ smoke validation は fixture から一時的な `_workflow-smoke` deck を作成し、invalid target、approval failure path、`plan` -> `compose` -> `polish` -> `deliver` の一連の実行、render evidence metadata、delivery artifact、rerun/force behavior を検証します。`--keep` を付けると、生成された deck と report を `slides/_workflow-smoke/` に残して確認できます。
99
+
100
+ ### Smoke fixture
101
+
102
+ 小さな入力fixtureがあります。
103
+
104
+ ```text
105
+ fixtures/marp-slide-workflow/_workflow-smoke/
106
+ ```
107
+
108
+ 新しいbriefを作らずにworkflow全体を試したい場合は、`slides/` 配下へコピーして使います。
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # takt-marp
2
+
3
+ [日本語](README.ja.md)
4
+
5
+ Marp slide decks and a TAKT workflow for semi-automated deck generation.
6
+
7
+ ## TAKT Marp workflow
8
+
9
+ The workflow starts from `slides/<deck>/brief.md` and moves through `plan`, `compose`, `polish`, and `deliver`.
10
+
11
+ Detailed workflow contract: [docs/marp-slide-workflow.md](docs/marp-slide-workflow.md)
12
+
13
+ ### 1. Create a brief
14
+
15
+ Create `slides/<deck>/brief.md`. Commands take the deck directory `slides/<deck>`, not the `brief.md` file path.
16
+
17
+ Minimum sections:
18
+
19
+ - `Goal`
20
+ - `Core Message`
21
+ - `Audience Context`
22
+ - `Output Requirements`
23
+
24
+ Example output requirement:
25
+
26
+ ```md
27
+ ## Output Requirements
28
+ - Format: Marp
29
+ - Language: Japanese
30
+ - Target slide count: 5
31
+ - Deliverables: html, pdf
32
+ ```
33
+
34
+ ### 2. Run the workflows
35
+
36
+ ```bash
37
+ npm run slide:plan -- "slides/<deck>"
38
+ npm run slide:approve -- "slides/<deck>" plan --by <name>
39
+ npm run slide:compose -- "slides/<deck>"
40
+ npm run slide:approve -- "slides/<deck>" compose --by <name>
41
+ npm run slide:polish -- "slides/<deck>"
42
+ npm run slide:deliver -- "slides/<deck>"
43
+ ```
44
+
45
+ Use `slides/<deck>` as the target:
46
+
47
+ ```bash
48
+ npm run slide:plan -- "slides/<deck>"
49
+ ```
50
+
51
+ Human approval is recorded by `slide:approve` for `plan` and `compose` only. `review`, `revise`, `qa`, and `build-qa` are internal workflow responsibilities, not top-level commands.
52
+
53
+ ### 3. Generated files
54
+
55
+ ```text
56
+ slides/<deck>/
57
+ brief.normalized.md
58
+ plan.md
59
+ design-system.md
60
+ SLIDES.md
61
+ images/*.svg
62
+ review/*.md
63
+ ```
64
+
65
+ `design-system.md` defines deck-local typography, spacing, layout, visual, color, and QA tokens. `SLIDES.md` should use those tokens through Marp classes instead of per-slide style tweaks.
66
+
67
+ ### 4. Polish and delivery scope
68
+
69
+ `polish` is responsible for visual inspection and repair loops:
70
+
71
+ - SVG references and XML validity
72
+ - slide frame fit, text fit, figure size, page number interference
73
+ - layout choice and split ratios
74
+ - typography consistency: letter spacing, line height, size hierarchy
75
+ - spatial balance: top/left bias, large unintended blank areas, visual center of gravity
76
+ - design-system usage: tokenized CSS, no per-slide style drift
77
+
78
+ `deliver` is responsible for requested artifacts. PDF generation builds only the target deck's `SLIDES.md`:
79
+
80
+ ```bash
81
+ npm run build:pdf -- <deck>
82
+ ```
83
+
84
+ ### 5. Validation
85
+
86
+ Use the foundation validation for fast local checks:
87
+
88
+ ```bash
89
+ npm test
90
+ ```
91
+
92
+ Use the smoke validation when changing workflow routing, state gates, render evidence, delivery verification, or approval handling:
93
+
94
+ ```bash
95
+ npm run slide:smoke -- --keep
96
+ ```
97
+
98
+ The smoke validation creates a temporary `_workflow-smoke` deck from the fixture, exercises invalid target and approval failure paths, runs the `plan` -> `compose` -> `polish` -> `deliver` sequence, verifies render evidence metadata, checks delivery artifacts, and covers rerun/force behavior. `--keep` leaves the generated deck and reports under `slides/_workflow-smoke/` for inspection.
99
+
100
+ ### Smoke fixture
101
+
102
+ A small input fixture is available at:
103
+
104
+ ```text
105
+ fixtures/marp-slide-workflow/_workflow-smoke/
106
+ ```
107
+
108
+ Copy it under `slides/` when you want to run the full workflow without creating a new brief.
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ // bin entry: Node version guard, then dispatcher startup. No other logic lives here.
3
+ // Keep the syntax in this file conservative so the guard is reached (instead of a
4
+ // syntax error) on unsupported Node versions; the dispatcher is loaded dynamically
5
+ // only after the guard passes.
6
+ var nodeMajor = Number(process.versions.node.split(".")[0]);
7
+ if (!(nodeMajor >= 24)) {
8
+ process.stderr.write(
9
+ "NODE_VERSION_UNSUPPORTED: takt-marp requires Node.js >= 24 (current: v" + process.versions.node + ")\n",
10
+ );
11
+ process.exit(1);
12
+ }
13
+
14
+ import("../scripts/lib/takt-marp-cli.mjs")
15
+ .then(function (cli) {
16
+ return cli.runCli(process.argv.slice(2));
17
+ })
18
+ .then(function (exitCode) {
19
+ process.exitCode = exitCode;
20
+ })
21
+ .catch(function (error) {
22
+ process.stderr.write(String((error && error.stack) || error) + "\n");
23
+ process.exitCode = 1;
24
+ });
@@ -0,0 +1,23 @@
1
+ # Workflow Smoke Deck
2
+
3
+ このfixtureは Marp slide TAKT workflow の実行確認用である。
4
+ 通常の `npm run build` が `slides/` 配下のMarkdownをすべて変換するため、fixtureは `slides/` 外に置く。
5
+
6
+ ## 実行手順
7
+
8
+ 実行確認時は、fixture の source artifact を smoke deck directory にコピーして使う。
9
+ コマンド target は deck directory の `slides/<deck>` であり、`brief.md` file path ではない。
10
+
11
+ ```bash
12
+ mkdir -p slides/_workflow-smoke
13
+ cp fixtures/marp-slide-workflow/_workflow-smoke/brief.md slides/_workflow-smoke/brief.md
14
+ npm run slide:plan -- "slides/_workflow-smoke"
15
+ npm run slide:approve -- "slides/_workflow-smoke" plan --by <name>
16
+ npm run slide:compose -- "slides/_workflow-smoke"
17
+ npm run slide:approve -- "slides/_workflow-smoke" compose --by <name>
18
+ npm run slide:polish -- "slides/_workflow-smoke"
19
+ npm run slide:deliver -- "slides/_workflow-smoke"
20
+ ```
21
+
22
+ `slides/_workflow-smoke/brief.md` は fixture から再現する source artifact である。
23
+ workflow が生成する `slides/_workflow-smoke/review/` や `.takt/render/_workflow-smoke/` は generated evidence であり、`dist/_workflow-smoke/` は delivery artifact である。
@@ -0,0 +1,44 @@
1
+ # Brief
2
+
3
+ ## Event
4
+ - Name: Workflow smoke test
5
+ - Date:
6
+ - Duration: 5 minutes
7
+ - Venue: local
8
+ - Audience: maintainer
9
+
10
+ ## Goal
11
+ Marp slide TAKT workflow が brief から plan、draft、SVG、review、build QA まで接続できることを確認する。
12
+
13
+ ## Core Message
14
+ 半自動のMarp生成workflowは、入力、構成、visual、レビュー、QAを分離すると安定する。
15
+
16
+ ## Audience Context
17
+ 聴衆はこのリポジトリのメンテナであり、TAKTとMarpの基本を理解している。
18
+
19
+ ## Required Topics
20
+ - briefを入力の正にする
21
+ - SVG-firstで図を管理する
22
+ - plan後とdraft後に人間確認を挟む
23
+ - build QAまでworkflowに含める
24
+
25
+ ## Optional Topics
26
+ - appendixは必要な場合だけ作る
27
+
28
+ ## Avoid
29
+ - Web画像の自動取得
30
+ - 他deck画像の自動流用
31
+ - 長い本文をスライドに詰め込むこと
32
+
33
+ ## Source Materials
34
+ - docs/marp-slide-workflow.md
35
+
36
+ ## Speaker Notes
37
+ 短い確認用deckなので、各スライドのnotesは2-3文でよい。
38
+
39
+ ## Output Requirements
40
+ - Format: Marp
41
+ - Language: Japanese
42
+ - Target slide count: 5
43
+ - Deliverables: html, pdf
44
+ - Visual scope: use exactly one SVG for the workflow overview slide. For paired discipline slides, use text-only split columns with Visual: none; do not plan unused SVG placeholders.
@@ -0,0 +1,3 @@
1
+ export default {
2
+ allowLocalFiles: true,
3
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "takt-marp",
3
+ "version": "0.1.0",
4
+ "description": "takt-marp",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/j5ik2o/takt-marp.git"
8
+ },
9
+ "bin": {
10
+ "takt-marp": "bin/takt-marp.mjs"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "scripts/",
15
+ "templates/",
16
+ "fixtures/marp-slide-workflow/",
17
+ "marp.config.mjs"
18
+ ],
19
+ "engines": {
20
+ "node": ">=24"
21
+ },
22
+ "scripts": {
23
+ "slide:plan": "node scripts/takt-marp-run-slide-workflow.mjs plan",
24
+ "slide:compose": "node scripts/takt-marp-run-slide-workflow.mjs compose",
25
+ "slide:polish": "node scripts/takt-marp-run-slide-workflow.mjs polish",
26
+ "slide:deliver": "node scripts/takt-marp-run-slide-workflow.mjs deliver",
27
+ "slide:check-state": "node scripts/takt-marp-check-slide-workflow-state.mjs",
28
+ "slide:approve": "node scripts/takt-marp-approve-slide-workflow-state.mjs",
29
+ "slide:render-evidence": "node scripts/takt-marp-render-slide-workflow-evidence.mjs",
30
+ "slide:validate-foundation": "node scripts/takt-marp-validate-slide-workflow-foundation.mjs",
31
+ "slide:smoke": "node scripts/takt-marp-validate-slide-workflow-smoke.mjs",
32
+ "test": "npm run slide:validate-foundation",
33
+ "build": "npm run build:html && npm run build:pdf",
34
+ "build:html": "marp slides/takt-sdd/SLIDES.md --html --allow-local-files -o dist/takt-sdd/SLIDES.html",
35
+ "build:pptx": "marp slides/takt-sdd/SLIDES.md --html --allow-local-files --pptx -o dist/takt-sdd/SLIDES.pptx",
36
+ "build:pdf": "marp slides/takt-sdd/SLIDES.md --html --allow-local-files --pdf -o dist/takt-sdd/SLIDES.pdf",
37
+ "preview": "marp -s . --html",
38
+ "installer:sync-templates": "node scripts/takt-marp-sync-project-templates.mjs --write",
39
+ "installer:check-templates": "node scripts/takt-marp-sync-project-templates.mjs",
40
+ "installer:check-package": "node scripts/takt-marp-validate-package-boundary.mjs",
41
+ "installer:validate": "node scripts/takt-marp-validate-global-install.mjs",
42
+ "release": "release-it"
43
+ },
44
+ "dependencies": {
45
+ "@fontsource/noto-sans-jp": "^5.2.5",
46
+ "@marp-team/marp-cli": "^4.4.0",
47
+ "takt": "^0.44.0"
48
+ },
49
+ "overrides": {
50
+ "yargs": "^18.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@release-it/conventional-changelog": "^11.0.1",
54
+ "release-it": "^20.2.0"
55
+ }
56
+ }
@@ -0,0 +1,199 @@
1
+ import { spawn } from "node:child_process";
2
+ import { statSync } from "node:fs";
3
+ import { mkdtemp } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { parseArgs } from "node:util";
7
+ import { initializeProject } from "./takt-marp-project-init.mjs";
8
+ import { packageScriptPath } from "./takt-marp-runtime-context.mjs";
9
+ import { formatError, SlideWorkflowError } from "./takt-marp-slide-workflow.mjs";
10
+
11
+ const WORKFLOW_COMMANDS = ["plan", "compose", "polish", "deliver"];
12
+ const VALID_COMMANDS = ["init", ...WORKFLOW_COMMANDS, "smoke"];
13
+ const RUNNER_SCRIPT = "scripts/takt-marp-run-slide-workflow.mjs";
14
+ const SMOKE_SCRIPT = "scripts/takt-marp-validate-slide-workflow-smoke.mjs";
15
+ const REQUIRED_PROJECT_DIRS = [".takt/workflows", ".takt/facets"];
16
+
17
+ function usage() {
18
+ return [
19
+ "Usage: takt-marp <command> [arguments]",
20
+ "",
21
+ "Commands:",
22
+ " init [dir] [--force|--overwrite] Install .takt/workflows and .takt/facets templates into <dir> (default: .)",
23
+ " plan <slides/deck> [options] Run the plan workflow for a deck in the current project",
24
+ " compose <slides/deck> [options] Run the compose workflow for a deck in the current project",
25
+ " polish <slides/deck> [options] Run the polish workflow for a deck in the current project",
26
+ " deliver <slides/deck> [options] Run the deliver workflow for a deck in the current project",
27
+ " smoke [--provider <name>] Run smoke validation in a temporary project (default provider: mock)",
28
+ "",
29
+ "Workflow options (passed through to the workflow runner unchanged):",
30
+ " --force Invalidate an already-successful state and rerun",
31
+ " --provider <name> Run the workflow with the specified TAKT provider",
32
+ ].join("\n");
33
+ }
34
+
35
+ function initUsage() {
36
+ return [
37
+ "Usage: takt-marp init [dir] [--force|--overwrite]",
38
+ "",
39
+ "Installs .takt/workflows/** and .takt/facets/** templates into <dir> (default: current directory).",
40
+ "",
41
+ "Options:",
42
+ " --force Overwrite existing template-owned files",
43
+ " --overwrite Alias of --force",
44
+ " --help Show this message",
45
+ ].join("\n");
46
+ }
47
+
48
+ function isDirectory(candidatePath) {
49
+ const stats = statSync(candidatePath, { throwIfNoEntry: false });
50
+ return stats !== undefined && stats.isDirectory();
51
+ }
52
+
53
+ function assertProjectInitialized() {
54
+ const cwd = process.cwd();
55
+ const missing = REQUIRED_PROJECT_DIRS.filter(
56
+ (relative) => !isDirectory(path.join(cwd, ...relative.split("/"))),
57
+ );
58
+ if (missing.length > 0) {
59
+ throw new SlideWorkflowError(
60
+ `Target project is not initialized: missing ${missing.join(" and ")} in ${cwd}. Run 'takt-marp init .' first.`,
61
+ "PROJECT_NOT_INITIALIZED",
62
+ );
63
+ }
64
+ }
65
+
66
+ function runPackageScript(relativeScriptPath, args, options = {}) {
67
+ return new Promise((resolve, reject) => {
68
+ const child = spawn(process.execPath, [packageScriptPath(relativeScriptPath), ...args], {
69
+ cwd: options.cwd ?? process.cwd(),
70
+ stdio: "inherit",
71
+ });
72
+ child.on("error", reject);
73
+ child.on("close", (code) => {
74
+ resolve(code ?? 1);
75
+ });
76
+ });
77
+ }
78
+
79
+ async function runWorkflowCommand(command, args) {
80
+ assertProjectInitialized();
81
+ return runPackageScript(RUNNER_SCRIPT, [command, ...args]);
82
+ }
83
+
84
+ async function runInit(args) {
85
+ let parsed;
86
+ try {
87
+ parsed = parseArgs({
88
+ args,
89
+ allowPositionals: true,
90
+ options: {
91
+ force: { type: "boolean", default: false },
92
+ overwrite: { type: "boolean", default: false },
93
+ help: { type: "boolean", short: "h", default: false },
94
+ },
95
+ });
96
+ } catch (error) {
97
+ throw new SlideWorkflowError(
98
+ `Invalid init arguments: ${error.message}. Run 'takt-marp init --help' for usage.`,
99
+ "INVALID_ARGS",
100
+ );
101
+ }
102
+ if (parsed.values.help) {
103
+ console.log(initUsage());
104
+ return 0;
105
+ }
106
+ if (parsed.positionals.length > 1) {
107
+ throw new SlideWorkflowError(
108
+ `Unexpected extra init arguments: ${parsed.positionals.slice(1).join(" ")}. Run 'takt-marp init --help' for usage.`,
109
+ "INVALID_ARGS",
110
+ );
111
+ }
112
+
113
+ const targetDir = path.resolve(process.cwd(), parsed.positionals[0] ?? ".");
114
+ const force = parsed.values.force || parsed.values.overwrite;
115
+ const { created, overwritten } = await initializeProject({ targetDir, force });
116
+
117
+ const lines = [`Initialized takt-marp templates in ${targetDir}`];
118
+ lines.push(`Created ${created.length} file(s)${created.length > 0 ? ":" : "."}`);
119
+ for (const relativePath of created) {
120
+ lines.push(` ${relativePath}`);
121
+ }
122
+ if (overwritten.length > 0) {
123
+ lines.push(`Overwrote ${overwritten.length} file(s):`);
124
+ for (const relativePath of overwritten) {
125
+ lines.push(` ${relativePath}`);
126
+ }
127
+ }
128
+ lines.push(
129
+ "",
130
+ "Next steps:",
131
+ " - Provider configuration stays under your ownership: takt-marp does not create or modify",
132
+ " TAKT provider settings, API keys, or credentials.",
133
+ " - Configure your TAKT provider before running workflows.",
134
+ ` - Run a workflow from the project root, e.g.: takt-marp plan slides/<deck>`,
135
+ );
136
+ console.log(lines.join("\n"));
137
+ return 0;
138
+ }
139
+
140
+ async function runSmoke(args) {
141
+ // Smoke runs in a freshly created temporary project so the user's current
142
+ // project is never touched. The temp project is retained after the run as
143
+ // the home of the provider-specific smoke summaries.
144
+ let tempProject;
145
+ try {
146
+ tempProject = await mkdtemp(path.join(os.tmpdir(), "takt-marp-smoke-"));
147
+ } catch (error) {
148
+ throw new SlideWorkflowError(
149
+ `Failed to create a temporary smoke project under ${os.tmpdir()}: ${error.message}`,
150
+ "SMOKE_PREPARE_FAILED",
151
+ );
152
+ }
153
+ await initializeProject({ targetDir: tempProject, force: false });
154
+ console.log(`Temporary smoke project: ${tempProject}`);
155
+
156
+ // Pass argv through unchanged: provider selection (--provider) and the
157
+ // mock default, mock/real summary generation, and surfacing real-provider
158
+ // misconfiguration as failures are all owned by the smoke script. The CLI
159
+ // never creates or modifies TAKT provider settings.
160
+ const exitCode = await runPackageScript(SMOKE_SCRIPT, args, { cwd: tempProject });
161
+
162
+ console.log(
163
+ [
164
+ "",
165
+ `Smoke validation finished with exit code ${exitCode}.`,
166
+ `Temporary smoke project (retained for inspection): ${tempProject}`,
167
+ "Provider-specific smoke summaries are written under the smoke deck's review directory",
168
+ `inside that project (default: ${path.join(tempProject, "slides", "_workflow-smoke", "review")}).`,
169
+ ].join("\n"),
170
+ );
171
+ return exitCode;
172
+ }
173
+
174
+ export async function runCli(argv) {
175
+ const [command, ...rest] = argv;
176
+ if (command === undefined || command === "--help" || command === "-h" || command === "help") {
177
+ console.log(usage());
178
+ return 0;
179
+ }
180
+
181
+ try {
182
+ if (!VALID_COMMANDS.includes(command)) {
183
+ throw new SlideWorkflowError(
184
+ `'${command}' is not a takt-marp command. Valid commands: ${VALID_COMMANDS.join(", ")}. Run 'takt-marp --help' for usage.`,
185
+ "UNKNOWN_COMMAND",
186
+ );
187
+ }
188
+ if (command === "init") {
189
+ return await runInit(rest);
190
+ }
191
+ if (command === "smoke") {
192
+ return await runSmoke(rest);
193
+ }
194
+ return await runWorkflowCommand(command, rest);
195
+ } catch (error) {
196
+ console.error(formatError(error));
197
+ return 1;
198
+ }
199
+ }
@@ -0,0 +1,81 @@
1
+ import { copyFile, mkdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { listTemplateEntries } from "./takt-marp-project-templates.mjs";
4
+ import { SlideWorkflowError } from "./takt-marp-slide-workflow.mjs";
5
+
6
+ const TAKT_DIR = ".takt";
7
+
8
+ async function assertTargetDirectory(targetDir) {
9
+ if (!path.isAbsolute(targetDir)) {
10
+ throw new SlideWorkflowError(
11
+ `Target directory must be an absolute path: ${targetDir}`,
12
+ "TARGET_DIR_NOT_FOUND",
13
+ );
14
+ }
15
+ let stats;
16
+ try {
17
+ stats = await stat(targetDir);
18
+ } catch (error) {
19
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") {
20
+ throw new SlideWorkflowError(
21
+ `Target directory does not exist: ${targetDir}`,
22
+ "TARGET_DIR_NOT_FOUND",
23
+ );
24
+ }
25
+ throw error;
26
+ }
27
+ if (!stats.isDirectory()) {
28
+ throw new SlideWorkflowError(
29
+ `Target path is not a directory: ${targetDir}`,
30
+ "TARGET_DIR_NOT_FOUND",
31
+ );
32
+ }
33
+ }
34
+
35
+ async function destinationExists(destinationPath) {
36
+ try {
37
+ await stat(destinationPath);
38
+ return true;
39
+ } catch (error) {
40
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") {
41
+ return false;
42
+ }
43
+ throw error;
44
+ }
45
+ }
46
+
47
+ export async function initializeProject(options) {
48
+ const { targetDir, force } = options;
49
+ await assertTargetDirectory(targetDir);
50
+
51
+ const entries = await listTemplateEntries();
52
+ const plan = [];
53
+ for (const entry of entries) {
54
+ const destinationRelativePath = `${TAKT_DIR}/${entry.relativePath}`;
55
+ const destinationPath = path.join(targetDir, TAKT_DIR, ...entry.relativePath.split("/"));
56
+ const exists = await destinationExists(destinationPath);
57
+ plan.push({ entry, destinationRelativePath, destinationPath, exists });
58
+ }
59
+
60
+ const conflicts = plan.filter((item) => item.exists).map((item) => item.destinationRelativePath);
61
+ if (!force && conflicts.length > 0) {
62
+ throw new SlideWorkflowError(
63
+ `Init conflict: existing template files would be overwritten. Conflicting paths:\n${conflicts.join("\n")}\nRe-run with --force (or --overwrite) to overwrite template-owned paths.`,
64
+ "INIT_CONFLICT",
65
+ );
66
+ }
67
+
68
+ const created = [];
69
+ const overwritten = [];
70
+ for (const item of plan) {
71
+ await mkdir(path.dirname(item.destinationPath), { recursive: true });
72
+ await copyFile(item.entry.sourcePath, item.destinationPath);
73
+ if (item.exists) {
74
+ overwritten.push(item.destinationRelativePath);
75
+ } else {
76
+ created.push(item.destinationRelativePath);
77
+ }
78
+ }
79
+
80
+ return { created, overwritten };
81
+ }