lean-spec 0.2.8 → 0.2.9

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.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # LeanSpec
2
+
3
+ <p align="center">
4
+ <img src="https://github.com/codervisor/lean-spec-docs/blob/main/static/img/logo-with-bg.svg" alt="LeanSpec Logo" width="120" height="120">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/codervisor/lean-spec/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/codervisor/lean-spec/ci.yml?branch=main" alt="CI Status"></a>
9
+ <a href="https://www.npmjs.com/package/lean-spec"><img src="https://img.shields.io/npm/v/lean-spec.svg" alt="npm version"></a>
10
+ <a href="https://www.npmjs.com/package/lean-spec"><img src="https://img.shields.io/npm/dm/lean-spec.svg" alt="npm downloads"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.lean-spec.dev"><strong>Documentation</strong></a>
16
+
17
+ <a href="https://www.lean-spec.dev/zh-Hans/docs/guide/"><strong>中文文档</strong></a>
18
+
19
+ <a href="https://web.lean-spec.dev"><strong>Live Examples</strong></a>
20
+
21
+ <a href="https://www.lean-spec.dev/docs/tutorials/first-spec-with-ai"><strong>Tutorials</strong></a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ **Ship faster with higher quality. Lean specs that both humans and AI understand.**
27
+
28
+ LeanSpec brings agile principles to SDD (Spec-Driven Development)—small, focused documents (<2,000 tokens) that keep you and your AI aligned.
29
+
30
+ ---
31
+
32
+ ## Quick Start
33
+
34
+ ```bash
35
+ # Try with a tutorial project
36
+ npx lean-spec init --example dark-theme
37
+ cd dark-theme && npm install && npm start
38
+
39
+ # Or add to your existing project
40
+ npm install -g lean-spec && lean-spec init
41
+ ```
42
+
43
+ **Visualize your project:**
44
+
45
+ ```bash
46
+ lean-spec board # Kanban view
47
+ lean-spec stats # Project metrics
48
+ lean-spec ui # Web UI at localhost:3000
49
+ ```
50
+
51
+ **Next:** [Your First Spec with AI](https://www.lean-spec.dev/docs/tutorials/first-spec-with-ai) (10 min tutorial)
52
+
53
+ ---
54
+
55
+ ## Why LeanSpec?
56
+
57
+ **High velocity + High quality.** Other SDD frameworks add process overhead (multi-step workflows, rigid templates). Vibe coding is fast but chaotic (no shared understanding). LeanSpec hits the sweet spot:
58
+
59
+ - **Fast iteration** - Living documents that grow with your code
60
+ - **AI performance** - Small specs = better AI output (context rot is real)
61
+ - **Always current** - Lightweight enough that you actually update them
62
+
63
+ 📖 [Compare with Spec Kit, OpenSpec, Kiro →](https://www.lean-spec.dev/docs/guide/why-leanspec)
64
+
65
+ ---
66
+
67
+ ## AI Integration
68
+
69
+ Works with any AI coding assistant via MCP or CLI:
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "lean-spec": { "command": "npx", "args": ["@leanspec/mcp"] }
75
+ }
76
+ }
77
+ ```
78
+
79
+ **Compatible with:** VS Code Copilot, Cursor, Windsurf, Claude Code, Gemini CLI, and more.
80
+
81
+ 📖 [Full AI integration guide →](https://www.lean-spec.dev/docs/guide/usage/ai-coding-workflow)
82
+
83
+ ---
84
+
85
+ ## Features
86
+
87
+ | Feature | Description |
88
+ |---------|-------------|
89
+ | **📊 Kanban Board** | `lean-spec board` - visual project tracking |
90
+ | **🔍 Smart Search** | `lean-spec search` - find specs by content or metadata |
91
+ | **🔗 Dependencies** | Track spec relationships with `depends_on` and `related` |
92
+ | **🎨 Web UI** | `lean-spec ui` - browser-based dashboard |
93
+ | **📈 Project Stats** | `lean-spec stats` - health metrics and bottleneck detection |
94
+ | **🤖 AI-Native** | MCP server + CLI for AI assistants |
95
+
96
+ <p align="center">
97
+ <img src="https://github.com/codervisor/lean-spec-docs/blob/main/static/img/ui/ui-board-view.png" alt="Kanban Board View" width="800">
98
+ </p>
99
+
100
+ ---
101
+
102
+ ## Documentation
103
+
104
+ 📖 [Full Documentation](https://www.lean-spec.dev) · [CLI Reference](https://www.lean-spec.dev/docs/reference/cli) · [First Principles](https://www.lean-spec.dev/docs/advanced/first-principles) · [FAQ](https://www.lean-spec.dev/docs/faq) · [中文文档](https://www.lean-spec.dev/zh-Hans/)
105
+
106
+ ## Community
107
+
108
+ 💬 [Discussions](https://github.com/codervisor/lean-spec/discussions) · 🐛 [Issues](https://github.com/codervisor/lean-spec/issues) · 🤝 [Contributing](CONTRIBUTING.md) · 📋 [Changelog](CHANGELOG) · 📄 [LICENSE](LICENSE)
109
+
110
+ ---
111
+
112
+ ### Contact Me | 联系我
113
+
114
+ If you find LeanSpec helpful, feel free to add me on WeChat (note "LeanSpec") to join the discussion group.
115
+
116
+ 如果您觉得 LeanSpec 对您有帮助,欢迎添加微信(备注 "LeanSpec")加入交流群。
117
+
118
+ <p align="center">
119
+ <img src="https://github.com/codervisor/lean-spec-docs/blob/main/static/img/qr-code.png" alt="WeChat Contact | 微信联系" height="280">
120
+ </p>
@@ -0,0 +1,5 @@
1
+ export { backfillCommand, backfillTimestamps } from './chunk-SZNMHOHN.js';
2
+ import './chunk-K4VTB6BF.js';
3
+ import './chunk-ENX5NYTE.js';
4
+ //# sourceMappingURL=backfill-WYW47B42.js.map
5
+ //# sourceMappingURL=backfill-WYW47B42.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"backfill-QIQLXSUG.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"backfill-WYW47B42.js"}
@@ -137,7 +137,6 @@ async function parseFrontmatter(filePath, config) {
137
137
  "created",
138
138
  "tags",
139
139
  "priority",
140
- "related",
141
140
  "depends_on",
142
141
  "updated",
143
142
  "completed",
@@ -294,5 +293,5 @@ function matchesFilter(frontmatter, filter) {
294
293
  }
295
294
 
296
295
  export { enrichWithTimestamps, getSpecFile, matchesFilter, normalizeDateFields, normalizeTagsField, parseFrontmatter, updateFrontmatter, validateCustomField, validateCustomFields };
297
- //# sourceMappingURL=chunk-LVD7ZAVZ.js.map
298
- //# sourceMappingURL=chunk-LVD7ZAVZ.js.map
296
+ //# sourceMappingURL=chunk-ENX5NYTE.js.map
297
+ //# sourceMappingURL=chunk-ENX5NYTE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/frontmatter.ts"],"names":[],"mappings":"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","file":"chunk-ENX5NYTE.js","sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport matter from 'gray-matter';\nimport yaml from 'js-yaml';\nimport dayjs from 'dayjs';\nimport type { LeanSpecConfig } from './config.js';\n\n// Valid status values\nexport type SpecStatus = 'planned' | 'in-progress' | 'complete' | 'archived';\n\n// Valid priority values\nexport type SpecPriority = 'low' | 'medium' | 'high' | 'critical';\n\n// Status transition record\nexport interface StatusTransition {\n status: SpecStatus;\n at: string; // ISO 8601 timestamp\n}\n\n// Core frontmatter fields\nexport interface SpecFrontmatter {\n // Required fields\n status: SpecStatus;\n created: string; // YYYY-MM-DD format\n\n // Recommended fields\n tags?: string[];\n priority?: SpecPriority;\n\n // Power user fields\n depends_on?: string[];\n updated?: string;\n completed?: string;\n assignee?: string;\n reviewer?: string;\n issue?: string;\n pr?: string;\n epic?: string;\n breaking?: boolean;\n due?: string; // YYYY-MM-DD format\n\n // Timestamp fields (for velocity tracking)\n created_at?: string; // ISO 8601 timestamp\n updated_at?: string; // ISO 8601 timestamp\n completed_at?: string; // ISO 8601 timestamp\n transitions?: StatusTransition[]; // Status change history\n\n // Allow any additional fields (for extensibility)\n [key: string]: unknown;\n}\n\n/**\n * Convert Date objects to YYYY-MM-DD string format\n * (gray-matter auto-parses YYYY-MM-DD strings as Date objects)\n */\nexport function normalizeDateFields(data: Record<string, unknown>): void {\n const dateFields = ['created', 'completed', 'updated', 'due'];\n \n for (const field of dateFields) {\n if (data[field] instanceof Date) {\n data[field] = (data[field] as Date).toISOString().split('T')[0];\n }\n }\n}\n\n/**\n * Enrich frontmatter with timestamps for velocity tracking\n * Auto-generates timestamps when missing and tracks status transitions\n */\nexport function enrichWithTimestamps(\n data: Record<string, unknown>,\n previousData?: Record<string, unknown>\n): void {\n const now = new Date().toISOString();\n\n // Set created_at if missing - always use current timestamp\n // Do NOT infer from created date field since that's just YYYY-MM-DD without time\n if (!data.created_at) {\n data.created_at = now;\n }\n\n // Update updated_at on any change (if previousData exists)\n if (previousData) {\n data.updated_at = now;\n }\n\n // Set completed_at when status changes to complete\n if (\n data.status === 'complete' &&\n previousData?.status !== 'complete' &&\n !data.completed_at\n ) {\n data.completed_at = now;\n // Also set the completed date field\n if (!data.completed) {\n data.completed = new Date().toISOString().split('T')[0];\n }\n }\n\n // Track status transition (optional)\n if (previousData && data.status !== previousData.status) {\n if (!Array.isArray(data.transitions)) {\n data.transitions = [];\n }\n (data.transitions as StatusTransition[]).push({\n status: data.status as SpecStatus,\n at: now,\n });\n }\n}\n\n/**\n * Normalize tags field - parse JSON strings into arrays\n * Handles cases where AI accidentally creates tags as '[\"..\",\"..\"]' strings\n */\nexport function normalizeTagsField(data: Record<string, unknown>): void {\n if (data.tags && typeof data.tags === 'string') {\n try {\n // Try to parse as JSON array\n const parsed = JSON.parse(data.tags as string);\n if (Array.isArray(parsed)) {\n data.tags = parsed;\n }\n } catch {\n // If not valid JSON, treat as comma-separated string\n data.tags = (data.tags as string).split(',').map(t => t.trim());\n }\n }\n}\n\n/**\n * Validate and coerce custom field types\n */\nexport function validateCustomField(\n value: unknown,\n expectedType: 'string' | 'number' | 'boolean' | 'array'\n): { valid: boolean; coerced?: unknown; error?: string } {\n switch (expectedType) {\n case 'string':\n if (typeof value === 'string') {\n return { valid: true, coerced: value };\n }\n // Coerce to string\n return { valid: true, coerced: String(value) };\n \n case 'number':\n if (typeof value === 'number') {\n return { valid: true, coerced: value };\n }\n // Try to coerce to number\n const num = Number(value);\n if (!isNaN(num)) {\n return { valid: true, coerced: num };\n }\n return { valid: false, error: `Cannot convert '${value}' to number` };\n \n case 'boolean':\n if (typeof value === 'boolean') {\n return { valid: true, coerced: value };\n }\n // Coerce string to boolean\n if (value === 'true' || value === 'yes' || value === '1') {\n return { valid: true, coerced: true };\n }\n if (value === 'false' || value === 'no' || value === '0') {\n return { valid: true, coerced: false };\n }\n return { valid: false, error: `Cannot convert '${value}' to boolean` };\n \n case 'array':\n if (Array.isArray(value)) {\n return { valid: true, coerced: value };\n }\n return { valid: false, error: `Expected array but got ${typeof value}` };\n \n default:\n return { valid: false, error: `Unknown type: ${expectedType}` };\n }\n}\n\n/**\n * Validate custom fields according to config\n */\nexport function validateCustomFields(\n frontmatter: Record<string, unknown>,\n config?: LeanSpecConfig\n): Record<string, unknown> {\n if (!config?.frontmatter?.custom) {\n return frontmatter;\n }\n \n const result = { ...frontmatter };\n \n for (const [fieldName, expectedType] of Object.entries(config.frontmatter.custom)) {\n if (fieldName in result) {\n const validation = validateCustomField(result[fieldName], expectedType);\n if (validation.valid) {\n result[fieldName] = validation.coerced;\n } else {\n console.warn(`Warning: Invalid custom field '${fieldName}': ${validation.error}`);\n }\n }\n }\n \n return result;\n}\n\n// Parse frontmatter from a spec file\nexport async function parseFrontmatter(\n filePath: string,\n config?: LeanSpecConfig\n): Promise<SpecFrontmatter | null> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const parsed = matter(content, {\n engines: {\n yaml: (str) => yaml.load(str, { schema: yaml.FAILSAFE_SCHEMA }) as Record<string, unknown>\n }\n });\n\n if (!parsed.data || Object.keys(parsed.data).length === 0) {\n // No frontmatter found, try fallback to inline fields\n return parseFallbackFields(content);\n }\n\n // Validate required fields\n if (!parsed.data.status) {\n console.warn(`Warning: Missing required field 'status' in ${filePath}`);\n return null;\n }\n\n if (!parsed.data.created) {\n console.warn(`Warning: Missing required field 'created' in ${filePath}`);\n return null;\n }\n\n // Validate status enum\n const validStatuses: SpecStatus[] = ['planned', 'in-progress', 'complete', 'archived'];\n if (!validStatuses.includes(parsed.data.status)) {\n console.warn(`Warning: Invalid status '${parsed.data.status}' in ${filePath}. Valid values: ${validStatuses.join(', ')}`);\n }\n\n // Validate priority enum if present\n if (parsed.data.priority) {\n const validPriorities: SpecPriority[] = ['low', 'medium', 'high', 'critical'];\n if (!validPriorities.includes(parsed.data.priority)) {\n console.warn(`Warning: Invalid priority '${parsed.data.priority}' in ${filePath}. Valid values: ${validPriorities.join(', ')}`);\n }\n }\n\n // Normalize tags field (parse JSON strings to arrays)\n normalizeTagsField(parsed.data);\n \n // Warn about unknown fields (informational only)\n const knownFields = [\n 'status', 'created', 'tags', 'priority', 'depends_on',\n 'updated', 'completed', 'assignee', 'reviewer', 'issue', 'pr', 'epic', 'breaking', 'due',\n 'created_at', 'updated_at', 'completed_at', 'transitions'\n ];\n \n // Add custom fields from config to known fields\n const customFields = config?.frontmatter?.custom ? Object.keys(config.frontmatter.custom) : [];\n const allKnownFields = [...knownFields, ...customFields];\n \n const unknownFields = Object.keys(parsed.data).filter(k => !allKnownFields.includes(k));\n if (unknownFields.length > 0) {\n console.warn(`Info: Unknown fields in ${filePath}: ${unknownFields.join(', ')}`);\n }\n \n // Validate and coerce custom fields\n const validatedData = validateCustomFields(parsed.data, config);\n\n return validatedData as SpecFrontmatter;\n } catch (error) {\n console.error(`Error parsing frontmatter from ${filePath}:`, error);\n return null;\n }\n}\n\n// Fallback: Parse inline fields from older specs\nfunction parseFallbackFields(content: string): SpecFrontmatter | null {\n const statusMatch = content.match(/\\*\\*Status\\*\\*:\\s*(?:📅\\s*)?(\\w+(?:-\\w+)?)/i);\n const createdMatch = content.match(/\\*\\*Created\\*\\*:\\s*(\\d{4}-\\d{2}-\\d{2})/);\n\n if (statusMatch && createdMatch) {\n const status = statusMatch[1].toLowerCase().replace(/\\s+/g, '-') as SpecStatus;\n const created = createdMatch[1];\n\n return {\n status,\n created,\n };\n }\n\n return null;\n}\n\n// Update frontmatter in a spec file\nexport async function updateFrontmatter(\n filePath: string,\n updates: Partial<SpecFrontmatter>\n): Promise<void> {\n const content = await fs.readFile(filePath, 'utf-8');\n const parsed = matter(content, {\n engines: {\n yaml: (str) => yaml.load(str, { schema: yaml.FAILSAFE_SCHEMA }) as Record<string, unknown>\n }\n });\n\n // Store previous data for timestamp enrichment\n const previousData = { ...parsed.data };\n\n // Merge updates with existing data\n const newData = { ...parsed.data, ...updates };\n\n // Ensure date fields remain as strings (gray-matter auto-parses YYYY-MM-DD as Date objects)\n normalizeDateFields(newData);\n\n // Enrich with timestamps\n enrichWithTimestamps(newData, previousData);\n\n // Auto-update timestamps if fields exist (legacy behavior)\n if (updates.status === 'complete' && !newData.completed) {\n newData.completed = dayjs().format('YYYY-MM-DD');\n }\n\n if ('updated' in parsed.data) {\n newData.updated = dayjs().format('YYYY-MM-DD');\n }\n\n // Update visual metadata badges in content\n let updatedContent = parsed.content;\n updatedContent = updateVisualMetadata(updatedContent, newData as SpecFrontmatter);\n\n // Stringify back to file\n const newContent = matter.stringify(updatedContent, newData);\n await fs.writeFile(filePath, newContent, 'utf-8');\n}\n\n// Update visual metadata badges in content\nfunction updateVisualMetadata(content: string, frontmatter: SpecFrontmatter): string {\n const statusEmoji = getStatusEmojiPlain(frontmatter.status);\n const statusLabel = frontmatter.status.charAt(0).toUpperCase() + frontmatter.status.slice(1).replace('-', ' ');\n \n // Parse created date with dayjs - handles all formats consistently\n const created = dayjs(frontmatter.created).format('YYYY-MM-DD');\n \n // Build metadata line\n let metadataLine = `> **Status**: ${statusEmoji} ${statusLabel}`;\n \n if (frontmatter.priority) {\n const priorityLabel = frontmatter.priority.charAt(0).toUpperCase() + frontmatter.priority.slice(1);\n metadataLine += ` · **Priority**: ${priorityLabel}`;\n }\n \n metadataLine += ` · **Created**: ${created}`;\n \n if (frontmatter.tags && frontmatter.tags.length > 0) {\n metadataLine += ` · **Tags**: ${frontmatter.tags.join(', ')}`;\n }\n \n // For enterprise template with assignee/reviewer\n let secondLine = '';\n if (frontmatter.assignee || frontmatter.reviewer) {\n const assignee = frontmatter.assignee || 'TBD';\n const reviewer = frontmatter.reviewer || 'TBD';\n secondLine = `\\n> **Assignee**: ${assignee} · **Reviewer**: ${reviewer}`;\n }\n \n // Replace existing metadata block or add after title\n const metadataPattern = /^>\\s+\\*\\*Status\\*\\*:.*(?:\\n>\\s+\\*\\*Assignee\\*\\*:.*)?/m;\n \n if (metadataPattern.test(content)) {\n // Replace existing metadata\n return content.replace(metadataPattern, metadataLine + secondLine);\n } else {\n // Add after title (# title)\n const titleMatch = content.match(/^#\\s+.+$/m);\n if (titleMatch) {\n const insertPos = titleMatch.index! + titleMatch[0].length;\n return content.slice(0, insertPos) + '\\n\\n' + metadataLine + secondLine + '\\n' + content.slice(insertPos);\n }\n }\n \n return content;\n}\n\nfunction getStatusEmojiPlain(status: string): string {\n switch (status) {\n case 'planned': return '🗓️';\n case 'in-progress': return '⏳';\n case 'complete': return '✅';\n case 'archived': return '📦';\n default: return '📄';\n }\n}\n\n// Get spec file path from spec directory\nexport async function getSpecFile(specDir: string, defaultFile: string = 'README.md'): Promise<string | null> {\n const specFile = path.join(specDir, defaultFile);\n \n try {\n await fs.access(specFile);\n return specFile;\n } catch {\n return null;\n }\n}\n\n// Filter specs by criteria\nexport interface SpecFilterOptions {\n status?: SpecStatus | SpecStatus[];\n tags?: string[];\n priority?: SpecPriority | SpecPriority[];\n assignee?: string;\n customFields?: Record<string, unknown>;\n}\n\nexport function matchesFilter(frontmatter: SpecFrontmatter, filter: SpecFilterOptions): boolean {\n // Status filter\n if (filter.status) {\n const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];\n if (!statuses.includes(frontmatter.status)) {\n return false;\n }\n }\n\n // Tags filter (spec must have ALL specified tags)\n if (filter.tags && filter.tags.length > 0) {\n if (!frontmatter.tags || frontmatter.tags.length === 0) {\n return false;\n }\n const hasAllTags = filter.tags.every(tag => frontmatter.tags!.includes(tag));\n if (!hasAllTags) {\n return false;\n }\n }\n\n // Priority filter\n if (filter.priority) {\n const priorities = Array.isArray(filter.priority) ? filter.priority : [filter.priority];\n if (!frontmatter.priority || !priorities.includes(frontmatter.priority)) {\n return false;\n }\n }\n\n // Assignee filter\n if (filter.assignee) {\n if (frontmatter.assignee !== filter.assignee) {\n return false;\n }\n }\n \n // Custom fields filter\n if (filter.customFields) {\n for (const [key, value] of Object.entries(filter.customFields)) {\n if (frontmatter[key] !== value) {\n return false;\n }\n }\n }\n\n return true;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { loadConfig, loadAllSpecs, loadSubFiles } from './chunk-LXOJW2FE.js';
1
+ import { loadConfig, loadAllSpecs, loadSubFiles } from './chunk-K4VTB6BF.js';
2
2
  import * as fs from 'fs/promises';
3
3
  import * as path from 'path';
4
4
  import chalk3 from 'chalk';
@@ -1303,8 +1303,7 @@ var SpecDependencyGraph = class {
1303
1303
  this.specs.set(spec.path, spec);
1304
1304
  this.graph.set(spec.path, {
1305
1305
  dependsOn: new Set(spec.frontmatter.depends_on || []),
1306
- requiredBy: /* @__PURE__ */ new Set(),
1307
- related: new Set(spec.frontmatter.related || [])
1306
+ requiredBy: /* @__PURE__ */ new Set()
1308
1307
  });
1309
1308
  }
1310
1309
  for (const [specPath, node] of this.graph.entries()) {
@@ -1314,12 +1313,6 @@ var SpecDependencyGraph = class {
1314
1313
  depNode.requiredBy.add(specPath);
1315
1314
  }
1316
1315
  }
1317
- for (const rel of node.related) {
1318
- const relNode = this.graph.get(rel);
1319
- if (relNode) {
1320
- relNode.related.add(specPath);
1321
- }
1322
- }
1323
1316
  }
1324
1317
  }
1325
1318
  /**
@@ -1337,8 +1330,7 @@ var SpecDependencyGraph = class {
1337
1330
  return {
1338
1331
  current: spec,
1339
1332
  dependsOn: this.getSpecsByPaths(Array.from(node.dependsOn)),
1340
- requiredBy: this.getSpecsByPaths(Array.from(node.requiredBy)),
1341
- related: this.getSpecsByPaths(Array.from(node.related))
1333
+ requiredBy: this.getSpecsByPaths(Array.from(node.requiredBy))
1342
1334
  };
1343
1335
  }
1344
1336
  /**
@@ -1401,22 +1393,17 @@ var SpecDependencyGraph = class {
1401
1393
  }
1402
1394
  /**
1403
1395
  * Get impact radius - all specs affected by changes to this spec
1404
- * Includes upstream dependencies, downstream dependents, and related specs
1396
+ * Includes upstream dependencies and downstream dependents
1405
1397
  */
1406
1398
  getImpactRadius(specPath, maxDepth = 3) {
1407
1399
  const spec = this.specs.get(specPath);
1408
1400
  if (!spec) {
1409
1401
  throw new Error(`Spec not found: ${specPath}`);
1410
1402
  }
1411
- const node = this.graph.get(specPath);
1412
- if (!node) {
1413
- throw new Error(`Graph node not found: ${specPath}`);
1414
- }
1415
1403
  return {
1416
1404
  current: spec,
1417
1405
  upstream: this.getUpstream(specPath, maxDepth),
1418
- downstream: this.getDownstream(specPath, maxDepth),
1419
- related: this.getSpecsByPaths(Array.from(node.related))
1406
+ downstream: this.getDownstream(specPath, maxDepth)
1420
1407
  };
1421
1408
  }
1422
1409
  /**
@@ -2538,26 +2525,14 @@ var SubSpecValidator = class {
2538
2525
  }
2539
2526
  }
2540
2527
  };
2541
- var SPEC_REF_PATTERNS = [
2542
- // "spec 045", "Spec 045", "spec-045"
2543
- /\bspec[- ]?(\d{3})\b/gi,
2544
- // "045-unified-dashboard" (full spec folder name)
2545
- /\b(\d{3})-[a-z][a-z0-9-]+\b/gi
2546
- ];
2547
2528
  var DEPENDS_ON_PATTERNS = [
2548
2529
  /depends on[:\s]+.*?\b(\d{3})\b/gi,
2549
2530
  /blocked by[:\s]+.*?\b(\d{3})\b/gi,
2550
2531
  /requires[:\s]+.*?spec[:\s]*(\d{3})\b/gi,
2551
2532
  /prerequisite[:\s]+.*?\b(\d{3})\b/gi,
2552
- /after[:\s]+.*?spec[:\s]*(\d{3})\b/gi
2553
- ];
2554
- var RELATED_PATTERNS = [
2555
- /related to[:\s]+.*?\b(\d{3})\b/gi,
2556
- /see (also )?spec[:\s]*(\d{3})\b/gi,
2533
+ /after[:\s]+.*?spec[:\s]*(\d{3})\b/gi,
2557
2534
  /builds on[:\s]+.*?\b(\d{3})\b/gi,
2558
- /similar to[:\s]+.*?\b(\d{3})\b/gi,
2559
- /complements[:\s]+.*?\b(\d{3})\b/gi,
2560
- /\brelated[:\s]+\[?(\d{3})/gi
2535
+ /extends[:\s]+.*?\b(\d{3})\b/gi
2561
2536
  ];
2562
2537
  var DependencyAlignmentValidator = class {
2563
2538
  name = "dependency-alignment";
@@ -2590,38 +2565,19 @@ var DependencyAlignmentValidator = class {
2590
2565
  const frontmatter = parsed.data;
2591
2566
  const bodyContent = parsed.content;
2592
2567
  const currentDependsOn = this.normalizeDeps(frontmatter.depends_on);
2593
- const currentRelated = this.normalizeDeps(frontmatter.related);
2594
- const allCurrentDeps = /* @__PURE__ */ new Set([...currentDependsOn, ...currentRelated]);
2595
2568
  const selfNumber = this.extractSpecNumber(spec.name);
2596
2569
  const detectedRefs = this.detectReferences(bodyContent, selfNumber);
2597
2570
  const missingDependsOn = [];
2598
- const missingRelated = [];
2599
2571
  for (const ref of detectedRefs) {
2600
- if (allCurrentDeps.has(ref.specNumber)) continue;
2572
+ if (currentDependsOn.includes(ref.specNumber)) continue;
2601
2573
  if (this.existingSpecNumbers && !this.existingSpecNumbers.has(ref.specNumber)) continue;
2602
- if (ref.type === "depends_on") {
2603
- missingDependsOn.push(ref);
2604
- } else {
2605
- missingRelated.push(ref);
2606
- }
2574
+ missingDependsOn.push(ref);
2607
2575
  }
2608
2576
  if (missingDependsOn.length > 0) {
2609
2577
  const specNumbers = [...new Set(missingDependsOn.map((r) => r.specNumber))];
2610
2578
  const issue = {
2611
- message: `Content references blocking dependencies not in frontmatter: ${specNumbers.join(", ")}`,
2612
- suggestion: `Run: lean-spec link ${spec.name} --depends-on ${specNumbers.join(" ")}`
2613
- };
2614
- if (this.strict) {
2615
- errors.push(issue);
2616
- } else {
2617
- warnings.push(issue);
2618
- }
2619
- }
2620
- if (missingRelated.length > 0) {
2621
- const specNumbers = [...new Set(missingRelated.map((r) => r.specNumber))];
2622
- const issue = {
2623
- message: `Content references related specs not in frontmatter: ${specNumbers.join(", ")}`,
2624
- suggestion: `Run: lean-spec link ${spec.name} --related ${specNumbers.join(" ")}`
2579
+ message: `Content references dependencies not in frontmatter: ${specNumbers.join(", ")}`,
2580
+ suggestion: `Run: lean-spec link ${spec.name} --depends-on ${specNumbers.join(",")}`
2625
2581
  };
2626
2582
  if (this.strict) {
2627
2583
  errors.push(issue);
@@ -2655,7 +2611,7 @@ var DependencyAlignmentValidator = class {
2655
2611
  return match ? match[1] : null;
2656
2612
  }
2657
2613
  /**
2658
- * Detect spec references in content
2614
+ * Detect spec references in content that indicate dependencies
2659
2615
  */
2660
2616
  detectReferences(content, selfNumber) {
2661
2617
  const refs = [];
@@ -2674,35 +2630,6 @@ var DependencyAlignmentValidator = class {
2674
2630
  }
2675
2631
  }
2676
2632
  }
2677
- for (const pattern of RELATED_PATTERNS) {
2678
- const matches = content.matchAll(new RegExp(pattern));
2679
- for (const match of matches) {
2680
- const specNumber = match[2] || match[1];
2681
- if (specNumber && specNumber !== selfNumber && !seenNumbers.has(specNumber)) {
2682
- seenNumbers.add(specNumber);
2683
- refs.push({
2684
- specNumber,
2685
- type: "related",
2686
- context: match[0].substring(0, 50)
2687
- });
2688
- }
2689
- }
2690
- }
2691
- for (const pattern of SPEC_REF_PATTERNS) {
2692
- const matches = content.matchAll(new RegExp(pattern));
2693
- for (const match of matches) {
2694
- const specNumber = match[1];
2695
- if (specNumber && specNumber !== selfNumber && !seenNumbers.has(specNumber)) {
2696
- seenNumbers.add(specNumber);
2697
- refs.push({
2698
- specNumber,
2699
- type: "related",
2700
- // Default to related for ambiguous refs
2701
- context: match[0].substring(0, 50)
2702
- });
2703
- }
2704
- }
2705
- }
2706
2633
  return refs;
2707
2634
  }
2708
2635
  };
@@ -3059,5 +2986,5 @@ async function validateSpecs(options = {}) {
3059
2986
  }
3060
2987
 
3061
2988
  export { PRIORITY_CONFIG, STATUS_CONFIG, SpecDependencyGraph, TokenCounter, advancedSearchSpecs, analyzeMarkdownStructure, countLines, countTokens, createUpdatedFrontmatter, extractLines, getPriorityEmoji, getSearchSyntaxHelp, getStatusEmoji, getStatusIndicator, parseFrontmatterFromString, removeLines, sanitizeUserInput, validateCommand, validateSpecs, withSpinner };
3062
- //# sourceMappingURL=chunk-FUIUCGUV.js.map
3063
- //# sourceMappingURL=chunk-FUIUCGUV.js.map
2989
+ //# sourceMappingURL=chunk-FILG4YF3.js.map
2990
+ //# sourceMappingURL=chunk-FILG4YF3.js.map