sddx-workflow 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +249 -63
  2. package/dist/cli.js +370 -129
  3. package/package.json +1 -1
  4. package/templates/AGENTS.md +38 -5
  5. package/templates/CLAUDE.md +76 -5
  6. package/templates/claude-commands/conventions-sync.md +3 -0
  7. package/templates/claude-commands/impl-gap.md +10 -0
  8. package/templates/claude-commands/research.md +7 -0
  9. package/templates/claude-commands/review.md +1 -1
  10. package/templates/claude-commands/scan.md +5 -0
  11. package/templates/claude-commands/spec-amend.md +7 -0
  12. package/templates/claude-commands/spec-analyze.md +5 -0
  13. package/templates/claude-commands/spec-clarify.md +5 -0
  14. package/templates/claude-commands/spec-conflicts.md +3 -0
  15. package/templates/claude-commands/spec-status.md +3 -0
  16. package/templates/claude-commands/spec-tasks.md +3 -3
  17. package/templates/claude-commands/verify.md +5 -0
  18. package/templates/codex-skills/conventions-sync/SKILL.md +8 -0
  19. package/templates/codex-skills/impl-gap/SKILL.md +10 -0
  20. package/templates/codex-skills/research/SKILL.md +10 -0
  21. package/templates/codex-skills/review/SKILL.md +2 -2
  22. package/templates/codex-skills/scan/SKILL.md +8 -0
  23. package/templates/codex-skills/spec-amend/SKILL.md +10 -0
  24. package/templates/codex-skills/spec-analyze/SKILL.md +8 -0
  25. package/templates/codex-skills/spec-clarify/SKILL.md +8 -0
  26. package/templates/codex-skills/spec-conflicts/SKILL.md +8 -0
  27. package/templates/codex-skills/spec-status/SKILL.md +8 -0
  28. package/templates/codex-skills/spec-tasks/SKILL.md +3 -3
  29. package/templates/codex-skills/verify/SKILL.md +8 -0
  30. package/templates/conventions/base.md +7 -0
  31. package/templates/copilot-instructions.md +35 -5
  32. package/templates/copilot-prompts/conventions-sync.prompt.md +8 -0
  33. package/templates/copilot-prompts/impl-gap.prompt.md +10 -0
  34. package/templates/copilot-prompts/research.prompt.md +10 -0
  35. package/templates/copilot-prompts/review.prompt.md +2 -2
  36. package/templates/copilot-prompts/scan.prompt.md +8 -0
  37. package/templates/copilot-prompts/spec-amend.prompt.md +10 -0
  38. package/templates/copilot-prompts/spec-analyze.prompt.md +8 -0
  39. package/templates/copilot-prompts/spec-clarify.prompt.md +8 -0
  40. package/templates/copilot-prompts/spec-conflicts.prompt.md +8 -0
  41. package/templates/copilot-prompts/spec-status.prompt.md +8 -0
  42. package/templates/copilot-prompts/spec-tasks.prompt.md +3 -3
  43. package/templates/copilot-prompts/verify.prompt.md +8 -0
  44. package/templates/cursor-rules/sddx-workflow.mdc +34 -5
  45. package/templates/gemini-commands/ask.toml +8 -0
  46. package/templates/gemini-commands/assume.toml +10 -0
  47. package/templates/gemini-commands/bootstrap.toml +9 -0
  48. package/templates/gemini-commands/bugfix.toml +10 -0
  49. package/templates/gemini-commands/conventions-sync.toml +6 -0
  50. package/templates/gemini-commands/finish.toml +8 -0
  51. package/templates/gemini-commands/impl-gap.toml +10 -0
  52. package/templates/gemini-commands/refactor.toml +8 -0
  53. package/templates/gemini-commands/research.toml +10 -0
  54. package/templates/gemini-commands/review.toml +8 -0
  55. package/templates/gemini-commands/scan.toml +6 -0
  56. package/templates/gemini-commands/spec-amend.toml +10 -0
  57. package/templates/gemini-commands/spec-analyze.toml +6 -0
  58. package/templates/gemini-commands/spec-clarify.toml +6 -0
  59. package/templates/gemini-commands/spec-conflicts.toml +6 -0
  60. package/templates/gemini-commands/spec-new.toml +7 -0
  61. package/templates/gemini-commands/spec-plan.toml +7 -0
  62. package/templates/gemini-commands/spec-status.toml +6 -0
  63. package/templates/gemini-commands/spec-tasks.toml +8 -0
  64. package/templates/gemini-commands/verify.toml +6 -0
  65. package/templates/gemini.md +37 -5
  66. package/templates/specs/_template/1-requirements.md +42 -0
  67. package/templates/specs/_template/2-plan.md +14 -0
  68. package/templates/specs/_template/2a-data-model.md +29 -0
  69. package/templates/specs/_template/2b-api-contracts.md +27 -0
  70. package/templates/specs/_template/2c-research.md +25 -0
  71. package/templates/specs/_template/3-tasks.md +2 -1
  72. package/templates/specs/_template/amendments.md +15 -0
  73. package/templates/specs/_template/analysis.md +49 -0
  74. package/templates/specs/_template/impl-gaps.md +15 -0
  75. package/templates/specs/_template/verify-report.md +43 -0
  76. package/templates/windsurf-rules/sddx-workflow.md +34 -5
  77. package/templates/windsurf-workflows/ask.md +9 -0
  78. package/templates/windsurf-workflows/assume.md +11 -0
  79. package/templates/windsurf-workflows/bootstrap.md +10 -0
  80. package/templates/windsurf-workflows/bugfix.md +11 -0
  81. package/templates/windsurf-workflows/conventions-sync.md +7 -0
  82. package/templates/windsurf-workflows/finish.md +9 -0
  83. package/templates/windsurf-workflows/impl-gap.md +9 -0
  84. package/templates/windsurf-workflows/refactor.md +9 -0
  85. package/templates/windsurf-workflows/research.md +9 -0
  86. package/templates/windsurf-workflows/review.md +9 -0
  87. package/templates/windsurf-workflows/scan.md +7 -0
  88. package/templates/windsurf-workflows/spec-amend.md +9 -0
  89. package/templates/windsurf-workflows/spec-analyze.md +9 -0
  90. package/templates/windsurf-workflows/spec-clarify.md +9 -0
  91. package/templates/windsurf-workflows/spec-conflicts.md +7 -0
  92. package/templates/windsurf-workflows/spec-new.md +10 -0
  93. package/templates/windsurf-workflows/spec-plan.md +10 -0
  94. package/templates/windsurf-workflows/spec-status.md +7 -0
  95. package/templates/windsurf-workflows/spec-tasks.md +11 -0
  96. package/templates/windsurf-workflows/verify.md +9 -0
  97. package/templates/workflow.md +323 -29
  98. package/templates/zed-rules/sddx-workflow.md +33 -5
package/dist/cli.js CHANGED
@@ -40,41 +40,88 @@ function ensureDir(dir) {
40
40
  import_fs.default.mkdirSync(dir, { recursive: true });
41
41
  }
42
42
  }
43
+ function displayPath(filePath) {
44
+ const relative = import_path.default.relative(process.cwd(), filePath);
45
+ return relative && !relative.startsWith("..") ? relative : filePath;
46
+ }
43
47
  function copyTemplate(src, dest, force) {
44
- const exists = import_fs.default.existsSync(dest);
45
- if (exists && !force) {
46
- console.log(` skip ${dest}`);
48
+ const exists2 = import_fs.default.existsSync(dest);
49
+ if (exists2 && !force) {
50
+ console.log(` skip ${displayPath(dest)}`);
47
51
  return;
48
52
  }
49
53
  import_fs.default.copyFileSync(import_path.default.join(TEMPLATES_DIR, src), dest);
50
- console.log(` ${exists ? "overwrite" : "create "} ${dest}`);
54
+ console.log(` ${exists2 ? "overwrite" : "create "} ${displayPath(dest)}`);
51
55
  }
52
56
 
53
- // src/commands/init.ts
57
+ // src/commands/command-names.ts
58
+ var COMMAND_NAMES = [
59
+ "bootstrap",
60
+ "ask",
61
+ "assume",
62
+ "bugfix",
63
+ "refactor",
64
+ "spec-new",
65
+ "spec-plan",
66
+ "spec-tasks",
67
+ "review",
68
+ "finish",
69
+ "spec-amend",
70
+ "impl-gap",
71
+ "research",
72
+ "verify",
73
+ "scan",
74
+ "conventions-sync",
75
+ "spec-status",
76
+ "spec-conflicts",
77
+ "spec-clarify",
78
+ "spec-analyze"
79
+ ];
80
+
81
+ // src/providers.ts
54
82
  var CORE_FILES = [
55
83
  { src: "workflow.md", dest: ".sdd/workflow.md" },
56
84
  { src: "project-overview.md", dest: ".sdd/project-overview.md" },
57
85
  { src: "conventions/base.md", dest: ".sdd/conventions.md" },
58
- { src: "CLAUDE.md", dest: "CLAUDE.md" },
59
86
  { src: "specs/_template/1-requirements.md", dest: "specs/_template/1-requirements.md" },
60
87
  { src: "specs/_template/2-plan.md", dest: "specs/_template/2-plan.md" },
61
- { src: "specs/_template/3-tasks.md", dest: "specs/_template/3-tasks.md" }
88
+ { src: "specs/_template/3-tasks.md", dest: "specs/_template/3-tasks.md" },
89
+ { src: "specs/_template/amendments.md", dest: "specs/_template/amendments.md" },
90
+ { src: "specs/_template/impl-gaps.md", dest: "specs/_template/impl-gaps.md" },
91
+ { src: "specs/_template/verify-report.md", dest: "specs/_template/verify-report.md" },
92
+ { src: "specs/_template/analysis.md", dest: "specs/_template/analysis.md" },
93
+ { src: "specs/_template/2a-data-model.md", dest: "specs/_template/2a-data-model.md" },
94
+ { src: "specs/_template/2b-api-contracts.md", dest: "specs/_template/2b-api-contracts.md" },
95
+ { src: "specs/_template/2c-research.md", dest: "specs/_template/2c-research.md" }
62
96
  ];
97
+ var claudeCommandFiles = COMMAND_NAMES.map((name) => ({
98
+ src: `claude-commands/${name}.md`,
99
+ dest: `.claude/commands/${name}.md`
100
+ }));
101
+ var copilotPromptFiles = COMMAND_NAMES.map((name) => ({
102
+ src: `copilot-prompts/${name}.prompt.md`,
103
+ dest: `.github/prompts/${name}.prompt.md`
104
+ }));
105
+ var geminiCommandFiles = COMMAND_NAMES.map((name) => ({
106
+ src: `gemini-commands/${name}.toml`,
107
+ dest: `.gemini/commands/${name}.toml`
108
+ }));
109
+ var windsurfWorkflowFiles = COMMAND_NAMES.map((name) => ({
110
+ src: `windsurf-workflows/${name}.md`,
111
+ dest: `.windsurf/workflows/${name}.md`
112
+ }));
113
+ var codexSkillDirs = COMMAND_NAMES.map((name) => `.agents/skills/${name}`);
114
+ var codexSkillFiles = COMMAND_NAMES.map((name) => ({
115
+ src: `codex-skills/${name}/SKILL.md`,
116
+ dest: `.agents/skills/${name}/SKILL.md`
117
+ }));
63
118
  var PROVIDERS = {
64
119
  "claude-code": {
65
120
  name: "Claude Code",
66
121
  dirs: [".claude/commands"],
67
122
  files: [
68
- { src: "claude-commands/bootstrap.md", dest: ".claude/commands/bootstrap.md" },
69
- { src: "claude-commands/ask.md", dest: ".claude/commands/ask.md" },
70
- { src: "claude-commands/assume.md", dest: ".claude/commands/assume.md" },
71
- { src: "claude-commands/bugfix.md", dest: ".claude/commands/bugfix.md" },
72
- { src: "claude-commands/refactor.md", dest: ".claude/commands/refactor.md" },
73
- { src: "claude-commands/spec-new.md", dest: ".claude/commands/spec-new.md" },
74
- { src: "claude-commands/spec-plan.md", dest: ".claude/commands/spec-plan.md" },
75
- { src: "claude-commands/spec-tasks.md", dest: ".claude/commands/spec-tasks.md" },
76
- { src: "claude-commands/review.md", dest: ".claude/commands/review.md" },
77
- { src: "claude-commands/finish.md", dest: ".claude/commands/finish.md" }
123
+ { src: "CLAUDE.md", dest: "CLAUDE.md" },
124
+ ...claudeCommandFiles
78
125
  ]
79
126
  },
80
127
  cursor: {
@@ -86,61 +133,34 @@ var PROVIDERS = {
86
133
  },
87
134
  windsurf: {
88
135
  name: "Windsurf",
89
- dirs: [".windsurf/rules"],
136
+ dirs: [".windsurf/rules", ".windsurf/workflows"],
90
137
  files: [
91
- { src: "windsurf-rules/sddx-workflow.md", dest: ".windsurf/rules/sddx-workflow.md" }
138
+ { src: "windsurf-rules/sddx-workflow.md", dest: ".windsurf/rules/sddx-workflow.md" },
139
+ ...windsurfWorkflowFiles
92
140
  ]
93
141
  },
94
142
  copilot: {
95
143
  name: "GitHub Copilot",
96
144
  dirs: [".github/prompts"],
97
145
  files: [
98
- { src: "copilot-prompts/bootstrap.prompt.md", dest: ".github/prompts/bootstrap.prompt.md" },
99
- { src: "copilot-prompts/ask.prompt.md", dest: ".github/prompts/ask.prompt.md" },
100
- { src: "copilot-prompts/assume.prompt.md", dest: ".github/prompts/assume.prompt.md" },
101
- { src: "copilot-prompts/bugfix.prompt.md", dest: ".github/prompts/bugfix.prompt.md" },
102
- { src: "copilot-prompts/refactor.prompt.md", dest: ".github/prompts/refactor.prompt.md" },
103
- { src: "copilot-prompts/spec-new.prompt.md", dest: ".github/prompts/spec-new.prompt.md" },
104
- { src: "copilot-prompts/spec-plan.prompt.md", dest: ".github/prompts/spec-plan.prompt.md" },
105
- { src: "copilot-prompts/spec-tasks.prompt.md", dest: ".github/prompts/spec-tasks.prompt.md" },
106
- { src: "copilot-prompts/review.prompt.md", dest: ".github/prompts/review.prompt.md" },
107
- { src: "copilot-prompts/finish.prompt.md", dest: ".github/prompts/finish.prompt.md" },
146
+ ...copilotPromptFiles,
108
147
  { src: "copilot-instructions.md", dest: ".github/copilot-instructions.md" }
109
148
  ]
110
149
  },
111
150
  codex: {
112
151
  name: "OpenAI Codex",
113
- dirs: [
114
- ".agents/skills/bootstrap",
115
- ".agents/skills/ask",
116
- ".agents/skills/assume",
117
- ".agents/skills/bugfix",
118
- ".agents/skills/refactor",
119
- ".agents/skills/spec-new",
120
- ".agents/skills/spec-plan",
121
- ".agents/skills/spec-tasks",
122
- ".agents/skills/review",
123
- ".agents/skills/finish"
124
- ],
152
+ dirs: codexSkillDirs,
125
153
  files: [
126
154
  { src: "AGENTS.md", dest: "AGENTS.md" },
127
- { src: "codex-skills/bootstrap/SKILL.md", dest: ".agents/skills/bootstrap/SKILL.md" },
128
- { src: "codex-skills/ask/SKILL.md", dest: ".agents/skills/ask/SKILL.md" },
129
- { src: "codex-skills/assume/SKILL.md", dest: ".agents/skills/assume/SKILL.md" },
130
- { src: "codex-skills/bugfix/SKILL.md", dest: ".agents/skills/bugfix/SKILL.md" },
131
- { src: "codex-skills/refactor/SKILL.md", dest: ".agents/skills/refactor/SKILL.md" },
132
- { src: "codex-skills/spec-new/SKILL.md", dest: ".agents/skills/spec-new/SKILL.md" },
133
- { src: "codex-skills/spec-plan/SKILL.md", dest: ".agents/skills/spec-plan/SKILL.md" },
134
- { src: "codex-skills/spec-tasks/SKILL.md", dest: ".agents/skills/spec-tasks/SKILL.md" },
135
- { src: "codex-skills/review/SKILL.md", dest: ".agents/skills/review/SKILL.md" },
136
- { src: "codex-skills/finish/SKILL.md", dest: ".agents/skills/finish/SKILL.md" }
155
+ ...codexSkillFiles
137
156
  ]
138
157
  },
139
158
  gemini: {
140
159
  name: "Gemini CLI",
141
- dirs: [],
160
+ dirs: [".gemini/commands"],
142
161
  files: [
143
- { src: "gemini.md", dest: "GEMINI.md" }
162
+ { src: "gemini.md", dest: "GEMINI.md" },
163
+ ...geminiCommandFiles
144
164
  ]
145
165
  },
146
166
  zed: {
@@ -152,7 +172,43 @@ var PROVIDERS = {
152
172
  }
153
173
  };
154
174
  var ALL_PROVIDER_IDS = Object.keys(PROVIDERS);
155
- async function selectProviders() {
175
+ var COMMAND_PROVIDER_IDS = ["claude-code", "copilot", "codex", "gemini", "windsurf"];
176
+ var WORKFLOW_FILES = [
177
+ { src: "workflow.md", dest: ".sdd/workflow.md" },
178
+ ...Object.values(PROVIDERS).flatMap((provider) => provider.files)
179
+ ];
180
+ function parseProviderList(value) {
181
+ const rawIds = value.split(",").map((id) => id.trim()).filter(Boolean);
182
+ if (rawIds.length === 0) {
183
+ throw new Error("Provider list cannot be empty");
184
+ }
185
+ const invalid = rawIds.filter((id) => !ALL_PROVIDER_IDS.includes(id));
186
+ if (invalid.length > 0) {
187
+ throw new Error(`Unknown provider: ${invalid.join(", ")}`);
188
+ }
189
+ return [...new Set(rawIds)];
190
+ }
191
+
192
+ // src/commands/init.ts
193
+ async function selectProviders(options) {
194
+ if (options.provider && options.all) {
195
+ console.error("\n error Use either --provider or --all, not both.\n");
196
+ process.exit(1);
197
+ }
198
+ if (options.provider) {
199
+ try {
200
+ return parseProviderList(options.provider);
201
+ } catch (error) {
202
+ console.error(`
203
+ error ${error.message}`);
204
+ console.error(` valid ${ALL_PROVIDER_IDS.join(", ")}
205
+ `);
206
+ process.exit(1);
207
+ }
208
+ }
209
+ if (options.all) {
210
+ return ALL_PROVIDER_IDS;
211
+ }
156
212
  if (!process.stdout.isTTY) {
157
213
  return ALL_PROVIDER_IDS;
158
214
  }
@@ -167,13 +223,16 @@ async function selectProviders() {
167
223
  });
168
224
  return selected;
169
225
  }
226
+ function formatList(items) {
227
+ return items.length > 0 ? items.join(", ") : "none";
228
+ }
170
229
  async function initCommand(options) {
171
230
  const cwd = process.cwd();
172
- const { force } = options;
231
+ const { force, existing } = options;
173
232
  console.log("");
174
- console.log(" SDD Workflow \u2014 initializing");
233
+ console.log(` SDD Workflow \u2014 initializing${existing ? " (existing project mode)" : ""}`);
175
234
  console.log("");
176
- const selectedProviders = await selectProviders();
235
+ const selectedProviders = await selectProviders(options);
177
236
  console.log("");
178
237
  ensureDir(import_path2.default.join(cwd, ".sdd/domains"));
179
238
  ensureDir(import_path2.default.join(cwd, "specs/_template"));
@@ -193,41 +252,58 @@ async function initCommand(options) {
193
252
  copyTemplate(file.src, import_path2.default.join(cwd, file.dest), force);
194
253
  }
195
254
  }
255
+ const providerNames = selectedProviders.map((id) => PROVIDERS[id].name);
256
+ const commandProviders = selectedProviders.filter((id) => COMMAND_PROVIDER_IDS.includes(id));
257
+ const rulesOnly = selectedProviders.filter((id) => !COMMAND_PROVIDER_IDS.includes(id));
258
+ console.log("");
259
+ console.log(" SDD Workflow initialized");
260
+ console.log("");
261
+ console.log(` Providers: ${formatList(providerNames)}`);
262
+ console.log(` Core: .sdd/workflow.md, .sdd/project-overview.md, .sdd/conventions.md, specs/_template/`);
263
+ if (commandProviders.length > 0) {
264
+ const names = commandProviders.map((id) => PROVIDERS[id].name);
265
+ console.log(` Commands: ${formatList(names)}`);
266
+ }
267
+ if (rulesOnly.length > 0) {
268
+ const names = rulesOnly.map((id) => PROVIDERS[id].name);
269
+ console.log(` Rules: ${formatList(names)}`);
270
+ }
196
271
  console.log("");
197
- console.log(" Done. Next steps:");
272
+ console.log(" Next steps:");
198
273
  console.log("");
199
- console.log(" 1. Run /bootstrap to populate project context (new project)");
200
- console.log(" or /bootstrap --scan to let the agent analyze the codebase (existing project)");
201
- if (!claudeExisted) {
202
- console.log(" 2. CLAUDE.md was created \u2014 share it with your AI agent as context");
274
+ if (existing) {
275
+ console.log(" 1. Run /scan to discover the codebase (no .sdd/ writes \u2014 produces scan-report.md)");
276
+ console.log(" then /bootstrap --scan to populate .sdd/project-overview.md and .sdd/conventions.md");
203
277
  } else {
204
- console.log(" 2. CLAUDE.md already exists \u2014 add a reference to .sdd/ files manually");
278
+ console.log(" 1. Run /bootstrap to populate project context (new project)");
279
+ console.log(" or /bootstrap --scan to let the agent analyze the codebase (existing project)");
280
+ }
281
+ const entryMessages = [];
282
+ if (selectedProviders.includes("claude-code")) {
283
+ entryMessages.push(claudeExisted ? "CLAUDE.md already exists \u2014 add a reference to .sdd/ files manually" : "CLAUDE.md was created \u2014 Claude Code will read it automatically");
205
284
  }
206
285
  if (selectedProviders.includes("gemini")) {
207
- if (!geminiExisted) {
208
- console.log(" GEMINI.md was created \u2014 Gemini CLI will read it automatically");
209
- } else {
210
- console.log(" GEMINI.md already exists \u2014 add a reference to .sdd/ files manually");
211
- }
286
+ entryMessages.push(geminiExisted ? "GEMINI.md already exists \u2014 add a reference to .sdd/ files manually" : "GEMINI.md was created \u2014 Gemini CLI will read it automatically");
212
287
  }
213
288
  if (selectedProviders.includes("codex")) {
214
- if (!agentsExisted) {
215
- console.log(" AGENTS.md was created \u2014 Codex will read it automatically");
216
- } else {
217
- console.log(" AGENTS.md already exists \u2014 add a reference to .sdd/ files manually");
289
+ entryMessages.push(agentsExisted ? "AGENTS.md already exists \u2014 add a reference to .sdd/ files manually" : "AGENTS.md was created \u2014 Codex will read it automatically");
290
+ }
291
+ let nextStep = 2;
292
+ if (entryMessages.length > 0) {
293
+ console.log(` ${nextStep}. ${entryMessages[0]}`);
294
+ for (const message of entryMessages.slice(1)) {
295
+ console.log(` ${message}`);
218
296
  }
297
+ nextStep++;
219
298
  }
220
- const commandProviders = ["claude-code", "copilot", "codex"];
221
- const withCommands = selectedProviders.filter((id) => commandProviders.includes(id));
222
- const rulesOnly = selectedProviders.filter((id) => !commandProviders.includes(id));
223
- if (withCommands.length > 0) {
224
- const names = withCommands.map((id) => PROVIDERS[id].name).join(", ");
225
- console.log(` 3. Slash commands ready in: ${names}. Type / to see them.`);
299
+ if (commandProviders.length > 0) {
300
+ const names = commandProviders.map((id) => PROVIDERS[id].name).join(", ");
301
+ console.log(` ${nextStep}. Slash commands ready in: ${names}. Type / to see them.`);
302
+ nextStep++;
226
303
  }
227
304
  if (rulesOnly.length > 0) {
228
305
  const names = rulesOnly.map((id) => PROVIDERS[id].name).join(", ");
229
- const step = withCommands.length === 0 ? "3." : " ";
230
- console.log(` ${step} Context rules installed for: ${names}. The agent reads workflow.md on every task.`);
306
+ console.log(` ${nextStep}. Context rules installed for: ${names}. The agent reads workflow.md on every task.`);
231
307
  }
232
308
  console.log("");
233
309
  }
@@ -265,52 +341,57 @@ function addCommand(type, name) {
265
341
  // src/commands/update.ts
266
342
  var import_fs4 = __toESM(require("fs"));
267
343
  var import_path4 = __toESM(require("path"));
268
- var WORKFLOW_FILES = [
269
- { src: "workflow.md", dest: ".sdd/workflow.md" },
270
- { src: "claude-commands/bootstrap.md", dest: ".claude/commands/bootstrap.md" },
271
- { src: "claude-commands/ask.md", dest: ".claude/commands/ask.md" },
272
- { src: "claude-commands/assume.md", dest: ".claude/commands/assume.md" },
273
- { src: "claude-commands/bugfix.md", dest: ".claude/commands/bugfix.md" },
274
- { src: "claude-commands/refactor.md", dest: ".claude/commands/refactor.md" },
275
- { src: "claude-commands/spec-new.md", dest: ".claude/commands/spec-new.md" },
276
- { src: "claude-commands/spec-plan.md", dest: ".claude/commands/spec-plan.md" },
277
- { src: "claude-commands/spec-tasks.md", dest: ".claude/commands/spec-tasks.md" },
278
- { src: "claude-commands/review.md", dest: ".claude/commands/review.md" },
279
- { src: "claude-commands/finish.md", dest: ".claude/commands/finish.md" },
280
- { src: "cursor-rules/sddx-workflow.mdc", dest: ".cursor/rules/sddx-workflow.mdc" },
281
- { src: "windsurf-rules/sddx-workflow.md", dest: ".windsurf/rules/sddx-workflow.md" },
282
- { src: "copilot-prompts/bootstrap.prompt.md", dest: ".github/prompts/bootstrap.prompt.md" },
283
- { src: "copilot-prompts/ask.prompt.md", dest: ".github/prompts/ask.prompt.md" },
284
- { src: "copilot-prompts/assume.prompt.md", dest: ".github/prompts/assume.prompt.md" },
285
- { src: "copilot-prompts/bugfix.prompt.md", dest: ".github/prompts/bugfix.prompt.md" },
286
- { src: "copilot-prompts/refactor.prompt.md", dest: ".github/prompts/refactor.prompt.md" },
287
- { src: "copilot-prompts/spec-new.prompt.md", dest: ".github/prompts/spec-new.prompt.md" },
288
- { src: "copilot-prompts/spec-plan.prompt.md", dest: ".github/prompts/spec-plan.prompt.md" },
289
- { src: "copilot-prompts/spec-tasks.prompt.md", dest: ".github/prompts/spec-tasks.prompt.md" },
290
- { src: "copilot-prompts/review.prompt.md", dest: ".github/prompts/review.prompt.md" },
291
- { src: "copilot-prompts/finish.prompt.md", dest: ".github/prompts/finish.prompt.md" },
292
- { src: "copilot-instructions.md", dest: ".github/copilot-instructions.md" },
293
- { src: "zed-rules/sddx-workflow.md", dest: ".rules" }
294
- ];
295
- function updateCommand() {
344
+ function fileChanged(src, dest) {
345
+ const template = import_fs4.default.readFileSync(import_path4.default.join(TEMPLATES_DIR, src), "utf8");
346
+ const current = import_fs4.default.readFileSync(dest, "utf8");
347
+ return template !== current;
348
+ }
349
+ function updateCommand(options = {}) {
296
350
  const cwd = process.cwd();
297
351
  if (!import_fs4.default.existsSync(import_path4.default.join(cwd, ".sdd"))) {
298
- console.error("\n error .sdd/ not found. Run `npx sddx-workflow init` first.\n");
352
+ console.error("\n error No SDD installation found in this directory.");
353
+ console.error(" next Run `npx sddx-workflow init` or cd into a project that already has .sdd/.\n");
299
354
  process.exit(1);
300
355
  }
301
356
  console.log("");
302
- console.log(" SDD Workflow \u2014 updating workflow files");
303
- console.log(" (project-overview.md, conventions.md, CLAUDE.md, and domains are yours \u2014 untouched)");
357
+ console.log(` SDD Workflow \u2014 ${options.check ? "checking" : options.dryRun ? "previewing" : "updating"} workflow files`);
358
+ console.log(" (project-overview.md, conventions.md, and domains are yours \u2014 untouched)");
359
+ console.log(" (only files that already exist are updated \u2014 run `init --force` to add new commands)");
304
360
  console.log("");
305
361
  let updated = 0;
362
+ let unchanged = 0;
363
+ let missing = 0;
306
364
  for (const file of WORKFLOW_FILES) {
307
365
  const dest = import_path4.default.join(cwd, file.dest);
308
- if (!import_fs4.default.existsSync(dest)) continue;
366
+ if (!import_fs4.default.existsSync(dest)) {
367
+ missing++;
368
+ continue;
369
+ }
370
+ if (!fileChanged(file.src, dest)) {
371
+ unchanged++;
372
+ continue;
373
+ }
374
+ if (options.check || options.dryRun) {
375
+ console.log(` update ${displayPath(dest)}`);
376
+ updated++;
377
+ continue;
378
+ }
309
379
  copyTemplate(file.src, dest, true);
310
380
  updated++;
311
381
  }
312
382
  console.log("");
313
- console.log(` Done. ${updated} file${updated !== 1 ? "s" : ""} updated.
383
+ if (options.check) {
384
+ console.log(` ${updated === 0 ? "ok" : "outdated"} ${updated} outdated, ${unchanged} current, ${missing} not installed`);
385
+ if (updated > 0) process.exit(1);
386
+ console.log("");
387
+ return;
388
+ }
389
+ if (options.dryRun) {
390
+ console.log(` Preview. ${updated} file${updated !== 1 ? "s" : ""} would be updated, ${unchanged} current, ${missing} not installed.
391
+ `);
392
+ return;
393
+ }
394
+ console.log(` Done. ${updated} file${updated !== 1 ? "s" : ""} updated, ${unchanged} current, ${missing} not installed.
314
395
  `);
315
396
  }
316
397
 
@@ -320,28 +401,97 @@ var import_path5 = __toESM(require("path"));
320
401
  function isBootstrapped(cwd) {
321
402
  const file = import_path5.default.join(cwd, ".sdd/project-overview.md");
322
403
  if (!import_fs5.default.existsSync(file)) return false;
323
- const lines = import_fs5.default.readFileSync(file, "utf8").split("\n");
404
+ const withoutComments = import_fs5.default.readFileSync(file, "utf8").replace(/<!--[\s\S]*?-->/g, "");
405
+ const lines = withoutComments.split("\n");
324
406
  return lines.some((line) => {
325
407
  const t = line.trim();
326
- return t.length > 0 && !t.startsWith("#") && !t.startsWith("<!--") && !t.startsWith("-->");
408
+ return t.length > 0 && !t.startsWith("#") && !t.startsWith(">");
327
409
  });
328
410
  }
411
+ function stripComments(content) {
412
+ return content.replace(/<!--[\s\S]*?-->/g, "");
413
+ }
414
+ function isPlanApproved(specDir) {
415
+ const planFile = import_path5.default.join(specDir, "2-plan.md");
416
+ const tasksFile = import_path5.default.join(specDir, "3-tasks.md");
417
+ if (import_fs5.default.existsSync(planFile)) {
418
+ const plan = stripComments(import_fs5.default.readFileSync(planFile, "utf8"));
419
+ if (/^- \[x\]\s+\*\*Approved\*\*/im.test(plan)) return true;
420
+ }
421
+ if (import_fs5.default.existsSync(tasksFile)) {
422
+ const tasks = import_fs5.default.readFileSync(tasksFile, "utf8");
423
+ const match = tasks.match(/^Plan approved:\s*(.+)$/im);
424
+ return Boolean(match && match[1].trim() && !match[1].includes("<!--"));
425
+ }
426
+ return false;
427
+ }
428
+ function countPendingCrs(specDir) {
429
+ const file = import_path5.default.join(specDir, "amendments.md");
430
+ if (!import_fs5.default.existsSync(file)) return 0;
431
+ const content = stripComments(import_fs5.default.readFileSync(file, "utf8"));
432
+ return (content.match(/\*\*Status:\*\*\s*Pending approval/gi) ?? []).length;
433
+ }
434
+ function countUnresolvedGaps(specDir) {
435
+ const file = import_path5.default.join(specDir, "impl-gaps.md");
436
+ if (!import_fs5.default.existsSync(file)) return 0;
437
+ const content = stripComments(import_fs5.default.readFileSync(file, "utf8"));
438
+ const entries = content.split(/^##\s+GAP-\d+/gim).slice(1);
439
+ return entries.filter((entry) => {
440
+ const resolution = entry.match(/\*\*Resolution:\*\*\s*(.*)/i);
441
+ return !resolution || resolution[1].trim().length === 0 || /filled|pending|tbd/i.test(resolution[1]);
442
+ }).length;
443
+ }
444
+ function inferPhase(specDir, planApproved, tasksDone, tasksTotal) {
445
+ const requirementsFile = import_path5.default.join(specDir, "1-requirements.md");
446
+ const planFile = import_path5.default.join(specDir, "2-plan.md");
447
+ const tasksFile = import_path5.default.join(specDir, "3-tasks.md");
448
+ const verifyFile = import_path5.default.join(specDir, "verify-report.md");
449
+ if (!import_fs5.default.existsSync(requirementsFile)) return "missing requirements";
450
+ if (!import_fs5.default.existsSync(planFile)) return "drafting requirements";
451
+ if (!planApproved) return "awaiting plan approval";
452
+ if (!import_fs5.default.existsSync(tasksFile)) return "awaiting tasks";
453
+ if (tasksTotal === 0) return "tasks not planned";
454
+ if (tasksDone < tasksTotal) return "in /spec-tasks";
455
+ if (!import_fs5.default.existsSync(verifyFile)) return "awaiting /verify";
456
+ const verify = import_fs5.default.readFileSync(verifyFile, "utf8");
457
+ return /\bFAIL\b/i.test(verify) ? "verify failed" : "review pending";
458
+ }
329
459
  function readSpec(specDir) {
330
460
  const name = import_path5.default.basename(specDir);
331
461
  const tasksFile = import_path5.default.join(specDir, "3-tasks.md");
462
+ const planApproved = isPlanApproved(specDir);
463
+ const pendingCrs = countPendingCrs(specDir);
464
+ const unresolvedGaps = countUnresolvedGaps(specDir);
332
465
  if (!import_fs5.default.existsSync(tasksFile)) {
333
- return { name, planApproved: false, tasksDone: 0, tasksTotal: 0 };
466
+ return {
467
+ name,
468
+ phase: inferPhase(specDir, planApproved, 0, 0),
469
+ planApproved,
470
+ tasksDone: 0,
471
+ tasksTotal: 0,
472
+ pendingCrs,
473
+ unresolvedGaps
474
+ };
334
475
  }
335
476
  const content = import_fs5.default.readFileSync(tasksFile, "utf8");
336
- const planApproved = !content.includes("<!-- date -->");
337
- const tasksDone = (content.match(/^- \[x\]/gim) ?? []).length;
338
- const tasksPending = (content.match(/^- \[ \]/gim) ?? []).length;
339
- return { name, planApproved, tasksDone, tasksTotal: tasksDone + tasksPending };
477
+ const taskMatches = [...content.matchAll(/^- \[(x| )\]\s+\*\*(?:T\d+|Task\b)/gim)];
478
+ const tasksDone = taskMatches.filter((match) => match[1].toLowerCase() === "x").length;
479
+ const tasksTotal = taskMatches.length;
480
+ return {
481
+ name,
482
+ phase: inferPhase(specDir, planApproved, tasksDone, tasksTotal),
483
+ planApproved,
484
+ tasksDone,
485
+ tasksTotal,
486
+ pendingCrs,
487
+ unresolvedGaps
488
+ };
340
489
  }
341
490
  function statusCommand() {
342
491
  const cwd = process.cwd();
343
492
  if (!import_fs5.default.existsSync(import_path5.default.join(cwd, ".sdd"))) {
344
- console.error("\n error .sdd/ not found. Run `npx sddx-workflow init` first.\n");
493
+ console.error("\n error No SDD installation found in this directory.");
494
+ console.error(" next Run `npx sddx-workflow init` or cd into a project that already has .sdd/.\n");
345
495
  process.exit(1);
346
496
  }
347
497
  console.log("");
@@ -357,14 +507,103 @@ function statusCommand() {
357
507
  console.log(` open specs ${specs.length}`);
358
508
  for (const spec of specs) {
359
509
  const label = spec.name.padEnd(14);
360
- if (!spec.planApproved) {
361
- console.log(` ${label} awaiting approval`);
362
- } else if (spec.tasksDone === spec.tasksTotal && spec.tasksTotal > 0) {
363
- console.log(` ${label} ${spec.tasksDone}/${spec.tasksTotal} tasks \xB7 done`);
364
- } else {
365
- console.log(` ${label} ${spec.tasksDone}/${spec.tasksTotal} tasks \xB7 in progress`);
510
+ const progress = spec.tasksTotal > 0 ? `${spec.tasksDone}/${spec.tasksTotal} tasks` : "no tasks";
511
+ const outstanding = [
512
+ spec.pendingCrs > 0 ? `${spec.pendingCrs} pending CR${spec.pendingCrs === 1 ? "" : "s"}` : "",
513
+ spec.unresolvedGaps > 0 ? `${spec.unresolvedGaps} unresolved gap${spec.unresolvedGaps === 1 ? "" : "s"}` : ""
514
+ ].filter(Boolean).join(" \xB7 ");
515
+ const suffix = outstanding ? ` \xB7 ${outstanding}` : "";
516
+ console.log(` ${label} ${spec.phase} \xB7 ${progress}${suffix}`);
517
+ }
518
+ console.log("");
519
+ }
520
+
521
+ // src/commands/doctor.ts
522
+ var import_fs6 = __toESM(require("fs"));
523
+ var import_path6 = __toESM(require("path"));
524
+ var OBSOLETE_PATHS = [
525
+ "src/commands/snapshot.ts",
526
+ "templates/claude-commands/spec-restore.md",
527
+ "templates/codex-skills/spec-restore/SKILL.md",
528
+ "templates/copilot-prompts/spec-restore.prompt.md",
529
+ "templates/gemini-commands/spec-restore.toml",
530
+ "templates/windsurf-workflows/spec-restore.md",
531
+ ".claude/commands/spec-restore.md",
532
+ ".agents/skills/spec-restore/SKILL.md",
533
+ ".github/prompts/spec-restore.prompt.md",
534
+ ".gemini/commands/spec-restore.toml",
535
+ ".windsurf/workflows/spec-restore.md"
536
+ ];
537
+ function exists(cwd, relativePath) {
538
+ return import_fs6.default.existsSync(import_path6.default.join(cwd, relativePath));
539
+ }
540
+ function providerHealth(cwd) {
541
+ return Object.entries(PROVIDERS).map(([id, provider]) => ({
542
+ id,
543
+ name: provider.name,
544
+ installed: provider.files.filter((file) => exists(cwd, file.dest)).length,
545
+ missing: provider.files.filter((file) => !exists(cwd, file.dest)).map((file) => file.dest)
546
+ })).filter((provider) => provider.installed > 0);
547
+ }
548
+ function doctorCommand() {
549
+ const cwd = process.cwd();
550
+ const issues = [];
551
+ const warnings = [];
552
+ console.log("");
553
+ console.log(" SDD Workflow doctor");
554
+ console.log("");
555
+ const hasSdd = exists(cwd, ".sdd");
556
+ console.log(` install ${hasSdd ? "found" : "missing"}`);
557
+ if (!hasSdd) {
558
+ console.log("");
559
+ console.log(" error No .sdd/ directory found. Run `npx sddx-workflow init`.");
560
+ console.log("");
561
+ process.exit(1);
562
+ }
563
+ const missingCore = CORE_FILES.map((file) => file.dest).filter((dest) => !exists(cwd, dest));
564
+ console.log(` core files ${missingCore.length === 0 ? "ok" : `${missingCore.length} missing`}`);
565
+ for (const missing of missingCore) {
566
+ warnings.push(`Missing core file: ${missing}`);
567
+ }
568
+ const providers = providerHealth(cwd);
569
+ console.log(` providers ${providers.length > 0 ? providers.map((provider) => provider.name).join(", ") : "none detected"}`);
570
+ if (providers.length === 0) {
571
+ warnings.push("No provider files detected. Run `npx sddx-workflow init --provider <id>` or `npx sddx-workflow init --all`.");
572
+ }
573
+ for (const provider of providers) {
574
+ if (provider.missing.length > 0) {
575
+ warnings.push(`${provider.name} appears partially installed (${provider.missing.length} missing file${provider.missing.length === 1 ? "" : "s"}). Run \`npx sddx-workflow init --force --provider ${provider.id}\` to reinstall it.`);
366
576
  }
367
577
  }
578
+ const obsolete = OBSOLETE_PATHS.filter((relativePath) => exists(cwd, relativePath));
579
+ console.log(` obsolete ${obsolete.length === 0 ? "none" : `${obsolete.length} found`}`);
580
+ for (const item of obsolete) {
581
+ warnings.push(`Obsolete snapshot/restore file remains: ${item}`);
582
+ }
583
+ console.log("");
584
+ if (issues.length === 0 && warnings.length === 0) {
585
+ console.log(" ok installation looks healthy");
586
+ console.log("");
587
+ return;
588
+ }
589
+ for (const issue of issues) {
590
+ console.log(` error ${issue}`);
591
+ }
592
+ for (const warning of warnings) {
593
+ console.log(` warn ${warning}`);
594
+ }
595
+ console.log("");
596
+ if (issues.length > 0) process.exit(1);
597
+ }
598
+
599
+ // src/commands/commands.ts
600
+ function commandsCommand() {
601
+ console.log("");
602
+ console.log(" Agent commands");
603
+ console.log("");
604
+ for (const name of COMMAND_NAMES) {
605
+ console.log(` /${name}`);
606
+ }
368
607
  console.log("");
369
608
  }
370
609
 
@@ -373,8 +612,10 @@ var import_module = require("module");
373
612
  var pkg = (0, import_module.createRequire)(__filename)("../package.json");
374
613
  var program = new import_commander.Command();
375
614
  program.name("sddx-workflow").description("Spec-Driven Development CLI").version(pkg.version);
376
- program.command("init").description("Initialize SDD protocol in the current project").option("--force", "Overwrite files that already exist").action(initCommand);
615
+ program.command("init").description("Initialize SDD protocol in the current project").option("--force", "Overwrite files that already exist").option("--existing", "Brownfield mode: prints next-steps that start with /scan and /bootstrap --scan").option("--provider <ids>", "Comma-separated providers to install (claude-code,cursor,windsurf,copilot,codex,gemini,zed)").option("--all", "Install all provider integrations without prompting").action(initCommand);
377
616
  program.command("add <type> <name>").description("Add an SDD component to an existing installation").addHelpText("after", "\nExamples:\n $ sddx-workflow add domain auth\n $ sddx-workflow add domain payments").action(addCommand);
378
- program.command("update").description("Update protocol files to the latest version (leaves your config files untouched)").action(updateCommand);
617
+ program.command("update").description("Update protocol files to the latest version").option("--dry-run", "Show which installed workflow files would change").option("--check", "Exit non-zero when installed workflow files are outdated").action(updateCommand);
379
618
  program.command("status").description("Show bootstrap status and open specs progress").action(statusCommand);
619
+ program.command("doctor").description("Check SDD installation health and provider files").action(doctorCommand);
620
+ program.command("commands").description("List agent commands installed by provider integrations").action(commandsCommand);
380
621
  program.parseAsync();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sddx-workflow",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Spec-Driven Development CLI — installs an SDD system in any project",
5
5
  "keywords": [
6
6
  "sdd",