specky-sdd 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 (78) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +446 -0
  3. package/dist/constants.d.ts +68 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +120 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/index.d.ts +10 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +95 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/schemas/common.d.ts +8 -0
  12. package/dist/schemas/common.d.ts.map +1 -0
  13. package/dist/schemas/common.js +18 -0
  14. package/dist/schemas/common.js.map +1 -0
  15. package/dist/schemas/pipeline.d.ts +296 -0
  16. package/dist/schemas/pipeline.d.ts.map +1 -0
  17. package/dist/schemas/pipeline.js +132 -0
  18. package/dist/schemas/pipeline.js.map +1 -0
  19. package/dist/schemas/transcript.d.ts +59 -0
  20. package/dist/schemas/transcript.d.ts.map +1 -0
  21. package/dist/schemas/transcript.js +61 -0
  22. package/dist/schemas/transcript.js.map +1 -0
  23. package/dist/schemas/utility.d.ts +92 -0
  24. package/dist/schemas/utility.d.ts.map +1 -0
  25. package/dist/schemas/utility.js +82 -0
  26. package/dist/schemas/utility.js.map +1 -0
  27. package/dist/services/codebase-scanner.d.ts +24 -0
  28. package/dist/services/codebase-scanner.d.ts.map +1 -0
  29. package/dist/services/codebase-scanner.js +185 -0
  30. package/dist/services/codebase-scanner.js.map +1 -0
  31. package/dist/services/ears-validator.d.ts +29 -0
  32. package/dist/services/ears-validator.d.ts.map +1 -0
  33. package/dist/services/ears-validator.js +163 -0
  34. package/dist/services/ears-validator.js.map +1 -0
  35. package/dist/services/file-manager.d.ts +56 -0
  36. package/dist/services/file-manager.d.ts.map +1 -0
  37. package/dist/services/file-manager.js +203 -0
  38. package/dist/services/file-manager.js.map +1 -0
  39. package/dist/services/state-machine.d.ts +46 -0
  40. package/dist/services/state-machine.d.ts.map +1 -0
  41. package/dist/services/state-machine.js +167 -0
  42. package/dist/services/state-machine.js.map +1 -0
  43. package/dist/services/template-engine.d.ts +37 -0
  44. package/dist/services/template-engine.d.ts.map +1 -0
  45. package/dist/services/template-engine.js +111 -0
  46. package/dist/services/template-engine.js.map +1 -0
  47. package/dist/services/transcript-parser.d.ts +61 -0
  48. package/dist/services/transcript-parser.d.ts.map +1 -0
  49. package/dist/services/transcript-parser.js +810 -0
  50. package/dist/services/transcript-parser.js.map +1 -0
  51. package/dist/tools/analysis.d.ts +10 -0
  52. package/dist/tools/analysis.d.ts.map +1 -0
  53. package/dist/tools/analysis.js +95 -0
  54. package/dist/tools/analysis.js.map +1 -0
  55. package/dist/tools/pipeline.d.ts +11 -0
  56. package/dist/tools/pipeline.d.ts.map +1 -0
  57. package/dist/tools/pipeline.js +583 -0
  58. package/dist/tools/pipeline.js.map +1 -0
  59. package/dist/tools/transcript.d.ts +14 -0
  60. package/dist/tools/transcript.d.ts.map +1 -0
  61. package/dist/tools/transcript.js +813 -0
  62. package/dist/tools/transcript.js.map +1 -0
  63. package/dist/tools/utility.d.ts +10 -0
  64. package/dist/tools/utility.d.ts.map +1 -0
  65. package/dist/tools/utility.js +239 -0
  66. package/dist/tools/utility.js.map +1 -0
  67. package/dist/types.d.ts +161 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +6 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +53 -0
  72. package/templates/analysis.md +54 -0
  73. package/templates/bugfix.md +45 -0
  74. package/templates/constitution.md +56 -0
  75. package/templates/design.md +47 -0
  76. package/templates/specification.md +49 -0
  77. package/templates/sync-report.md +43 -0
  78. package/templates/tasks.md +38 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ears-validator.js","sourceRoot":"","sources":["../../src/services/ears-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * FileManager — All disk I/O goes through here.
3
+ * Path sanitization, atomic writes, directory scanning.
4
+ */
5
+ import type { DirectoryTree, FeatureInfo } from "../types.js";
6
+ export declare class FileManager {
7
+ private readonly root;
8
+ constructor(workspaceRoot: string);
9
+ get workspaceRoot(): string;
10
+ /**
11
+ * Sanitize a relative path — rejects ".." traversal and absolute paths.
12
+ * Returns resolved absolute path within workspace.
13
+ */
14
+ sanitizePath(relativePath: string): string;
15
+ /**
16
+ * Ensure the spec directory exists, creating it if needed.
17
+ */
18
+ ensureSpecDir(specDir?: string): Promise<string>;
19
+ /**
20
+ * Write a spec file atomically (temp file + rename).
21
+ * Creates parent directories as needed.
22
+ * Refuses to overwrite unless force=true.
23
+ */
24
+ writeSpecFile(featureDir: string, fileName: string, content: string, force?: boolean): Promise<string>;
25
+ /**
26
+ * Read a spec file from a feature directory.
27
+ */
28
+ readSpecFile(featureDir: string, fileName: string): Promise<string>;
29
+ /**
30
+ * Check if a relative path exists.
31
+ */
32
+ fileExists(relativePath: string): Promise<boolean>;
33
+ /**
34
+ * List all files in a feature directory.
35
+ */
36
+ listSpecFiles(featureDir: string): Promise<string[]>;
37
+ /**
38
+ * List all feature directories in a spec dir.
39
+ */
40
+ listFeatures(specDir?: string): Promise<FeatureInfo[]>;
41
+ /**
42
+ * Scan a directory recursively up to a depth limit.
43
+ */
44
+ scanDirectory(dir?: string, depth?: number, exclude?: readonly string[]): Promise<DirectoryTree>;
45
+ /**
46
+ * Read a project file by relative path.
47
+ */
48
+ readProjectFile(relativePath: string): Promise<string>;
49
+ /**
50
+ * List files in a directory matching given extensions.
51
+ */
52
+ listFilesByExtension(dir: string, extensions: readonly string[]): Promise<string[]>;
53
+ private pathExists;
54
+ private scanRecursive;
55
+ }
56
+ //# sourceMappingURL=file-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-manager.d.ts","sourceRoot":"","sources":["../../src/services/file-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE9D,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAElB,aAAa,EAAE,MAAM;IAIjC,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;;OAGG;IACH,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAc1C;;OAEG;IACG,aAAa,CAAC,OAAO,GAAE,MAAyB,GAAG,OAAO,CAAC,MAAM,CAAC;IAMxE;;;;OAIG;IACG,aAAa,CACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,OAAe,GACrB,OAAO,CAAC,MAAM,CAAC;IAiClB;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKzE;;OAEG;IACG,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxD;;OAEG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAU1D;;OAEG;IACG,YAAY,CAAC,OAAO,GAAE,MAAyB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA4B9E;;OAEG;IACG,aAAa,CACjB,GAAG,GAAE,MAAY,EACjB,KAAK,GAAE,MAA2B,EAClC,OAAO,GAAE,SAAS,MAAM,EAA6B,GACpD,OAAO,CAAC,aAAa,CAAC;IAMzB;;OAEG;IACG,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK5D;;OAEG;IACG,oBAAoB,CACxB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,MAAM,EAAE,CAAC;YAqBN,UAAU;YASV,aAAa;CAoC5B"}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * FileManager — All disk I/O goes through here.
3
+ * Path sanitization, atomic writes, directory scanning.
4
+ */
5
+ import { mkdir, readFile, writeFile, readdir, stat, rename, access, unlink } from "node:fs/promises";
6
+ import { join, resolve, relative, basename, dirname } from "node:path";
7
+ import { randomUUID } from "node:crypto";
8
+ import { DEFAULT_SPEC_DIR, DEFAULT_EXCLUDE_PATTERNS, DEFAULT_SCAN_DEPTH, MAX_SCAN_DEPTH } from "../constants.js";
9
+ export class FileManager {
10
+ root;
11
+ constructor(workspaceRoot) {
12
+ this.root = resolve(workspaceRoot);
13
+ }
14
+ get workspaceRoot() {
15
+ return this.root;
16
+ }
17
+ /**
18
+ * Sanitize a relative path — rejects ".." traversal and absolute paths.
19
+ * Returns resolved absolute path within workspace.
20
+ */
21
+ sanitizePath(relativePath) {
22
+ if (relativePath.startsWith("/") || relativePath.startsWith("\\")) {
23
+ throw new Error(`Absolute paths are not allowed: ${relativePath}`);
24
+ }
25
+ if (relativePath.includes("..")) {
26
+ throw new Error(`Path traversal is not allowed: ${relativePath}`);
27
+ }
28
+ const resolved = resolve(this.root, relativePath);
29
+ if (!resolved.startsWith(this.root)) {
30
+ throw new Error(`Path escapes workspace root: ${relativePath}`);
31
+ }
32
+ return resolved;
33
+ }
34
+ /**
35
+ * Ensure the spec directory exists, creating it if needed.
36
+ */
37
+ async ensureSpecDir(specDir = DEFAULT_SPEC_DIR) {
38
+ const absPath = this.sanitizePath(specDir);
39
+ await mkdir(absPath, { recursive: true });
40
+ return absPath;
41
+ }
42
+ /**
43
+ * Write a spec file atomically (temp file + rename).
44
+ * Creates parent directories as needed.
45
+ * Refuses to overwrite unless force=true.
46
+ */
47
+ async writeSpecFile(featureDir, fileName, content, force = false) {
48
+ const absPath = this.sanitizePath(join(featureDir, fileName));
49
+ if (!force) {
50
+ const exists = await this.pathExists(absPath);
51
+ if (exists) {
52
+ throw new Error(`File already exists: ${relative(this.root, absPath)}. Use force: true to overwrite.`);
53
+ }
54
+ }
55
+ // Ensure parent directory exists
56
+ await mkdir(dirname(absPath), { recursive: true });
57
+ // Atomic write: write to temp, then rename
58
+ const tempPath = `${absPath}.${randomUUID()}.tmp`;
59
+ try {
60
+ await writeFile(tempPath, content, "utf-8");
61
+ await rename(tempPath, absPath);
62
+ }
63
+ catch (err) {
64
+ // Clean up temp file on failure
65
+ try {
66
+ await unlink(tempPath);
67
+ }
68
+ catch {
69
+ // Ignore cleanup errors
70
+ }
71
+ throw err;
72
+ }
73
+ return absPath;
74
+ }
75
+ /**
76
+ * Read a spec file from a feature directory.
77
+ */
78
+ async readSpecFile(featureDir, fileName) {
79
+ const absPath = this.sanitizePath(join(featureDir, fileName));
80
+ return readFile(absPath, "utf-8");
81
+ }
82
+ /**
83
+ * Check if a relative path exists.
84
+ */
85
+ async fileExists(relativePath) {
86
+ const absPath = this.sanitizePath(relativePath);
87
+ return this.pathExists(absPath);
88
+ }
89
+ /**
90
+ * List all files in a feature directory.
91
+ */
92
+ async listSpecFiles(featureDir) {
93
+ const absPath = this.sanitizePath(featureDir);
94
+ try {
95
+ const entries = await readdir(absPath);
96
+ return entries.filter((e) => !e.startsWith("."));
97
+ }
98
+ catch {
99
+ return [];
100
+ }
101
+ }
102
+ /**
103
+ * List all feature directories in a spec dir.
104
+ */
105
+ async listFeatures(specDir = DEFAULT_SPEC_DIR) {
106
+ const absPath = this.sanitizePath(specDir);
107
+ const features = [];
108
+ try {
109
+ const entries = await readdir(absPath, { withFileTypes: true });
110
+ for (const entry of entries) {
111
+ if (entry.isDirectory() && /^\d{3}-/.test(entry.name)) {
112
+ const featureDir = join(specDir, entry.name);
113
+ const match = entry.name.match(/^(\d{3})-(.+)$/);
114
+ if (match) {
115
+ const files = await this.listSpecFiles(featureDir);
116
+ features.push({
117
+ number: match[1],
118
+ name: match[2],
119
+ directory: featureDir,
120
+ files,
121
+ });
122
+ }
123
+ }
124
+ }
125
+ }
126
+ catch {
127
+ // Spec dir doesn't exist yet
128
+ }
129
+ return features.sort((a, b) => a.number.localeCompare(b.number));
130
+ }
131
+ /**
132
+ * Scan a directory recursively up to a depth limit.
133
+ */
134
+ async scanDirectory(dir = ".", depth = DEFAULT_SCAN_DEPTH, exclude = DEFAULT_EXCLUDE_PATTERNS) {
135
+ const clampedDepth = Math.min(depth, MAX_SCAN_DEPTH);
136
+ const absPath = this.sanitizePath(dir);
137
+ return this.scanRecursive(absPath, basename(absPath) || ".", clampedDepth, exclude);
138
+ }
139
+ /**
140
+ * Read a project file by relative path.
141
+ */
142
+ async readProjectFile(relativePath) {
143
+ const absPath = this.sanitizePath(relativePath);
144
+ return readFile(absPath, "utf-8");
145
+ }
146
+ /**
147
+ * List files in a directory matching given extensions.
148
+ */
149
+ async listFilesByExtension(dir, extensions) {
150
+ const absPath = this.sanitizePath(dir);
151
+ try {
152
+ const entries = await readdir(absPath, { withFileTypes: true });
153
+ const matched = [];
154
+ for (const entry of entries) {
155
+ if (entry.isFile()) {
156
+ const lower = entry.name.toLowerCase();
157
+ if (extensions.some((ext) => lower.endsWith(ext))) {
158
+ matched.push(join(dir, entry.name));
159
+ }
160
+ }
161
+ }
162
+ return matched.sort();
163
+ }
164
+ catch {
165
+ return [];
166
+ }
167
+ }
168
+ // --- Private helpers ---
169
+ async pathExists(absPath) {
170
+ try {
171
+ await access(absPath);
172
+ return true;
173
+ }
174
+ catch {
175
+ return false;
176
+ }
177
+ }
178
+ async scanRecursive(absPath, name, depth, exclude) {
179
+ const stats = await stat(absPath);
180
+ if (!stats.isDirectory()) {
181
+ return { name, type: "file" };
182
+ }
183
+ if (depth <= 0) {
184
+ return { name, type: "directory" };
185
+ }
186
+ const entries = await readdir(absPath, { withFileTypes: true });
187
+ const children = [];
188
+ for (const entry of entries) {
189
+ if (exclude.includes(entry.name) || entry.name.startsWith(".")) {
190
+ continue;
191
+ }
192
+ const childPath = join(absPath, entry.name);
193
+ children.push(await this.scanRecursive(childPath, entry.name, depth - 1, exclude));
194
+ }
195
+ children.sort((a, b) => {
196
+ if (a.type !== b.type)
197
+ return a.type === "directory" ? -1 : 1;
198
+ return a.name.localeCompare(b.name);
199
+ });
200
+ return { name, type: "directory", children };
201
+ }
202
+ }
203
+ //# sourceMappingURL=file-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-manager.js","sourceRoot":"","sources":["../../src/services/file-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * StateMachine — Phase tracking, transition validation, state persistence.
3
+ * State persists in .specs/.sdd-state.json.
4
+ */
5
+ import { Phase } from "../constants.js";
6
+ import type { SddState, TransitionResult } from "../types.js";
7
+ import type { FileManager } from "./file-manager.js";
8
+ export declare class StateMachine {
9
+ private fileManager;
10
+ constructor(fileManager: FileManager);
11
+ /**
12
+ * Load state from .sdd-state.json, or return a default "not initialized" state.
13
+ */
14
+ loadState(specDir?: string): Promise<SddState>;
15
+ /**
16
+ * Save state to .sdd-state.json.
17
+ */
18
+ saveState(specDir: string, state: SddState): Promise<void>;
19
+ /**
20
+ * Get current phase from persisted state.
21
+ */
22
+ getCurrentPhase(specDir?: string): Promise<Phase>;
23
+ /**
24
+ * Check if transition to target phase is allowed.
25
+ */
26
+ canTransition(specDir: string, targetPhase: Phase): Promise<TransitionResult>;
27
+ /**
28
+ * Advance to the next phase. Validates prerequisites first.
29
+ */
30
+ advancePhase(specDir: string, featureNumber: string): Promise<SddState>;
31
+ /**
32
+ * Record that a phase has started.
33
+ */
34
+ recordPhaseStart(specDir: string, phase: Phase): Promise<void>;
35
+ /**
36
+ * Record that a phase has completed.
37
+ */
38
+ recordPhaseComplete(specDir: string, phase: Phase): Promise<void>;
39
+ /** Get required files for a given phase */
40
+ getRequiredFiles(phase: Phase): string[];
41
+ /** Get phase order */
42
+ getPhaseOrder(): Phase[];
43
+ /** Create a fresh state object */
44
+ createDefaultState(projectName: string): SddState;
45
+ }
46
+ //# sourceMappingURL=state-machine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../../src/services/state-machine.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAmE,MAAM,iBAAiB,CAAC;AACzG,OAAO,KAAK,EAAE,QAAQ,EAAe,gBAAgB,EAAgB,MAAM,aAAa,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,qBAAa,YAAY;IACX,OAAO,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAE5C;;OAEG;IACG,SAAS,CAAC,OAAO,GAAE,MAAyB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAUtE;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhE;;OAEG;IACG,eAAe,CAAC,OAAO,GAAE,MAAyB,GAAG,OAAO,CAAC,KAAK,CAAC;IAKzE;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmDnF;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAiC7E;;OAEG;IACG,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpE;;OAEG;IACG,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvE,2CAA2C;IAC3C,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE;IAIxC,sBAAsB;IACtB,aAAa,IAAI,KAAK,EAAE;IAIxB,kCAAkC;IAClC,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ;CAelD"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * StateMachine — Phase tracking, transition validation, state persistence.
3
+ * State persists in .specs/.sdd-state.json.
4
+ */
5
+ import { Phase, PHASE_ORDER, PHASE_REQUIRED_FILES, STATE_FILE, DEFAULT_SPEC_DIR } from "../constants.js";
6
+ import { join } from "node:path";
7
+ export class StateMachine {
8
+ fileManager;
9
+ constructor(fileManager) {
10
+ this.fileManager = fileManager;
11
+ }
12
+ /**
13
+ * Load state from .sdd-state.json, or return a default "not initialized" state.
14
+ */
15
+ async loadState(specDir = DEFAULT_SPEC_DIR) {
16
+ const statePath = join(specDir, STATE_FILE);
17
+ try {
18
+ const raw = await this.fileManager.readProjectFile(statePath);
19
+ return JSON.parse(raw);
20
+ }
21
+ catch {
22
+ return this.createDefaultState("");
23
+ }
24
+ }
25
+ /**
26
+ * Save state to .sdd-state.json.
27
+ */
28
+ async saveState(specDir, state) {
29
+ const statePath = join(specDir, STATE_FILE);
30
+ await this.fileManager.writeSpecFile(specDir, STATE_FILE, JSON.stringify(state, null, 2), true // always overwrite state file
31
+ );
32
+ }
33
+ /**
34
+ * Get current phase from persisted state.
35
+ */
36
+ async getCurrentPhase(specDir = DEFAULT_SPEC_DIR) {
37
+ const state = await this.loadState(specDir);
38
+ return state.current_phase;
39
+ }
40
+ /**
41
+ * Check if transition to target phase is allowed.
42
+ */
43
+ async canTransition(specDir, targetPhase) {
44
+ const state = await this.loadState(specDir);
45
+ const currentIndex = PHASE_ORDER.indexOf(state.current_phase);
46
+ const targetIndex = PHASE_ORDER.indexOf(targetPhase);
47
+ // Can only advance to the next phase
48
+ if (targetIndex !== currentIndex + 1) {
49
+ const nextPhase = currentIndex < PHASE_ORDER.length - 1
50
+ ? PHASE_ORDER[currentIndex + 1]
51
+ : undefined;
52
+ return {
53
+ allowed: false,
54
+ from_phase: state.current_phase,
55
+ to_phase: targetPhase,
56
+ error_message: nextPhase
57
+ ? `Cannot skip to "${targetPhase}". Next phase is "${nextPhase}".`
58
+ : `Already at terminal phase "${state.current_phase}".`,
59
+ };
60
+ }
61
+ // Check required files for current phase
62
+ const requiredFiles = PHASE_REQUIRED_FILES[state.current_phase];
63
+ const missingFiles = [];
64
+ for (const fileName of requiredFiles) {
65
+ const featureDir = state.features[0];
66
+ if (featureDir) {
67
+ const exists = await this.fileManager.fileExists(join(featureDir, fileName));
68
+ if (!exists) {
69
+ missingFiles.push(fileName);
70
+ }
71
+ }
72
+ }
73
+ if (missingFiles.length > 0) {
74
+ return {
75
+ allowed: false,
76
+ from_phase: state.current_phase,
77
+ to_phase: targetPhase,
78
+ missing_files: missingFiles,
79
+ error_message: `Cannot advance: missing required files: ${missingFiles.join(", ")}`,
80
+ };
81
+ }
82
+ return {
83
+ allowed: true,
84
+ from_phase: state.current_phase,
85
+ to_phase: targetPhase,
86
+ };
87
+ }
88
+ /**
89
+ * Advance to the next phase. Validates prerequisites first.
90
+ */
91
+ async advancePhase(specDir, featureNumber) {
92
+ const state = await this.loadState(specDir);
93
+ const currentIndex = PHASE_ORDER.indexOf(state.current_phase);
94
+ if (currentIndex >= PHASE_ORDER.length - 1) {
95
+ throw new Error(`Already at terminal phase "${state.current_phase}". Pipeline is complete.`);
96
+ }
97
+ const nextPhase = PHASE_ORDER[currentIndex + 1];
98
+ const transition = await this.canTransition(specDir, nextPhase);
99
+ if (!transition.allowed) {
100
+ throw new Error(transition.error_message || "Transition not allowed.");
101
+ }
102
+ // Mark current phase as completed
103
+ state.phases[state.current_phase] = {
104
+ ...state.phases[state.current_phase],
105
+ status: "completed",
106
+ completed_at: new Date().toISOString(),
107
+ };
108
+ // Advance to next phase
109
+ state.current_phase = nextPhase;
110
+ state.phases[nextPhase] = {
111
+ status: "in_progress",
112
+ started_at: new Date().toISOString(),
113
+ };
114
+ await this.saveState(specDir, state);
115
+ return state;
116
+ }
117
+ /**
118
+ * Record that a phase has started.
119
+ */
120
+ async recordPhaseStart(specDir, phase) {
121
+ const state = await this.loadState(specDir);
122
+ state.phases[phase] = {
123
+ ...state.phases[phase],
124
+ status: "in_progress",
125
+ started_at: new Date().toISOString(),
126
+ };
127
+ state.current_phase = phase;
128
+ await this.saveState(specDir, state);
129
+ }
130
+ /**
131
+ * Record that a phase has completed.
132
+ */
133
+ async recordPhaseComplete(specDir, phase) {
134
+ const state = await this.loadState(specDir);
135
+ state.phases[phase] = {
136
+ ...state.phases[phase],
137
+ status: "completed",
138
+ completed_at: new Date().toISOString(),
139
+ };
140
+ await this.saveState(specDir, state);
141
+ }
142
+ /** Get required files for a given phase */
143
+ getRequiredFiles(phase) {
144
+ return [...PHASE_REQUIRED_FILES[phase]];
145
+ }
146
+ /** Get phase order */
147
+ getPhaseOrder() {
148
+ return [...PHASE_ORDER];
149
+ }
150
+ /** Create a fresh state object */
151
+ createDefaultState(projectName) {
152
+ const phases = {};
153
+ for (const phase of PHASE_ORDER) {
154
+ phases[phase] = { status: "pending" };
155
+ }
156
+ return {
157
+ version: "3.0.0",
158
+ project_name: projectName,
159
+ current_phase: Phase.Init,
160
+ phases,
161
+ features: [],
162
+ amendments: [],
163
+ gate_decision: null,
164
+ };
165
+ }
166
+ }
167
+ //# sourceMappingURL=state-machine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-machine.js","sourceRoot":"","sources":["../../src/services/state-machine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * TemplateEngine — Template loading, variable replacement, YAML frontmatter.
3
+ */
4
+ import type { TemplateName } from "../constants.js";
5
+ import type { TemplateContext } from "../types.js";
6
+ import type { FileManager } from "./file-manager.js";
7
+ export declare class TemplateEngine {
8
+ private fileManager;
9
+ constructor(fileManager: FileManager);
10
+ /**
11
+ * Load and render a template with variable replacement.
12
+ */
13
+ render(templateName: TemplateName, context: Record<string, string | string[]>): Promise<string>;
14
+ /**
15
+ * Render a template with YAML frontmatter prepended.
16
+ */
17
+ renderWithFrontmatter(templateName: TemplateName, context: TemplateContext): Promise<string>;
18
+ /**
19
+ * Get raw template content without any processing.
20
+ */
21
+ getTemplate(templateName: TemplateName): Promise<string>;
22
+ /**
23
+ * Replace {{variable}} placeholders with context values.
24
+ * Unknown variables become [TODO: variable].
25
+ * Supports {{#each items}}...{{/each}} for arrays.
26
+ */
27
+ replaceVariables(template: string, context: Record<string, string | string[]>): string;
28
+ /**
29
+ * Generate YAML frontmatter block.
30
+ */
31
+ generateFrontmatter(context: TemplateContext): string;
32
+ /**
33
+ * Load raw template from templates/ directory.
34
+ */
35
+ private loadTemplate;
36
+ }
37
+ //# sourceMappingURL=template-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-engine.d.ts","sourceRoot":"","sources":["../../src/services/template-engine.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMrD,qBAAa,cAAc;IACb,OAAO,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAE5C;;OAEG;IACG,MAAM,CACV,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACzC,OAAO,CAAC,MAAM,CAAC;IAKlB;;OAEG;IACG,qBAAqB,CACzB,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,MAAM,CAAC;IAMlB;;OAEG;IACG,WAAW,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAI9D;;;;OAIG;IACH,gBAAgB,CACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACzC,MAAM;IA4BT;;OAEG;IACH,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM;IAyBrD;;OAEG;YACW,YAAY;CAa3B"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * TemplateEngine — Template loading, variable replacement, YAML frontmatter.
3
+ */
4
+ import { join } from "node:path";
5
+ import { readFile } from "node:fs/promises";
6
+ import { fileURLToPath } from "node:url";
7
+ import { dirname } from "node:path";
8
+ import { TEMPLATE_NAMES } from "../constants.js";
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const TEMPLATES_DIR = join(__dirname, "..", "..", "templates");
12
+ export class TemplateEngine {
13
+ fileManager;
14
+ constructor(fileManager) {
15
+ this.fileManager = fileManager;
16
+ }
17
+ /**
18
+ * Load and render a template with variable replacement.
19
+ */
20
+ async render(templateName, context) {
21
+ const template = await this.loadTemplate(templateName);
22
+ return this.replaceVariables(template, context);
23
+ }
24
+ /**
25
+ * Render a template with YAML frontmatter prepended.
26
+ */
27
+ async renderWithFrontmatter(templateName, context) {
28
+ const frontmatter = this.generateFrontmatter(context);
29
+ const body = await this.render(templateName, context);
30
+ return `${frontmatter}\n${body}`;
31
+ }
32
+ /**
33
+ * Get raw template content without any processing.
34
+ */
35
+ async getTemplate(templateName) {
36
+ return this.loadTemplate(templateName);
37
+ }
38
+ /**
39
+ * Replace {{variable}} placeholders with context values.
40
+ * Unknown variables become [TODO: variable].
41
+ * Supports {{#each items}}...{{/each}} for arrays.
42
+ */
43
+ replaceVariables(template, context) {
44
+ let result = template;
45
+ // Handle {{#each key}}...{{/each}} blocks
46
+ const eachRegex = /\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g;
47
+ result = result.replace(eachRegex, (_match, key, body) => {
48
+ const value = context[key];
49
+ if (Array.isArray(value)) {
50
+ return value.map((item) => body.replace(/\{\{this\}\}/g, item)).join("");
51
+ }
52
+ return `[TODO: ${key}]`;
53
+ });
54
+ // Handle {{variable}} replacements
55
+ result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
56
+ const value = context[key];
57
+ if (value === undefined) {
58
+ return `[TODO: ${key}]`;
59
+ }
60
+ if (Array.isArray(value)) {
61
+ return value.join(", ");
62
+ }
63
+ return value;
64
+ });
65
+ return result;
66
+ }
67
+ /**
68
+ * Generate YAML frontmatter block.
69
+ */
70
+ generateFrontmatter(context) {
71
+ const lines = ["---"];
72
+ const fields = [
73
+ ["title", context.title],
74
+ ["feature_id", context.feature_id],
75
+ ["version", context.version || "1.0.0"],
76
+ ["date", context.date || new Date().toISOString().split("T")[0]],
77
+ ["author", context.author || "SDD Pipeline"],
78
+ ["status", context.status || "Draft"],
79
+ ];
80
+ for (const [key, value] of fields) {
81
+ if (value !== undefined) {
82
+ if (Array.isArray(value)) {
83
+ lines.push(`${key}: [${value.map((v) => `"${v}"`).join(", ")}]`);
84
+ }
85
+ else {
86
+ lines.push(`${key}: "${value}"`);
87
+ }
88
+ }
89
+ }
90
+ lines.push("---");
91
+ return lines.join("\n");
92
+ }
93
+ /**
94
+ * Load raw template from templates/ directory.
95
+ */
96
+ async loadTemplate(name) {
97
+ if (!TEMPLATE_NAMES.includes(name)) {
98
+ throw new Error(`Unknown template: "${name}". Available: ${TEMPLATE_NAMES.join(", ")}`);
99
+ }
100
+ // Map template name to file: sync_report -> sync-report.md
101
+ const fileName = name.replace(/_/g, "-") + ".md";
102
+ const templatePath = join(TEMPLATES_DIR, fileName);
103
+ try {
104
+ return await readFile(templatePath, "utf-8");
105
+ }
106
+ catch {
107
+ throw new Error(`Template file not found: ${fileName}. Ensure templates/ directory exists.`);
108
+ }
109
+ }
110
+ }
111
+ //# sourceMappingURL=template-engine.js.map