sdd-forge 0.1.0-alpha.692 → 0.1.0-alpha.702
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 +6 -6
- package/package.json +1 -1
- package/src/docs/commands/init.js +1 -1
- package/src/docs/commands/text.js +5 -3
- package/src/docs.js +10 -0
- package/src/flow/lib/run-gate.js +1 -1
- package/src/flow/lib/run-retro.js +1 -1
- package/src/flow/lib/set-step.js +2 -0
- package/src/lib/process.js +14 -6
- package/src/lib/skills.js +55 -3
- package/src/lib/types.js +34 -1
- package/src/locale/en/ui.json +1 -0
- package/src/locale/ja/ui.json +1 -0
- package/src/sdd-forge.js +2 -1
- package/src/templates/config.example.json +29 -23
- package/src/templates/partials/core-principle.md +2 -2
- package/src/templates/skills/{sdd-forge.flow-auto-on → sdd-forge.flow-auto}/SKILL.md +25 -4
- package/src/templates/skills/sdd-forge.flow-finalize/SKILL.md +1 -1
- package/src/upgrade.js +22 -16
- package/src/templates/skills/sdd-forge.flow-auto-off/SKILL.md +0 -17
package/README.md
CHANGED
|
@@ -129,12 +129,12 @@ See the [configuration reference](docs/configuration.md) for details.
|
|
|
129
129
|
<!-- {{data("cli.docs.chapters", {header: "", labels: "Chapter|Summary", ignoreError: true})}} -->
|
|
130
130
|
| Chapter | Summary |
|
|
131
131
|
| --- | --- |
|
|
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) |
|
|
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) | |
|
|
138
138
|
<!-- {{/data}} -->
|
|
139
139
|
|
|
140
140
|
## License
|
package/package.json
CHANGED
|
@@ -141,7 +141,7 @@ function main(ctx) {
|
|
|
141
141
|
console.log([h.usage, "", h.desc, "", "Options:", ` ${o.type}`, ` ${o.force}`, ` ${o.dryRun}`, ` ${o.help}`].join("\n"));
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
|
-
ctx = resolveCommandContext(cli);
|
|
144
|
+
ctx = resolveCommandContext(cli, { commandId: "docs.init" });
|
|
145
145
|
ctx.force = cli.force;
|
|
146
146
|
ctx.dryRun = cli.dryRun;
|
|
147
147
|
}
|
|
@@ -218,7 +218,11 @@ function applyBatchJsonToFile(text, textFills, jsonData) {
|
|
|
218
218
|
* @returns {{ text: string, filled: number, skipped: number }}
|
|
219
219
|
*/
|
|
220
220
|
async function processTemplateFileBatch(text, analysis, fileName, agent, timeoutMs, cwd, dryRun, _preamblePatterns, systemPrompt, _filterId, _concurrency, lang, srcRoot, retryCount) {
|
|
221
|
-
|
|
221
|
+
// cleanText を先に計算してから parseDirectives を呼ぶ。
|
|
222
|
+
// stripFillContent は既存コンテンツを除去するため行数が変わる。
|
|
223
|
+
// parseDirectives の行番号は applyBatchJsonToFile に渡す text と一致させる必要がある。
|
|
224
|
+
const cleanText = stripFillContent(text);
|
|
225
|
+
const directives = parseDirectives(cleanText);
|
|
222
226
|
const textFills = directives.filter((d) => d.type === "text");
|
|
223
227
|
|
|
224
228
|
if (textFills.length === 0) return { text, filled: 0, skipped: 0 };
|
|
@@ -227,8 +231,6 @@ async function processTemplateFileBatch(text, analysis, fileName, agent, timeout
|
|
|
227
231
|
const hasDeep = textFills.some((d) => d.params?.mode === "deep");
|
|
228
232
|
const batchMode = hasDeep ? "deep" : "light";
|
|
229
233
|
const enriched = getEnrichedContext(analysis, fileName, batchMode, srcRoot);
|
|
230
|
-
|
|
231
|
-
const cleanText = stripFillContent(text);
|
|
232
234
|
let prompt = buildBatchPrompt(fileName, cleanText, textFills, lang);
|
|
233
235
|
if (enriched) {
|
|
234
236
|
prompt = enriched + "\n\n" + prompt;
|
package/src/docs.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from "fs";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { PKG_DIR } from "./lib/cli.js";
|
|
11
|
+
import { Logger } from "./lib/log.js";
|
|
11
12
|
import { resolveCommandContext } from "./docs/lib/command-context.js";
|
|
12
13
|
import { resolveOutputConfig } from "./lib/types.js";
|
|
13
14
|
import { EXIT_ERROR } from "./lib/exit-codes.js";
|
|
@@ -99,11 +100,13 @@ if (subCmd === "build") {
|
|
|
99
100
|
|
|
100
101
|
try {
|
|
101
102
|
// 1. scan
|
|
103
|
+
Logger.getInstance().event("pipeline-step", { step: "scan", phase: "start" });
|
|
102
104
|
progress.start("scan");
|
|
103
105
|
await scanMain({ ...baseCtx });
|
|
104
106
|
progress.stepDone();
|
|
105
107
|
|
|
106
108
|
// 2. enrich
|
|
109
|
+
Logger.getInstance().event("pipeline-step", { step: "enrich", phase: "start" });
|
|
107
110
|
progress.start("enrich");
|
|
108
111
|
if (hasAgent) {
|
|
109
112
|
await enrichMain({ ...baseCtx, commandId: "docs.enrich" });
|
|
@@ -122,17 +125,20 @@ if (subCmd === "build") {
|
|
|
122
125
|
process.exit(EXIT_ERROR);
|
|
123
126
|
}
|
|
124
127
|
} else {
|
|
128
|
+
Logger.getInstance().event("pipeline-step", { step: "init", phase: "start" });
|
|
125
129
|
progress.start("init");
|
|
126
130
|
await initMain({ ...baseCtx, force: hasForce, dryRun: isDryRun, commandId: "docs.init" });
|
|
127
131
|
progress.stepDone();
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
// 4. data
|
|
135
|
+
Logger.getInstance().event("pipeline-step", { step: "data", phase: "start" });
|
|
131
136
|
progress.start("data");
|
|
132
137
|
await dataMain({ ...baseCtx, dryRun: isDryRun });
|
|
133
138
|
progress.stepDone();
|
|
134
139
|
|
|
135
140
|
// 5. text — delegate file selection, diff detection, and strip to text.js
|
|
141
|
+
Logger.getInstance().event("pipeline-step", { step: "text", phase: "start" });
|
|
136
142
|
progress.start("text");
|
|
137
143
|
if (hasAgent) {
|
|
138
144
|
const textResult = await textMain({ ...baseCtx, dryRun: isDryRun, commandId: "docs.text", force: hasForce });
|
|
@@ -145,17 +151,20 @@ if (subCmd === "build") {
|
|
|
145
151
|
progress.stepDone();
|
|
146
152
|
|
|
147
153
|
// 6. readme
|
|
154
|
+
Logger.getInstance().event("pipeline-step", { step: "readme", phase: "start" });
|
|
148
155
|
progress.start("readme");
|
|
149
156
|
await readmeMain({ ...baseCtx, dryRun: isDryRun, commandId: "docs.readme" });
|
|
150
157
|
progress.stepDone();
|
|
151
158
|
|
|
152
159
|
// 7. agents
|
|
160
|
+
Logger.getInstance().event("pipeline-step", { step: "agents", phase: "start" });
|
|
153
161
|
progress.start("agents");
|
|
154
162
|
await agentsMain({ ...baseCtx, dryRun: isDryRun, commandId: "docs.agents" });
|
|
155
163
|
progress.stepDone();
|
|
156
164
|
|
|
157
165
|
// 8. Multi-language: generate non-default languages
|
|
158
166
|
if (outputCfg.isMultiLang) {
|
|
167
|
+
Logger.getInstance().event("pipeline-step", { step: "translate", phase: "start" });
|
|
159
168
|
progress.start("translate");
|
|
160
169
|
const nonDefaultLangs = outputCfg.languages.filter((l) => l !== outputCfg.default);
|
|
161
170
|
const docsDir = baseCtx.docsDir;
|
|
@@ -210,6 +219,7 @@ if (subCmd === "build") {
|
|
|
210
219
|
progress.done();
|
|
211
220
|
} catch (err) {
|
|
212
221
|
progress.done();
|
|
222
|
+
Logger.getInstance().event("error", { message: err.message });
|
|
213
223
|
console.error(`[build] ERROR: ${err.message}`);
|
|
214
224
|
process.exit(EXIT_ERROR);
|
|
215
225
|
}
|
package/src/flow/lib/run-gate.js
CHANGED
|
@@ -442,7 +442,7 @@ export class RunGateCommand extends FlowCommand {
|
|
|
442
442
|
|
|
443
443
|
// Requirements check via AI
|
|
444
444
|
const agent = resolveAgent(ctx.config, "spec.gate");
|
|
445
|
-
if (!agent) throw new Error("no agent configured
|
|
445
|
+
if (!agent) throw new Error("no AI agent configured (agent.default or agent.profiles.<name>.spec.gate)");
|
|
446
446
|
|
|
447
447
|
const reqPrompt = buildImplCheckPrompt(specText, diff);
|
|
448
448
|
const reqResponse = callAgentWithLog(agent, reqPrompt);
|
|
@@ -185,7 +185,7 @@ export class RunRetroCommand extends FlowCommand {
|
|
|
185
185
|
|
|
186
186
|
const agent = resolveAgent(config, "flow.retro");
|
|
187
187
|
if (!agent) {
|
|
188
|
-
throw new Error("no AI agent configured (agent.default or agent.
|
|
188
|
+
throw new Error("no AI agent configured (agent.default or agent.profiles.<name>.flow.retro)");
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
// Build prompt and call AI
|
package/src/flow/lib/set-step.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { FlowCommand } from "./base-command.js";
|
|
11
11
|
import { updateStepStatus } from "../../lib/flow-state.js";
|
|
12
|
+
import { Logger } from "../../lib/log.js";
|
|
12
13
|
|
|
13
14
|
export default class SetStepCommand extends FlowCommand {
|
|
14
15
|
execute(ctx) {
|
|
@@ -19,6 +20,7 @@ export default class SetStepCommand extends FlowCommand {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
updateStepStatus(ctx.root, id, status);
|
|
23
|
+
Logger.getInstance().event("flow-step-change", { step: id, status });
|
|
22
24
|
|
|
23
25
|
return { id, status };
|
|
24
26
|
}
|
package/src/lib/process.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { execFileSync, execFile } from "child_process";
|
|
9
|
+
import { Logger } from "./log.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Run a command synchronously.
|
|
@@ -30,9 +31,11 @@ export function runCmd(cmd, args, opts = {}) {
|
|
|
30
31
|
stdio: ["pipe", "pipe", "pipe"],
|
|
31
32
|
...(opts.env && { env: opts.env }),
|
|
32
33
|
});
|
|
33
|
-
|
|
34
|
+
const result = { ok: true, status: 0, stdout: String(stdout || ""), stderr: "", signal: null, killed: false };
|
|
35
|
+
if (cmd === "git") Logger.getInstance().git({ cmd: [cmd, ...args], exitCode: 0, stderr: "" });
|
|
36
|
+
return result;
|
|
34
37
|
} catch (e) {
|
|
35
|
-
|
|
38
|
+
const result = {
|
|
36
39
|
ok: false,
|
|
37
40
|
status: e.status ?? 1,
|
|
38
41
|
stdout: String(e.stdout || ""),
|
|
@@ -40,6 +43,8 @@ export function runCmd(cmd, args, opts = {}) {
|
|
|
40
43
|
signal: e.signal ?? null,
|
|
41
44
|
killed: e.killed ?? false,
|
|
42
45
|
};
|
|
46
|
+
if (cmd === "git") Logger.getInstance().git({ cmd: [cmd, ...args], exitCode: result.status, stderr: result.stderr });
|
|
47
|
+
return result;
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -69,25 +74,28 @@ export function runCmdAsync(cmd, args, opts = {}) {
|
|
|
69
74
|
...(opts.env && { env: opts.env }),
|
|
70
75
|
},
|
|
71
76
|
(err, stdout, stderr) => {
|
|
77
|
+
let result;
|
|
72
78
|
if (err) {
|
|
73
|
-
|
|
79
|
+
result = {
|
|
74
80
|
ok: false,
|
|
75
81
|
status: typeof err.code === "number" ? err.code : 1,
|
|
76
82
|
stdout: String(stdout || ""),
|
|
77
83
|
stderr: String(stderr || err.message || ""),
|
|
78
84
|
signal: err.signal ?? null,
|
|
79
85
|
killed: err.killed ?? false,
|
|
80
|
-
}
|
|
86
|
+
};
|
|
81
87
|
} else {
|
|
82
|
-
|
|
88
|
+
result = {
|
|
83
89
|
ok: true,
|
|
84
90
|
status: 0,
|
|
85
91
|
stdout: String(stdout || ""),
|
|
86
92
|
stderr: String(stderr || ""),
|
|
87
93
|
signal: null,
|
|
88
94
|
killed: false,
|
|
89
|
-
}
|
|
95
|
+
};
|
|
90
96
|
}
|
|
97
|
+
if (cmd === "git") Logger.getInstance().git({ cmd: [cmd, ...args], exitCode: result.status, stderr: result.stderr });
|
|
98
|
+
resolve(result);
|
|
91
99
|
},
|
|
92
100
|
);
|
|
93
101
|
});
|
package/src/lib/skills.js
CHANGED
|
@@ -7,6 +7,12 @@ import path from "path";
|
|
|
7
7
|
import { PKG_DIR } from "./cli.js";
|
|
8
8
|
import { resolveIncludes } from "./include.js";
|
|
9
9
|
|
|
10
|
+
/** Canonical path to the bundled main skill templates directory. */
|
|
11
|
+
export const MAIN_SKILLS_TEMPLATES_DIR = path.join(PKG_DIR, "templates", "skills");
|
|
12
|
+
|
|
13
|
+
/** Directories under workRoot where skills are deployed. */
|
|
14
|
+
const SKILL_TARGET_BASES = [".agents", ".claude"];
|
|
15
|
+
|
|
10
16
|
/**
|
|
11
17
|
* Resolve the skill template file in the given directory.
|
|
12
18
|
*/
|
|
@@ -43,8 +49,8 @@ function removeIfSymlink(filePath) {
|
|
|
43
49
|
function deploySkillsFromDir({ templatesDir, workRoot, lang, dryRun = false }) {
|
|
44
50
|
if (!fs.existsSync(templatesDir)) return [];
|
|
45
51
|
|
|
46
|
-
const agentsSkillsDir = path.join(workRoot,
|
|
47
|
-
const claudeSkillsDir = path.join(workRoot,
|
|
52
|
+
const agentsSkillsDir = path.join(workRoot, SKILL_TARGET_BASES[0], "skills");
|
|
53
|
+
const claudeSkillsDir = path.join(workRoot, SKILL_TARGET_BASES[1], "skills");
|
|
48
54
|
|
|
49
55
|
const skillDirs = fs.readdirSync(templatesDir, { withFileTypes: true })
|
|
50
56
|
.filter((d) => d.isDirectory())
|
|
@@ -112,7 +118,7 @@ function deploySkillsFromDir({ templatesDir, workRoot, lang, dryRun = false }) {
|
|
|
112
118
|
*/
|
|
113
119
|
export function deploySkills(workRoot, lang, opts = {}) {
|
|
114
120
|
return deploySkillsFromDir({
|
|
115
|
-
templatesDir:
|
|
121
|
+
templatesDir: MAIN_SKILLS_TEMPLATES_DIR,
|
|
116
122
|
workRoot,
|
|
117
123
|
lang,
|
|
118
124
|
dryRun: opts.dryRun,
|
|
@@ -139,3 +145,49 @@ export function deployProjectSkills(workRoot, templatesDir, lang, opts = {}) {
|
|
|
139
145
|
dryRun: opts.dryRun,
|
|
140
146
|
});
|
|
141
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Remove sdd-forge.* skill directories from .claude/skills/ and .agents/skills/
|
|
151
|
+
* that are no longer present in any of the provided template directories.
|
|
152
|
+
*
|
|
153
|
+
* Only directories whose names start with "sdd-forge." are considered.
|
|
154
|
+
* Skills found in any of the validTemplatesDirs are kept; all others are removed.
|
|
155
|
+
*
|
|
156
|
+
* @param {string} workRoot Project root directory
|
|
157
|
+
* @param {string[]} validTemplatesDirs All active skill template directories (main + experimental)
|
|
158
|
+
* @param {object} [opts]
|
|
159
|
+
* @param {boolean} [opts.dryRun=false]
|
|
160
|
+
* @returns {{ name: string, status: "removed" }[]}
|
|
161
|
+
*/
|
|
162
|
+
export function cleanupObsoleteSkills(workRoot, validTemplatesDirs, opts = {}) {
|
|
163
|
+
const { dryRun = false } = opts;
|
|
164
|
+
|
|
165
|
+
const validNames = new Set(
|
|
166
|
+
validTemplatesDirs.flatMap((dir) => {
|
|
167
|
+
if (!fs.existsSync(dir)) return [];
|
|
168
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
169
|
+
.filter((d) => d.isDirectory())
|
|
170
|
+
.map((d) => d.name);
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const obsoleteNames = new Set();
|
|
175
|
+
for (const base of SKILL_TARGET_BASES) {
|
|
176
|
+
const skillsDir = path.join(workRoot, base, "skills");
|
|
177
|
+
if (!fs.existsSync(skillsDir)) continue;
|
|
178
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
179
|
+
if (!entry.isDirectory() || !entry.name.startsWith("sdd-forge.")) continue;
|
|
180
|
+
if (!validNames.has(entry.name)) obsoleteNames.add(entry.name);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!dryRun) {
|
|
185
|
+
for (const name of obsoleteNames) {
|
|
186
|
+
for (const base of SKILL_TARGET_BASES) {
|
|
187
|
+
fs.rmSync(path.join(workRoot, base, "skills", name), { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return [...obsoleteNames].map((name) => ({ name, status: "removed" }));
|
|
193
|
+
}
|
package/src/lib/types.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* JSDoc 型定義と config / context のバリデーション関数。
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { BUILTIN_PROVIDERS } from "./agent.js";
|
|
8
|
+
|
|
7
9
|
// ---------------------------------------------------------------------------
|
|
8
10
|
// JSDoc 型定義
|
|
9
11
|
// ---------------------------------------------------------------------------
|
|
@@ -63,7 +65,7 @@
|
|
|
63
65
|
* @property {number} [timeout] - Agent execution timeout in seconds
|
|
64
66
|
* @property {number} [retryCount] - Retry count for docs enrich agent calls
|
|
65
67
|
* @property {Object<string, AgentProvider>} [providers] - Agent provider definitions
|
|
66
|
-
* @property {Object} [
|
|
68
|
+
* @property {Object<string, Object<string, string>>} [profiles] - Named profiles mapping commandId prefixes to provider keys
|
|
67
69
|
*/
|
|
68
70
|
|
|
69
71
|
/**
|
|
@@ -345,6 +347,37 @@ export function validateConfig(raw) {
|
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
349
|
|
|
350
|
+
// agent.profiles (省略可)
|
|
351
|
+
if (raw.agent?.profiles != null) {
|
|
352
|
+
if (typeof raw.agent.profiles !== "object" || Array.isArray(raw.agent.profiles)) {
|
|
353
|
+
errors.push("'agent.profiles' must be an object");
|
|
354
|
+
} else {
|
|
355
|
+
const allProviders = { ...BUILTIN_PROVIDERS, ...(raw.agent?.providers || {}) };
|
|
356
|
+
for (const [profileName, profile] of Object.entries(raw.agent.profiles)) {
|
|
357
|
+
if (typeof profile !== "object" || profile == null || Array.isArray(profile)) {
|
|
358
|
+
errors.push(`'agent.profiles.${profileName}' must be an object`);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
for (const [commandId, providerKey] of Object.entries(profile)) {
|
|
362
|
+
if (typeof providerKey !== "string") {
|
|
363
|
+
errors.push(`'agent.profiles.${profileName}.${commandId}' must be a string`);
|
|
364
|
+
} else if (!allProviders[providerKey]) {
|
|
365
|
+
errors.push(`'agent.profiles.${profileName}.${commandId}': unknown provider "${providerKey}"`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// agent.useProfile (省略可)
|
|
373
|
+
if (raw.agent?.useProfile != null) {
|
|
374
|
+
if (typeof raw.agent.useProfile !== "string") {
|
|
375
|
+
errors.push("'agent.useProfile' must be a string");
|
|
376
|
+
} else if (raw.agent.profiles && !raw.agent.profiles[raw.agent.useProfile]) {
|
|
377
|
+
errors.push(`'agent.useProfile': profile "${raw.agent.useProfile}" is not defined in agent.profiles`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
348
381
|
if (errors.length > 0) {
|
|
349
382
|
throw new Error(`Config validation failed:\n - ${errors.join("\n - ")}`);
|
|
350
383
|
}
|
package/src/locale/en/ui.json
CHANGED
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"dryRunHeader": "[upgrade] DRY-RUN: showing changes without writing files.",
|
|
76
76
|
"skillUpdated": "[upgrade] skill updated: {{name}}/SKILL.md",
|
|
77
77
|
"skillUnchanged": "[upgrade] skill unchanged: {{name}}/SKILL.md",
|
|
78
|
+
"skillRemoved": "[upgrade] skill removed (obsolete): {{name}}",
|
|
78
79
|
"agentsUpdated": "[upgrade] AGENTS.md SDD section updated.",
|
|
79
80
|
"agentsNotFound": "[upgrade] AGENTS.md not found or has no SDD section. Skipped.",
|
|
80
81
|
"agentsUnchanged": "[upgrade] AGENTS.md SDD section unchanged.",
|
package/src/locale/ja/ui.json
CHANGED
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"dryRunHeader": "[upgrade] DRY-RUN: ファイルを変更せず差分を表示します。",
|
|
76
76
|
"skillUpdated": "[upgrade] スキル更新: {{name}}/SKILL.md",
|
|
77
77
|
"skillUnchanged": "[upgrade] スキル変更なし: {{name}}/SKILL.md",
|
|
78
|
+
"skillRemoved": "[upgrade] スキル削除(廃止): {{name}}",
|
|
78
79
|
"agentsUpdated": "[upgrade] AGENTS.md の SDD セクションを更新しました。",
|
|
79
80
|
"agentsNotFound": "[upgrade] AGENTS.md が見つからないか SDD セクションがありません。スキップしました。",
|
|
80
81
|
"agentsUnchanged": "[upgrade] AGENTS.md の SDD セクションに変更はありません。",
|
package/src/sdd-forge.js
CHANGED
|
@@ -37,7 +37,7 @@ if (!subCmd || subCmd === "-h" || subCmd === "--help") {
|
|
|
37
37
|
|
|
38
38
|
// Initialize Logger singleton (best-effort — config may not exist yet)
|
|
39
39
|
try {
|
|
40
|
-
const { loadConfig } = await import("./lib/config.js");
|
|
40
|
+
const { loadConfig, sddConfigPath } = await import("./lib/config.js");
|
|
41
41
|
const root = repoRoot();
|
|
42
42
|
const cfg = loadConfig(root);
|
|
43
43
|
const entryCommand = rawArgs.join(" ");
|
|
@@ -46,6 +46,7 @@ try {
|
|
|
46
46
|
process.stderr.write("[sdd-forge] WARN: cfg.logs.prompts is deprecated. Use cfg.logs.enabled instead.\n");
|
|
47
47
|
}
|
|
48
48
|
Logger.getInstance().init(root, cfg, { entryCommand });
|
|
49
|
+
Logger.getInstance().event("config-loaded", { path: sddConfigPath(root), keys: Object.keys(cfg) });
|
|
49
50
|
} catch (err) {
|
|
50
51
|
/* pre-setup or missing config — Logger stays uninitialized */
|
|
51
52
|
if (err?.code !== "ERR_MISSING_FILE") process.stderr.write(`[sdd-forge] Logger init failed: ${err?.message}\n`);
|
|
@@ -12,40 +12,46 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"agent": {
|
|
15
|
-
"default": "claude",
|
|
15
|
+
"default": "claude/sonnet",
|
|
16
16
|
"workDir": ".tmp",
|
|
17
17
|
"timeout": 300,
|
|
18
18
|
"retryCount": 1,
|
|
19
19
|
"providers": {
|
|
20
|
-
"claude": {
|
|
20
|
+
"claude/sonnet": {
|
|
21
21
|
"command": "claude",
|
|
22
|
-
"args": ["-p", "{{PROMPT}}"],
|
|
22
|
+
"args": ["-p", "{{PROMPT}}", "--model", "sonnet"],
|
|
23
23
|
"systemPromptFlag": "--system-prompt",
|
|
24
|
-
"jsonOutputFlag": "--output-format json"
|
|
25
|
-
"profiles": {
|
|
26
|
-
"default": ["--model", "sonnet"],
|
|
27
|
-
"opus": ["--model", "opus"],
|
|
28
|
-
"sonnet": ["--model", "sonnet"]
|
|
29
|
-
}
|
|
24
|
+
"jsonOutputFlag": "--output-format json"
|
|
30
25
|
},
|
|
31
|
-
"
|
|
26
|
+
"claude/opus": {
|
|
27
|
+
"command": "claude",
|
|
28
|
+
"args": ["-p", "{{PROMPT}}", "--model", "opus"],
|
|
29
|
+
"systemPromptFlag": "--system-prompt",
|
|
30
|
+
"jsonOutputFlag": "--output-format json"
|
|
31
|
+
},
|
|
32
|
+
"codex/gpt-5.4": {
|
|
32
33
|
"command": "codex",
|
|
33
34
|
"args": ["exec", "--full-auto", "-C", ".tmp", "{{PROMPT}}"],
|
|
34
|
-
"
|
|
35
|
-
"default": []
|
|
36
|
-
}
|
|
35
|
+
"jsonOutputFlag": "--json"
|
|
37
36
|
}
|
|
38
37
|
},
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
"profiles": {
|
|
39
|
+
"default": {
|
|
40
|
+
"docs.init": "claude/sonnet",
|
|
41
|
+
"docs.enrich": "claude/opus",
|
|
42
|
+
"docs.text": "claude/opus",
|
|
43
|
+
"docs.forge": "claude/opus",
|
|
44
|
+
"docs.readme": "claude/sonnet",
|
|
45
|
+
"docs.agents": "claude/opus",
|
|
46
|
+
"docs.translate": "codex/gpt-5.4",
|
|
47
|
+
"spec.gate": "claude/sonnet",
|
|
48
|
+
"flow.review.draft": "codex/gpt-5.4",
|
|
49
|
+
"flow.review.final": "claude/opus",
|
|
50
|
+
"flow.review.test": "claude/sonnet",
|
|
51
|
+
"flow.review.spec": "claude/sonnet",
|
|
52
|
+
"flow.retro": "claude/sonnet",
|
|
53
|
+
"context.search": "claude/sonnet"
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
}
|
|
@@ -8,6 +8,6 @@ Before presenting any choice to the user, you MUST run `sdd-forge flow get statu
|
|
|
8
8
|
- Continue to the next step without waiting for user input only when `autoApprove: true`.
|
|
9
9
|
- If a step fails (command error, gate FAIL, test failure), apply the retry limits defined in each skill. If the retry limit is reached, STOP and return control to the user.
|
|
10
10
|
|
|
11
|
-
**NEVER run `sdd-forge flow set auto on` yourself.** Only the user can enable autoApprove mode (via `/sdd-forge.flow-auto
|
|
11
|
+
**NEVER run `sdd-forge flow set auto on` yourself.** Only the user can enable autoApprove mode (via `/sdd-forge.flow-auto` or explicit instruction). The AI reads `autoApprove` from flow.json but never writes it.
|
|
12
12
|
|
|
13
|
-
**NEVER chain or background `sdd-forge` commands.** Each `sdd-forge` command must be run as a separate, foreground Bash invocation. Do not use `&&`, `||`, `;`, pipes, or `run_in_background`.
|
|
13
|
+
**NEVER chain or background `sdd-forge` commands.** Each `sdd-forge` command must be run as a separate, foreground Bash invocation. Do not use `&&`, `||`, `;`, pipes, or `run_in_background`. If a command nevertheless ends up in the background (e.g., due to tool behavior), wait for its completion notification before proceeding — do not treat it as complete or advance to the next step until the command's result has been received and read.
|
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: sdd-forge.flow-auto
|
|
3
|
-
description:
|
|
2
|
+
name: sdd-forge.flow-auto
|
|
3
|
+
description: Toggle autoApprove mode for the current SDD flow. Use "on" to enable (default) or "off" to disable.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# SDD Flow Auto
|
|
6
|
+
# SDD Flow Auto
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Toggle autoApprove mode for the current SDD flow.
|
|
9
|
+
|
|
10
|
+
**Usage:** `/sdd-forge.flow-auto [on|off]`
|
|
11
|
+
- No argument → treated as `on`
|
|
12
|
+
- `on` → enable autoApprove and continue the flow automatically
|
|
13
|
+
- `off` → disable autoApprove
|
|
14
|
+
- Any other argument → show error and stop
|
|
9
15
|
|
|
10
16
|
## Procedure
|
|
11
17
|
|
|
18
|
+
### If argument is `off`
|
|
19
|
+
|
|
20
|
+
1. Disable autoApprove.
|
|
21
|
+
- Run `sdd-forge flow set auto off`.
|
|
22
|
+
- If it fails (e.g. no active flow), display the error message and STOP.
|
|
23
|
+
|
|
24
|
+
2. Confirm.
|
|
25
|
+
- Display: "autoApprove mode has been disabled. The AI will ask for confirmation at each step."
|
|
26
|
+
|
|
27
|
+
### If argument is `on` or no argument
|
|
28
|
+
|
|
12
29
|
1. Check flow state.
|
|
13
30
|
- Run `sdd-forge flow get status`.
|
|
14
31
|
- If the command fails or returns `ok: false`, display: "No active flow. Start a flow first with `/sdd-forge.flow-plan`." and STOP.
|
|
@@ -29,3 +46,7 @@ Enable autoApprove mode and continue the current flow automatically.
|
|
|
29
46
|
- If all plan and impl steps are `done` but finalize-phase steps (commit, push, merge, pr-create, branch-cleanup) have any not `done` → invoke `/sdd-forge.flow-finalize`
|
|
30
47
|
- If all steps are `done` → display "All steps are already complete." and STOP.
|
|
31
48
|
- Use the Skill tool to invoke the determined skill.
|
|
49
|
+
|
|
50
|
+
### If argument is anything else
|
|
51
|
+
|
|
52
|
+
- Display: "Unknown argument: '<argument>'. Usage: /sdd-forge.flow-auto [on|off]" and STOP.
|
|
@@ -82,7 +82,7 @@ Available status values: `pending`, `in_progress`, `done`, `skipped`
|
|
|
82
82
|
|
|
83
83
|
- Do not run `sdd-forge flow run finalize` if resolve-context reports `dirty: true` and commit step is not included.
|
|
84
84
|
- Do not proceed to next step without user confirmation.
|
|
85
|
-
- **NEVER chain or background `sdd-forge` commands.** Each `sdd-forge` command must be run as a separate, foreground Bash invocation. Do not use `&&`, `||`, `;`, pipes, or `run_in_background`.
|
|
85
|
+
- **NEVER chain or background `sdd-forge` commands.** Each `sdd-forge` command must be run as a separate, foreground Bash invocation. Do not use `&&`, `||`, `;`, pipes, or `run_in_background`. If a command nevertheless ends up in the background (e.g., due to tool behavior), wait for its completion notification before proceeding — do not treat it as complete or advance to the next step until the command's result has been received and read.
|
|
86
86
|
|
|
87
87
|
**autoApprove exception:** When `autoApprove: true`, the rule "do not proceed to next step without user confirmation" does NOT apply. All other hard stops remain in effect.
|
|
88
88
|
|
package/src/upgrade.js
CHANGED
|
@@ -19,7 +19,7 @@ import { repoRoot, parseArgs } from "./lib/cli.js";
|
|
|
19
19
|
import { EXIT_ERROR } from "./lib/exit-codes.js";
|
|
20
20
|
import { loadConfig, sddConfigPath } from "./lib/config.js";
|
|
21
21
|
import { translate } from "./lib/i18n.js";
|
|
22
|
-
import { deploySkills, deployProjectSkills } from "./lib/skills.js";
|
|
22
|
+
import { deploySkills, deployProjectSkills, cleanupObsoleteSkills, MAIN_SKILLS_TEMPLATES_DIR } from "./lib/skills.js";
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
@@ -65,7 +65,18 @@ async function main() {
|
|
|
65
65
|
console.log(t("ui:upgrade.dryRunHeader"));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function logSkillResults(results) {
|
|
69
|
+
for (const { name, status } of results) {
|
|
70
|
+
if (status === "updated") {
|
|
71
|
+
console.log(t("ui:upgrade.skillUpdated", { name }));
|
|
72
|
+
} else {
|
|
73
|
+
console.log(t("ui:upgrade.skillUnchanged", { name }));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
// 1. Skills upgrade
|
|
79
|
+
const validTemplatesDirs = [MAIN_SKILLS_TEMPLATES_DIR];
|
|
69
80
|
let skillResults;
|
|
70
81
|
try {
|
|
71
82
|
skillResults = deploySkills(root, config.lang, { dryRun });
|
|
@@ -73,28 +84,23 @@ async function main() {
|
|
|
73
84
|
console.error(`upgrade failed: ${e.message}`);
|
|
74
85
|
process.exit(EXIT_ERROR);
|
|
75
86
|
}
|
|
76
|
-
|
|
77
|
-
if (status === "updated") {
|
|
78
|
-
console.log(t("ui:upgrade.skillUpdated", { name }));
|
|
79
|
-
} else {
|
|
80
|
-
console.log(t("ui:upgrade.skillUnchanged", { name }));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
87
|
+
logSkillResults(skillResults);
|
|
83
88
|
|
|
84
89
|
// 1b. Experimental skills (opt-in via config flags)
|
|
85
90
|
if (config.experimental?.workflow?.enable === true) {
|
|
86
91
|
const expDir = path.join(root, "experimental", "workflow", "templates", "skills");
|
|
92
|
+
validTemplatesDirs.push(expDir);
|
|
87
93
|
const expResults = deployProjectSkills(root, expDir, config.lang, { dryRun });
|
|
88
|
-
|
|
89
|
-
if (status === "updated") {
|
|
90
|
-
console.log(t("ui:upgrade.skillUpdated", { name }));
|
|
91
|
-
} else {
|
|
92
|
-
console.log(t("ui:upgrade.skillUnchanged", { name }));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
94
|
+
logSkillResults(expResults);
|
|
95
95
|
skillResults.push(...expResults);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// 1c. Remove obsolete sdd-forge.* skills no longer in any template directory
|
|
99
|
+
const removedSkills = cleanupObsoleteSkills(root, validTemplatesDirs, { dryRun });
|
|
100
|
+
for (const { name } of removedSkills) {
|
|
101
|
+
console.log(t("ui:upgrade.skillRemoved", { name }));
|
|
102
|
+
}
|
|
103
|
+
|
|
98
104
|
// 2. Migrate chapters format (string[] → object[])
|
|
99
105
|
const configPath = sddConfigPath(root);
|
|
100
106
|
try {
|
|
@@ -111,7 +117,7 @@ async function main() {
|
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
// Summary
|
|
114
|
-
const hasChanges = skillResults.some((r) => r.status === "updated");
|
|
120
|
+
const hasChanges = skillResults.some((r) => r.status === "updated") || removedSkills.length > 0;
|
|
115
121
|
if (!hasChanges) {
|
|
116
122
|
console.log(t("ui:upgrade.noChanges"));
|
|
117
123
|
} else if (dryRun) {
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sdd-forge.flow-auto-off
|
|
3
|
-
description: Disable autoApprove mode for the current SDD flow. The AI will resume asking the user for confirmation at each step.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# SDD Flow Auto OFF
|
|
7
|
-
|
|
8
|
-
Disable autoApprove mode.
|
|
9
|
-
|
|
10
|
-
## Procedure
|
|
11
|
-
|
|
12
|
-
1. Disable autoApprove.
|
|
13
|
-
- Run `sdd-forge flow set auto off`.
|
|
14
|
-
- If it fails (e.g. no active flow), display the error message and STOP.
|
|
15
|
-
|
|
16
|
-
2. Confirm.
|
|
17
|
-
- Display: "autoApprove mode has been disabled. The AI will ask for confirmation at each step."
|