sdd-forge 0.1.0-alpha.702 → 0.1.0-alpha.774
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.md +10 -15
- package/package.json +7 -3
- package/src/AGENTS.md +58 -11
- package/src/api.js +12 -0
- package/src/docs/commands/agents.js +1 -1
- package/src/docs/commands/data.js +2 -2
- package/src/docs/commands/forge.js +1 -1
- package/src/docs/commands/init.js +2 -1
- package/src/docs/commands/readme.js +2 -1
- package/src/docs/commands/review.js +2 -2
- package/src/docs/commands/scan.js +2 -5
- package/src/docs/commands/text.js +2 -2
- package/src/docs/commands/translate.js +1 -1
- package/src/docs/data/docs.js +2 -2
- package/src/docs/lib/command-context.js +3 -1
- package/src/docs/lib/data-source-loader.js +26 -14
- package/src/docs/lib/resolver-factory.js +12 -8
- package/src/docs/lib/template-merger.js +11 -7
- package/src/flow/commands/merge.js +6 -6
- package/src/flow/commands/report.js +68 -12
- package/src/flow/commands/review.js +26 -19
- package/src/flow/lib/get-check.js +6 -8
- package/src/flow/lib/get-context.js +3 -2
- package/src/flow/lib/get-guardrail.js +2 -2
- package/src/flow/lib/get-prompt.js +7 -25
- package/src/flow/lib/get-status.js +53 -26
- package/src/flow/lib/get-test-result.js +90 -0
- package/src/flow/lib/run-finalize.js +55 -15
- package/src/flow/lib/run-gate.js +73 -14
- package/src/flow/lib/run-impl-confirm.js +7 -3
- package/src/flow/lib/run-prepare-spec.js +29 -15
- package/src/flow/lib/run-resume.js +2 -0
- package/src/flow/lib/run-retro.js +8 -7
- package/src/flow/lib/run-review.js +6 -0
- package/src/flow/lib/run-sync.js +4 -3
- package/src/flow/lib/set-auto.js +3 -2
- package/src/flow/lib/set-init.js +34 -0
- package/src/flow/lib/set-issue.js +8 -3
- package/src/flow/lib/set-metric.js +5 -6
- package/src/flow/lib/set-req.js +10 -3
- package/src/flow/lib/set-step.js +5 -0
- package/src/flow/lib/set-summary.js +10 -1
- package/src/flow/registry.js +29 -8
- package/src/flow.js +89 -29
- package/src/help.js +2 -0
- package/src/lib/agent.js +10 -10
- package/src/lib/cli.js +4 -0
- package/src/lib/config.js +16 -3
- package/src/lib/constants.js +109 -0
- package/src/lib/flow-state.js +324 -57
- package/src/lib/git-helpers.js +27 -6
- package/src/lib/lint.js +3 -2
- package/src/lib/log.js +15 -2
- package/src/lib/presets.js +63 -7
- package/src/lib/process.js +5 -8
- package/src/lib/types.js +1 -1
- package/src/loader.js +36 -0
- package/src/locale/en/messages.json +1 -1
- package/src/locale/en/ui.json +1 -0
- package/src/locale/ja/messages.json +1 -1
- package/src/locale/ja/ui.json +1 -0
- package/src/metrics/commands/token.js +416 -0
- package/src/metrics.js +38 -0
- package/src/presets/cakephp2/data/config.js +11 -9
- package/src/presets/cakephp2/data/email.js +5 -4
- package/src/presets/cakephp2/data/libs.js +4 -3
- package/src/presets/cakephp2/data/tests.js +2 -1
- package/src/presets/cakephp2/data/views.js +2 -1
- package/src/presets/laravel/data/commands.js +2 -1
- package/src/presets/laravel/data/config.js +6 -5
- package/src/presets/laravel/data/controllers.js +2 -1
- package/src/presets/laravel/data/models.js +2 -1
- package/src/presets/laravel/data/routes.js +2 -1
- package/src/presets/laravel/data/tables.js +2 -1
- package/src/presets/lib/path-match.js +36 -0
- package/src/presets/mysql/NOTICE +26 -0
- package/src/presets/mysql/guardrail.json +108 -0
- package/src/presets/mysql/preset.json +6 -0
- package/src/presets/nextjs/data/components.js +3 -2
- package/src/presets/nextjs/data/routes.js +3 -2
- package/src/presets/symfony/data/commands.js +2 -1
- package/src/presets/symfony/data/config.js +5 -4
- package/src/presets/symfony/data/controllers.js +2 -1
- package/src/presets/symfony/data/entities.js +2 -1
- package/src/presets/symfony/data/routes.js +2 -1
- package/src/presets/symfony/data/tables.js +2 -1
- package/src/presets/webapp/NOTICE +16 -0
- package/src/presets/webapp/guardrail.json +32 -0
- package/src/sdd-forge.js +13 -3
- package/src/setup.js +11 -11
- package/src/templates/config.example.json +7 -7
- package/src/templates/partials/core-principle.md +6 -0
- package/src/templates/skills/sdd-forge.flow-finalize/SKILL.md +2 -0
- package/src/templates/skills/sdd-forge.flow-impl/SKILL.md +3 -1
- package/src/templates/skills/sdd-forge.flow-plan/SKILL.md +37 -13
- package/src/templates/skills/sdd-forge.flow-status/SKILL.md +2 -1
- package/src/upgrade.js +2 -2
- package/src/flow/lib/phases.js +0 -15
package/README.md
CHANGED
|
@@ -88,9 +88,9 @@ If you already have source code, generate documentation to get a complete pictur
|
|
|
88
88
|
|
|
89
89
|
| Command | Phase |
|
|
90
90
|
|---|---|
|
|
91
|
-
| `$sdd-forge
|
|
92
|
-
| `$sdd-forge
|
|
93
|
-
| `$sdd-forge
|
|
91
|
+
| `$sdd-forge.flow-plan` | plan (specification) |
|
|
92
|
+
| `$sdd-forge.flow-impl` | implement (coding + review) |
|
|
93
|
+
| `$sdd-forge.flow-finalize` | finalize (commit, merge, docs sync, cleanup) |
|
|
94
94
|
|
|
95
95
|
## Commands
|
|
96
96
|
|
|
@@ -98,12 +98,6 @@ If you already have source code, generate documentation to get a complete pictur
|
|
|
98
98
|
|---|---|
|
|
99
99
|
| `setup` | Register project and generate config |
|
|
100
100
|
| `docs build` | Run the full documentation pipeline |
|
|
101
|
-
| `docs readme` | Generate `README.md` from `docs/` |
|
|
102
|
-
| `docs review` | Check documentation quality |
|
|
103
|
-
| `flow prepare` | Create spec and branch |
|
|
104
|
-
| `flow get status` | Show flow progress |
|
|
105
|
-
| `presets` | List available presets |
|
|
106
|
-
| `help` | Show all commands |
|
|
107
101
|
|
|
108
102
|
See `sdd-forge help` or the [command reference](docs/cli_commands.md) for the full list.
|
|
109
103
|
|
|
@@ -129,12 +123,13 @@ See the [configuration reference](docs/configuration.md) for details.
|
|
|
129
123
|
<!-- {{data("cli.docs.chapters", {header: "", labels: "Chapter|Summary", ignoreError: true})}} -->
|
|
130
124
|
| Chapter | Summary |
|
|
131
125
|
| --- | --- |
|
|
132
|
-
| [Tool Overview and Architecture](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/overview.md) |
|
|
133
|
-
| [Technology Stack and Operations](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/stack_and_ops.md) |
|
|
134
|
-
| [Project Structure](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/project_structure.md) |
|
|
135
|
-
| [CLI Command Reference](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/cli_commands.md) |
|
|
136
|
-
| [Configuration and Customization](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/configuration.md) |
|
|
137
|
-
| [Internal Design](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/internal_design.md) |
|
|
126
|
+
| [Tool Overview and Architecture](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/overview.md) | This chapter introduces sdd-forge, a CLI tool that automates documentation generation from source code analysis and e… |
|
|
127
|
+
| [Technology Stack and Operations](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/stack_and_ops.md) | This chapter covers the technology stack, dependency management, deployment, and operations procedures for sdd-forge,… |
|
|
128
|
+
| [Project Structure](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/project_structure.md) | This chapter describes the overall directory organization of the sdd-forge project, which is structured around seven … |
|
|
129
|
+
| [CLI Command Reference](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/cli_commands.md) | This chapter documents 8 CLI entrypoints and routed commands in the analyzed files: setup, check, check freshness, do… |
|
|
130
|
+
| [Configuration and Customization](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/configuration.md) | sdd-forge is configured through a single project-level JSON file (.sdd-forge/config.json) and optionally extended by … |
|
|
131
|
+
| [Internal Design](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/internal_design.md) | This chapter describes the internal structure of sdd-forge: a layered CLI tool built entirely on Node.js built-in mod… |
|
|
132
|
+
| [Preset Creation Guide](https://github.com/SpreadWorks/sdd-forge/blob/main/docs/creating_presets.md) | |
|
|
138
133
|
<!-- {{/data}} -->
|
|
139
134
|
|
|
140
135
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdd-forge",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.774",
|
|
4
4
|
"description": "Spec-Driven Development tooling for automated documentation generation",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,12 +10,17 @@
|
|
|
10
10
|
"bin": {
|
|
11
11
|
"sdd-forge": "./src/sdd-forge.js"
|
|
12
12
|
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/sdd-forge.js",
|
|
15
|
+
"./api": "./src/api.js",
|
|
16
|
+
"./presets/*": "./src/presets/*"
|
|
17
|
+
},
|
|
13
18
|
"files": [
|
|
14
19
|
"src/",
|
|
15
20
|
"!src/presets/*/tests/"
|
|
16
21
|
],
|
|
17
22
|
"engines": {
|
|
18
|
-
"node": ">=18.
|
|
23
|
+
"node": ">=18.19.0"
|
|
19
24
|
},
|
|
20
25
|
"keywords": [
|
|
21
26
|
"sdd",
|
|
@@ -25,7 +30,6 @@
|
|
|
25
30
|
"technical-docs"
|
|
26
31
|
],
|
|
27
32
|
"scripts": {
|
|
28
|
-
"test": "node tests/run.js",
|
|
29
33
|
"test:unit": "node tests/run.js --scope unit",
|
|
30
34
|
"test:e2e": "node tests/run.js --scope e2e",
|
|
31
35
|
"test:acceptance": "node tests/acceptance/run.js"
|
package/src/AGENTS.md
CHANGED
|
@@ -43,7 +43,7 @@ src/
|
|
|
43
43
|
│ ├── set.js set サブディスパッチャ
|
|
44
44
|
│ ├── run.js run サブディスパッチャ
|
|
45
45
|
│ ├── get/ status, resolve-context, check, prompt, qa-count, guardrail, issue
|
|
46
|
-
│ ├── set/ step, request, issue, note, summary, req, metric
|
|
46
|
+
│ ├── set/ step, request, issue, note, summary, req, metric
|
|
47
47
|
│ ├── run/ prepare-spec, gate, review, impl-confirm, finalize, sync
|
|
48
48
|
│ └── commands/ 内部ヘルパー(merge, cleanup, review の実体)
|
|
49
49
|
├── spec/commands/ init, gate, guardrail(flow/run/prepare-spec, gate が内部で呼ぶ)
|
|
@@ -152,16 +152,12 @@ base → api → graphql
|
|
|
152
152
|
```
|
|
153
153
|
presets/<key>/
|
|
154
154
|
├── preset.json プリセット定義
|
|
155
|
-
├── data/ DataSource
|
|
155
|
+
├── data/ DataSource クラス群(scan + resolve を兼ねる)
|
|
156
156
|
│ ├── config.js 設定解析
|
|
157
157
|
│ ├── controllers.js コントローラ解析
|
|
158
158
|
│ └── ...
|
|
159
|
-
├── scan/ scan パーサー群
|
|
160
|
-
│ ├── routes.js ルート解析
|
|
161
|
-
│ ├── config.js 設定解析
|
|
162
|
-
│ └── ...
|
|
163
159
|
├── tests/ プリセット固有テスト
|
|
164
|
-
│ ├── unit/ ユニットテスト(
|
|
160
|
+
│ ├── unit/ ユニットテスト(DataSource の match/parse I/O テスト等)
|
|
165
161
|
│ ├── e2e/ E2E テスト(統合スキャンテスト等)
|
|
166
162
|
│ └── acceptance/ acceptance テスト(preset ローカル fixture + test.js)
|
|
167
163
|
└── templates/ 章テンプレート
|
|
@@ -177,21 +173,36 @@ presets/<key>/
|
|
|
177
173
|
|
|
178
174
|
## プリセット作成ルール
|
|
179
175
|
|
|
176
|
+
### MUST: プリセット作成ガイドとの同期
|
|
177
|
+
|
|
178
|
+
プリセットの仕様・作成手順・契約(`preset.json` スキーマ、DataSource のインターフェース、`match()` / `parse()` の引数契約、resolve メソッドの戻り値型、import ルール、テンプレートディレクティブ、scan/data ペアリング規則等)を変更した場合、**`.sdd-forge/templates/*/docs/creating_presets.md`(全言語)を同じコミット内で必ず更新すること。**
|
|
179
|
+
|
|
180
|
+
対象となる変更の例:
|
|
181
|
+
|
|
182
|
+
- `src/api.js` の公開クラス追加・削除・シグネチャ変更
|
|
183
|
+
- `package.json` の `exports` 変更
|
|
184
|
+
- `DataSource` / `Scannable` / `AnalysisEntry` / `Table` / `MarkdownText` のインターフェース変更
|
|
185
|
+
- `preset.json` スキーマの追加・変更
|
|
186
|
+
- `data/` loader のロード規約変更
|
|
187
|
+
- テンプレートディレクティブ(`{%extends%}`, `{%block%}`, `{{data}}`, `{{text}}`)の文法・挙動変更
|
|
188
|
+
- プリセット作成手順・MUST ルールの追加・変更
|
|
189
|
+
|
|
190
|
+
本ガイドは AI エージェントがプリセットを作成する際の単一の参照ドキュメントである。**ガイドが実装とズレるとプリセット作成が破綻する**ため、実装変更と文書更新を同一 PR で行うこと。別 PR に分割してはならない。
|
|
191
|
+
|
|
180
192
|
### MUST: プリセット作成手順(トップダウン設計)
|
|
181
193
|
|
|
182
194
|
プリセットの構成要素は以下の順序で作成すること:
|
|
183
195
|
|
|
184
196
|
1. **テンプレート** (`templates/`) — どんなドキュメントを出力するか定義する
|
|
185
|
-
2. **DataSource** (`data/`) —
|
|
186
|
-
3. **scan パーサー** (`scan/`) — DataSource にデータを供給するパーサーを実装する
|
|
197
|
+
2. **DataSource** (`data/`) — テンプレートが必要とするデータを `Scannable` DataSource の `match` / `parse` / `scan` で収集し、resolve メソッドで提供する
|
|
187
198
|
|
|
188
|
-
消費者(テンプレート)→
|
|
199
|
+
消費者(テンプレート)→ 生産者(DataSource)の順に作ることで、不要な解析を書かず、必要なデータの漏れがなくなる。scan 処理は独立した `scan/` ディレクトリではなく DataSource クラス自身が `Scannable` mixin 経由で担う。
|
|
189
200
|
|
|
190
201
|
### MUST: プリセットテストの作成
|
|
191
202
|
|
|
192
203
|
プリセットは `tests/` ディレクトリを含むこと。
|
|
193
204
|
|
|
194
|
-
- `tests/unit/` —
|
|
205
|
+
- `tests/unit/` — DataSource の `match` / `parse` I/O テスト。最小限のフィクスチャを `createTmpDir()` で作成し、入出力を検証する
|
|
195
206
|
- `tests/e2e/` — preset.json の scan 設定検証、フルスキャンパイプラインテスト
|
|
196
207
|
- `tests/acceptance/test.js` — preset ローカル fixture を使う acceptance テスト。共有処理は `tests/acceptance/lib/` を使う
|
|
197
208
|
- テンプレートを作成・変更した場合は acceptance テストも実装し、実行すること
|
|
@@ -295,6 +306,42 @@ export default class SchemaSource extends DataSource {
|
|
|
295
306
|
- analysis にデータがあれば動作する(なければ null を返す)
|
|
296
307
|
- **MUST**: このパターンを使う場合、読む analysis キーを書く scan DataSource がチェーン内に存在する必要がある。対応する scan がないなら data DataSource を作ってはならない
|
|
297
308
|
|
|
309
|
+
### DataSource メソッドの戻り値型(設計方針)
|
|
310
|
+
|
|
311
|
+
DataSource の resolve メソッド(`list()` / `tables()` 等、テンプレートから `{{data}}` で呼ばれるメソッド)の戻り値は、**レンダリング可能な値クラスのインスタンスまたは null** とする。生の Markdown 文字列を返してはならない。
|
|
312
|
+
|
|
313
|
+
プロジェクトは TypeScript を採用しないため、値の構造は OOP(クラス)で表現する(プロジェクト `CLAUDE.md` の「OOP による型表現」を参照)。戻り値には以下の専用クラスを用いる。
|
|
314
|
+
|
|
315
|
+
- `Table` — 表形式データ(labels と rows を保持、列数の整合を invariant として強制)
|
|
316
|
+
- `MarkdownText` — そのまま埋め込む Markdown 断片
|
|
317
|
+
- (必要に応じて)`BulletList` / `Heading` 等の追加クラス
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
import { Table } from "../../../docs/lib/renderable.js";
|
|
321
|
+
|
|
322
|
+
export default class RoutesSource extends DataSource {
|
|
323
|
+
list(analysis, labels) {
|
|
324
|
+
const routes = analysis.routes?.entries || [];
|
|
325
|
+
if (routes.length === 0) return null;
|
|
326
|
+
const rows = routes.map(r => [r.pattern, r.controller, r.action]);
|
|
327
|
+
return new Table(labels, rows);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**利点**:
|
|
333
|
+
|
|
334
|
+
- コンストラクタで invariant を強制(例: Table は列数一致、labels 非空)
|
|
335
|
+
- `instanceof` による確実な型判別
|
|
336
|
+
- `toMarkdown()` を型に所属させることで、出力形式の拡張(将来の `toHtml()` 等)が Open/Closed に従う
|
|
337
|
+
- テンプレート展開層は `result.toMarkdown()` をポリモルフィックに呼ぶだけで済む
|
|
338
|
+
- オブジェクトリテラル(`{ type: "table", ... }`)のような discriminated union もどきは採用しない
|
|
339
|
+
|
|
340
|
+
**禁止事項**:
|
|
341
|
+
|
|
342
|
+
- 基底 `DataSource` の `toMarkdownTable()` のような Markdown 文字列生成ヘルパーを新設してはならない(既存のものは `Table` クラスへの段階移行対象)
|
|
343
|
+
- 戻り値として生の文字列を返してはならない。構造が単なるテキストなら `MarkdownText` でラップする
|
|
344
|
+
|
|
298
345
|
---
|
|
299
346
|
|
|
300
347
|
## テンプレート構文
|
package/src/api.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/api.js — sdd-forge public API entry point
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the base classes needed by external presets and
|
|
5
|
+
* .sdd-forge/data/ DataSource overrides.
|
|
6
|
+
*
|
|
7
|
+
* Usage (from external preset files):
|
|
8
|
+
* import { DataSource, Scannable, AnalysisEntry } from 'sdd-forge/api';
|
|
9
|
+
*/
|
|
10
|
+
export { DataSource } from "./docs/lib/data-source.js";
|
|
11
|
+
export { Scannable } from "./docs/lib/scan-source.js";
|
|
12
|
+
export { AnalysisEntry } from "./docs/lib/analysis-entry.js";
|
|
@@ -178,7 +178,7 @@ async function main(ctx) {
|
|
|
178
178
|
|
|
179
179
|
// Load generated docs as context (instead of raw analysis.json)
|
|
180
180
|
const docsDir = path.join(root, "docs");
|
|
181
|
-
const chapterFiles = getChapterFiles(docsDir, { type: ctx.type, configChapters: ctx.config?.chapters });
|
|
181
|
+
const chapterFiles = getChapterFiles(docsDir, { type: ctx.type, configChapters: ctx.config?.chapters, projectRoot: root });
|
|
182
182
|
const docsContent = chapterFiles.map((f) => readText(path.join(docsDir, f))).join("\n\n");
|
|
183
183
|
const readmeContent = readText(path.join(srcRoot, "README.md"));
|
|
184
184
|
const combinedDocs = [docsContent, readmeContent].filter(Boolean).join("\n\n---\n\n");
|
|
@@ -76,7 +76,7 @@ export function populateFromAnalysis(root, analysis, resolveFn, opts) {
|
|
|
76
76
|
const docsDir = path.join(root, "docs");
|
|
77
77
|
const changedFiles = [];
|
|
78
78
|
|
|
79
|
-
const docsFiles = getChapterFiles(docsDir, { type: opts?.type, configChapters: opts?.configChapters });
|
|
79
|
+
const docsFiles = getChapterFiles(docsDir, { type: opts?.type, configChapters: opts?.configChapters, projectRoot: root });
|
|
80
80
|
|
|
81
81
|
for (const file of docsFiles) {
|
|
82
82
|
const filePath = path.join(docsDir, file);
|
|
@@ -137,7 +137,7 @@ async function main(ctx) {
|
|
|
137
137
|
throw new Error(t("messages:data.resolverFailed", { message: err.message }));
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
const docsFiles = getChapterFiles(docsDir, { type, configChapters: ctx.config?.chapters });
|
|
140
|
+
const docsFiles = getChapterFiles(docsDir, { type, configChapters: ctx.config?.chapters, projectRoot: root });
|
|
141
141
|
|
|
142
142
|
// Determine relative path prefix for lang.links context
|
|
143
143
|
const docsDirRel = path.relative(root, docsDir).replace(/\\/g, "/");
|
|
@@ -44,7 +44,7 @@ const DEFAULT_MODE = "local";
|
|
|
44
44
|
|
|
45
45
|
function getTargetFiles(root, type, configChapters) {
|
|
46
46
|
const docsDir = path.join(root, "docs");
|
|
47
|
-
return getChapterFiles(docsDir, { type, configChapters }).map((f) => `docs/${f}`);
|
|
47
|
+
return getChapterFiles(docsDir, { type, configChapters, projectRoot: root }).map((f) => `docs/${f}`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -169,12 +169,13 @@ function main(ctx) {
|
|
|
169
169
|
? configLangs
|
|
170
170
|
: [...configLangs, "en"];
|
|
171
171
|
const configChapters = config?.chapters;
|
|
172
|
-
const chaptersOrder = resolveChaptersOrder(type, configChapters);
|
|
172
|
+
const chaptersOrder = resolveChaptersOrder(type, configChapters, root);
|
|
173
173
|
|
|
174
174
|
const resolutions = resolveTemplates(type, lang, {
|
|
175
175
|
projectLocalDir,
|
|
176
176
|
fallbackLangs,
|
|
177
177
|
chaptersOrder,
|
|
178
|
+
projectRoot: root,
|
|
178
179
|
});
|
|
179
180
|
|
|
180
181
|
// 解決結果からチャプターを生成(README は除外)
|
|
@@ -75,11 +75,12 @@ async function main(ctx) {
|
|
|
75
75
|
|
|
76
76
|
// ボトムアップでテンプレート解決
|
|
77
77
|
const configChapters = config?.chapters;
|
|
78
|
-
const chaptersOrder = resolveChaptersOrder(type, configChapters);
|
|
78
|
+
const chaptersOrder = resolveChaptersOrder(type, configChapters, root);
|
|
79
79
|
const resolutions = resolveTemplates(type, lang, {
|
|
80
80
|
projectLocalDir,
|
|
81
81
|
fallbackLangs,
|
|
82
82
|
chaptersOrder,
|
|
83
|
+
projectRoot: root,
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
const readmeRes = resolutions.find((r) => r.fileName === "README.md");
|
|
@@ -114,7 +114,7 @@ function main() {
|
|
|
114
114
|
// Discover chapter files
|
|
115
115
|
let chapterFiles = [];
|
|
116
116
|
if (fs.existsSync(targetDir) && fs.statSync(targetDir).isDirectory()) {
|
|
117
|
-
chapterFiles = getChapterFiles(targetDir, { type, configChapters: config.chapters });
|
|
117
|
+
chapterFiles = getChapterFiles(targetDir, { type, configChapters: config.chapters, projectRoot: root });
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
if (chapterFiles.length === 0) {
|
|
@@ -232,7 +232,7 @@ function main() {
|
|
|
232
232
|
reportFail("messages:review.langDirMissing", { lang });
|
|
233
233
|
continue;
|
|
234
234
|
}
|
|
235
|
-
const langFiles = getChapterFiles(langDir, { type, configChapters: config.chapters });
|
|
235
|
+
const langFiles = getChapterFiles(langDir, { type, configChapters: config.chapters, projectRoot: root });
|
|
236
236
|
if (langFiles.length === 0) {
|
|
237
237
|
reportFail("messages:review.langNoChapters", { lang });
|
|
238
238
|
continue;
|
|
@@ -22,7 +22,7 @@ import crypto from "crypto";
|
|
|
22
22
|
import { fileURLToPath } from "url";
|
|
23
23
|
import { runIfDirect } from "../../lib/entrypoint.js";
|
|
24
24
|
import { repoRoot, parseArgs } from "../../lib/cli.js";
|
|
25
|
-
import {
|
|
25
|
+
import { sddOutputDir } from "../../lib/config.js";
|
|
26
26
|
import { collectFiles } from "../lib/scanner.js";
|
|
27
27
|
import { loadDataSources } from "../lib/data-source-loader.js";
|
|
28
28
|
import { presetByLeaf, resolveChainSafe, resolveMultiChains } from "../../lib/presets.js";
|
|
@@ -289,7 +289,7 @@ async function main(ctx) {
|
|
|
289
289
|
const currentFilePaths = new Set(files.map((f) => f.relPath));
|
|
290
290
|
|
|
291
291
|
// 3. Load Scannable DataSources from preset chain
|
|
292
|
-
const chains = resolveMultiChains(types);
|
|
292
|
+
const chains = resolveMultiChains(types, root);
|
|
293
293
|
const seenDirs = new Set();
|
|
294
294
|
|
|
295
295
|
let dataSources = new Map();
|
|
@@ -301,9 +301,6 @@ async function main(ctx) {
|
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
const projectDataDir = sddDataDir(root);
|
|
305
|
-
dataSources = await loadScanSources(projectDataDir, dataSources);
|
|
306
|
-
|
|
307
304
|
// 3b. DataSource hash detection: clear entry hashes for categories whose DataSource changed
|
|
308
305
|
if (existing) {
|
|
309
306
|
for (const [name, source] of dataSources) {
|
|
@@ -489,7 +489,7 @@ export async function textFillFromAnalysis(root, analysis, commandId, srcRoot, o
|
|
|
489
489
|
const docsDir = path.join(root, "docs");
|
|
490
490
|
const resolvedSrcRoot = srcRoot || root;
|
|
491
491
|
|
|
492
|
-
const targetFiles = opts?.files || getChapterFiles(docsDir, { type, configChapters: cfg.chapters });
|
|
492
|
+
const targetFiles = opts?.files || getChapterFiles(docsDir, { type, configChapters: cfg.chapters, projectRoot: root });
|
|
493
493
|
|
|
494
494
|
const changedFiles = [];
|
|
495
495
|
let totalFilled = 0;
|
|
@@ -640,7 +640,7 @@ async function main(ctx) {
|
|
|
640
640
|
if (ctx.files) {
|
|
641
641
|
targetFiles = ctx.files;
|
|
642
642
|
} else {
|
|
643
|
-
targetFiles = getChapterFiles(docsDir, { type: ctx.type, configChapters: cfg.chapters });
|
|
643
|
+
targetFiles = getChapterFiles(docsDir, { type: ctx.type, configChapters: cfg.chapters, projectRoot: root });
|
|
644
644
|
|
|
645
645
|
// Diff-based chapter filtering: skip chapters whose entries are unchanged
|
|
646
646
|
if (!ctx.force) {
|
|
@@ -175,7 +175,7 @@ async function main(ctx) {
|
|
|
175
175
|
throw new Error("docs/ directory not found. Run 'sdd-forge init' first.");
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
const sourceFiles = getChapterFiles(docsDir, { type: ctx.type, configChapters: ctx.config?.chapters });
|
|
178
|
+
const sourceFiles = getChapterFiles(docsDir, { type: ctx.type, configChapters: ctx.config?.chapters, projectRoot: root });
|
|
179
179
|
const readmePath = path.join(root, "README.md");
|
|
180
180
|
const hasReadme = fs.existsSync(readmePath);
|
|
181
181
|
|
package/src/docs/data/docs.js
CHANGED
|
@@ -170,7 +170,7 @@ export default class DocsSource extends DataSource {
|
|
|
170
170
|
const docsDir = this._docsDir || path.join(this._root, "docs");
|
|
171
171
|
if (!fs.existsSync(docsDir)) return null;
|
|
172
172
|
|
|
173
|
-
const files = getChapterFiles(docsDir, { type: this._type, configChapters: this._configChapters });
|
|
173
|
+
const files = getChapterFiles(docsDir, { type: this._type, configChapters: this._configChapters, projectRoot: this._root });
|
|
174
174
|
|
|
175
175
|
if (files.length === 0) return null;
|
|
176
176
|
|
|
@@ -240,7 +240,7 @@ export default class DocsSource extends DataSource {
|
|
|
240
240
|
const docsDir = this._docsDir || path.join(this._root, "docs");
|
|
241
241
|
if (!fs.existsSync(docsDir)) return null;
|
|
242
242
|
|
|
243
|
-
const files = getChapterFiles(docsDir, { type: this._type, configChapters: this._configChapters });
|
|
243
|
+
const files = getChapterFiles(docsDir, { type: this._type, configChapters: this._configChapters, projectRoot: this._root });
|
|
244
244
|
if (files.length <= 1) return null;
|
|
245
245
|
|
|
246
246
|
// Find current file in the chapter list
|
|
@@ -128,6 +128,7 @@ export function loadFullAnalysis(root) {
|
|
|
128
128
|
* @param {Object} [options]
|
|
129
129
|
* @param {string} [options.type] - プロジェクトタイプ(例: "cli/node-cli")
|
|
130
130
|
* @param {string[]} [options.configChapters] - config.json の chapters 配列(最優先)
|
|
131
|
+
* @param {string} [options.projectRoot] - プロジェクトルート(.sdd-forge/presets/ 検索用)
|
|
131
132
|
* @returns {string[]} ファイル名の配列(順序付き)
|
|
132
133
|
*/
|
|
133
134
|
export function getChapterFiles(docsDir, options) {
|
|
@@ -135,10 +136,11 @@ export function getChapterFiles(docsDir, options) {
|
|
|
135
136
|
|
|
136
137
|
const type = options?.type;
|
|
137
138
|
const configChapters = options?.configChapters;
|
|
139
|
+
const projectRoot = options?.projectRoot;
|
|
138
140
|
const EXCLUDE = new Set(["README.md", "AGENTS.sdd.md", "layout.md"]);
|
|
139
141
|
|
|
140
142
|
if (type || configChapters?.length) {
|
|
141
|
-
const chapters = resolveChaptersOrder(type || "base", configChapters);
|
|
143
|
+
const chapters = resolveChaptersOrder(type || "base", configChapters, projectRoot);
|
|
142
144
|
if (chapters.length > 0) {
|
|
143
145
|
const existing = chapters.filter((f) => fs.existsSync(path.join(docsDir, f)));
|
|
144
146
|
if (existing.length > 0) return existing;
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
|
-
import {
|
|
9
|
+
import { pathToFileURL } from "url";
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const MAX_DATA_SOURCE_FILES = 1000;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Load DataSource classes from a directory and instantiate them.
|
|
@@ -23,22 +23,34 @@ const logger = createLogger("datasource");
|
|
|
23
23
|
export async function loadDataSources(dataDir, opts) {
|
|
24
24
|
const { existing, onInstance } = opts || {};
|
|
25
25
|
const sources = new Map(existing || []);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = await fs.promises.readdir(dataDir);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err.code === "ENOENT") return sources;
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
const files = entries.filter((f) => f.endsWith(".js"));
|
|
34
|
+
if (files.length > MAX_DATA_SOURCE_FILES) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`DataSource directory ${dataDir} contains ${files.length} files, exceeding limit ${MAX_DATA_SOURCE_FILES}`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
29
39
|
for (const file of files) {
|
|
30
40
|
const name = path.basename(file, ".js");
|
|
41
|
+
const filePath = path.join(dataDir, file);
|
|
42
|
+
let mod;
|
|
31
43
|
try {
|
|
32
|
-
|
|
33
|
-
const Source = mod.default;
|
|
34
|
-
if (typeof Source === "function") {
|
|
35
|
-
const instance = new Source();
|
|
36
|
-
instance._sourceFilePath = path.join(dataDir, file);
|
|
37
|
-
if (onInstance && onInstance(instance, name) === false) continue;
|
|
38
|
-
sources.set(name, instance);
|
|
39
|
-
}
|
|
44
|
+
mod = await import(pathToFileURL(filePath).href);
|
|
40
45
|
} catch (err) {
|
|
41
|
-
|
|
46
|
+
throw new Error(`failed to load DataSource at ${filePath}: ${err.message}`, { cause: err });
|
|
47
|
+
}
|
|
48
|
+
const Source = mod.default;
|
|
49
|
+
if (typeof Source === "function") {
|
|
50
|
+
const instance = new Source();
|
|
51
|
+
instance._sourceFilePath = filePath;
|
|
52
|
+
if (onInstance && onInstance(instance, name) === false) continue;
|
|
53
|
+
sources.set(name, instance);
|
|
42
54
|
}
|
|
43
55
|
}
|
|
44
56
|
return sources;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import fs from "fs";
|
|
10
10
|
import path from "path";
|
|
11
|
-
import { sddDir
|
|
11
|
+
import { sddDir } from "../../lib/config.js";
|
|
12
12
|
import { loadDataSources as loadDataSourcesBase } from "./data-source-loader.js";
|
|
13
13
|
import { resolveMultiChains, resolveChainSafe } from "../../lib/presets.js";
|
|
14
14
|
import { createLogger } from "../../lib/progress.js";
|
|
@@ -66,7 +66,7 @@ const COMMON_DATA_DIR = path.resolve(
|
|
|
66
66
|
* @param {Object} ctx - 共有コンテキスト
|
|
67
67
|
* @returns {Promise<Map<string, Object>>} DataSource マップ
|
|
68
68
|
*/
|
|
69
|
-
async function loadChainDataSources(chain,
|
|
69
|
+
async function loadChainDataSources(chain, ctx) {
|
|
70
70
|
let dataSources = await loadDataSources(COMMON_DATA_DIR, ctx);
|
|
71
71
|
|
|
72
72
|
for (const preset of chain) {
|
|
@@ -74,10 +74,6 @@ async function loadChainDataSources(chain, root, ctx) {
|
|
|
74
74
|
dataSources = await loadDataSources(dataDir, ctx, dataSources);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// プロジェクト固有 DataSource(最高優先)
|
|
78
|
-
const projectDataDir = sddDataDir(root);
|
|
79
|
-
dataSources = await loadDataSources(projectDataDir, ctx, dataSources);
|
|
80
|
-
|
|
81
77
|
return dataSources;
|
|
82
78
|
}
|
|
83
79
|
|
|
@@ -92,11 +88,19 @@ async function loadChainDataSources(chain, root, ctx) {
|
|
|
92
88
|
* @returns {Promise<{ resolve: (preset: string, source: string, method: string, analysis: Object, labels: string[]) => string|null }>}
|
|
93
89
|
*/
|
|
94
90
|
export async function createResolver(type, root, opts) {
|
|
91
|
+
// Warn about deprecated .sdd-forge/data/ directory
|
|
92
|
+
if (root) {
|
|
93
|
+
const deprecatedDataDir = path.join(root, ".sdd-forge", "data");
|
|
94
|
+
if (fs.existsSync(deprecatedDataDir)) {
|
|
95
|
+
process.stderr.write(`[sdd-forge] WARN: .sdd-forge/data/ is deprecated. Move DataSources to .sdd-forge/presets/<type>/data/ instead.\n`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
95
99
|
const desc = descFactory(root);
|
|
96
100
|
const loadOverrides = () => loadOverridesFor(root);
|
|
97
101
|
const ctx = { desc, loadOverrides, root, docsDir: opts?.docsDir, type, configChapters: opts?.configChapters };
|
|
98
102
|
|
|
99
|
-
const chains = resolveMultiChains(type);
|
|
103
|
+
const chains = resolveMultiChains(type, root);
|
|
100
104
|
|
|
101
105
|
// 各チェーンの leaf key → DataSource マップ
|
|
102
106
|
const resolverMap = new Map();
|
|
@@ -104,7 +108,7 @@ export async function createResolver(type, root, opts) {
|
|
|
104
108
|
const ancestorMap = new Map();
|
|
105
109
|
for (const chain of chains) {
|
|
106
110
|
const leafKey = chain[chain.length - 1].key;
|
|
107
|
-
const dataSources = await loadChainDataSources(chain,
|
|
111
|
+
const dataSources = await loadChainDataSources(chain, ctx);
|
|
108
112
|
resolverMap.set(leafKey, dataSources);
|
|
109
113
|
// chain 内の全プリセットを ancestor マップに登録
|
|
110
114
|
for (const p of chain) {
|
|
@@ -28,9 +28,10 @@ const SPECIAL_FILES = new Set(["README.md", "AGENTS.sdd.md", "layout.md"]);
|
|
|
28
28
|
* @param {string} presetKey - preset 名(例: "cakephp2", "node-cli")
|
|
29
29
|
* @param {string} lang - ロケール(例: "ja")
|
|
30
30
|
* @param {string|null} [projectLocalDir] - プロジェクトローカルテンプレートディレクトリ
|
|
31
|
+
* @param {string} [projectRoot] - プロジェクトルート(.sdd-forge/presets/ 検索用)
|
|
31
32
|
* @returns {string[]} レイヤーディレクトリ配列(優先度高い順)
|
|
32
33
|
*/
|
|
33
|
-
export function buildLayers(presetKey, lang, projectLocalDir) {
|
|
34
|
+
export function buildLayers(presetKey, lang, projectLocalDir, projectRoot) {
|
|
34
35
|
const layers = [];
|
|
35
36
|
|
|
36
37
|
// 1. project-local(最高優先)
|
|
@@ -39,7 +40,7 @@ export function buildLayers(presetKey, lang, projectLocalDir) {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// 2. parent チェーンを leaf → root の順で追加(優先度高い順)
|
|
42
|
-
const chain = resolveChainSafe(presetKey);
|
|
43
|
+
const chain = resolveChainSafe(presetKey, projectRoot);
|
|
43
44
|
|
|
44
45
|
// chain は root → leaf の順なので、逆順(leaf → root)で追加
|
|
45
46
|
for (let i = chain.length - 1; i >= 0; i--) {
|
|
@@ -195,20 +196,21 @@ function mergeSourcesAdditive(sources) {
|
|
|
195
196
|
* @param {string|null} [opts.projectLocalDir] - プロジェクトローカルテンプレートディレクトリ
|
|
196
197
|
* @param {string[]} [opts.fallbackLangs] - フォールバック言語リスト
|
|
197
198
|
* @param {string[]} [opts.chaptersOrder] - preset.json の chapters 順序配列
|
|
199
|
+
* @param {string} [opts.projectRoot] - プロジェクトルート(.sdd-forge/presets/ 検索用)
|
|
198
200
|
* @returns {FileResolution[]}
|
|
199
201
|
*/
|
|
200
202
|
export function resolveTemplates(typePath, lang, opts = {}) {
|
|
201
|
-
const { projectLocalDir, fallbackLangs, chaptersOrder } = opts;
|
|
203
|
+
const { projectLocalDir, fallbackLangs, chaptersOrder, projectRoot } = opts;
|
|
202
204
|
|
|
203
205
|
const types = Array.isArray(typePath) ? typePath : [typePath];
|
|
204
206
|
|
|
205
207
|
// 各チェーンの leaf key ごとにレイヤーセットを構築
|
|
206
|
-
const chains = resolveMultiChains(types);
|
|
208
|
+
const chains = resolveMultiChains(types, projectRoot);
|
|
207
209
|
const chainLayerSets = chains.map((chain) => {
|
|
208
210
|
const leafKey = chain[chain.length - 1].key;
|
|
209
211
|
return {
|
|
210
212
|
leafKey,
|
|
211
|
-
layers: buildLayers(leafKey, lang, projectLocalDir),
|
|
213
|
+
layers: buildLayers(leafKey, lang, projectLocalDir, projectRoot),
|
|
212
214
|
fallbackSets: (fallbackLangs || [])
|
|
213
215
|
.filter((l) => l !== lang)
|
|
214
216
|
.map((fbLang) => ({
|
|
@@ -217,6 +219,7 @@ export function resolveTemplates(typePath, lang, opts = {}) {
|
|
|
217
219
|
leafKey,
|
|
218
220
|
fbLang,
|
|
219
221
|
deriveFallbackProjectLocalDir(projectLocalDir, fbLang),
|
|
222
|
+
projectRoot,
|
|
220
223
|
),
|
|
221
224
|
})),
|
|
222
225
|
};
|
|
@@ -359,9 +362,10 @@ function discoverFileNames(layers, fallbackSets, chaptersOrder) {
|
|
|
359
362
|
*
|
|
360
363
|
* @param {string|string[]} presetKeys - preset 名または配列
|
|
361
364
|
* @param {string[]} [configChapters] - config.json の chapters 配列(最優先)
|
|
365
|
+
* @param {string} [projectRoot] - プロジェクトルート(.sdd-forge/presets/ 検索用)
|
|
362
366
|
* @returns {string[]} 章ファイル名の順序配列
|
|
363
367
|
*/
|
|
364
|
-
export function resolveChaptersOrder(presetKeys, configChapters) {
|
|
368
|
+
export function resolveChaptersOrder(presetKeys, configChapters, projectRoot) {
|
|
365
369
|
// config.json の chapters が定義されていればプリセットを完全上書き
|
|
366
370
|
if (configChapters?.length) {
|
|
367
371
|
// Support both old string[] and new object[] formats
|
|
@@ -377,7 +381,7 @@ export function resolveChaptersOrder(presetKeys, configChapters) {
|
|
|
377
381
|
const result = [];
|
|
378
382
|
|
|
379
383
|
for (const key of keys) {
|
|
380
|
-
const chain = resolveChainSafe(key);
|
|
384
|
+
const chain = resolveChainSafe(key, projectRoot);
|
|
381
385
|
|
|
382
386
|
// chain 内で最も具体的な(leaf 側の)chapters を使用する。
|
|
383
387
|
// 子が chapters を定義していれば親の chapters は含めない(上書き)。
|
|
@@ -9,7 +9,7 @@ import { readFileSync } from "fs";
|
|
|
9
9
|
import { runCmd, assertOk } from "../../lib/process.js";
|
|
10
10
|
import path from "path";
|
|
11
11
|
import { loadConfig } from "../../lib/config.js";
|
|
12
|
-
import { isGhAvailable } from "../../lib/git-helpers.js";
|
|
12
|
+
import { isGhAvailable, runGit } from "../../lib/git-helpers.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Resolve push remote from config.
|
|
@@ -165,7 +165,7 @@ function main(ctx) {
|
|
|
165
165
|
const title = buildPrTitle(spec, fallbackTitle);
|
|
166
166
|
const body = buildPrBody(state, spec);
|
|
167
167
|
|
|
168
|
-
const pushRes =
|
|
168
|
+
const pushRes = runGit(["push", "-u", remote, featureBranch]);
|
|
169
169
|
assertOk(pushRes, "git push failed");
|
|
170
170
|
const prRes = runCmd("gh", [
|
|
171
171
|
"pr", "create",
|
|
@@ -185,12 +185,12 @@ function main(ctx) {
|
|
|
185
185
|
function runSquashMerge(gitPrefix, hint) {
|
|
186
186
|
const mergeArgs = [...gitPrefix, "merge", "--squash", featureBranch];
|
|
187
187
|
const resetArgs = [...gitPrefix, "reset", "--merge"];
|
|
188
|
-
const mergeRes =
|
|
188
|
+
const mergeRes = runGit(mergeArgs);
|
|
189
189
|
if (!mergeRes.ok) {
|
|
190
|
-
|
|
190
|
+
runGit(resetArgs);
|
|
191
191
|
throw new Error(`Merge conflict detected. ${hint}`);
|
|
192
192
|
}
|
|
193
|
-
const commitRes =
|
|
193
|
+
const commitRes = runGit([...gitPrefix, "commit", "-m", commitMsg]);
|
|
194
194
|
assertOk(commitRes, "commit after squash merge failed");
|
|
195
195
|
}
|
|
196
196
|
|
|
@@ -200,7 +200,7 @@ function main(ctx) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// Branch mode
|
|
203
|
-
const checkoutRes =
|
|
203
|
+
const checkoutRes = runGit(["checkout", baseBranch]);
|
|
204
204
|
assertOk(checkoutRes, "git checkout failed");
|
|
205
205
|
runSquashMerge([], `Run 'git rebase ${baseBranch}' and retry finalize.`);
|
|
206
206
|
return { strategy: "squash" };
|