lean-spec 0.2.8 → 0.2.9-dev.20251205030455

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.
@@ -12,132 +12,86 @@
12
12
 
13
13
  > **Why?** Skipping discovery creates duplicate work. Manual file creation breaks LeanSpec tooling.
14
14
 
15
- ## 🔧 How to Manage Specs
16
-
17
- ### Primary Method: MCP Tools (Recommended)
18
-
19
- If you have LeanSpec MCP tools available, **ALWAYS use them**:
20
-
21
- | Action | MCP Tool | Description |
22
- |--------|----------|-------------|
23
- | See project status | `board` | Kanban view + project health metrics |
24
- | List all specs | `list` | Filterable list with metadata |
25
- | Search specs | `search` | Semantic search across all content |
26
- | View a spec | `view` | Full content with formatting |
27
- | Create new spec | `create` | Auto-sequences, proper structure |
28
- | Update spec | `update` | Validates transitions, timestamps |
29
- | Link specs | `link` | Add relationships (depends_on, related) |
30
- | Unlink specs | `unlink` | Remove relationships |
31
- | Check dependencies | `deps` | Visual dependency graph |
32
-
33
- **Why MCP over CLI?**
34
- - Direct tool integration (no shell execution needed)
35
- - ✅ Structured responses (better for AI reasoning)
36
- - Real-time validation (immediate feedback)
37
- - Context-aware (understands project state)
38
-
39
- ### Fallback: CLI Commands
40
-
41
- If MCP tools are not available, use CLI commands:
42
-
43
- ```bash
44
- lean-spec board # Project overview
45
- lean-spec list # See all specs
46
- lean-spec search "query" # Find relevant specs
47
- lean-spec create <name> # Create new spec
48
- lean-spec update <spec> --status <status> # Update status
49
- lean-spec link <spec> --related <other> # Add relationships
50
- lean-spec unlink <spec> --related <other> # Remove relationships
51
- lean-spec deps <spec> # Show dependencies
52
- ```
53
-
54
- **Tip:** Check if you have LeanSpec MCP tools available before using CLI.
55
-
56
- ## ⚠️ SDD Workflow Checkpoints
57
-
58
- ### Before Starting ANY Task
59
-
60
- 1. 📋 **Run `board`** - What's the current project state?
61
- 2. 🔍 **Run `search`** - Are there related specs already?
62
- 3. 📝 **Check existing specs** - Is there one for this work?
63
-
64
- ### During Implementation
65
-
66
- 4. 📊 **Update status to `in-progress`** BEFORE coding
67
- 5. 📝 **Document decisions** in the spec as you work
68
- 6. 🔗 **Link related specs** if you discover connections
69
-
70
- ### After Completing Work
71
-
72
- 7. ✅ **Update status to `complete`** when done
73
- 8. 📄 **Document what you learned** in the spec
74
- 9. 🤔 **Create follow-up specs** if needed
75
-
76
- ### 🚫 Common Mistakes to Avoid
15
+ ## 🔧 Managing Specs
16
+
17
+ ### MCP Tools (Preferred) with CLI Fallback
18
+
19
+ | Action | MCP Tool | CLI Fallback |
20
+ |--------|----------|--------------|
21
+ | Project status | `board` | `lean-spec board` |
22
+ | List specs | `list` | `lean-spec list` |
23
+ | Search specs | `search` | `lean-spec search "query"` |
24
+ | View spec | `view` | `lean-spec view <spec>` |
25
+ | Create spec | `create` | `lean-spec create <name>` |
26
+ | Update spec | `update` | `lean-spec update <spec> --status <status>` |
27
+ | Link specs | `link` | `lean-spec link <spec> --depends-on <other>` |
28
+ | Unlink specs | `unlink` | `lean-spec unlink <spec> --depends-on <other>` |
29
+ | Dependencies | `deps` | `lean-spec deps <spec>` |
30
+ | Token count | `tokens` | `lean-spec tokens <spec>` |
31
+
32
+ ## ⚠️ Core Rules
33
+
34
+ | Rule | Details |
35
+ |------|---------|
36
+ | **NEVER edit frontmatter manually** | Use `update`, `link`, `unlink` for: `status`, `priority`, `tags`, `assignee`, `transitions`, timestamps, `depends_on` |
37
+ | **ALWAYS link spec references** | Content mentions another spec → `lean-spec link <spec> --depends-on <other>` |
38
+ | **Track status transitions** | `planned` → `in-progress` (before coding) → `complete` (after done) |
39
+ | **No nested code blocks** | Use indentation instead |
40
+
41
+ ### 🚫 Common Mistakes
77
42
 
78
43
  | ❌ Don't | ✅ Do Instead |
79
44
  |----------|---------------|
80
45
  | Create spec files manually | Use `create` tool |
81
- | Skip discovery before new work | Run `board` and `search` first |
82
- | Leave status as "planned" after starting | Update to `in-progress` immediately |
83
- | Finish work without updating spec | Document decisions, update status |
46
+ | Skip discovery | Run `board` and `search` first |
47
+ | Leave status as "planned" | Update to `in-progress` before coding |
84
48
  | Edit frontmatter manually | Use `update` tool |
85
- | Forget about specs mid-conversation | Check spec status periodically |
86
-
87
- ## Core Rules
88
-
89
- 1. **Read README.md first** - Understand project context
90
- 2. **Check specs/** - Review existing specs before starting
91
- 3. **Use MCP tools** - Prefer MCP over CLI when available
92
- 4. **Follow LeanSpec principles** - Clarity over documentation
93
- 5. **Keep it minimal** - If it doesn't add clarity, cut it
94
- 6. **NEVER manually edit frontmatter** - Use `update`, `link`, `unlink` tools
95
- 7. **Track progress in specs** - Update status and document decisions
96
-
97
- ## When to Use Specs
98
49
 
99
- **Write a spec for:**
100
- - Features affecting multiple parts of the system
101
- - Breaking changes or significant refactors
102
- - Design decisions needing team alignment
50
+ ## 📋 SDD Workflow
103
51
 
104
- **Skip specs for:**
105
- - Bug fixes
106
- - Trivial changes
107
- - Self-explanatory refactors
52
+ ```
53
+ BEFORE: board → search → check existing specs
54
+ DURING: update status to in-progress code → document decisions → link dependencies
55
+ AFTER: update status to complete → document learnings
56
+ ```
108
57
 
109
- ## Spec Relationships
58
+ **Status tracks implementation, NOT spec writing.**
110
59
 
111
- ### `related` - Bidirectional Soft Reference
112
- Informational relationship between specs. Shown from both sides.
113
- **Use when:** Related topics, coordinated but not blocking work.
60
+ ## Spec Dependencies
114
61
 
115
- ### `depends_on` - Directional Blocking Dependency
116
- Hard dependency - spec cannot start until dependencies complete.
117
- **Use when:** True blocking dependency, work order matters.
62
+ Use `depends_on` to express blocking relationships between specs:
63
+ - **`depends_on`** = True blocker, work order matters, directional (A depends on B)
118
64
 
119
- **Default:** Use `related`. Reserve `depends_on` for true blockers.
65
+ Link dependencies when one spec builds on another:
66
+ ```bash
67
+ lean-spec link <spec> --depends-on <other-spec>
68
+ ```
120
69
 
121
- ## Quality Standards
70
+ ## When to Use Specs
122
71
 
123
- - **Status tracking is mandatory:**
124
- - `planned` → after creation
125
- - `in-progress` BEFORE starting implementation
126
- - `complete` AFTER finishing implementation
127
- - Specs stay in sync with implementation
128
- - Never leave specs with stale status
72
+ | Write spec | ❌ Skip spec |
73
+ |---------------|--------------|
74
+ | Multi-part features | Bug fixes |
75
+ | Breaking changes | Trivial changes |
76
+ | Design decisions | Self-explanatory refactors |
129
77
 
130
- ## Spec Complexity Guidelines
78
+ ## Token Thresholds
131
79
 
132
80
  | Tokens | Status |
133
81
  |--------|--------|
134
82
  | <2,000 | ✅ Optimal |
135
83
  | 2,000-3,500 | ✅ Good |
136
84
  | 3,500-5,000 | ⚠️ Consider splitting |
137
- | >5,000 | 🔴 Should split |
85
+ | >5,000 | 🔴 Must split |
86
+
87
+ ## First Principles (Priority Order)
138
88
 
139
- Use `tokens` tool to check spec size.
89
+ 1. **Context Economy** - <2,000 tokens optimal, >3,500 needs splitting
90
+ 2. **Signal-to-Noise** - Every word must inform a decision
91
+ 3. **Intent Over Implementation** - Capture why, let how emerge
92
+ 4. **Bridge the Gap** - Both human and AI must understand
93
+ 5. **Progressive Disclosure** - Add complexity only when pain is felt
140
94
 
141
95
  ---
142
96
 
143
- **Remember:** LeanSpec tracks what you're building. Keep specs in sync with your work!
97
+ **Remember:** LeanSpec tracks what you're building. Keep specs in sync with your work!
@@ -1,5 +0,0 @@
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
@@ -1 +0,0 @@
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"]}