@gordon.gan/specflow 1.0.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 (178) hide show
  1. package/LICENSE +31 -0
  2. package/README.md +442 -0
  3. package/bin/specflow.js +2 -0
  4. package/dist/cli/commands/change-archive.d.ts +26 -0
  5. package/dist/cli/commands/change-archive.js +50 -0
  6. package/dist/cli/commands/change-new.d.ts +18 -0
  7. package/dist/cli/commands/change-new.js +53 -0
  8. package/dist/cli/commands/change-phase.d.ts +33 -0
  9. package/dist/cli/commands/change-phase.js +66 -0
  10. package/dist/cli/commands/change-status.d.ts +35 -0
  11. package/dist/cli/commands/change-status.js +95 -0
  12. package/dist/cli/commands/doctor.d.ts +9 -0
  13. package/dist/cli/commands/doctor.js +48 -0
  14. package/dist/cli/commands/init.d.ts +16 -0
  15. package/dist/cli/commands/init.js +122 -0
  16. package/dist/cli/commands/instructions.d.ts +21 -0
  17. package/dist/cli/commands/instructions.js +75 -0
  18. package/dist/cli/commands/parity-report.d.ts +10 -0
  19. package/dist/cli/commands/parity-report.js +67 -0
  20. package/dist/cli/commands/sync.d.ts +4 -0
  21. package/dist/cli/commands/sync.js +40 -0
  22. package/dist/cli/commands/validate.d.ts +21 -0
  23. package/dist/cli/commands/validate.js +46 -0
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +52 -0
  26. package/dist/core/archive.d.ts +39 -0
  27. package/dist/core/archive.js +154 -0
  28. package/dist/core/artifact-graph/graph.d.ts +60 -0
  29. package/dist/core/artifact-graph/graph.js +141 -0
  30. package/dist/core/artifact-graph/index.d.ts +10 -0
  31. package/dist/core/artifact-graph/index.js +7 -0
  32. package/dist/core/artifact-graph/instruction-loader.d.ts +45 -0
  33. package/dist/core/artifact-graph/instruction-loader.js +37 -0
  34. package/dist/core/artifact-graph/outputs.d.ts +35 -0
  35. package/dist/core/artifact-graph/outputs.js +48 -0
  36. package/dist/core/artifact-graph/resolver.d.ts +54 -0
  37. package/dist/core/artifact-graph/resolver.js +139 -0
  38. package/dist/core/artifact-graph/schema.d.ts +23 -0
  39. package/dist/core/artifact-graph/schema.js +115 -0
  40. package/dist/core/artifact-graph/state.d.ts +9 -0
  41. package/dist/core/artifact-graph/state.js +93 -0
  42. package/dist/core/artifact-graph/types.d.ts +123 -0
  43. package/dist/core/artifact-graph/types.js +29 -0
  44. package/dist/core/parsers/change-parser.d.ts +20 -0
  45. package/dist/core/parsers/change-parser.js +22 -0
  46. package/dist/core/parsers/index.d.ts +7 -0
  47. package/dist/core/parsers/index.js +4 -0
  48. package/dist/core/parsers/markdown-parser.d.ts +35 -0
  49. package/dist/core/parsers/markdown-parser.js +193 -0
  50. package/dist/core/parsers/requirement-blocks.d.ts +21 -0
  51. package/dist/core/parsers/requirement-blocks.js +57 -0
  52. package/dist/core/parsers/spec-structure.d.ts +22 -0
  53. package/dist/core/parsers/spec-structure.js +105 -0
  54. package/dist/core/specs-apply.d.ts +22 -0
  55. package/dist/core/specs-apply.js +165 -0
  56. package/dist/core/templates/index.d.ts +31 -0
  57. package/dist/core/templates/index.js +80 -0
  58. package/dist/core/templates/types.d.ts +4 -0
  59. package/dist/core/templates/types.js +1 -0
  60. package/dist/core/validation/constants.d.ts +14 -0
  61. package/dist/core/validation/constants.js +14 -0
  62. package/dist/core/validation/types.d.ts +13 -0
  63. package/dist/core/validation/types.js +6 -0
  64. package/dist/core/validation/validator.d.ts +24 -0
  65. package/dist/core/validation/validator.js +250 -0
  66. package/dist/integrations/claude/adapter.d.ts +2 -0
  67. package/dist/integrations/claude/adapter.js +106 -0
  68. package/dist/integrations/codex/adapter.d.ts +2 -0
  69. package/dist/integrations/codex/adapter.js +141 -0
  70. package/dist/integrations/cursor/adapter.d.ts +2 -0
  71. package/dist/integrations/cursor/adapter.js +126 -0
  72. package/dist/integrations/shared/adapter-registry.d.ts +4 -0
  73. package/dist/integrations/shared/adapter-registry.js +27 -0
  74. package/dist/integrations/shared/asset-copy.d.ts +2 -0
  75. package/dist/integrations/shared/asset-copy.js +44 -0
  76. package/dist/integrations/shared/asset-hash.d.ts +4 -0
  77. package/dist/integrations/shared/asset-hash.js +54 -0
  78. package/dist/integrations/shared/capability-evidence.d.ts +1 -0
  79. package/dist/integrations/shared/capability-evidence.js +42 -0
  80. package/dist/integrations/shared/command-catalog.d.ts +6 -0
  81. package/dist/integrations/shared/command-catalog.js +13 -0
  82. package/dist/integrations/shared/managed-assets.d.ts +2 -0
  83. package/dist/integrations/shared/managed-assets.js +21 -0
  84. package/dist/integrations/shared/marker-write.d.ts +3 -0
  85. package/dist/integrations/shared/marker-write.js +48 -0
  86. package/dist/integrations/shared/migration-state.d.ts +2 -0
  87. package/dist/integrations/shared/migration-state.js +67 -0
  88. package/dist/integrations/shared/parity-comparator.d.ts +6 -0
  89. package/dist/integrations/shared/parity-comparator.js +97 -0
  90. package/dist/integrations/shared/parity-manifest.d.ts +13 -0
  91. package/dist/integrations/shared/parity-manifest.js +58 -0
  92. package/dist/integrations/shared/runtime-assets.d.ts +2 -0
  93. package/dist/integrations/shared/runtime-assets.js +21 -0
  94. package/dist/integrations/shared/skill-renderer.d.ts +5 -0
  95. package/dist/integrations/shared/skill-renderer.js +77 -0
  96. package/dist/integrations/shared/types.d.ts +35 -0
  97. package/dist/integrations/shared/types.js +1 -0
  98. package/dist/utils/change-metadata.d.ts +38 -0
  99. package/dist/utils/change-metadata.js +82 -0
  100. package/dist/utils/change-utils.d.ts +53 -0
  101. package/dist/utils/change-utils.js +97 -0
  102. package/dist/utils/file-system.d.ts +35 -0
  103. package/dist/utils/file-system.js +67 -0
  104. package/dist/utils/index.d.ts +7 -0
  105. package/dist/utils/index.js +5 -0
  106. package/dist/utils/project-root.d.ts +22 -0
  107. package/dist/utils/project-root.js +44 -0
  108. package/dist/utils/task-progress.d.ts +39 -0
  109. package/dist/utils/task-progress.js +62 -0
  110. package/package.json +72 -0
  111. package/prompts/build/ecc-go-reviewer.md +80 -0
  112. package/prompts/build/ecc-java-reviewer.md +185 -0
  113. package/prompts/build/ecc-kotlin-reviewer.md +163 -0
  114. package/prompts/build/ecc-python-reviewer.md +102 -0
  115. package/prompts/build/ecc-rust-reviewer.md +98 -0
  116. package/prompts/build/ecc-typescript-reviewer.md +116 -0
  117. package/prompts/build/language-router.md +22 -0
  118. package/prompts/build/phase-a-plan.md +288 -0
  119. package/prompts/build/phase-b-execute.md +276 -0
  120. package/prompts/build/phase-b-review.md +119 -0
  121. package/prompts/build/phase-b-worktree.md +215 -0
  122. package/prompts/build/plan-document-reviewer.md +51 -0
  123. package/prompts/build/tdd.md +184 -0
  124. package/prompts/done/branch-finish.md +204 -0
  125. package/prompts/fix/debug.md +249 -0
  126. package/prompts/plan/design-draft.md +123 -0
  127. package/prompts/plan/proposal.md +85 -0
  128. package/prompts/plan/specs.md +114 -0
  129. package/prompts/plan/tasks-draft.md +123 -0
  130. package/prompts/reference/openspec/apply-instruction.md +2 -0
  131. package/prompts/reference/openspec/design-instruction.md +20 -0
  132. package/prompts/reference/openspec/proposal-instruction.md +18 -0
  133. package/prompts/reference/openspec/specs-instruction.md +47 -0
  134. package/prompts/reference/openspec/tasks-instruction.md +26 -0
  135. package/prompts/reference/specflow/example-design.md +284 -0
  136. package/prompts/reference/superpowers/anthropic-best-practices.md +1150 -0
  137. package/prompts/reference/superpowers/codex-tools.md +29 -0
  138. package/prompts/reference/superpowers/condition-based-waiting-example.ts +158 -0
  139. package/prompts/reference/superpowers/condition-based-waiting.md +115 -0
  140. package/prompts/reference/superpowers/defense-in-depth.md +122 -0
  141. package/prompts/reference/superpowers/find-polluter.sh +63 -0
  142. package/prompts/reference/superpowers/graphviz-conventions.dot +172 -0
  143. package/prompts/reference/superpowers/persuasion-principles.md +187 -0
  144. package/prompts/reference/superpowers/root-cause-tracing.md +169 -0
  145. package/prompts/reference/superpowers/testing-anti-patterns.md +299 -0
  146. package/prompts/reference/superpowers/testing-skills-with-subagents.md +384 -0
  147. package/prompts/reference/superpowers/using-superpowers.md +117 -0
  148. package/prompts/reference/superpowers/writing-skills.md +655 -0
  149. package/prompts/refine/brainstorm.md +241 -0
  150. package/prompts/refine/design-output.md +126 -0
  151. package/prompts/refine/spec-document-reviewer.md +51 -0
  152. package/prompts/refine/update-artifacts.md +185 -0
  153. package/prompts/review/code-review.md +223 -0
  154. package/prompts/shared/code-reviewer-prompt.md +98 -0
  155. package/prompts/shared/dispatching-parallel-agents.md +143 -0
  156. package/prompts/shared/executing-plans.md +67 -0
  157. package/prompts/shared/implementer-prompt.md +115 -0
  158. package/prompts/shared/receiving-code-review.md +174 -0
  159. package/prompts/shared/spec-reviewer-prompt.md +63 -0
  160. package/prompts/test/tdd.md +236 -0
  161. package/prompts/test/verification.md +129 -0
  162. package/prompts/verify/ecc-security-reviewer.md +112 -0
  163. package/prompts/verify/verification.md +196 -0
  164. package/schemas/specflow/schema.yaml +48 -0
  165. package/skills/specflow-build/SKILL.md +129 -0
  166. package/skills/specflow-done/SKILL.md +68 -0
  167. package/skills/specflow-fix/SKILL.md +74 -0
  168. package/skills/specflow-plan/SKILL.md +82 -0
  169. package/skills/specflow-refine/SKILL.md +128 -0
  170. package/skills/specflow-review/SKILL.md +40 -0
  171. package/skills/specflow-scan/SKILL.md +48 -0
  172. package/skills/specflow-snap/SKILL.md +46 -0
  173. package/skills/specflow-test/SKILL.md +48 -0
  174. package/skills/specflow-verify/SKILL.md +77 -0
  175. package/templates/design.md +19 -0
  176. package/templates/proposal.md +23 -0
  177. package/templates/spec.md +42 -0
  178. package/templates/tasks.md +9 -0
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Valid template identifiers corresponding to markdown files in the templates/ directory.
3
+ */
4
+ export type TemplateId = 'proposal' | 'spec' | 'design' | 'tasks';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Validation constants.
3
+ *
4
+ * Regex patterns and heading markers used during spec validation.
5
+ */
6
+ export declare const SECTION_HEADER: RegExp;
7
+ export declare const REQUIREMENT_HEADER: RegExp;
8
+ export declare const SCENARIO_HEADER_CORRECT: RegExp;
9
+ export declare const SCENARIO_HEADER_WRONG_LEVEL: RegExp;
10
+ export declare const WHEN_LINE: RegExp;
11
+ export declare const THEN_LINE: RegExp;
12
+ export declare const REASON_LINE: RegExp;
13
+ export declare const FROM_LINE: RegExp;
14
+ export declare const TO_LINE: RegExp;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Validation constants.
3
+ *
4
+ * Regex patterns and heading markers used during spec validation.
5
+ */
6
+ export const SECTION_HEADER = /^##\s+(ADDED|MODIFIED|REMOVED|RENAMED)\s+Requirements\s*$/i;
7
+ export const REQUIREMENT_HEADER = /^###\s+Requirement:\s*(.+)\s*$/;
8
+ export const SCENARIO_HEADER_CORRECT = /^####\s+Scenario:\s*(.+)\s*$/;
9
+ export const SCENARIO_HEADER_WRONG_LEVEL = /^###\s+Scenario:\s*(.+)\s*$/;
10
+ export const WHEN_LINE = /^-\s+\*\*WHEN\*\*\s+(.+)$/;
11
+ export const THEN_LINE = /^-\s+\*\*THEN\*\*\s+(.+)$/;
12
+ export const REASON_LINE = /^\*\*Reason\*\*:\s*(.+)$/;
13
+ export const FROM_LINE = /^FROM:\s*(.+)$/;
14
+ export const TO_LINE = /^TO:\s*(.+)$/;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Validation types.
3
+ *
4
+ * Defines the result and error shapes returned by spec validation.
5
+ */
6
+ export interface ValidationError {
7
+ readonly message: string;
8
+ readonly line?: number;
9
+ }
10
+ export interface ValidationResult {
11
+ readonly valid: boolean;
12
+ readonly errors: readonly ValidationError[];
13
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Validation types.
3
+ *
4
+ * Defines the result and error shapes returned by spec validation.
5
+ */
6
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Spec validator.
3
+ *
4
+ * Validates delta-spec markdown content for structural correctness:
5
+ * - ADDED/MODIFIED sections must have requirements with scenarios
6
+ * - Scenarios must use #### heading (not ###)
7
+ * - Scenarios must have WHEN and THEN clauses
8
+ * - REMOVED sections must have a Reason
9
+ * - RENAMED sections must have FROM and TO lines
10
+ */
11
+ import type { ValidationResult } from './types.js';
12
+ /**
13
+ * Validate a delta-spec markdown content string.
14
+ *
15
+ * Checks structural rules including:
16
+ * - ADDED/MODIFIED requirements must have scenarios with WHEN/THEN
17
+ * - Scenarios must use #### heading level
18
+ * - REMOVED requirements must have a Reason
19
+ * - RENAMED entries must have FROM and TO
20
+ *
21
+ * @param content - The raw markdown content of a delta spec
22
+ * @returns A ValidationResult with valid flag and any errors found
23
+ */
24
+ export declare function validateSpec(content: string): ValidationResult;
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Spec validator.
3
+ *
4
+ * Validates delta-spec markdown content for structural correctness:
5
+ * - ADDED/MODIFIED sections must have requirements with scenarios
6
+ * - Scenarios must use #### heading (not ###)
7
+ * - Scenarios must have WHEN and THEN clauses
8
+ * - REMOVED sections must have a Reason
9
+ * - RENAMED sections must have FROM and TO lines
10
+ */
11
+ import { SECTION_HEADER, REQUIREMENT_HEADER, SCENARIO_HEADER_CORRECT, SCENARIO_HEADER_WRONG_LEVEL, WHEN_LINE, THEN_LINE, REASON_LINE, FROM_LINE, TO_LINE, } from './constants.js';
12
+ function splitIntoSections(allLines) {
13
+ const sections = [];
14
+ let current = null;
15
+ for (let i = 0; i < allLines.length; i++) {
16
+ const match = SECTION_HEADER.exec(allLines[i]);
17
+ if (match) {
18
+ if (current) {
19
+ sections.push({ kind: current.kind, startLine: current.startLine, lines: current.lines });
20
+ }
21
+ current = {
22
+ kind: match[1].toUpperCase(),
23
+ startLine: i,
24
+ lines: [],
25
+ };
26
+ continue;
27
+ }
28
+ if (current) {
29
+ current.lines.push(allLines[i]);
30
+ }
31
+ }
32
+ if (current) {
33
+ sections.push({ kind: current.kind, startLine: current.startLine, lines: current.lines });
34
+ }
35
+ return sections;
36
+ }
37
+ function validateRequirementSection(section, errors) {
38
+ const { lines, startLine } = section;
39
+ // Check for scenarios at wrong heading level (### instead of ####)
40
+ for (let i = 0; i < lines.length; i++) {
41
+ if (SCENARIO_HEADER_WRONG_LEVEL.test(lines[i])) {
42
+ errors.push({
43
+ message: `Scenario has incorrect heading level (use #### not ###)`,
44
+ line: startLine + 1 + i + 1,
45
+ });
46
+ }
47
+ }
48
+ // Check for orphan scenarios before any requirement header
49
+ let foundFirstReq = false;
50
+ for (let i = 0; i < lines.length; i++) {
51
+ if (REQUIREMENT_HEADER.test(lines[i])) {
52
+ foundFirstReq = true;
53
+ break;
54
+ }
55
+ if (SCENARIO_HEADER_CORRECT.test(lines[i])) {
56
+ errors.push({
57
+ message: `Scenario found with missing requirement header`,
58
+ line: startLine + 1 + i + 1,
59
+ });
60
+ break;
61
+ }
62
+ }
63
+ // If section has no requirement headers at all but has content, skip req parsing
64
+ const hasAnyRequirement = lines.some((l) => REQUIREMENT_HEADER.test(l));
65
+ if (!hasAnyRequirement) {
66
+ // If there are scenarios without a requirement, we already flagged it above
67
+ return;
68
+ }
69
+ // Parse requirements and validate each has at least one scenario with WHEN/THEN
70
+ let currentReqName = null;
71
+ let currentReqLine = 0;
72
+ let scenarioCount = 0;
73
+ let scenariosForCurrent = [];
74
+ const flushReq = () => {
75
+ if (currentReqName !== null) {
76
+ if (scenarioCount === 0) {
77
+ errors.push({
78
+ message: `Requirement "${currentReqName}" has no scenarios`,
79
+ line: currentReqLine,
80
+ });
81
+ }
82
+ for (const sc of scenariosForCurrent) {
83
+ if (!sc.hasWhen) {
84
+ errors.push({
85
+ message: `Scenario "${sc.name}" is missing WHEN clause`,
86
+ line: sc.line,
87
+ });
88
+ }
89
+ if (!sc.hasThen) {
90
+ errors.push({
91
+ message: `Scenario "${sc.name}" is missing THEN clause`,
92
+ line: sc.line,
93
+ });
94
+ }
95
+ }
96
+ }
97
+ };
98
+ let currentScenario = null;
99
+ for (let i = 0; i < lines.length; i++) {
100
+ const lineNum = startLine + 1 + i + 1;
101
+ const line = lines[i];
102
+ const reqMatch = REQUIREMENT_HEADER.exec(line);
103
+ if (reqMatch) {
104
+ // Flush previous scenario into list
105
+ if (currentScenario) {
106
+ scenariosForCurrent.push(currentScenario);
107
+ currentScenario = null;
108
+ }
109
+ flushReq();
110
+ currentReqName = reqMatch[1].trim();
111
+ currentReqLine = lineNum;
112
+ scenarioCount = 0;
113
+ scenariosForCurrent = [];
114
+ continue;
115
+ }
116
+ const scMatch = SCENARIO_HEADER_CORRECT.exec(line);
117
+ if (scMatch && currentReqName !== null) {
118
+ if (currentScenario) {
119
+ scenariosForCurrent.push(currentScenario);
120
+ }
121
+ scenarioCount++;
122
+ currentScenario = { name: scMatch[1].trim(), line: lineNum, hasWhen: false, hasThen: false };
123
+ continue;
124
+ }
125
+ if (currentScenario !== null) {
126
+ const hasWhen = currentScenario.hasWhen || WHEN_LINE.test(line);
127
+ const hasThen = currentScenario.hasThen || THEN_LINE.test(line);
128
+ if (hasWhen !== currentScenario.hasWhen || hasThen !== currentScenario.hasThen) {
129
+ currentScenario = {
130
+ name: currentScenario.name,
131
+ line: currentScenario.line,
132
+ hasWhen,
133
+ hasThen,
134
+ };
135
+ }
136
+ }
137
+ }
138
+ // Flush last scenario and requirement
139
+ if (currentScenario) {
140
+ scenariosForCurrent.push(currentScenario);
141
+ }
142
+ flushReq();
143
+ }
144
+ function validateRemovedSection(section, errors) {
145
+ const { lines, startLine } = section;
146
+ let currentReqName = null;
147
+ let currentReqLine = 0;
148
+ let hasReason = false;
149
+ const flushRemoved = () => {
150
+ if (currentReqName !== null && !hasReason) {
151
+ errors.push({
152
+ message: `REMOVED requirement "${currentReqName}" is missing Reason`,
153
+ line: currentReqLine,
154
+ });
155
+ }
156
+ };
157
+ for (let i = 0; i < lines.length; i++) {
158
+ const lineNum = startLine + 1 + i + 1;
159
+ const line = lines[i];
160
+ const reqMatch = REQUIREMENT_HEADER.exec(line);
161
+ if (reqMatch) {
162
+ flushRemoved();
163
+ currentReqName = reqMatch[1].trim();
164
+ currentReqLine = lineNum;
165
+ hasReason = false;
166
+ continue;
167
+ }
168
+ if (REASON_LINE.test(line)) {
169
+ hasReason = true;
170
+ }
171
+ }
172
+ flushRemoved();
173
+ }
174
+ function validateRenamedSection(section, errors) {
175
+ const { lines, startLine } = section;
176
+ let hasFrom = false;
177
+ let hasTo = false;
178
+ let fromLine = 0;
179
+ for (let i = 0; i < lines.length; i++) {
180
+ const line = lines[i];
181
+ if (FROM_LINE.test(line)) {
182
+ // If we had a previous FROM without TO, report it
183
+ if (hasFrom && !hasTo) {
184
+ errors.push({
185
+ message: `RENAMED entry is missing TO (has FROM but no TO)`,
186
+ line: startLine + 1 + fromLine + 1,
187
+ });
188
+ }
189
+ hasFrom = true;
190
+ hasTo = false;
191
+ fromLine = i;
192
+ }
193
+ if (TO_LINE.test(line)) {
194
+ hasTo = true;
195
+ }
196
+ }
197
+ // Check last pair
198
+ if (hasFrom && !hasTo) {
199
+ errors.push({
200
+ message: `RENAMED entry is missing TO (has FROM but no TO)`,
201
+ line: startLine + 1 + fromLine + 1,
202
+ });
203
+ }
204
+ // If no FROM found at all
205
+ if (!hasFrom) {
206
+ const hasContent = lines.some((l) => l.trim().length > 0);
207
+ if (hasContent) {
208
+ errors.push({
209
+ message: `RENAMED section is missing FROM/TO entries`,
210
+ line: startLine + 1,
211
+ });
212
+ }
213
+ }
214
+ }
215
+ /**
216
+ * Validate a delta-spec markdown content string.
217
+ *
218
+ * Checks structural rules including:
219
+ * - ADDED/MODIFIED requirements must have scenarios with WHEN/THEN
220
+ * - Scenarios must use #### heading level
221
+ * - REMOVED requirements must have a Reason
222
+ * - RENAMED entries must have FROM and TO
223
+ *
224
+ * @param content - The raw markdown content of a delta spec
225
+ * @returns A ValidationResult with valid flag and any errors found
226
+ */
227
+ export function validateSpec(content) {
228
+ const normalized = content.replace(/\r\n?/g, '\n');
229
+ const allLines = normalized.split('\n');
230
+ const errors = [];
231
+ const sections = splitIntoSections(allLines);
232
+ for (const section of sections) {
233
+ switch (section.kind) {
234
+ case 'ADDED':
235
+ case 'MODIFIED':
236
+ validateRequirementSection(section, errors);
237
+ break;
238
+ case 'REMOVED':
239
+ validateRemovedSection(section, errors);
240
+ break;
241
+ case 'RENAMED':
242
+ validateRenamedSection(section, errors);
243
+ break;
244
+ }
245
+ }
246
+ return {
247
+ valid: errors.length === 0,
248
+ errors,
249
+ };
250
+ }
@@ -0,0 +1,2 @@
1
+ import type { Adapter } from '../shared/types.js';
2
+ export declare const claudeAdapter: Adapter;
@@ -0,0 +1,106 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { COMMAND_CATALOG } from '../shared/command-catalog.js';
4
+ import { directoryExists, hashDirectoryTree } from '../shared/asset-hash.js';
5
+ import { deriveCapabilityIds } from '../shared/capability-evidence.js';
6
+ import { copyRuntimeAssets } from '../shared/runtime-assets.js';
7
+ import { renderIdeContent } from '../shared/skill-renderer.js';
8
+ function extractSkillDescription(content, fallback) {
9
+ const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
10
+ if (!frontmatterMatch) {
11
+ return fallback;
12
+ }
13
+ const descMatch = frontmatterMatch[1].match(/description:\s*["']?(.+?)["']?\s*$/m);
14
+ return descMatch ? descMatch[1] : fallback;
15
+ }
16
+ function generateCommandAlias(commandName, description) {
17
+ return [
18
+ '---',
19
+ `description: "${description}"`,
20
+ '---',
21
+ `Invoke the specflow:${commandName} skill.`,
22
+ '',
23
+ ].join('\n');
24
+ }
25
+ async function generateClaude(ctx) {
26
+ const { projectRoot, packageRoot } = ctx;
27
+ const skillsSourceDir = join(packageRoot, 'skills');
28
+ const skillsDestDir = join(projectRoot, '.claude', 'skills');
29
+ const commandsDestDir = join(projectRoot, '.claude', 'commands', 'specflow');
30
+ await fs.mkdir(commandsDestDir, { recursive: true });
31
+ for (const command of COMMAND_CATALOG) {
32
+ const skillDirName = `specflow-${command.id}`;
33
+ const srcSkillMd = join(skillsSourceDir, skillDirName, 'SKILL.md');
34
+ const destSkillDir = join(skillsDestDir, skillDirName);
35
+ await fs.mkdir(destSkillDir, { recursive: true });
36
+ let description = command.description;
37
+ try {
38
+ const content = await fs.readFile(srcSkillMd, 'utf-8');
39
+ await fs.writeFile(join(destSkillDir, 'SKILL.md'), renderIdeContent(content, 'claude'), 'utf-8');
40
+ description = extractSkillDescription(content, command.description);
41
+ }
42
+ catch {
43
+ const placeholder = [
44
+ '---',
45
+ `name: ${skillDirName}`,
46
+ `description: "${command.description}"`,
47
+ '---',
48
+ '',
49
+ `# SpecFlow: ${command.id}`,
50
+ '',
51
+ 'Skill content pending.',
52
+ '',
53
+ ].join('\n');
54
+ await fs.writeFile(join(destSkillDir, 'SKILL.md'), renderIdeContent(placeholder, 'claude'), 'utf-8');
55
+ }
56
+ await fs.writeFile(join(commandsDestDir, `${command.id}.md`), generateCommandAlias(command.id, description), 'utf-8');
57
+ }
58
+ const runtimePaths = await copyRuntimeAssets(packageRoot, projectRoot, 'claude');
59
+ return {
60
+ ide: 'claude',
61
+ generatedPaths: ['.claude/skills', '.claude/commands/specflow', ...runtimePaths],
62
+ };
63
+ }
64
+ async function inspectClaude(projectRoot) {
65
+ const commandDir = join(projectRoot, '.claude', 'commands', 'specflow');
66
+ const skillDir = join(projectRoot, '.claude', 'skills');
67
+ const promptsDir = join(projectRoot, '.claude', 'specflow', 'prompts');
68
+ const schemasDir = join(projectRoot, '.claude', 'specflow', 'schemas');
69
+ const templatesDir = join(projectRoot, '.claude', 'specflow', 'templates');
70
+ const supported = [];
71
+ const skillContentsByCommand = {};
72
+ for (const command of COMMAND_CATALOG) {
73
+ try {
74
+ const stat = await fs.stat(join(commandDir, `${command.id}.md`));
75
+ if (stat.isFile()) {
76
+ supported.push(command.id);
77
+ }
78
+ }
79
+ catch {
80
+ // ignore
81
+ }
82
+ try {
83
+ skillContentsByCommand[command.id] = await fs.readFile(join(skillDir, `specflow-${command.id}`, 'SKILL.md'), 'utf-8');
84
+ }
85
+ catch {
86
+ skillContentsByCommand[command.id] = '';
87
+ }
88
+ }
89
+ return {
90
+ ide: 'claude',
91
+ supportedCommandIds: supported,
92
+ supportedCapabilityIds: deriveCapabilityIds(supported, skillContentsByCommand),
93
+ assets: {
94
+ commandsDir: { exists: await directoryExists(commandDir), hash: await hashDirectoryTree(commandDir) },
95
+ skillsDir: { exists: await directoryExists(skillDir), hash: await hashDirectoryTree(skillDir) },
96
+ runtimePromptsDir: { exists: await directoryExists(promptsDir), hash: await hashDirectoryTree(promptsDir) },
97
+ runtimeSchemasDir: { exists: await directoryExists(schemasDir), hash: await hashDirectoryTree(schemasDir) },
98
+ runtimeTemplatesDir: { exists: await directoryExists(templatesDir), hash: await hashDirectoryTree(templatesDir) },
99
+ },
100
+ };
101
+ }
102
+ export const claudeAdapter = {
103
+ ide: 'claude',
104
+ generate: generateClaude,
105
+ inspect: inspectClaude,
106
+ };
@@ -0,0 +1,2 @@
1
+ import type { Adapter } from '../shared/types.js';
2
+ export declare const codexAdapter: Adapter;
@@ -0,0 +1,141 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { COMMAND_CATALOG } from '../shared/command-catalog.js';
4
+ import { directoryExists, hashDirectoryTree, hashFile } from '../shared/asset-hash.js';
5
+ import { deriveCapabilityIds } from '../shared/capability-evidence.js';
6
+ import { managedBlockPresent, upsertManagedBlock } from '../shared/marker-write.js';
7
+ import { copyRuntimeAssets } from '../shared/runtime-assets.js';
8
+ import { renderIdeContent, validateSkillFrontmatter } from '../shared/skill-renderer.js';
9
+ const CODEX_AGENTS_MARKER_START = '# Added by specflow init (Codex context)';
10
+ const CODEX_AGENTS_MARKER_END = '# End specflow init (Codex context)';
11
+ function extractSkillDescription(content, fallback) {
12
+ const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
13
+ if (!frontmatterMatch) {
14
+ return fallback;
15
+ }
16
+ const descMatch = frontmatterMatch[1].match(/description:\s*["']?(.+?)["']?\s*$/m);
17
+ return descMatch ? descMatch[1] : fallback;
18
+ }
19
+ function titleCaseCommandId(commandId) {
20
+ return commandId.charAt(0).toUpperCase() + commandId.slice(1);
21
+ }
22
+ function renderOpenAiYaml(commandId, description) {
23
+ return [
24
+ 'interface:',
25
+ ` display_name: "SpecFlow: ${titleCaseCommandId(commandId)}"`,
26
+ ` short_description: "${description.replace(/"/g, '\\"')}"`,
27
+ 'policy:',
28
+ ' allow_implicit_invocation: true',
29
+ '',
30
+ ].join('\n');
31
+ }
32
+ function renderCodexAgentsBlock() {
33
+ return [
34
+ '# SpecFlow Codex Context',
35
+ '',
36
+ '- Respect specflow phase gates: plan -> refined -> built -> archived',
37
+ '- Never bypass CLI validation and archive checks',
38
+ '- Use artifacts under specflow/changes/<change>/ before coding',
39
+ '- Workflow skills: $specflow-plan, $specflow-refine, $specflow-build, $specflow-review, $specflow-test, $specflow-verify, $specflow-done, $specflow-fix, $specflow-snap',
40
+ '- Runtime prompts live under .agents/specflow/prompts/',
41
+ '',
42
+ ].join('\n');
43
+ }
44
+ async function generateCodex(ctx) {
45
+ const { projectRoot, packageRoot } = ctx;
46
+ const skillsSourceDir = join(packageRoot, 'skills');
47
+ const skillsDestDir = join(projectRoot, '.agents', 'skills');
48
+ for (const command of COMMAND_CATALOG) {
49
+ const skillDirName = `specflow-${command.id}`;
50
+ const srcSkillMd = join(skillsSourceDir, skillDirName, 'SKILL.md');
51
+ const destSkillDir = join(skillsDestDir, skillDirName);
52
+ const agentsMetaDir = join(destSkillDir, 'agents');
53
+ await fs.mkdir(agentsMetaDir, { recursive: true });
54
+ let description = command.description;
55
+ try {
56
+ const content = await fs.readFile(srcSkillMd, 'utf-8');
57
+ const rendered = renderIdeContent(content, 'codex');
58
+ validateSkillFrontmatter(rendered, skillDirName);
59
+ await fs.writeFile(join(destSkillDir, 'SKILL.md'), rendered, 'utf-8');
60
+ description = extractSkillDescription(content, command.description);
61
+ }
62
+ catch (error) {
63
+ if (error instanceof Error && error.message.includes('frontmatter')) {
64
+ throw error;
65
+ }
66
+ const placeholder = [
67
+ '---',
68
+ `name: ${skillDirName}`,
69
+ `description: "${command.description}"`,
70
+ '---',
71
+ '',
72
+ `# SpecFlow: ${command.id}`,
73
+ '',
74
+ 'Skill content pending.',
75
+ '',
76
+ ].join('\n');
77
+ const rendered = renderIdeContent(placeholder, 'codex');
78
+ validateSkillFrontmatter(rendered, skillDirName);
79
+ await fs.writeFile(join(destSkillDir, 'SKILL.md'), rendered, 'utf-8');
80
+ }
81
+ await fs.writeFile(join(agentsMetaDir, 'openai.yaml'), renderOpenAiYaml(command.id, description), 'utf-8');
82
+ }
83
+ const runtimePaths = await copyRuntimeAssets(packageRoot, projectRoot, 'codex');
84
+ await upsertManagedBlock(join(projectRoot, 'AGENTS.md'), CODEX_AGENTS_MARKER_START, CODEX_AGENTS_MARKER_END, renderCodexAgentsBlock().split('\n'));
85
+ return {
86
+ ide: 'codex',
87
+ generatedPaths: ['.agents/skills', 'AGENTS.md', ...runtimePaths],
88
+ };
89
+ }
90
+ async function inspectCodex(projectRoot) {
91
+ const skillDir = join(projectRoot, '.agents', 'skills');
92
+ const agentsFile = join(projectRoot, 'AGENTS.md');
93
+ const promptsDir = join(projectRoot, '.agents', 'specflow', 'prompts');
94
+ const schemasDir = join(projectRoot, '.agents', 'specflow', 'schemas');
95
+ const templatesDir = join(projectRoot, '.agents', 'specflow', 'templates');
96
+ let agentsFileExists = false;
97
+ try {
98
+ const agentsContent = await fs.readFile(agentsFile, 'utf-8');
99
+ agentsFileExists = managedBlockPresent(agentsContent, CODEX_AGENTS_MARKER_START, CODEX_AGENTS_MARKER_END);
100
+ }
101
+ catch {
102
+ agentsFileExists = false;
103
+ }
104
+ const supported = [];
105
+ const skillContentsByCommand = {};
106
+ for (const command of COMMAND_CATALOG) {
107
+ const skillPath = join(skillDir, `specflow-${command.id}`, 'SKILL.md');
108
+ try {
109
+ const stat = await fs.stat(skillPath);
110
+ if (stat.isFile()) {
111
+ supported.push(command.id);
112
+ }
113
+ }
114
+ catch {
115
+ // ignore
116
+ }
117
+ try {
118
+ skillContentsByCommand[command.id] = await fs.readFile(skillPath, 'utf-8');
119
+ }
120
+ catch {
121
+ skillContentsByCommand[command.id] = '';
122
+ }
123
+ }
124
+ return {
125
+ ide: 'codex',
126
+ supportedCommandIds: supported,
127
+ supportedCapabilityIds: deriveCapabilityIds(supported, skillContentsByCommand),
128
+ assets: {
129
+ skillsDir: { exists: await directoryExists(skillDir), hash: await hashDirectoryTree(skillDir) },
130
+ agentsFile: { exists: agentsFileExists, hash: agentsFileExists ? await hashFile(agentsFile) : undefined },
131
+ runtimePromptsDir: { exists: await directoryExists(promptsDir), hash: await hashDirectoryTree(promptsDir) },
132
+ runtimeSchemasDir: { exists: await directoryExists(schemasDir), hash: await hashDirectoryTree(schemasDir) },
133
+ runtimeTemplatesDir: { exists: await directoryExists(templatesDir), hash: await hashDirectoryTree(templatesDir) },
134
+ },
135
+ };
136
+ }
137
+ export const codexAdapter = {
138
+ ide: 'codex',
139
+ generate: generateCodex,
140
+ inspect: inspectCodex,
141
+ };
@@ -0,0 +1,2 @@
1
+ import type { Adapter } from '../shared/types.js';
2
+ export declare const cursorAdapter: Adapter;