sdd-forge 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/package.json +23 -0
  4. package/src/analyzers/analyze-controllers.js +85 -0
  5. package/src/analyzers/analyze-extras.js +944 -0
  6. package/src/analyzers/analyze-models.js +130 -0
  7. package/src/analyzers/analyze-routes.js +50 -0
  8. package/src/analyzers/analyze-shells.js +75 -0
  9. package/src/analyzers/lib/php-array-parser.js +138 -0
  10. package/src/analyzers/scan.js +116 -0
  11. package/src/bin/sdd-forge.js +117 -0
  12. package/src/engine/directive-parser.js +72 -0
  13. package/src/engine/init.js +253 -0
  14. package/src/engine/populate.js +192 -0
  15. package/src/engine/readme.js +174 -0
  16. package/src/engine/renderers.js +150 -0
  17. package/src/engine/resolver.js +568 -0
  18. package/src/engine/tfill.js +617 -0
  19. package/src/flow/flow.js +183 -0
  20. package/src/forge/forge.js +684 -0
  21. package/src/help.js +55 -0
  22. package/src/lib/cli.js +83 -0
  23. package/src/lib/config.js +40 -0
  24. package/src/lib/process.js +32 -0
  25. package/src/projects/add.js +73 -0
  26. package/src/projects/projects.js +91 -0
  27. package/src/projects/setdefault.js +37 -0
  28. package/src/spec/gate.js +101 -0
  29. package/src/spec/spec.js +198 -0
  30. package/src/templates/checks/check-controller-coverage.sh +46 -0
  31. package/src/templates/checks/check-db-coverage.sh +87 -0
  32. package/src/templates/checks/check-node-cli-docs.sh +125 -0
  33. package/src/templates/checks/check-temp-docs.sh +100 -0
  34. package/src/templates/checks/generate-change-log.sh +142 -0
  35. package/src/templates/checks/self-review-temp-docs.sh +18 -0
  36. package/src/templates/locale/ja/messages.json +9 -0
  37. package/src/templates/locale/ja/node-cli/01_overview.md +23 -0
  38. package/src/templates/locale/ja/node-cli/02_cli_commands.md +23 -0
  39. package/src/templates/locale/ja/node-cli/03_configuration.md +23 -0
  40. package/src/templates/locale/ja/node-cli/04_internal_design.md +30 -0
  41. package/src/templates/locale/ja/node-cli/05_development.md +49 -0
  42. package/src/templates/locale/ja/node-cli/README.md +41 -0
  43. package/src/templates/locale/ja/php-mvc/01_architecture.md +23 -0
  44. package/src/templates/locale/ja/php-mvc/02_stack_and_ops.md +45 -0
  45. package/src/templates/locale/ja/php-mvc/03_project_structure.md +46 -0
  46. package/src/templates/locale/ja/php-mvc/04_development.md +69 -0
  47. package/src/templates/locale/ja/php-mvc/05_auth_and_session.md +48 -0
  48. package/src/templates/locale/ja/php-mvc/06_database_architecture.md +23 -0
  49. package/src/templates/locale/ja/php-mvc/07_db_tables.md +31 -0
  50. package/src/templates/locale/ja/php-mvc/08_controller_routes.md +33 -0
  51. package/src/templates/locale/ja/php-mvc/09_business_logic.md +27 -0
  52. package/src/templates/locale/ja/php-mvc/10_batch_and_shell.md +25 -0
  53. package/src/templates/locale/ja/php-mvc/README.md +34 -0
  54. package/src/templates/locale/ja/prompts.json +4 -0
  55. package/src/templates/locale/ja/sections.json +6 -0
  56. package/src/templates/review-checklist.md +48 -0
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sdd-forge/flow/flow.js
4
+ *
5
+ * 修正要求を受けて以下を自動実行する:
6
+ * 1) spec 作成(未指定時)+ spec gate
7
+ * 2) gate 失敗時は NEEDS_INPUT を返して停止
8
+ * 3) gate 成功時は sdd:forge を実行
9
+ */
10
+
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ import { repoRoot, parseArgs } from "../lib/cli.js";
15
+ import { runSync } from "../lib/process.js";
16
+
17
+ // npm パッケージとして呼ばれた場合でもサブスクリプトを直接起動できるよう
18
+ // パッケージディレクトリを保持する
19
+ const PKG_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
20
+
21
+ function run(root, cmd, args) {
22
+ const res = runSync(cmd, args, { cwd: root });
23
+ return {
24
+ ok: res.ok,
25
+ status: res.status,
26
+ out: res.stdout,
27
+ err: res.stderr,
28
+ };
29
+ }
30
+
31
+ function collectGateReasons(text) {
32
+ const reasons = String(text || "")
33
+ .split("\n")
34
+ .map((x) => x.trim())
35
+ .filter((x) => x.startsWith("- "));
36
+ return reasons.slice(0, 8);
37
+ }
38
+
39
+ function deriveTitle(request) {
40
+ return request
41
+ .slice(0, 40)
42
+ .toLowerCase()
43
+ .replace(/[^a-z0-9\u3040-\u30ff\u4e00-\u9faf]+/g, "-")
44
+ .replace(/^-+|-+$/g, "") || "task";
45
+ }
46
+
47
+ function parseCreatedSpecPath(output) {
48
+ const m = output.match(/created spec:\s+([^\n]+)/);
49
+ return m?.[1]?.trim() || "";
50
+ }
51
+
52
+ function fallbackLatestSpec(root) {
53
+ const specsDir = path.join(root, "specs");
54
+ if (!fs.existsSync(specsDir)) return "";
55
+ const candidates = fs
56
+ .readdirSync(specsDir, { withFileTypes: true })
57
+ .filter((d) => d.isDirectory() && /^[0-9]{3}-/.test(d.name))
58
+ .map((d) => d.name)
59
+ .sort();
60
+ const latest = candidates[candidates.length - 1];
61
+ if (!latest) return "";
62
+ const p = path.join("specs", latest, "spec.md");
63
+ return fs.existsSync(path.join(root, p)) ? p : "";
64
+ }
65
+
66
+ function ensureRequestInSpec(specAbs, request) {
67
+ if (!fs.existsSync(specAbs)) return;
68
+ const src = fs.readFileSync(specAbs, "utf8");
69
+ if (src.includes("**Input**: User request")) {
70
+ const next = src.replace(
71
+ "**Input**: User request",
72
+ `**Input**: ${request.replace(/\n/g, " ").trim()}`,
73
+ );
74
+ fs.writeFileSync(specAbs, next);
75
+ }
76
+ }
77
+
78
+ function detectUserConfirmationIssue(specAbs) {
79
+ if (!fs.existsSync(specAbs)) return false;
80
+ const text = fs.readFileSync(specAbs, "utf8");
81
+ if (!/^\s*##\s+User Confirmation\b/im.test(text)) return true;
82
+ return !/-\s*\[\s*x\s*\]\s*User approved this spec\b/i.test(text);
83
+ }
84
+
85
+ function main() {
86
+ const root = repoRoot(import.meta.url);
87
+ const cli = parseArgs(process.argv.slice(2), {
88
+ options: ["--request", "--title", "--spec", "--agent", "--max-runs", "--forge-mode"],
89
+ defaults: { request: "", title: "", spec: "", agent: "", maxRuns: "5", forgeMode: "local" },
90
+ });
91
+ if (cli.help) {
92
+ console.log(
93
+ [
94
+ "Usage: node sdd-forge/flow/flow.js --request \"...\" [options]",
95
+ "",
96
+ "Options:",
97
+ " --request <text> 実装要求(必須)",
98
+ " --title <text> spec 用タイトル(省略時は request 先頭を利用)",
99
+ " --spec <path> 既存 spec.md を使う",
100
+ " --agent <name> AIエージェント: codex|claude",
101
+ " --max-runs <n> docs:forge 反復回数",
102
+ " --forge-mode <m> docs:forge mode: local|assist|agent (default: local)",
103
+ ].join("\n"),
104
+ );
105
+ return;
106
+ }
107
+ if (!cli.request) throw new Error("--request is required");
108
+ if (!["local", "assist", "agent"].includes(cli.forgeMode)) {
109
+ throw new Error("--forge-mode must be one of: local, assist, agent");
110
+ }
111
+ let specRel = cli.spec;
112
+
113
+ if (!specRel) {
114
+ const title = cli.title || deriveTitle(cli.request);
115
+ const s = run(root, "node", [
116
+ path.join(PKG_DIR, "spec", "spec.js"),
117
+ "--title",
118
+ title,
119
+ "--allow-dirty",
120
+ ]);
121
+ process.stdout.write(s.out);
122
+ process.stderr.write(s.err);
123
+ if (!s.ok) {
124
+ process.exit(s.status);
125
+ }
126
+ specRel = parseCreatedSpecPath(s.out);
127
+ if (!specRel) {
128
+ specRel = fallbackLatestSpec(root);
129
+ if (!specRel) {
130
+ console.error("flow: failed to parse created spec path.");
131
+ process.exit(1);
132
+ }
133
+ console.warn(`flow: fallback spec path used: ${specRel}`);
134
+ }
135
+ }
136
+
137
+ const specAbs = path.resolve(root, specRel);
138
+ ensureRequestInSpec(specAbs, cli.request);
139
+
140
+ const gate = run(root, "node", [path.join(PKG_DIR, "spec", "gate.js"), "--spec", specRel]);
141
+ process.stdout.write(gate.out);
142
+ process.stderr.write(gate.err);
143
+ if (!gate.ok) {
144
+ const lines = collectGateReasons(`${gate.out}\n${gate.err}`);
145
+ const needsApproval = lines.some((l) =>
146
+ l.toLowerCase().includes("user confirmation is required"),
147
+ );
148
+ console.log("NEEDS_INPUT");
149
+ console.log("- sdd:gate が失敗しました。以下を解消してください。");
150
+ if (needsApproval || detectUserConfirmationIssue(specAbs)) {
151
+ console.log("- この仕様で実装して問題ないか、ユーザー確認を先に取ってください。");
152
+ console.log("- 承認後、spec.md の `## User Confirmation` に `- [x] User approved this spec` を設定してください。");
153
+ } else if (lines.length === 0) {
154
+ console.log("- spec の未解決事項を解消してください。");
155
+ } else {
156
+ for (const l of lines.slice(0, 8)) {
157
+ console.log(l);
158
+ }
159
+ }
160
+ process.exit(2);
161
+ }
162
+
163
+ const forgeArgs = [
164
+ path.join(PKG_DIR, "forge", "forge.js"),
165
+ "--prompt",
166
+ cli.request,
167
+ "--spec",
168
+ specRel,
169
+ "--max-runs",
170
+ cli.maxRuns,
171
+ "--mode",
172
+ cli.forgeMode,
173
+ ];
174
+ if (cli.agent) {
175
+ forgeArgs.push("--agent", cli.agent);
176
+ }
177
+ const forge = run(root, "node", forgeArgs);
178
+ process.stdout.write(forge.out);
179
+ process.stderr.write(forge.err);
180
+ process.exit(forge.status);
181
+ }
182
+
183
+ main();