sdd-forge 0.1.0-alpha.702 → 0.1.0-alpha.759
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 +9 -15
- package/package.json +6 -3
- package/src/AGENTS.md +1 -1
- 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/resolver-factory.js +12 -8
- package/src/docs/lib/template-merger.js +11 -7
- package/src/flow/commands/report.js +68 -12
- package/src/flow/commands/review.js +6 -6
- package/src/flow/lib/get-check.js +4 -5
- 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 +41 -2
- package/src/flow/lib/run-gate.js +70 -12
- package/src/flow/lib/run-impl-confirm.js +5 -1
- package/src/flow/lib/run-prepare-spec.js +15 -2
- package/src/flow/lib/run-resume.js +2 -0
- package/src/flow/lib/run-retro.js +5 -4
- package/src/flow/lib/run-review.js +6 -0
- 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/config.js +16 -3
- package/src/lib/constants.js +109 -0
- package/src/lib/flow-state.js +246 -13
- package/src/lib/log.js +15 -2
- package/src/lib/presets.js +63 -7
- package/src/lib/process.js +6 -3
- package/src/lib/types.js +1 -1
- package/src/loader.js +36 -0
- package/src/locale/en/ui.json +1 -0
- 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,12 @@ 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… |
|
|
138
132
|
<!-- {{/data}} -->
|
|
139
133
|
|
|
140
134
|
## 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.759",
|
|
4
4
|
"description": "Spec-Driven Development tooling for automated documentation generation",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,12 +10,16 @@
|
|
|
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
|
+
},
|
|
13
17
|
"files": [
|
|
14
18
|
"src/",
|
|
15
19
|
"!src/presets/*/tests/"
|
|
16
20
|
],
|
|
17
21
|
"engines": {
|
|
18
|
-
"node": ">=18.
|
|
22
|
+
"node": ">=18.19.0"
|
|
19
23
|
},
|
|
20
24
|
"keywords": [
|
|
21
25
|
"sdd",
|
|
@@ -25,7 +29,6 @@
|
|
|
25
29
|
"technical-docs"
|
|
26
30
|
],
|
|
27
31
|
"scripts": {
|
|
28
|
-
"test": "node tests/run.js",
|
|
29
32
|
"test:unit": "node tests/run.js --scope unit",
|
|
30
33
|
"test:e2e": "node tests/run.js --scope e2e",
|
|
31
34
|
"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 が内部で呼ぶ)
|
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;
|
|
@@ -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 は含めない(上書き)。
|
|
@@ -11,19 +11,53 @@ import { loadIssueLog } from "../lib/set-issue-log.js";
|
|
|
11
11
|
import { pushSection, DIVIDER } from "../../lib/formatter.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {Object|null} metrics
|
|
16
|
-
* @
|
|
14
|
+
* Iterate over each phase object in metrics, skipping null/non-object entries.
|
|
15
|
+
* @param {Object|null} metrics
|
|
16
|
+
* @param {(phase: Object) => void} fn
|
|
17
17
|
*/
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
if (!metrics) return totals;
|
|
18
|
+
function forEachPhase(metrics, fn) {
|
|
19
|
+
if (!metrics) return;
|
|
21
20
|
for (const phase of Object.values(metrics)) {
|
|
22
21
|
if (!phase || typeof phase !== "object") continue;
|
|
22
|
+
fn(phase);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Aggregate activity metrics (docs/src reads, Q&A, issue-log) across all phases.
|
|
28
|
+
* @param {Object|null} metrics - flow.json metrics object keyed by phase
|
|
29
|
+
* @returns {{ docsRead: number, srcRead: number, question: number, issueLog: number }}
|
|
30
|
+
*/
|
|
31
|
+
function aggregateActivityMetrics(metrics) {
|
|
32
|
+
const totals = { docsRead: 0, srcRead: 0, question: 0, issueLog: 0 };
|
|
33
|
+
forEachPhase(metrics, (phase) => {
|
|
23
34
|
for (const key of Object.keys(totals)) {
|
|
24
35
|
totals[key] += phase[key] || 0;
|
|
25
36
|
}
|
|
26
|
-
}
|
|
37
|
+
});
|
|
38
|
+
return totals;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Aggregate token/cost metrics across all phases (R3-1, R3-2).
|
|
43
|
+
* @param {Object|null} metrics - flow.json metrics object keyed by phase
|
|
44
|
+
* @returns {{ input: number, output: number, cacheRead: number, cacheCreation: number, cost: number|null, callCount: number }}
|
|
45
|
+
*/
|
|
46
|
+
function aggregateTokenMetrics(metrics) {
|
|
47
|
+
const totals = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, cost: null, callCount: 0 };
|
|
48
|
+
forEachPhase(metrics, (phase) => {
|
|
49
|
+
if (phase.tokens) {
|
|
50
|
+
totals.input += phase.tokens.input || 0;
|
|
51
|
+
totals.output += phase.tokens.output || 0;
|
|
52
|
+
totals.cacheRead += phase.tokens.cacheRead || 0;
|
|
53
|
+
totals.cacheCreation += phase.tokens.cacheCreation || 0;
|
|
54
|
+
}
|
|
55
|
+
// R3-2: only accumulate cost when it has been recorded (never treat null as 0)
|
|
56
|
+
if (phase.cost != null) {
|
|
57
|
+
totals.cost = (totals.cost || 0) + phase.cost;
|
|
58
|
+
}
|
|
59
|
+
totals.callCount += phase.callCount || 0;
|
|
60
|
+
});
|
|
27
61
|
return totals;
|
|
28
62
|
}
|
|
29
63
|
|
|
@@ -64,7 +98,8 @@ export function generateReport(input) {
|
|
|
64
98
|
};
|
|
65
99
|
|
|
66
100
|
// Metrics
|
|
67
|
-
const metrics =
|
|
101
|
+
const metrics = aggregateActivityMetrics(state.metrics);
|
|
102
|
+
const tokenMetrics = aggregateTokenMetrics(state.metrics);
|
|
68
103
|
|
|
69
104
|
// Sync
|
|
70
105
|
let sync;
|
|
@@ -88,7 +123,7 @@ export function generateReport(input) {
|
|
|
88
123
|
}
|
|
89
124
|
|
|
90
125
|
// Tests (R1, R3)
|
|
91
|
-
const testSummary = state.
|
|
126
|
+
const testSummary = state.test?.summary;
|
|
92
127
|
let tests = null;
|
|
93
128
|
if (testSummary) {
|
|
94
129
|
const unit = testSummary.unit || 0;
|
|
@@ -97,7 +132,7 @@ export function generateReport(input) {
|
|
|
97
132
|
tests = { unit, integration, acceptance, total: unit + integration + acceptance };
|
|
98
133
|
}
|
|
99
134
|
|
|
100
|
-
const data = { implementation, retro, issueLog: issueLogData, metrics, tests, sync };
|
|
135
|
+
const data = { implementation, retro, issueLog: issueLogData, metrics, tokenMetrics, tests, sync };
|
|
101
136
|
const text = formatText(data);
|
|
102
137
|
|
|
103
138
|
return { data, text };
|
|
@@ -148,10 +183,31 @@ function formatText(data) {
|
|
|
148
183
|
lines.push(" -");
|
|
149
184
|
}
|
|
150
185
|
|
|
151
|
-
|
|
186
|
+
const formatInt = (value) => Number(value || 0).toLocaleString("en-US");
|
|
187
|
+
const metricLine = (label, value) => {
|
|
188
|
+
const dots = ".".repeat(Math.max(1, 28 - label.length));
|
|
189
|
+
return ` ${label} ${dots} ${value}`;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Metrics
|
|
152
193
|
pushSection(lines, "Metrics", thin);
|
|
153
194
|
const m = data.metrics;
|
|
154
|
-
lines.push(
|
|
195
|
+
lines.push(metricLine("docs read", formatInt(m.docsRead)));
|
|
196
|
+
lines.push(metricLine("src read", formatInt(m.srcRead)));
|
|
197
|
+
lines.push(metricLine("Q&A", formatInt(m.question)));
|
|
198
|
+
lines.push(metricLine("issue-log", formatInt(m.issueLog)));
|
|
199
|
+
|
|
200
|
+
// Agent metrics (token/cost) — R3-1, R3-2
|
|
201
|
+
if (data.tokenMetrics && data.tokenMetrics.callCount > 0) {
|
|
202
|
+
const t = data.tokenMetrics;
|
|
203
|
+
const costStr = t.cost != null ? `$${t.cost.toFixed(4)}` : "N/A";
|
|
204
|
+
lines.push(metricLine("agent calls", formatInt(t.callCount)));
|
|
205
|
+
lines.push(metricLine("input tokens", formatInt(t.input)));
|
|
206
|
+
lines.push(metricLine("output tokens", formatInt(t.output)));
|
|
207
|
+
lines.push(metricLine("cache-read tokens", formatInt(t.cacheRead)));
|
|
208
|
+
lines.push(metricLine("cache-create tokens", formatInt(t.cacheCreation)));
|
|
209
|
+
lines.push(metricLine("cost", costStr));
|
|
210
|
+
}
|
|
155
211
|
|
|
156
212
|
// Tests (always shown)
|
|
157
213
|
pushSection(lines, "Tests", thin);
|
|
@@ -26,7 +26,7 @@ const callReviewAgent = (agent, prompt, root, systemPrompt) =>
|
|
|
26
26
|
callAgentAwaitLog(agent, prompt, undefined, root, { systemPrompt });
|
|
27
27
|
import { runCmd } from "../../lib/process.js";
|
|
28
28
|
import { EXIT_ERROR } from "../../lib/exit-codes.js";
|
|
29
|
-
import { VALID_PHASES } from "
|
|
29
|
+
import { VALID_PHASES } from "../../lib/constants.js";
|
|
30
30
|
|
|
31
31
|
/** Maximum retry iterations for review auto-fix loops (test and spec). */
|
|
32
32
|
const MAX_REVIEW_RETRIES = 3;
|
|
@@ -499,7 +499,7 @@ async function runTestReview(root, flow, config, dryRun) {
|
|
|
499
499
|
process.exit(EXIT_ERROR);
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
const agent = loadAgentConfig(config, "flow.review
|
|
502
|
+
const agent = loadAgentConfig(config, "flow.test.review");
|
|
503
503
|
ensureAgentWorkDir(agent, root);
|
|
504
504
|
|
|
505
505
|
// Step 1: Generate test design
|
|
@@ -683,9 +683,9 @@ async function runSpecReview(root, flow, config, dryRun) {
|
|
|
683
683
|
console.error(` [spec-review] Warning: failed to load codebase context: ${e.message}`);
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
-
const agent = loadAgentConfig(config, "flow.review
|
|
686
|
+
const agent = loadAgentConfig(config, "flow.spec.review");
|
|
687
687
|
ensureAgentWorkDir(agent, root);
|
|
688
|
-
const validationAgent = loadAgentConfig(config, "flow.review.final");
|
|
688
|
+
const validationAgent = loadAgentConfig(config, "flow.impl.review.final");
|
|
689
689
|
ensureAgentWorkDir(validationAgent, root);
|
|
690
690
|
|
|
691
691
|
const { history, finalIssues, verdict } = await runReviewLoop({
|
|
@@ -829,7 +829,7 @@ async function main() {
|
|
|
829
829
|
|
|
830
830
|
// --- Draft phase ---
|
|
831
831
|
console.error(" [draft] Generating proposals...");
|
|
832
|
-
const draftAgent = loadAgentConfig(config, "flow.review.draft");
|
|
832
|
+
const draftAgent = loadAgentConfig(config, "flow.impl.review.draft");
|
|
833
833
|
ensureAgentWorkDir(draftAgent, root);
|
|
834
834
|
const draftResult = await callReviewAgent(draftAgent, diff, root, buildDraftSystemPrompt());
|
|
835
835
|
|
|
@@ -852,7 +852,7 @@ async function main() {
|
|
|
852
852
|
|
|
853
853
|
// --- Final phase ---
|
|
854
854
|
console.error(" [final] Validating proposals...");
|
|
855
|
-
const finalAgent = loadAgentConfig(config, "flow.review.final");
|
|
855
|
+
const finalAgent = loadAgentConfig(config, "flow.impl.review.final");
|
|
856
856
|
ensureAgentWorkDir(finalAgent, root);
|
|
857
857
|
const finalPrompt = [
|
|
858
858
|
"Validate these refactoring proposals:",
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { runCmd } from "../../lib/process.js";
|
|
10
10
|
import { isGhAvailable } from "../../lib/git-helpers.js";
|
|
11
|
+
import { VALID_CHECK_TARGETS } from "../../lib/constants.js";
|
|
11
12
|
import { FlowCommand } from "./base-command.js";
|
|
12
13
|
|
|
13
|
-
const VALID_TARGETS = ["impl", "finalize", "dirty", "gh"];
|
|
14
|
-
|
|
15
14
|
const PREREQS = {
|
|
16
15
|
impl: ["gate", "test"],
|
|
17
16
|
finalize: ["implement"],
|
|
@@ -58,11 +57,11 @@ export default class GetCheckCommand extends FlowCommand {
|
|
|
58
57
|
const target = ctx.target;
|
|
59
58
|
|
|
60
59
|
if (!target) {
|
|
61
|
-
throw new Error(`target required. valid: ${
|
|
60
|
+
throw new Error(`target required. valid: ${VALID_CHECK_TARGETS.join(", ")}`);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
if (!
|
|
65
|
-
throw new Error(`unknown target '${target}'. valid: ${
|
|
63
|
+
if (!VALID_CHECK_TARGETS.includes(target)) {
|
|
64
|
+
throw new Error(`unknown target '${target}'. valid: ${VALID_CHECK_TARGETS.join(", ")}`);
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
if (target === "dirty") {
|
|
@@ -15,7 +15,7 @@ import path from "path";
|
|
|
15
15
|
import { sddOutputDir, loadConfig } from "../../lib/config.js";
|
|
16
16
|
import { FlowCommand } from "./base-command.js";
|
|
17
17
|
import { ANALYSIS_META_KEYS } from "../../docs/lib/analysis-entry.js";
|
|
18
|
-
import { resolveAgent, callAgentWithLog } from "../../lib/agent.js";
|
|
18
|
+
import { resolveAgent, callAgentWithLog, ensureAgentWorkDir } from "../../lib/agent.js";
|
|
19
19
|
|
|
20
20
|
const EXCLUDE_FIELDS = new Set(["hash", "mtime", "lines", "id", "enrich", "detail"]);
|
|
21
21
|
|
|
@@ -215,10 +215,11 @@ function aiSearch(allEntries, analysis, query, root) {
|
|
|
215
215
|
|
|
216
216
|
let config;
|
|
217
217
|
try { config = loadConfig(root); } catch (_) { config = {}; }
|
|
218
|
-
const agent = resolveAgent(config, "context.search");
|
|
218
|
+
const agent = resolveAgent(config, "flow.context.search");
|
|
219
219
|
if (!agent) return fallbackSearch(allEntries, query);
|
|
220
220
|
|
|
221
221
|
const prompt = buildKeywordSelectionPrompt(allKeywords, query);
|
|
222
|
+
ensureAgentWorkDir(agent, root);
|
|
222
223
|
let response;
|
|
223
224
|
try {
|
|
224
225
|
response = callAgentWithLog(agent, prompt, 30000, root);
|