lean-spec 0.2.7-dev.20251128013112 → 0.2.7-dev.20251128014554

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.
@@ -0,0 +1,5 @@
1
+ export { backfillCommand, backfillTimestamps } from './chunk-A6JDTXPV.js';
2
+ import './chunk-LXOJW2FE.js';
3
+ import './chunk-LVD7ZAVZ.js';
4
+ //# sourceMappingURL=backfill-QIQLXSUG.js.map
5
+ //# sourceMappingURL=backfill-QIQLXSUG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"backfill-QIQLXSUG.js"}
@@ -0,0 +1,444 @@
1
+ import { loadConfig, getSpec, loadAllSpecs } from './chunk-LXOJW2FE.js';
2
+ import { updateFrontmatter } from './chunk-LVD7ZAVZ.js';
3
+ import * as path from 'path';
4
+ import { Command } from 'commander';
5
+ import { execSync } from 'child_process';
6
+ import * as fs from 'fs/promises';
7
+
8
+ function isGitRepository() {
9
+ try {
10
+ execSync("git rev-parse --is-inside-work-tree", {
11
+ stdio: "ignore",
12
+ encoding: "utf-8"
13
+ });
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ function getFirstCommitTimestamp(filePath) {
20
+ try {
21
+ const timestamp = execSync(
22
+ `git log --follow --format="%aI" --diff-filter=A -- "${filePath}" | tail -1`,
23
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
24
+ ).trim();
25
+ return timestamp || null;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function getLastCommitTimestamp(filePath) {
31
+ try {
32
+ const timestamp = execSync(
33
+ `git log --format="%aI" -n 1 -- "${filePath}"`,
34
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
35
+ ).trim();
36
+ return timestamp || null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ function getCompletionTimestamp(filePath) {
42
+ try {
43
+ const gitLog = execSync(
44
+ `git log --format="%H|%aI" -p -- "${filePath}"`,
45
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
46
+ );
47
+ const commits = gitLog.split("\ndiff --git").map((section) => section.trim());
48
+ for (const commit of commits) {
49
+ if (!commit) continue;
50
+ const headerMatch = commit.match(/^([a-f0-9]{40})\|([^\n]+)/);
51
+ if (!headerMatch) continue;
52
+ const [, , timestamp] = headerMatch;
53
+ if (/^\+status:\s*['"]?complete['"]?/m.test(commit) || /^\+\*\*Status\*\*:.*complete/mi.test(commit)) {
54
+ return timestamp;
55
+ }
56
+ }
57
+ return null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+ function getFirstCommitAuthor(filePath) {
63
+ try {
64
+ const author = execSync(
65
+ `git log --follow --format="%an" --diff-filter=A -- "${filePath}" | tail -1`,
66
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
67
+ ).trim();
68
+ return author || null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+ function parseStatusTransitions(filePath) {
74
+ const transitions = [];
75
+ try {
76
+ const gitLog = execSync(
77
+ `git log --format="%H|%aI" -p --reverse -- "${filePath}"`,
78
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
79
+ );
80
+ const commits = gitLog.split("\ndiff --git").map((section) => section.trim());
81
+ const validStatuses = ["planned", "in-progress", "complete", "archived"];
82
+ for (const commit of commits) {
83
+ if (!commit) continue;
84
+ const headerMatch = commit.match(/^([a-f0-9]{40})\|([^\n]+)/);
85
+ if (!headerMatch) continue;
86
+ const [, , timestamp] = headerMatch;
87
+ const statusMatch = commit.match(/^\+status:\s*['"]?(\w+(?:-\w+)?)['"]?/m);
88
+ if (statusMatch) {
89
+ const status = statusMatch[1];
90
+ if (validStatuses.includes(status)) {
91
+ const lastTransition = transitions[transitions.length - 1];
92
+ if (!lastTransition || lastTransition.status !== status) {
93
+ transitions.push({ status, at: timestamp });
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return transitions;
99
+ } catch {
100
+ return [];
101
+ }
102
+ }
103
+ function extractGitTimestamps(filePath, options = {}) {
104
+ const data = {};
105
+ data.created_at = getFirstCommitTimestamp(filePath) ?? void 0;
106
+ data.updated_at = getLastCommitTimestamp(filePath) ?? void 0;
107
+ data.completed_at = getCompletionTimestamp(filePath) ?? void 0;
108
+ if (options.includeAssignee) {
109
+ const author = getFirstCommitAuthor(filePath);
110
+ if (author) {
111
+ data.assignee = author;
112
+ }
113
+ }
114
+ if (options.includeTransitions) {
115
+ const transitions = parseStatusTransitions(filePath);
116
+ if (transitions.length > 0) {
117
+ data.transitions = transitions;
118
+ }
119
+ }
120
+ return data;
121
+ }
122
+ function fileExistsInGit(filePath) {
123
+ try {
124
+ execSync(
125
+ `git log -n 1 -- "${filePath}"`,
126
+ { stdio: "ignore", encoding: "utf-8" }
127
+ );
128
+ return true;
129
+ } catch {
130
+ return false;
131
+ }
132
+ }
133
+ function createSpecDirPattern() {
134
+ return /(?:^|\D)(\d{2,4})-[a-z]/i;
135
+ }
136
+ async function getGlobalNextSeq(specsDir, digits) {
137
+ try {
138
+ const seqNumbers = [];
139
+ const specPattern = createSpecDirPattern();
140
+ async function scanDirectory(dir) {
141
+ try {
142
+ const entries = await fs.readdir(dir, { withFileTypes: true });
143
+ for (const entry of entries) {
144
+ if (!entry.isDirectory()) continue;
145
+ const match = entry.name.match(specPattern);
146
+ if (match) {
147
+ const seqNum = parseInt(match[1], 10);
148
+ if (!isNaN(seqNum) && seqNum > 0) {
149
+ seqNumbers.push(seqNum);
150
+ }
151
+ }
152
+ if (entry.name === "archived") continue;
153
+ const subDir = path.join(dir, entry.name);
154
+ await scanDirectory(subDir);
155
+ }
156
+ } catch {
157
+ }
158
+ }
159
+ await scanDirectory(specsDir);
160
+ if (seqNumbers.length === 0) {
161
+ return "1".padStart(digits, "0");
162
+ }
163
+ const maxSeq = Math.max(...seqNumbers);
164
+ return String(maxSeq + 1).padStart(digits, "0");
165
+ } catch {
166
+ return "1".padStart(digits, "0");
167
+ }
168
+ }
169
+ async function resolveSpecPath(specPath, cwd, specsDir) {
170
+ if (path.isAbsolute(specPath)) {
171
+ try {
172
+ await fs.access(specPath);
173
+ return specPath;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+ const cwdPath = path.resolve(cwd, specPath);
179
+ try {
180
+ await fs.access(cwdPath);
181
+ return cwdPath;
182
+ } catch {
183
+ }
184
+ const specsPath = path.join(specsDir, specPath);
185
+ try {
186
+ await fs.access(specsPath);
187
+ return specsPath;
188
+ } catch {
189
+ }
190
+ const seqMatch = specPath.match(/^0*(\d+)$/);
191
+ if (seqMatch) {
192
+ const seqNum = parseInt(seqMatch[1], 10);
193
+ const result2 = await searchBySequence(specsDir, seqNum);
194
+ if (result2) return result2;
195
+ }
196
+ const specName = specPath.replace(/^.*\//, "");
197
+ const result = await searchInAllDirectories(specsDir, specName);
198
+ return result;
199
+ }
200
+ async function searchBySequence(specsDir, seqNum) {
201
+ const specPattern = createSpecDirPattern();
202
+ async function scanDirectory(dir) {
203
+ try {
204
+ const entries = await fs.readdir(dir, { withFileTypes: true });
205
+ for (const entry of entries) {
206
+ if (!entry.isDirectory()) continue;
207
+ const match = entry.name.match(specPattern);
208
+ if (match) {
209
+ const entrySeq = parseInt(match[1], 10);
210
+ if (entrySeq === seqNum) {
211
+ return path.join(dir, entry.name);
212
+ }
213
+ }
214
+ const subDir = path.join(dir, entry.name);
215
+ const result = await scanDirectory(subDir);
216
+ if (result) return result;
217
+ }
218
+ } catch {
219
+ }
220
+ return null;
221
+ }
222
+ return scanDirectory(specsDir);
223
+ }
224
+ async function searchInAllDirectories(specsDir, specName) {
225
+ async function scanDirectory(dir) {
226
+ try {
227
+ const entries = await fs.readdir(dir, { withFileTypes: true });
228
+ for (const entry of entries) {
229
+ if (!entry.isDirectory()) continue;
230
+ if (entry.name === specName) {
231
+ return path.join(dir, entry.name);
232
+ }
233
+ const subDir = path.join(dir, entry.name);
234
+ const result = await scanDirectory(subDir);
235
+ if (result) return result;
236
+ }
237
+ } catch {
238
+ }
239
+ return null;
240
+ }
241
+ return scanDirectory(specsDir);
242
+ }
243
+
244
+ // src/commands/backfill.ts
245
+ function backfillCommand() {
246
+ return new Command("backfill").description("Backfill timestamps from git history").argument("[specs...]", "Specific specs to backfill (optional)").option("--dry-run", "Show what would be updated without making changes").option("--force", "Overwrite existing timestamp values").option("--assignee", "Include assignee from first commit author").option("--transitions", "Include full status transition history").option("--all", "Include all optional fields (assignee + transitions)").option("--json", "Output as JSON").action(async (specs, options) => {
247
+ await backfillTimestamps({
248
+ dryRun: options.dryRun,
249
+ force: options.force,
250
+ includeAssignee: options.assignee || options.all,
251
+ includeTransitions: options.transitions || options.all,
252
+ specs: specs && specs.length > 0 ? specs : void 0,
253
+ json: options.json
254
+ });
255
+ });
256
+ }
257
+ async function backfillTimestamps(options = {}) {
258
+ const results = [];
259
+ if (!isGitRepository()) {
260
+ console.error("\x1B[31mError:\x1B[0m Not in a git repository");
261
+ console.error("Git history is required for backfilling timestamps");
262
+ process.exit(1);
263
+ }
264
+ let specs;
265
+ if (options.specs && options.specs.length > 0) {
266
+ specs = [];
267
+ const config = await loadConfig();
268
+ const cwd = process.cwd();
269
+ const specsDir = path.join(cwd, config.specsDir);
270
+ for (const specPath of options.specs) {
271
+ const resolved = await resolveSpecPath(specPath, cwd, specsDir);
272
+ if (!resolved) {
273
+ console.warn(`\x1B[33mWarning:\x1B[0m Spec not found: ${specPath}`);
274
+ continue;
275
+ }
276
+ const spec = await getSpec(resolved);
277
+ if (spec) {
278
+ specs.push(spec);
279
+ }
280
+ }
281
+ } else {
282
+ specs = await loadAllSpecs({ includeArchived: true });
283
+ }
284
+ if (specs.length === 0) {
285
+ console.log("No specs found to backfill");
286
+ return results;
287
+ }
288
+ if (options.dryRun) {
289
+ console.log("\x1B[36m\u{1F50D} Dry run mode - no changes will be made\x1B[0m\n");
290
+ }
291
+ console.log(`Analyzing git history for ${specs.length} spec${specs.length === 1 ? "" : "s"}...
292
+ `);
293
+ for (const spec of specs) {
294
+ const result = await backfillSpecTimestamps(spec, options);
295
+ results.push(result);
296
+ }
297
+ printSummary(results, options);
298
+ return results;
299
+ }
300
+ async function backfillSpecTimestamps(spec, options) {
301
+ const result = {
302
+ specPath: spec.path,
303
+ specName: spec.name,
304
+ source: "skipped"
305
+ };
306
+ if (!fileExistsInGit(spec.filePath)) {
307
+ result.reason = "Not in git history";
308
+ console.log(`\x1B[33m\u2298\x1B[0m ${spec.name} - Not in git history`);
309
+ return result;
310
+ }
311
+ const gitData = extractGitTimestamps(spec.filePath, {
312
+ includeAssignee: options.includeAssignee,
313
+ includeTransitions: options.includeTransitions
314
+ });
315
+ const updates = {};
316
+ let hasUpdates = false;
317
+ if (gitData.created_at && (options.force || !spec.frontmatter.created_at)) {
318
+ updates.created_at = gitData.created_at;
319
+ result.created_at = gitData.created_at;
320
+ result.source = "git";
321
+ hasUpdates = true;
322
+ } else if (spec.frontmatter.created_at) {
323
+ result.created_at = spec.frontmatter.created_at;
324
+ result.source = "existing";
325
+ }
326
+ if (gitData.updated_at && (options.force || !spec.frontmatter.updated_at)) {
327
+ updates.updated_at = gitData.updated_at;
328
+ result.updated_at = gitData.updated_at;
329
+ result.source = "git";
330
+ hasUpdates = true;
331
+ } else if (spec.frontmatter.updated_at) {
332
+ result.updated_at = spec.frontmatter.updated_at;
333
+ result.source = "existing";
334
+ }
335
+ if (gitData.completed_at && (options.force || !spec.frontmatter.completed_at)) {
336
+ updates.completed_at = gitData.completed_at;
337
+ result.completed_at = gitData.completed_at;
338
+ result.source = "git";
339
+ hasUpdates = true;
340
+ } else if (spec.frontmatter.completed_at) {
341
+ result.completed_at = spec.frontmatter.completed_at;
342
+ result.source = "existing";
343
+ }
344
+ if (options.includeAssignee && gitData.assignee && (options.force || !spec.frontmatter.assignee)) {
345
+ updates.assignee = gitData.assignee;
346
+ result.assignee = gitData.assignee;
347
+ result.source = "git";
348
+ hasUpdates = true;
349
+ } else if (spec.frontmatter.assignee) {
350
+ result.assignee = spec.frontmatter.assignee;
351
+ }
352
+ if (options.includeTransitions && gitData.transitions && gitData.transitions.length > 0) {
353
+ if (options.force || !spec.frontmatter.transitions || spec.frontmatter.transitions.length === 0) {
354
+ updates.transitions = gitData.transitions;
355
+ result.transitionsCount = gitData.transitions.length;
356
+ result.source = "git";
357
+ hasUpdates = true;
358
+ } else {
359
+ result.transitionsCount = spec.frontmatter.transitions.length;
360
+ }
361
+ }
362
+ if (updates.updated_at && !updates.updated) {
363
+ updates.updated = updates.updated_at.split("T")[0];
364
+ }
365
+ if (!hasUpdates) {
366
+ result.reason = "Already has complete data";
367
+ console.log(`\x1B[90m\u2713\x1B[0m ${spec.name} - Already complete`);
368
+ return result;
369
+ }
370
+ if (!options.dryRun) {
371
+ try {
372
+ await updateFrontmatter(spec.filePath, updates);
373
+ console.log(`\x1B[32m\u2713\x1B[0m ${spec.name} - Updated`);
374
+ } catch (error) {
375
+ result.source = "skipped";
376
+ result.reason = `Error: ${error instanceof Error ? error.message : String(error)}`;
377
+ console.log(`\x1B[31m\u2717\x1B[0m ${spec.name} - Failed: ${result.reason}`);
378
+ }
379
+ } else {
380
+ console.log(`\x1B[36m\u2192\x1B[0m ${spec.name} - Would update`);
381
+ if (updates.created_at) console.log(` created_at: ${updates.created_at} (git)`);
382
+ if (updates.updated_at) console.log(` updated_at: ${updates.updated_at} (git)`);
383
+ if (updates.completed_at) console.log(` completed_at: ${updates.completed_at} (git)`);
384
+ if (updates.assignee) console.log(` assignee: ${updates.assignee} (git)`);
385
+ if (updates.transitions) console.log(` transitions: ${updates.transitions.length} status changes (git)`);
386
+ }
387
+ return result;
388
+ }
389
+ function printSummary(results, options) {
390
+ console.log("\n" + "\u2500".repeat(60));
391
+ console.log("\x1B[1mSummary:\x1B[0m\n");
392
+ const total = results.length;
393
+ const updated = results.filter((r) => r.source === "git").length;
394
+ const existing = results.filter((r) => r.source === "existing").length;
395
+ const skipped = results.filter((r) => r.source === "skipped").length;
396
+ const timestampUpdates = results.filter(
397
+ (r) => r.source === "git" && (r.created_at || r.updated_at || r.completed_at)
398
+ ).length;
399
+ const assigneeUpdates = results.filter(
400
+ (r) => r.source === "git" && r.assignee
401
+ ).length;
402
+ const transitionUpdates = results.filter(
403
+ (r) => r.source === "git" && r.transitionsCount
404
+ ).length;
405
+ console.log(` ${total} specs analyzed`);
406
+ if (options.dryRun) {
407
+ console.log(` ${updated} would be updated`);
408
+ if (timestampUpdates > 0) {
409
+ console.log(` \u2514\u2500 ${timestampUpdates} with timestamps`);
410
+ }
411
+ if (options.includeAssignee && assigneeUpdates > 0) {
412
+ console.log(` \u2514\u2500 ${assigneeUpdates} with assignee`);
413
+ }
414
+ if (options.includeTransitions && transitionUpdates > 0) {
415
+ console.log(` \u2514\u2500 ${transitionUpdates} with transitions`);
416
+ }
417
+ } else {
418
+ console.log(` ${updated} updated`);
419
+ }
420
+ console.log(` ${existing} already complete`);
421
+ console.log(` ${skipped} skipped`);
422
+ const skipReasons = results.filter((r) => r.source === "skipped" && r.reason).map((r) => r.reason);
423
+ if (skipReasons.length > 0) {
424
+ console.log("\n\x1B[33mSkipped reasons:\x1B[0m");
425
+ const uniqueReasons = [...new Set(skipReasons)];
426
+ for (const reason of uniqueReasons) {
427
+ const count = skipReasons.filter((r) => r === reason).length;
428
+ console.log(` - ${reason} (${count})`);
429
+ }
430
+ }
431
+ if (options.dryRun) {
432
+ console.log("\n\x1B[36m\u2139\x1B[0m Run without --dry-run to apply changes");
433
+ if (!options.includeAssignee || !options.includeTransitions) {
434
+ console.log("\x1B[36m\u2139\x1B[0m Use --all to include optional fields (assignee, transitions)");
435
+ }
436
+ } else if (updated > 0) {
437
+ console.log("\n\x1B[32m\u2713\x1B[0m Backfill complete!");
438
+ console.log(" Run \x1B[36mlspec stats\x1B[0m to see velocity metrics");
439
+ }
440
+ }
441
+
442
+ export { backfillCommand, backfillTimestamps, createSpecDirPattern, getGlobalNextSeq, resolveSpecPath };
443
+ //# sourceMappingURL=chunk-A6JDTXPV.js.map
444
+ //# sourceMappingURL=chunk-A6JDTXPV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/git-timestamps.ts","../src/utils/path-helpers.ts","../src/commands/backfill.ts"],"names":["result","path2"],"mappings":"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","file":"chunk-A6JDTXPV.js","sourcesContent":["import { execSync } from 'node:child_process';\nimport * as path from 'node:path';\nimport type { SpecStatus, StatusTransition } from '../frontmatter.js';\n\nexport interface GitTimestampData {\n created_at?: string;\n updated_at?: string;\n completed_at?: string;\n assignee?: string;\n transitions?: StatusTransition[];\n}\n\n/**\n * Check if the current directory is a git repository\n */\nexport function isGitRepository(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', { \n stdio: 'ignore',\n encoding: 'utf-8' \n });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the timestamp of the first commit that created a file\n */\nexport function getFirstCommitTimestamp(filePath: string): string | null {\n try {\n // Use --follow to track file renames\n // Use --diff-filter=A to find the commit that added the file\n // Format as ISO 8601 timestamp\n const timestamp = execSync(\n `git log --follow --format=\"%aI\" --diff-filter=A -- \"${filePath}\" | tail -1`,\n { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }\n ).trim();\n \n return timestamp || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the timestamp of the most recent commit that modified a file\n */\nexport function getLastCommitTimestamp(filePath: string): string | null {\n try {\n const timestamp = execSync(\n `git log --format=\"%aI\" -n 1 -- \"${filePath}\"`,\n { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }\n ).trim();\n \n return timestamp || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the timestamp when a spec was marked as complete\n * Searches git history for status changes to \"complete\"\n */\nexport function getCompletionTimestamp(filePath: string): string | null {\n try {\n // Get all commits that modified the file, with patch output\n const gitLog = execSync(\n `git log --format=\"%H|%aI\" -p -- \"${filePath}\"`,\n { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }\n );\n \n // Parse commits to find status change to complete\n const commits = gitLog.split('\\ndiff --git').map(section => section.trim());\n \n for (const commit of commits) {\n if (!commit) continue;\n \n // Extract commit hash and timestamp from header\n const headerMatch = commit.match(/^([a-f0-9]{40})\\|([^\\n]+)/);\n if (!headerMatch) continue;\n \n const [, , timestamp] = headerMatch;\n \n // Look for status: complete in the diff\n // Check for both YAML frontmatter and inline status changes\n if (\n /^\\+status:\\s*['\"]?complete['\"]?/m.test(commit) ||\n /^\\+\\*\\*Status\\*\\*:.*complete/mi.test(commit)\n ) {\n return timestamp;\n }\n }\n \n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the author of the first commit (for assignee inference)\n */\nexport function getFirstCommitAuthor(filePath: string): string | null {\n try {\n const author = execSync(\n `git log --follow --format=\"%an\" --diff-filter=A -- \"${filePath}\" | tail -1`,\n { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }\n ).trim();\n \n return author || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse all status transitions from git history\n * Reconstructs the full status change timeline\n */\nexport function parseStatusTransitions(filePath: string): StatusTransition[] {\n const transitions: StatusTransition[] = [];\n \n try {\n // Get all commits that modified the file, with patch output\n const gitLog = execSync(\n `git log --format=\"%H|%aI\" -p --reverse -- \"${filePath}\"`,\n { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }\n );\n \n // Parse commits in chronological order (--reverse)\n const commits = gitLog.split('\\ndiff --git').map(section => section.trim());\n \n const validStatuses: SpecStatus[] = ['planned', 'in-progress', 'complete', 'archived'];\n \n for (const commit of commits) {\n if (!commit) continue;\n \n // Extract commit hash and timestamp from header\n const headerMatch = commit.match(/^([a-f0-9]{40})\\|([^\\n]+)/);\n if (!headerMatch) continue;\n \n const [, , timestamp] = headerMatch;\n \n // Look for status changes in the diff\n // Match: +status: complete or +status: 'complete'\n const statusMatch = commit.match(/^\\+status:\\s*['\"]?(\\w+(?:-\\w+)?)['\"]?/m);\n if (statusMatch) {\n const status = statusMatch[1] as SpecStatus;\n \n // Only record valid status values\n if (validStatuses.includes(status)) {\n // Avoid duplicate consecutive transitions\n const lastTransition = transitions[transitions.length - 1];\n if (!lastTransition || lastTransition.status !== status) {\n transitions.push({ status, at: timestamp });\n }\n }\n }\n }\n \n return transitions;\n } catch {\n return [];\n }\n}\n\n/**\n * Extract all git timestamp data for a spec file\n */\nexport function extractGitTimestamps(\n filePath: string,\n options: {\n includeAssignee?: boolean;\n includeTransitions?: boolean;\n } = {}\n): GitTimestampData {\n const data: GitTimestampData = {};\n \n // Core timestamps (always extracted)\n data.created_at = getFirstCommitTimestamp(filePath) ?? undefined;\n data.updated_at = getLastCommitTimestamp(filePath) ?? undefined;\n data.completed_at = getCompletionTimestamp(filePath) ?? undefined;\n \n // Optional fields\n if (options.includeAssignee) {\n const author = getFirstCommitAuthor(filePath);\n if (author) {\n data.assignee = author;\n }\n }\n \n if (options.includeTransitions) {\n const transitions = parseStatusTransitions(filePath);\n if (transitions.length > 0) {\n data.transitions = transitions;\n }\n }\n \n return data;\n}\n\n/**\n * Validate that a file exists in git history\n */\nexport function fileExistsInGit(filePath: string): boolean {\n try {\n execSync(\n `git log -n 1 -- \"${filePath}\"`,\n { stdio: 'ignore', encoding: 'utf-8' }\n );\n return true;\n } catch {\n return false;\n }\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\n/**\n * Create a regex pattern to match spec directories with sequence numbers\n * Handles optional date prefixes like 20251103-001-name\n */\nexport function createSpecDirPattern(): RegExp {\n // Match spec directories, handling optional date prefix\n // Patterns:\n // - 001-name (simple sequence)\n // - 20251103-001-name (date prefix + sequence)\n // - spec-001-name (custom prefix + sequence)\n // We look for: optional-prefix + NNN + dash + name\n // The sequence is 2-4 digits (to avoid matching 8-digit dates as sequences)\n // Requires dash followed by letter to ensure this is a spec directory name\n return /(?:^|\\D)(\\d{2,4})-[a-z]/i;\n}\n\n/**\n * Get next global sequence number across entire specs directory\n */\nexport async function getGlobalNextSeq(specsDir: string, digits: number): Promise<string> {\n try {\n // Recursively find all spec directories with sequence numbers\n const seqNumbers: number[] = [];\n const specPattern = createSpecDirPattern();\n \n async function scanDirectory(dir: string): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n \n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n \n // Check if this is a spec directory (NNN-name format)\n const match = entry.name.match(specPattern);\n if (match) {\n const seqNum = parseInt(match[1], 10);\n if (!isNaN(seqNum) && seqNum > 0) {\n seqNumbers.push(seqNum);\n }\n }\n \n // Skip archived directory to avoid confusion\n if (entry.name === 'archived') continue;\n \n // Recursively scan subdirectories (for custom pattern grouping)\n const subDir = path.join(dir, entry.name);\n await scanDirectory(subDir);\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n }\n \n await scanDirectory(specsDir);\n \n if (seqNumbers.length === 0) {\n return '1'.padStart(digits, '0');\n }\n \n const maxSeq = Math.max(...seqNumbers);\n return String(maxSeq + 1).padStart(digits, '0');\n } catch {\n return '1'.padStart(digits, '0');\n }\n}\n\n/**\n * Get next sequence number for a date directory (legacy, kept for backward compatibility)\n */\nexport async function getNextSeq(dateDir: string, digits: number): Promise<string> {\n try {\n const specPattern = createSpecDirPattern();\n const entries = await fs.readdir(dateDir, { withFileTypes: true });\n const seqNumbers = entries\n .filter((e) => e.isDirectory() && specPattern.test(e.name))\n .map((e) => {\n const match = e.name.match(specPattern);\n return match ? parseInt(match[1], 10) : NaN;\n })\n .filter((n) => !isNaN(n));\n\n if (seqNumbers.length === 0) {\n return '1'.padStart(digits, '0');\n }\n\n const maxSeq = Math.max(...seqNumbers);\n return String(maxSeq + 1).padStart(digits, '0');\n } catch {\n return '1'.padStart(digits, '0');\n }\n}\n\n/**\n * Resolve spec path in multiple ways:\n * 1. Absolute path as given\n * 2. Relative to current directory\n * 3. Relative to specs directory\n * 4. Search by spec name in all subdirectories (flat or grouped)\n * 5. Search by sequence number only\n */\nexport async function resolveSpecPath(\n specPath: string,\n cwd: string,\n specsDir: string\n): Promise<string | null> {\n // Try absolute path\n if (path.isAbsolute(specPath)) {\n try {\n await fs.access(specPath);\n return specPath;\n } catch {\n return null;\n }\n }\n\n // Try relative to cwd\n const cwdPath = path.resolve(cwd, specPath);\n try {\n await fs.access(cwdPath);\n return cwdPath;\n } catch {\n // Continue to next method\n }\n\n // Try relative to specs directory\n const specsPath = path.join(specsDir, specPath);\n try {\n await fs.access(specsPath);\n return specsPath;\n } catch {\n // Continue to next method\n }\n\n // Search by sequence number only (e.g., \"5\" or \"005\")\n const seqMatch = specPath.match(/^0*(\\d+)$/);\n if (seqMatch) {\n const seqNum = parseInt(seqMatch[1], 10);\n const result = await searchBySequence(specsDir, seqNum);\n if (result) return result;\n }\n\n // Last resort: search for spec name in all subdirectories\n const specName = specPath.replace(/^.*\\//, ''); // Get last part\n const result = await searchInAllDirectories(specsDir, specName);\n return result;\n}\n\n/**\n * Search for a spec by sequence number across all directories\n */\nasync function searchBySequence(specsDir: string, seqNum: number): Promise<string | null> {\n const specPattern = createSpecDirPattern();\n \n async function scanDirectory(dir: string): Promise<string | null> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n \n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n \n // Check if this matches the sequence number\n const match = entry.name.match(specPattern);\n if (match) {\n const entrySeq = parseInt(match[1], 10);\n if (entrySeq === seqNum) {\n return path.join(dir, entry.name);\n }\n }\n \n // Recursively search subdirectories (including archived)\n const subDir = path.join(dir, entry.name);\n const result = await scanDirectory(subDir);\n if (result) return result;\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n \n return null;\n }\n \n return scanDirectory(specsDir);\n}\n\n/**\n * Search for a spec by name in all subdirectories\n */\nasync function searchInAllDirectories(specsDir: string, specName: string): Promise<string | null> {\n async function scanDirectory(dir: string): Promise<string | null> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n \n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n \n // Check if this matches the spec name\n if (entry.name === specName) {\n return path.join(dir, entry.name);\n }\n \n // Recursively search subdirectories (including archived)\n const subDir = path.join(dir, entry.name);\n const result = await scanDirectory(subDir);\n if (result) return result;\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n \n return null;\n }\n \n return scanDirectory(specsDir);\n}\n","import * as path from 'node:path';\nimport { Command } from 'commander';\nimport { loadAllSpecs, getSpec, type SpecInfo } from '../spec-loader.js';\nimport { updateFrontmatter, type SpecFrontmatter } from '../frontmatter.js';\nimport { loadConfig } from '../config.js';\nimport { \n isGitRepository,\n extractGitTimestamps,\n fileExistsInGit,\n type GitTimestampData,\n} from '../utils/git-timestamps.js';\nimport { resolveSpecPath } from '../utils/path-helpers.js';\n\nexport interface BackfillResult {\n specPath: string;\n specName: string;\n created_at?: string;\n updated_at?: string;\n completed_at?: string;\n assignee?: string;\n transitionsCount?: number;\n source: 'git' | 'existing' | 'skipped';\n reason?: string;\n}\n\nexport interface BackfillOptions {\n dryRun?: boolean;\n force?: boolean;\n includeAssignee?: boolean;\n includeTransitions?: boolean;\n specs?: string[]; // specific specs to target\n json?: boolean;\n}\n\n/**\n * Backfill command - backfill timestamps from git history\n */\nexport function backfillCommand(): Command {\n return new Command('backfill')\n .description('Backfill timestamps from git history')\n .argument('[specs...]', 'Specific specs to backfill (optional)')\n .option('--dry-run', 'Show what would be updated without making changes')\n .option('--force', 'Overwrite existing timestamp values')\n .option('--assignee', 'Include assignee from first commit author')\n .option('--transitions', 'Include full status transition history')\n .option('--all', 'Include all optional fields (assignee + transitions)')\n .option('--json', 'Output as JSON')\n .action(async (specs: string[] | undefined, options: {\n dryRun?: boolean;\n force?: boolean;\n assignee?: boolean;\n transitions?: boolean;\n all?: boolean;\n json?: boolean;\n }) => {\n await backfillTimestamps({\n dryRun: options.dryRun,\n force: options.force,\n includeAssignee: options.assignee || options.all,\n includeTransitions: options.transitions || options.all,\n specs: specs && specs.length > 0 ? specs : undefined,\n json: options.json,\n });\n });\n}\n\n/**\n * Backfill timestamps from git history for all or specific specs\n */\nexport async function backfillTimestamps(options: BackfillOptions = {}): Promise<BackfillResult[]> {\n const results: BackfillResult[] = [];\n \n // Check if we're in a git repository\n if (!isGitRepository()) {\n console.error('\\x1b[31mError:\\x1b[0m Not in a git repository');\n console.error('Git history is required for backfilling timestamps');\n process.exit(1);\n }\n \n // Load specs to process\n let specs: SpecInfo[];\n \n if (options.specs && options.specs.length > 0) {\n // Load specific specs\n specs = [];\n const config = await loadConfig();\n const cwd = process.cwd();\n const specsDir = path.join(cwd, config.specsDir);\n \n for (const specPath of options.specs) {\n const resolved = await resolveSpecPath(specPath, cwd, specsDir);\n if (!resolved) {\n console.warn(`\\x1b[33mWarning:\\x1b[0m Spec not found: ${specPath}`);\n continue;\n }\n const spec = await getSpec(resolved);\n if (spec) {\n specs.push(spec);\n }\n }\n } else {\n // Load all specs (including archived)\n specs = await loadAllSpecs({ includeArchived: true });\n }\n \n if (specs.length === 0) {\n console.log('No specs found to backfill');\n return results;\n }\n \n // Show what we're doing\n if (options.dryRun) {\n console.log('\\x1b[36m🔍 Dry run mode - no changes will be made\\x1b[0m\\n');\n }\n \n console.log(`Analyzing git history for ${specs.length} spec${specs.length === 1 ? '' : 's'}...\\n`);\n \n // Process each spec\n for (const spec of specs) {\n const result = await backfillSpecTimestamps(spec, options);\n results.push(result);\n }\n \n // Print summary\n printSummary(results, options);\n \n return results;\n}\n\n/**\n * Backfill timestamps for a single spec\n */\nasync function backfillSpecTimestamps(\n spec: SpecInfo,\n options: BackfillOptions\n): Promise<BackfillResult> {\n const result: BackfillResult = {\n specPath: spec.path,\n specName: spec.name,\n source: 'skipped',\n };\n \n // Check if file exists in git history\n if (!fileExistsInGit(spec.filePath)) {\n result.reason = 'Not in git history';\n console.log(`\\x1b[33m⊘\\x1b[0m ${spec.name} - Not in git history`);\n return result;\n }\n \n // Extract git timestamps\n const gitData = extractGitTimestamps(spec.filePath, {\n includeAssignee: options.includeAssignee,\n includeTransitions: options.includeTransitions,\n });\n \n // Determine what needs to be updated\n const updates: Partial<SpecFrontmatter> = {};\n let hasUpdates = false;\n \n // Check created_at\n if (gitData.created_at && (options.force || !spec.frontmatter.created_at)) {\n updates.created_at = gitData.created_at;\n result.created_at = gitData.created_at;\n result.source = 'git';\n hasUpdates = true;\n } else if (spec.frontmatter.created_at) {\n result.created_at = spec.frontmatter.created_at;\n result.source = 'existing';\n }\n \n // Check updated_at\n if (gitData.updated_at && (options.force || !spec.frontmatter.updated_at)) {\n updates.updated_at = gitData.updated_at;\n result.updated_at = gitData.updated_at;\n result.source = 'git';\n hasUpdates = true;\n } else if (spec.frontmatter.updated_at) {\n result.updated_at = spec.frontmatter.updated_at;\n result.source = 'existing';\n }\n \n // Check completed_at\n if (gitData.completed_at && (options.force || !spec.frontmatter.completed_at)) {\n updates.completed_at = gitData.completed_at;\n result.completed_at = gitData.completed_at;\n result.source = 'git';\n hasUpdates = true;\n } else if (spec.frontmatter.completed_at) {\n result.completed_at = spec.frontmatter.completed_at;\n result.source = 'existing';\n }\n \n // Check assignee (optional)\n if (options.includeAssignee && gitData.assignee && (options.force || !spec.frontmatter.assignee)) {\n updates.assignee = gitData.assignee;\n result.assignee = gitData.assignee;\n result.source = 'git';\n hasUpdates = true;\n } else if (spec.frontmatter.assignee) {\n result.assignee = spec.frontmatter.assignee;\n }\n \n // Check transitions (optional)\n if (options.includeTransitions && gitData.transitions && gitData.transitions.length > 0) {\n if (options.force || !spec.frontmatter.transitions || spec.frontmatter.transitions.length === 0) {\n updates.transitions = gitData.transitions;\n result.transitionsCount = gitData.transitions.length;\n result.source = 'git';\n hasUpdates = true;\n } else {\n // Merge with existing transitions (optional: could implement smart merge)\n result.transitionsCount = spec.frontmatter.transitions.length;\n }\n }\n \n // Sync updated date field with updated_at timestamp\n if (updates.updated_at && !updates.updated) {\n updates.updated = updates.updated_at.split('T')[0];\n }\n \n if (!hasUpdates) {\n result.reason = 'Already has complete data';\n console.log(`\\x1b[90m✓\\x1b[0m ${spec.name} - Already complete`);\n return result;\n }\n \n // Apply updates (unless dry run)\n if (!options.dryRun) {\n try {\n await updateFrontmatter(spec.filePath, updates);\n console.log(`\\x1b[32m✓\\x1b[0m ${spec.name} - Updated`);\n } catch (error) {\n result.source = 'skipped';\n result.reason = `Error: ${error instanceof Error ? error.message : String(error)}`;\n console.log(`\\x1b[31m✗\\x1b[0m ${spec.name} - Failed: ${result.reason}`);\n }\n } else {\n console.log(`\\x1b[36m→\\x1b[0m ${spec.name} - Would update`);\n // Show what would be updated\n if (updates.created_at) console.log(` created_at: ${updates.created_at} (git)`);\n if (updates.updated_at) console.log(` updated_at: ${updates.updated_at} (git)`);\n if (updates.completed_at) console.log(` completed_at: ${updates.completed_at} (git)`);\n if (updates.assignee) console.log(` assignee: ${updates.assignee} (git)`);\n if (updates.transitions) console.log(` transitions: ${updates.transitions.length} status changes (git)`);\n }\n \n return result;\n}\n\n/**\n * Print summary of backfill results\n */\nfunction printSummary(results: BackfillResult[], options: BackfillOptions): void {\n console.log('\\n' + '─'.repeat(60));\n console.log('\\x1b[1mSummary:\\x1b[0m\\n');\n \n const total = results.length;\n const updated = results.filter(r => r.source === 'git').length;\n const existing = results.filter(r => r.source === 'existing').length;\n const skipped = results.filter(r => r.source === 'skipped').length;\n \n const timestampUpdates = results.filter(r => \n r.source === 'git' && (r.created_at || r.updated_at || r.completed_at)\n ).length;\n \n const assigneeUpdates = results.filter(r => \n r.source === 'git' && r.assignee\n ).length;\n \n const transitionUpdates = results.filter(r => \n r.source === 'git' && r.transitionsCount\n ).length;\n \n console.log(` ${total} specs analyzed`);\n \n if (options.dryRun) {\n console.log(` ${updated} would be updated`);\n if (timestampUpdates > 0) {\n console.log(` └─ ${timestampUpdates} with timestamps`);\n }\n if (options.includeAssignee && assigneeUpdates > 0) {\n console.log(` └─ ${assigneeUpdates} with assignee`);\n }\n if (options.includeTransitions && transitionUpdates > 0) {\n console.log(` └─ ${transitionUpdates} with transitions`);\n }\n } else {\n console.log(` ${updated} updated`);\n }\n \n console.log(` ${existing} already complete`);\n console.log(` ${skipped} skipped`);\n \n // Show reasons for skipped specs\n const skipReasons = results\n .filter(r => r.source === 'skipped' && r.reason)\n .map(r => r.reason);\n \n if (skipReasons.length > 0) {\n console.log('\\n\\x1b[33mSkipped reasons:\\x1b[0m');\n const uniqueReasons = [...new Set(skipReasons)];\n for (const reason of uniqueReasons) {\n const count = skipReasons.filter(r => r === reason).length;\n console.log(` - ${reason} (${count})`);\n }\n }\n \n // Guidance\n if (options.dryRun) {\n console.log('\\n\\x1b[36mℹ\\x1b[0m Run without --dry-run to apply changes');\n \n if (!options.includeAssignee || !options.includeTransitions) {\n console.log('\\x1b[36mℹ\\x1b[0m Use --all to include optional fields (assignee, transitions)');\n }\n } else if (updated > 0) {\n console.log('\\n\\x1b[32m✓\\x1b[0m Backfill complete!');\n console.log(' Run \\x1b[36mlspec stats\\x1b[0m to see velocity metrics');\n }\n}\n"]}