takt-marp 0.3.0 → 0.4.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 (57) hide show
  1. package/README.ja.md +4 -1
  2. package/README.md +4 -1
  3. package/fixtures/marp-slide-workflow/_workflow-smoke/brief.md +100 -14
  4. package/fixtures/marp-slide-workflow/_workflow-smoke/research/research-brief.md +12 -0
  5. package/package.json +5 -2
  6. package/scripts/lib/takt-marp-cli.mjs +33 -45
  7. package/scripts/lib/takt-marp-errors.mjs +11 -0
  8. package/scripts/lib/takt-marp-project-eject.mjs +136 -0
  9. package/scripts/lib/takt-marp-project-templates.mjs +108 -1
  10. package/scripts/lib/takt-marp-runtime-context.mjs +1 -0
  11. package/scripts/lib/takt-marp-slide-workflow.mjs +177 -43
  12. package/scripts/takt-marp-run-slide-workflow.mjs +106 -16
  13. package/scripts/takt-marp-sync-project-templates.mjs +5 -16
  14. package/scripts/takt-marp-validate-global-install.mjs +362 -99
  15. package/scripts/takt-marp-validate-installer-ci-entrypoint.mjs +96 -0
  16. package/scripts/takt-marp-validate-package-boundary.mjs +19 -8
  17. package/scripts/takt-marp-validate-slide-workflow-foundation.mjs +2920 -50
  18. package/scripts/takt-marp-validate-slide-workflow-smoke.mjs +1052 -78
  19. package/templates/project/facets/instructions/takt-marp-adapt-research.md +35 -0
  20. package/templates/project/facets/instructions/takt-marp-ai-antipattern-fix.md +3 -3
  21. package/templates/project/facets/instructions/takt-marp-analyze-reference-deck.md +40 -0
  22. package/templates/project/facets/instructions/takt-marp-assemble-slides.md +36 -0
  23. package/templates/project/facets/instructions/takt-marp-compose-fix.md +2 -2
  24. package/templates/project/facets/instructions/takt-marp-compose-review.md +7 -6
  25. package/templates/project/facets/instructions/takt-marp-compose-sections.md +31 -0
  26. package/templates/project/facets/instructions/takt-marp-compose-work-summary.md +6 -3
  27. package/templates/project/facets/instructions/takt-marp-deliver-build.md +1 -1
  28. package/templates/project/facets/instructions/takt-marp-design-system.md +6 -4
  29. package/templates/project/facets/instructions/takt-marp-normalize-brief.md +8 -2
  30. package/templates/project/facets/instructions/takt-marp-plan-fix.md +4 -3
  31. package/templates/project/facets/instructions/takt-marp-plan-review.md +20 -4
  32. package/templates/project/facets/instructions/takt-marp-plan-work-summary.md +12 -5
  33. package/templates/project/facets/instructions/takt-marp-plan.md +30 -8
  34. package/templates/project/facets/instructions/takt-marp-polish-fix.md +2 -2
  35. package/templates/project/facets/instructions/takt-marp-polish-inspect.md +1 -1
  36. package/templates/project/facets/instructions/takt-marp-supervise-research.md +23 -0
  37. package/templates/project/facets/instructions/takt-marp-visual-generate.md +15 -15
  38. package/templates/project/facets/knowledge/takt-marp-repo-conventions.md +26 -4
  39. package/templates/project/facets/output-contracts/takt-marp-normalized-brief.md +19 -1
  40. package/templates/project/facets/output-contracts/takt-marp-open-questions.md +48 -0
  41. package/templates/project/facets/output-contracts/takt-marp-reference-analysis.md +52 -0
  42. package/templates/project/facets/output-contracts/takt-marp-research-claims.md +50 -0
  43. package/templates/project/facets/output-contracts/takt-marp-research-sources.md +50 -0
  44. package/templates/project/facets/output-contracts/takt-marp-research-supervision.md +49 -0
  45. package/templates/project/facets/output-contracts/takt-marp-section-compose.md +36 -0
  46. package/templates/project/facets/output-contracts/takt-marp-slide-blueprint.md +44 -0
  47. package/templates/project/facets/output-contracts/takt-marp-slide-plan.md +30 -2
  48. package/templates/project/facets/policies/takt-marp-general-slide-quality.md +1 -1
  49. package/templates/project/facets/policies/takt-marp-slide-quality.md +6 -4
  50. package/templates/project/facets/policies/takt-marp-visual-composition.md +87 -0
  51. package/templates/project/workflows/takt-marp-slide-compose.yaml +37 -18
  52. package/templates/project/workflows/takt-marp-slide-plan.yaml +32 -4
  53. package/templates/project/workflows/takt-marp-slide-polish.yaml +5 -5
  54. package/templates/project/workflows/takt-marp-slide-research.yaml +81 -0
  55. package/scripts/lib/takt-marp-project-init.mjs +0 -81
  56. package/templates/project/facets/instructions/takt-marp-compose-slides.md +0 -35
  57. package/templates/project/facets/policies/takt-marp-svg-first-visual.md +0 -68
package/README.ja.md CHANGED
@@ -6,7 +6,7 @@ Marpスライドデッキと、半自動でデッキを生成するためのTAKT
6
6
 
7
7
  ## TAKT Marp workflow
8
8
 
9
- このworkflowは `slides/<deck>/brief.md` を起点に、`plan`、`compose`、`polish`、`deliver` の状態へ進めます。
9
+ このworkflowは `slides/<deck>/brief.md` を起点に、`plan`、`compose`、`polish`、`deliver` の状態へ進めます。外部調査が必要な deck だけ、任意で先に `research` を実行します。
10
10
 
11
11
  詳細なworkflow contract: [docs/marp-slide-workflow.md](docs/marp-slide-workflow.md)
12
12
 
@@ -34,6 +34,7 @@ Output Requirementsの例:
34
34
  ### 2. workflowを実行する
35
35
 
36
36
  ```bash
37
+ takt-marp research "slides/<deck>"
37
38
  takt-marp plan "slides/<deck>"
38
39
  takt-marp approve "slides/<deck>" plan --by <name>
39
40
  takt-marp compose "slides/<deck>"
@@ -49,6 +50,7 @@ takt-marp plan "slides/<deck>"
49
50
  ```
50
51
 
51
52
  人間承認は `plan` と `compose` に対してのみ `takt-marp approve` で記録します。`review`、`revise`、`qa`、`build-qa` はworkflow内部の責務であり、トップレベルコマンドではありません。
53
+ `research` は `slides/<deck>/research/research-brief.md` を読み、`plan` の必須前提ではありません。
52
54
 
53
55
  ### 3. 生成されるファイル
54
56
 
@@ -59,6 +61,7 @@ slides/<deck>/
59
61
  design-system.md
60
62
  SLIDES.md
61
63
  images/*.svg
64
+ research/*.md
62
65
  review/*.md
63
66
  ```
64
67
 
package/README.md CHANGED
@@ -6,7 +6,7 @@ Marp slide decks and a TAKT workflow for semi-automated deck generation.
6
6
 
7
7
  ## TAKT Marp workflow
8
8
 
9
- The workflow starts from `slides/<deck>/brief.md` and moves through `plan`, `compose`, `polish`, and `deliver`.
9
+ The workflow starts from `slides/<deck>/brief.md` and moves through `plan`, `compose`, `polish`, and `deliver`. Use optional `research` first only when a deck needs external research context.
10
10
 
11
11
  Detailed workflow contract: [docs/marp-slide-workflow.md](docs/marp-slide-workflow.md)
12
12
 
@@ -34,6 +34,7 @@ Example output requirement:
34
34
  ### 2. Run the workflows
35
35
 
36
36
  ```bash
37
+ takt-marp research "slides/<deck>"
37
38
  takt-marp plan "slides/<deck>"
38
39
  takt-marp approve "slides/<deck>" plan --by <name>
39
40
  takt-marp compose "slides/<deck>"
@@ -49,6 +50,7 @@ takt-marp plan "slides/<deck>"
49
50
  ```
50
51
 
51
52
  Human approval is recorded by `takt-marp approve` for `plan` and `compose` only. `review`, `revise`, `qa`, and `build-qa` are internal workflow responsibilities, not top-level commands.
53
+ `research` reads `slides/<deck>/research/research-brief.md` and is not required before `plan`.
52
54
 
53
55
  ### 3. Generated files
54
56
 
@@ -59,6 +61,7 @@ slides/<deck>/
59
61
  design-system.md
60
62
  SLIDES.md
61
63
  images/*.svg
64
+ research/*.md
62
65
  review/*.md
63
66
  ```
64
67
 
@@ -1,26 +1,65 @@
1
- # Brief
1
+ # Workflow Smoke Deck Brief
2
2
 
3
3
  ## Event
4
- - Name: Workflow smoke test
5
- - Date:
6
- - Duration: 5 minutes
7
- - Venue: local
8
- - Audience: maintainer
4
+ - Name: 架空研修ラボ ドメインモデリング講座
5
+ - Date: 2031年4月17日(木)10:00〜16:30
6
+ - Duration: 360 minutes
7
+ - Venue: ミラージュホール A / サンプル配信スタジオ
8
+ - Organizer: サンプル研修ラボ株式会社
9
+ - Audience: メンテナとワークフロー検証者
9
10
 
10
11
  ## Goal
11
- Marp slide TAKT workflow brief から plandraft、SVG、review、build QA まで接続できることを確認する。
12
+ Marp slide TAKT workflow が、情報量の多い講義ブリーフから normalized brief、reference analysis、coverage matrixslide blueprintsection source、HTML/CSS visual、SVG visual、review、build QA まで接続できることを確認する。
13
+
14
+ ## Critical Constraints
15
+ - Official Title: 変更に強いドメインモデリングの実践ワークショップ
16
+ - Speaker Name: 山田 サンプル
17
+ - Speaker Affiliation: サンプルデザイン合同会社
18
+ - Design: 白基調だが白黒ではない。カラー印刷を前提に、青・緑・橙・赤を意味づけて使う。
19
+ - Information Density: 投影だけでなく印刷配布テキストとして読める密度を許容する。
20
+
21
+ ### Fixed Outline
22
+ 1. 入力を正にする
23
+ (1)briefを保全する
24
+ a. 正式タイトル、講師名、所属を落とさない
25
+ b. 禁止語と避けるべき表現を保持する
26
+ (2)事実を棚卸しする
27
+ a. 架空の日時、主催、形式、対象、場所をFact Inventoryに残す
28
+ b. Target slide countと要求密度の矛盾をfindingにする
29
+ 2. 設計をplanへ渡す
30
+ (1)coverage matrixを作る
31
+ a. 固定アウトラインの全leaf項目をslide IDへ対応させる
32
+ b. 未対応項目はneeds_inputではなくPlan Findingsへ残す
33
+ (2)visual strategyを決める
34
+ a. カード、比較、表、軽量フローはhtml:で構成する
35
+ b. 座標制御や複雑な矢印だけsvg:またはinline-svg:にする
36
+ 3. section sourceからassembleする
37
+ (1)blueprintを分ける
38
+ a. slide-blueprint.mdにcontent atomsとsection manifestを残す
39
+ b. sections/*.mdからSLIDES.mdへassembleする
40
+ (2)deliveryを検証する
41
+ a. HTMLとPDFをbuildできることを確認する
42
+ b. generated artifactをsource artifactへ混ぜない
12
43
 
13
44
  ## Core Message
14
- 半自動のMarp生成workflowは、入力、構成、visual、レビュー、QAを分離すると安定する。
45
+ 半自動のMarp生成workflowは、入力、coverage、blueprint、section source、visual、レビュー、QAを分離すると安定する。
15
46
 
16
47
  ## Audience Context
17
48
  聴衆はこのリポジトリのメンテナであり、TAKTとMarpの基本を理解している。
18
49
 
19
50
  ## Required Topics
20
- - briefを入力の正にする
21
- - SVG-firstで図を管理する
22
- - plan後とdraft後に人間確認を挟む
23
- - build QAまでworkflowに含める
51
+ - 1. 入力を正にする > (1)briefを保全する > a. 正式タイトル、講師名、所属を落とさない
52
+ - 1. 入力を正にする > (1)briefを保全する > b. 禁止語と避けるべき表現を保持する
53
+ - 1. 入力を正にする > (2)事実を棚卸しする > a. 架空の日時、主催、形式、対象、場所をFact Inventoryに残す
54
+ - 1. 入力を正にする > (2)事実を棚卸しする > b. Target slide countと要求密度の矛盾をfindingにする
55
+ - 2. 設計をplanへ渡す > (1)coverage matrixを作る > a. 固定アウトラインの全leaf項目をslide IDへ対応させる
56
+ - 2. 設計をplanへ渡す > (1)coverage matrixを作る > b. 未対応項目はneeds_inputではなくPlan Findingsへ残す
57
+ - 2. 設計をplanへ渡す > (2)visual strategyを決める > a. カード、比較、表、軽量フローはhtml:で構成する
58
+ - 2. 設計をplanへ渡す > (2)visual strategyを決める > b. 座標制御や複雑な矢印だけsvg:またはinline-svg:にする
59
+ - 3. section sourceからassembleする > (1)blueprintを分ける > a. slide-blueprint.mdにcontent atomsとsection manifestを残す
60
+ - 3. section sourceからassembleする > (1)blueprintを分ける > b. sections/*.mdからSLIDES.mdへassembleする
61
+ - 3. section sourceからassembleする > (2)deliveryを検証する > a. HTMLとPDFをbuildできることを確認する
62
+ - 3. section sourceからassembleする > (2)deliveryを検証する > b. generated artifactをsource artifactへ混ぜない
24
63
 
25
64
  ## Optional Topics
26
65
  - appendixは必要な場合だけ作る
@@ -29,16 +68,63 @@ Marp slide TAKT workflow が brief から plan、draft、SVG、review、build QA
29
68
  - Web画像の自動取得
30
69
  - 他deck画像の自動流用
31
70
  - 長い本文をスライドに詰め込むこと
71
+ - SVG-firstという方針に戻すこと
72
+ - htmlで十分なカード、比較、表、軽量フローをSVG化すること
73
+ - サンプル主催名、架空日付、架空場所を実在情報に置き換えること
32
74
 
33
75
  ## Source Materials
34
76
  - docs/marp-slide-workflow.md
77
+ - _workflow-smoke internal synthetic brief only
35
78
 
36
79
  ## Speaker Notes
37
- 短い確認用deckなので、各スライドのnotesは2-3文でよい。
80
+ 短い確認用deckなので、各スライドのnotesは2-3文でよい。Durationが分単位で指定されているため、notesには尺マーカーを付ける。
38
81
 
39
82
  ## Output Requirements
40
83
  - Format: Marp
41
84
  - Language: Japanese
42
85
  - Target slide count: 5
86
+ - Deck Mode: overview
43
87
  - 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.
88
+ - Slide Count Consistency Scenario: Target slide count: 5 は概要版として扱う。講義本体を要求する場合は100〜140または期待値相当へ修正する必要がある、というPlan Findingを作れること。
89
+ - Visual scope: html: cards/comparison/table/light-flow を優先し、座標制御が必要な1箇所だけ inline-svg: を使う。未使用SVG placeholderを計画しない。
90
+
91
+ ## Fact Inventory
92
+ - 主催: サンプル研修ラボ株式会社
93
+ - 形式: ミラージュホール A / サンプル配信スタジオ
94
+ - 日時: 2031年4月17日(木)10:00〜16:30
95
+ - 対象: メンテナとワークフロー検証者
96
+ - 場所: ミラージュホール A
97
+ - 講師所属: サンプルデザイン合同会社
98
+
99
+ ## Terminology Policy
100
+ - Domain-Driven Design はドメイン駆動設計と表記する。
101
+ - Workflow は workflow と英字表記で統一する。
102
+ - Fixed Outline の語句は言い換えない。
103
+
104
+ ## Example Policy
105
+ - 共通題材は「備品購入申請・承認」とし、章をまたいで一貫利用する。
106
+
107
+ ## Code Example Policy
108
+ - Java風の疑似コードでBefore / Afterを示す。
109
+ - 業務の意味がコード構造へ表れることを示す。
110
+ - フレームワーク固有APIへ寄せすぎない。
111
+
112
+ ## Exercise Policy
113
+ - 短時間の個人演習にする。
114
+ - 模範回答または解説を巻末またはnotesに残す。
115
+
116
+ ## Appendix Requirements
117
+ - 用語集
118
+ - 実践チェックリスト
119
+ - 演習模範回答
120
+
121
+ ## Quality Checklist
122
+ - 正式タイトル完全一致
123
+ - 講師所属がサンプルデザイン合同会社
124
+ - 禁止語を使わない
125
+ - 固定アウトラインを変更しない
126
+ - 白基調だが白黒ではない
127
+ - coverage matrixに全leaf項目がある
128
+ - html: visual と svg:/inline-svg: visual の責務が分かれている
129
+ - sections/*.mdからSLIDES.mdへassembleできる
130
+ - build:html / build:pdf が成功する
@@ -0,0 +1,12 @@
1
+ # Workflow Smoke Research Brief
2
+
3
+ ## Research Goal
4
+ Validate that the optional research command can synchronize built-in deep research output into deck-local research artifacts before planning.
5
+
6
+ ## Scope
7
+ - Use only deterministic mock research content.
8
+ - Preserve missing source URL, retrieval time, confidence, and claim-source mapping as absent information.
9
+ - Do not infer or supplement facts that are not present in the built-in research report.
10
+
11
+ ## Expected Consumer
12
+ The subsequent plan command may read the generated research artifacts as optional context, while still using `brief.md` as the primary input.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "takt-marp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "takt-marp",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,6 +20,7 @@
20
20
  "node": ">=24"
21
21
  },
22
22
  "scripts": {
23
+ "slide:research": "node scripts/takt-marp-run-slide-workflow.mjs research",
23
24
  "slide:plan": "node scripts/takt-marp-run-slide-workflow.mjs plan",
24
25
  "slide:compose": "node scripts/takt-marp-run-slide-workflow.mjs compose",
25
26
  "slide:polish": "node scripts/takt-marp-run-slide-workflow.mjs polish",
@@ -29,12 +30,14 @@
29
30
  "slide:render-evidence": "node scripts/takt-marp-render-slide-workflow-evidence.mjs",
30
31
  "slide:validate-foundation": "node scripts/takt-marp-validate-slide-workflow-foundation.mjs",
31
32
  "slide:smoke": "node scripts/takt-marp-validate-slide-workflow-smoke.mjs",
32
- "test": "npm run slide:validate-foundation",
33
+ "test": "npm run slide:validate-foundation && npm run slide:smoke -- --provider mock",
33
34
  "build": "npm run build:html && npm run build:pdf",
34
35
  "build:html": "node scripts/takt-marp-build-slide-artifact.mjs html",
35
36
  "build:pptx": "node scripts/takt-marp-build-slide-artifact.mjs pptx",
36
37
  "build:pdf": "node scripts/takt-marp-build-slide-artifact.mjs pdf",
37
38
  "preview": "node scripts/takt-marp-preview-slide.mjs takt-sdd",
39
+ "installer:ci": "npm run slide:validate-foundation && npm run installer:check-templates && npm run installer:check-package && npm run installer:validate && npm run slide:smoke -- --provider mock",
40
+ "installer:check-ci": "node scripts/takt-marp-validate-installer-ci-entrypoint.mjs",
38
41
  "installer:sync-templates": "node scripts/takt-marp-sync-project-templates.mjs --write",
39
42
  "installer:check-templates": "node scripts/takt-marp-sync-project-templates.mjs",
40
43
  "installer:check-package": "node scripts/takt-marp-validate-package-boundary.mjs",
@@ -1,10 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
- import { statSync } from "node:fs";
3
2
  import { mkdtemp } from "node:fs/promises";
4
3
  import os from "node:os";
5
4
  import path from "node:path";
6
5
  import { parseArgs } from "node:util";
7
- import { initializeProject } from "./takt-marp-project-init.mjs";
6
+ import { ejectProject } from "./takt-marp-project-eject.mjs";
7
+ import { resolveTemplateSource, workflowFilePath } from "./takt-marp-project-templates.mjs";
8
8
  import { packageScriptPath } from "./takt-marp-runtime-context.mjs";
9
9
  import {
10
10
  APPROVAL_COMMANDS,
@@ -12,20 +12,20 @@ import {
12
12
  SlideWorkflowError,
13
13
  } from "./takt-marp-slide-workflow.mjs";
14
14
 
15
- const WORKFLOW_COMMANDS = ["plan", "compose", "polish", "deliver"];
15
+ const RESEARCH_COMMANDS = ["research"];
16
+ const WORKFLOW_COMMANDS = [...RESEARCH_COMMANDS, "plan", "compose", "polish", "deliver"];
16
17
  const BUILD_COMMANDS = Object.freeze({
17
18
  "build:html": "html",
18
19
  "build:pdf": "pdf",
19
20
  "build:pptx": "pptx",
20
21
  });
21
22
  const UTILITY_COMMANDS = [...Object.keys(BUILD_COMMANDS), "preview"];
22
- const VALID_COMMANDS = ["init", ...WORKFLOW_COMMANDS, ...UTILITY_COMMANDS, "approve", "smoke"];
23
+ const VALID_COMMANDS = ["eject", ...WORKFLOW_COMMANDS, "approve", "smoke", ...UTILITY_COMMANDS];
23
24
  const RUNNER_SCRIPT = "scripts/takt-marp-run-slide-workflow.mjs";
24
25
  const APPROVE_SCRIPT = "scripts/takt-marp-approve-slide-workflow-state.mjs";
25
26
  const SMOKE_SCRIPT = "scripts/takt-marp-validate-slide-workflow-smoke.mjs";
26
27
  const BUILD_SCRIPT = "scripts/takt-marp-build-slide-artifact.mjs";
27
28
  const PREVIEW_SCRIPT = "scripts/takt-marp-preview-slide.mjs";
28
- const REQUIRED_PROJECT_DIRS = [".takt/workflows", ".takt/facets"];
29
29
  const FORWARDED_SIGNALS = new Set(["SIGINT", "SIGTERM"]);
30
30
 
31
31
  function usage() {
@@ -33,11 +33,15 @@ function usage() {
33
33
  "Usage: takt-marp <command> [arguments]",
34
34
  "",
35
35
  "Commands:",
36
- " init [dir] [--force|--overwrite] Install .takt/workflows and .takt/facets templates into <dir> (default: .)",
36
+ " eject [dir] [--force|--overwrite] Copy .takt/workflows and .takt/facets templates into <dir> (default: .)",
37
+ " research <slides/deck> [options] Optional pre-research command before slide planning",
37
38
  " plan <slides/deck> [options] Run the plan workflow for a deck in the current project",
38
39
  " compose <slides/deck> [options] Run the compose workflow for a deck in the current project",
39
40
  " polish <slides/deck> [options] Run the polish workflow for a deck in the current project",
40
41
  " deliver <slides/deck> [options] Run the deliver workflow for a deck in the current project",
42
+ " approve <slides/deck> <command> --by <name> [--force]",
43
+ " Approve a workflow state (command: plan or compose)",
44
+ " smoke [--provider <name>] Run smoke validation in a temporary project (default provider: mock)",
41
45
  " build:html [deck|slides/<deck>|slides/<deck>/SLIDES.md]",
42
46
  " Build HTML artifact without changing workflow state",
43
47
  " build:pdf [deck|slides/<deck>|slides/<deck>/SLIDES.md]",
@@ -46,9 +50,6 @@ function usage() {
46
50
  " Build PPTX artifact without changing workflow state",
47
51
  " preview <deck|slides/<deck>|slides/<deck>/SLIDES.md>",
48
52
  " Start Marp server mode without changing workflow state",
49
- " approve <slides/deck> <command> --by <name> [--force]",
50
- " Approve a workflow state (command: plan or compose)",
51
- " smoke [--provider <name>] Run smoke validation in a temporary project (default provider: mock)",
52
53
  "",
53
54
  "Workflow options (passed through to the workflow runner unchanged):",
54
55
  " --force Invalidate an already-successful state and rerun",
@@ -56,11 +57,11 @@ function usage() {
56
57
  ].join("\n");
57
58
  }
58
59
 
59
- function initUsage() {
60
+ function ejectUsage() {
60
61
  return [
61
- "Usage: takt-marp init [dir] [--force|--overwrite]",
62
+ "Usage: takt-marp eject [dir] [--force|--overwrite]",
62
63
  "",
63
- "Installs .takt/workflows/** and .takt/facets/** templates into <dir> (default: current directory).",
64
+ "Copies .takt/workflows/** and .takt/facets/** templates into <dir> (default: current directory).",
64
65
  "",
65
66
  "Options:",
66
67
  " --force Overwrite existing template-owned files",
@@ -69,24 +70,6 @@ function initUsage() {
69
70
  ].join("\n");
70
71
  }
71
72
 
72
- function isDirectory(candidatePath) {
73
- const stats = statSync(candidatePath, { throwIfNoEntry: false });
74
- return stats !== undefined && stats.isDirectory();
75
- }
76
-
77
- function assertProjectInitialized() {
78
- const cwd = process.cwd();
79
- const missing = REQUIRED_PROJECT_DIRS.filter(
80
- (relative) => !isDirectory(path.join(cwd, ...relative.split("/"))),
81
- );
82
- if (missing.length > 0) {
83
- throw new SlideWorkflowError(
84
- `Target project is not initialized: missing ${missing.join(" and ")} in ${cwd}. Run 'takt-marp init .' first.`,
85
- "PROJECT_NOT_INITIALIZED",
86
- );
87
- }
88
- }
89
-
90
73
  function runPackageScript(relativeScriptPath, args, options = {}) {
91
74
  return new Promise((resolve, reject) => {
92
75
  let stopping = false;
@@ -128,8 +111,9 @@ function runPackageScript(relativeScriptPath, args, options = {}) {
128
111
  }
129
112
 
130
113
  async function runWorkflowCommand(command, args) {
131
- assertProjectInitialized();
132
- return runPackageScript(RUNNER_SCRIPT, [command, ...args]);
114
+ const source = resolveTemplateSource({ projectRoot: process.cwd() });
115
+ const selectedWorkflowFilePath = workflowFilePath(source, command);
116
+ return runPackageScript(RUNNER_SCRIPT, [command, ...args, "--workflow-file", selectedWorkflowFilePath]);
133
117
  }
134
118
 
135
119
  async function runBuildCommand(command, args) {
@@ -140,7 +124,7 @@ async function runPreview(args) {
140
124
  return runPackageScript(PREVIEW_SCRIPT, args, { successOnSignal: true });
141
125
  }
142
126
 
143
- async function runInit(args) {
127
+ async function runEject(args) {
144
128
  let parsed;
145
129
  try {
146
130
  parsed = parseArgs({
@@ -154,26 +138,26 @@ async function runInit(args) {
154
138
  });
155
139
  } catch (error) {
156
140
  throw new SlideWorkflowError(
157
- `Invalid init arguments: ${error.message}. Run 'takt-marp init --help' for usage.`,
141
+ `Invalid eject arguments: ${error.message}. Run 'takt-marp eject --help' for usage.`,
158
142
  "INVALID_ARGS",
159
143
  );
160
144
  }
161
145
  if (parsed.values.help) {
162
- console.log(initUsage());
146
+ console.log(ejectUsage());
163
147
  return 0;
164
148
  }
165
149
  if (parsed.positionals.length > 1) {
166
150
  throw new SlideWorkflowError(
167
- `Unexpected extra init arguments: ${parsed.positionals.slice(1).join(" ")}. Run 'takt-marp init --help' for usage.`,
151
+ `Unexpected extra eject arguments: ${parsed.positionals.slice(1).join(" ")}. Run 'takt-marp eject --help' for usage.`,
168
152
  "INVALID_ARGS",
169
153
  );
170
154
  }
171
155
 
172
156
  const targetDir = path.resolve(process.cwd(), parsed.positionals[0] ?? ".");
173
157
  const force = parsed.values.force || parsed.values.overwrite;
174
- const { created, overwritten } = await initializeProject({ targetDir, force });
158
+ const { created, overwritten } = await ejectProject({ targetDir, force });
175
159
 
176
- const lines = [`Initialized takt-marp templates in ${targetDir}`];
160
+ const lines = [`Ejected takt-marp templates in ${targetDir}`];
177
161
  lines.push(`Created ${created.length} file(s)${created.length > 0 ? ":" : "."}`);
178
162
  for (const relativePath of created) {
179
163
  lines.push(` ${relativePath}`);
@@ -189,7 +173,7 @@ async function runInit(args) {
189
173
  "Next steps:",
190
174
  " - Provider configuration stays under your ownership: takt-marp does not create or modify",
191
175
  " TAKT provider settings, API keys, or credentials.",
192
- " - Configure your TAKT provider before running workflows.",
176
+ " - Edit ejected workflow/facet templates only when you need project-local customization.",
193
177
  ` - Run a workflow from the project root, e.g.: takt-marp plan slides/<deck>`,
194
178
  );
195
179
  console.log(lines.join("\n"));
@@ -198,8 +182,8 @@ async function runInit(args) {
198
182
 
199
183
  async function runSmoke(args) {
200
184
  // Smoke runs in a freshly created temporary project so the user's current
201
- // project is never touched. The temp project is retained after the run as
202
- // the home of the provider-specific smoke summaries.
185
+ // project is never touched. Template source selection stays no-copy by
186
+ // default; only the smoke validator may create smoke deck artifacts.
203
187
  let tempProject;
204
188
  try {
205
189
  tempProject = await mkdtemp(path.join(os.tmpdir(), "takt-marp-smoke-"));
@@ -209,7 +193,6 @@ async function runSmoke(args) {
209
193
  "SMOKE_PREPARE_FAILED",
210
194
  );
211
195
  }
212
- await initializeProject({ targetDir: tempProject, force: false });
213
196
  console.log(`Temporary smoke project: ${tempProject}`);
214
197
 
215
198
  // Pass argv through unchanged: provider selection (--provider) and the
@@ -265,7 +248,6 @@ async function runApprove(args) {
265
248
  console.log(approveUsage());
266
249
  return 0;
267
250
  }
268
- assertProjectInitialized();
269
251
  if (parsed.positionals.length !== 2) {
270
252
  throw new SlideWorkflowError(
271
253
  `Expected <slides/deck> and <command>. Run 'takt-marp approve --help' for usage.`,
@@ -294,14 +276,20 @@ export async function runCli(argv) {
294
276
  }
295
277
 
296
278
  try {
279
+ if (command === "init") {
280
+ throw new SlideWorkflowError(
281
+ "`init` has been removed. Use `takt-marp eject .` to copy template assets.",
282
+ "COMMAND_REMOVED",
283
+ );
284
+ }
297
285
  if (!VALID_COMMANDS.includes(command)) {
298
286
  throw new SlideWorkflowError(
299
287
  `'${command}' is not a takt-marp command. Valid commands: ${VALID_COMMANDS.join(", ")}. Run 'takt-marp --help' for usage.`,
300
288
  "UNKNOWN_COMMAND",
301
289
  );
302
290
  }
303
- if (command === "init") {
304
- return await runInit(rest);
291
+ if (command === "eject") {
292
+ return await runEject(rest);
305
293
  }
306
294
  if (command === "approve") {
307
295
  return await runApprove(rest);
@@ -0,0 +1,11 @@
1
+ export class SlideWorkflowError extends Error {
2
+ constructor(message, code = "SLIDE_WORKFLOW_ERROR") {
3
+ super(message);
4
+ this.name = "SlideWorkflowError";
5
+ this.code = code;
6
+ }
7
+ }
8
+
9
+ export function formatError(error) {
10
+ return error instanceof SlideWorkflowError ? `${error.code}: ${error.message}` : String(error?.stack ?? error);
11
+ }
@@ -0,0 +1,136 @@
1
+ import { copyFile, lstat, mkdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { SlideWorkflowError } from "./takt-marp-errors.mjs";
4
+ import { assertNoProhibitedEntries, listTemplateEntries } from "./takt-marp-project-templates.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
+
16
+ let stats;
17
+ try {
18
+ stats = await stat(targetDir);
19
+ } catch (error) {
20
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") {
21
+ throw new SlideWorkflowError(
22
+ `Target directory does not exist: ${targetDir}`,
23
+ "TARGET_DIR_NOT_FOUND",
24
+ );
25
+ }
26
+ throw error;
27
+ }
28
+
29
+ if (!stats.isDirectory()) {
30
+ throw new SlideWorkflowError(
31
+ `Target path is not a directory: ${targetDir}`,
32
+ "TARGET_DIR_NOT_FOUND",
33
+ );
34
+ }
35
+ }
36
+
37
+ async function readOptionalStats(filePath) {
38
+ try {
39
+ return await lstat(filePath);
40
+ } catch (error) {
41
+ if (error.code === "ENOENT") {
42
+ return undefined;
43
+ }
44
+ throw error;
45
+ }
46
+ }
47
+
48
+ function templateDestination(targetDir, relativePath) {
49
+ return path.join(targetDir, TAKT_DIR, ...relativePath.split("/"));
50
+ }
51
+
52
+ function templateResultPath(relativePath) {
53
+ return `${TAKT_DIR}/${relativePath}`;
54
+ }
55
+
56
+ async function inspectDestination(targetDir, relativePath) {
57
+ const relativeParts = [TAKT_DIR, ...relativePath.split("/")];
58
+ for (let index = 1; index < relativeParts.length; index += 1) {
59
+ const ancestorParts = relativeParts.slice(0, index);
60
+ const ancestorPath = path.join(targetDir, ...ancestorParts);
61
+ const stats = await readOptionalStats(ancestorPath);
62
+ if (!stats) {
63
+ return { exists: false };
64
+ }
65
+ if (!stats.isDirectory()) {
66
+ return { exists: false, conflictPath: ancestorParts.join("/") };
67
+ }
68
+ }
69
+
70
+ const destinationPath = path.join(targetDir, ...relativeParts);
71
+ const stats = await readOptionalStats(destinationPath);
72
+ if (!stats) {
73
+ return { exists: false };
74
+ }
75
+ if (!stats.isFile()) {
76
+ return { exists: false, conflictPath: relativeParts.join("/") };
77
+ }
78
+ return { exists: true };
79
+ }
80
+
81
+ function pushUnique(items, item) {
82
+ if (!items.includes(item)) {
83
+ items.push(item);
84
+ }
85
+ }
86
+
87
+ function createEjectConflict(conflicts) {
88
+ const error = new SlideWorkflowError(
89
+ `Eject conflict: existing template files would be overwritten. Conflicting paths:\n${conflicts.join("\n")}\nRe-run with --force (or --overwrite) to overwrite template-owned paths.`,
90
+ "EJECT_CONFLICT",
91
+ );
92
+ error.conflicts = conflicts;
93
+ return error;
94
+ }
95
+
96
+ export async function ejectProject(options) {
97
+ const { targetDir, force = false } = options;
98
+ await assertTargetDirectory(targetDir);
99
+
100
+ const entries = await listTemplateEntries();
101
+ assertNoProhibitedEntries(entries);
102
+
103
+ const plan = [];
104
+ for (const entry of entries) {
105
+ const destinationRelativePath = templateResultPath(entry.relativePath);
106
+ const destinationPath = templateDestination(targetDir, entry.relativePath);
107
+ const destinationState = await inspectDestination(targetDir, entry.relativePath);
108
+ plan.push({ entry, destinationRelativePath, destinationPath, ...destinationState });
109
+ }
110
+
111
+ const conflicts = [];
112
+ for (const item of plan) {
113
+ if (item.conflictPath) {
114
+ pushUnique(conflicts, item.conflictPath);
115
+ } else if (!force && item.exists) {
116
+ pushUnique(conflicts, item.destinationRelativePath);
117
+ }
118
+ }
119
+ if (conflicts.length > 0) {
120
+ throw createEjectConflict(conflicts);
121
+ }
122
+
123
+ const created = [];
124
+ const overwritten = [];
125
+ for (const item of plan) {
126
+ await mkdir(path.dirname(item.destinationPath), { recursive: true });
127
+ await copyFile(item.entry.sourcePath, item.destinationPath);
128
+ if (item.exists) {
129
+ overwritten.push(item.destinationRelativePath);
130
+ } else {
131
+ created.push(item.destinationRelativePath);
132
+ }
133
+ }
134
+
135
+ return { created, overwritten };
136
+ }