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.
- package/README.ja.md +4 -1
- package/README.md +4 -1
- package/fixtures/marp-slide-workflow/_workflow-smoke/brief.md +100 -14
- package/fixtures/marp-slide-workflow/_workflow-smoke/research/research-brief.md +12 -0
- package/package.json +5 -2
- package/scripts/lib/takt-marp-cli.mjs +33 -45
- package/scripts/lib/takt-marp-errors.mjs +11 -0
- package/scripts/lib/takt-marp-project-eject.mjs +136 -0
- package/scripts/lib/takt-marp-project-templates.mjs +108 -1
- package/scripts/lib/takt-marp-runtime-context.mjs +1 -0
- package/scripts/lib/takt-marp-slide-workflow.mjs +177 -43
- package/scripts/takt-marp-run-slide-workflow.mjs +106 -16
- package/scripts/takt-marp-sync-project-templates.mjs +5 -16
- package/scripts/takt-marp-validate-global-install.mjs +362 -99
- package/scripts/takt-marp-validate-installer-ci-entrypoint.mjs +96 -0
- package/scripts/takt-marp-validate-package-boundary.mjs +19 -8
- package/scripts/takt-marp-validate-slide-workflow-foundation.mjs +2920 -50
- package/scripts/takt-marp-validate-slide-workflow-smoke.mjs +1052 -78
- package/templates/project/facets/instructions/takt-marp-adapt-research.md +35 -0
- package/templates/project/facets/instructions/takt-marp-ai-antipattern-fix.md +3 -3
- package/templates/project/facets/instructions/takt-marp-analyze-reference-deck.md +40 -0
- package/templates/project/facets/instructions/takt-marp-assemble-slides.md +36 -0
- package/templates/project/facets/instructions/takt-marp-compose-fix.md +2 -2
- package/templates/project/facets/instructions/takt-marp-compose-review.md +7 -6
- package/templates/project/facets/instructions/takt-marp-compose-sections.md +31 -0
- package/templates/project/facets/instructions/takt-marp-compose-work-summary.md +6 -3
- package/templates/project/facets/instructions/takt-marp-deliver-build.md +1 -1
- package/templates/project/facets/instructions/takt-marp-design-system.md +6 -4
- package/templates/project/facets/instructions/takt-marp-normalize-brief.md +8 -2
- package/templates/project/facets/instructions/takt-marp-plan-fix.md +4 -3
- package/templates/project/facets/instructions/takt-marp-plan-review.md +20 -4
- package/templates/project/facets/instructions/takt-marp-plan-work-summary.md +12 -5
- package/templates/project/facets/instructions/takt-marp-plan.md +30 -8
- package/templates/project/facets/instructions/takt-marp-polish-fix.md +2 -2
- package/templates/project/facets/instructions/takt-marp-polish-inspect.md +1 -1
- package/templates/project/facets/instructions/takt-marp-supervise-research.md +23 -0
- package/templates/project/facets/instructions/takt-marp-visual-generate.md +15 -15
- package/templates/project/facets/knowledge/takt-marp-repo-conventions.md +26 -4
- package/templates/project/facets/output-contracts/takt-marp-normalized-brief.md +19 -1
- package/templates/project/facets/output-contracts/takt-marp-open-questions.md +48 -0
- package/templates/project/facets/output-contracts/takt-marp-reference-analysis.md +52 -0
- package/templates/project/facets/output-contracts/takt-marp-research-claims.md +50 -0
- package/templates/project/facets/output-contracts/takt-marp-research-sources.md +50 -0
- package/templates/project/facets/output-contracts/takt-marp-research-supervision.md +49 -0
- package/templates/project/facets/output-contracts/takt-marp-section-compose.md +36 -0
- package/templates/project/facets/output-contracts/takt-marp-slide-blueprint.md +44 -0
- package/templates/project/facets/output-contracts/takt-marp-slide-plan.md +30 -2
- package/templates/project/facets/policies/takt-marp-general-slide-quality.md +1 -1
- package/templates/project/facets/policies/takt-marp-slide-quality.md +6 -4
- package/templates/project/facets/policies/takt-marp-visual-composition.md +87 -0
- package/templates/project/workflows/takt-marp-slide-compose.yaml +37 -18
- package/templates/project/workflows/takt-marp-slide-plan.yaml +32 -4
- package/templates/project/workflows/takt-marp-slide-polish.yaml +5 -5
- package/templates/project/workflows/takt-marp-slide-research.yaml +81 -0
- package/scripts/lib/takt-marp-project-init.mjs +0 -81
- package/templates/project/facets/instructions/takt-marp-compose-slides.md +0 -35
- 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:
|
|
5
|
-
- Date:
|
|
6
|
-
- Duration:
|
|
7
|
-
- Venue:
|
|
8
|
-
-
|
|
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
|
|
12
|
+
Marp slide TAKT workflow が、情報量の多い講義ブリーフから normalized brief、reference analysis、coverage matrix、slide blueprint、section 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
|
|
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
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
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
|
-
-
|
|
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
|
+
"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 {
|
|
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
|
|
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 = ["
|
|
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
|
-
"
|
|
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
|
|
60
|
+
function ejectUsage() {
|
|
60
61
|
return [
|
|
61
|
-
"Usage: takt-marp
|
|
62
|
+
"Usage: takt-marp eject [dir] [--force|--overwrite]",
|
|
62
63
|
"",
|
|
63
|
-
"
|
|
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
|
-
|
|
132
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
158
|
+
const { created, overwritten } = await ejectProject({ targetDir, force });
|
|
175
159
|
|
|
176
|
-
const lines = [`
|
|
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
|
-
" -
|
|
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.
|
|
202
|
-
// the
|
|
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 === "
|
|
304
|
-
return await
|
|
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
|
+
}
|