lean-spec 0.2.7-dev.20251128011647 → 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.
- package/dist/backfill-QIQLXSUG.js +5 -0
- package/dist/backfill-QIQLXSUG.js.map +1 -0
- package/dist/chunk-A6JDTXPV.js +444 -0
- package/dist/chunk-A6JDTXPV.js.map +1 -0
- package/dist/chunk-FUIUCGUV.js +3063 -0
- package/dist/chunk-FUIUCGUV.js.map +1 -0
- package/dist/{chunk-KKBRTXTX.js → chunk-JMVYXJV4.js} +3163 -6547
- package/dist/chunk-JMVYXJV4.js.map +1 -0
- package/dist/chunk-LXOJW2FE.js +298 -0
- package/dist/chunk-LXOJW2FE.js.map +1 -0
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.js +4 -1
- package/dist/validate-PT6GAS57.js +5 -0
- package/dist/validate-PT6GAS57.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-KKBRTXTX.js.map +0 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { getSpecFile, parseFrontmatter, matchesFilter } from './chunk-LVD7ZAVZ.js';
|
|
2
|
+
import * as fs2 from 'fs/promises';
|
|
3
|
+
import * as path2 from 'path';
|
|
4
|
+
|
|
5
|
+
var DEFAULT_CONFIG = {
|
|
6
|
+
template: "spec-template.md",
|
|
7
|
+
templates: {
|
|
8
|
+
default: "spec-template.md"
|
|
9
|
+
},
|
|
10
|
+
specsDir: "specs",
|
|
11
|
+
structure: {
|
|
12
|
+
pattern: "flat",
|
|
13
|
+
// Default to flat for new projects
|
|
14
|
+
prefix: "",
|
|
15
|
+
// No prefix by default - global sequence numbers only
|
|
16
|
+
dateFormat: "YYYYMMDD",
|
|
17
|
+
sequenceDigits: 3,
|
|
18
|
+
defaultFile: "README.md"
|
|
19
|
+
},
|
|
20
|
+
features: {
|
|
21
|
+
aiAgents: true,
|
|
22
|
+
examples: true
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
26
|
+
const configPath = path2.join(cwd, ".lean-spec", "config.json");
|
|
27
|
+
try {
|
|
28
|
+
const content = await fs2.readFile(configPath, "utf-8");
|
|
29
|
+
const userConfig = JSON.parse(content);
|
|
30
|
+
const merged = { ...DEFAULT_CONFIG, ...userConfig };
|
|
31
|
+
normalizeLegacyPattern(merged);
|
|
32
|
+
return merged;
|
|
33
|
+
} catch {
|
|
34
|
+
return DEFAULT_CONFIG;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function saveConfig(config, cwd = process.cwd()) {
|
|
38
|
+
const configDir = path2.join(cwd, ".lean-spec");
|
|
39
|
+
const configPath = path2.join(configDir, "config.json");
|
|
40
|
+
await fs2.mkdir(configDir, { recursive: true });
|
|
41
|
+
await fs2.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
42
|
+
}
|
|
43
|
+
function getToday(format = "YYYYMMDD") {
|
|
44
|
+
const now = /* @__PURE__ */ new Date();
|
|
45
|
+
const year = now.getFullYear();
|
|
46
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
47
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
48
|
+
switch (format) {
|
|
49
|
+
case "YYYYMMDD":
|
|
50
|
+
return `${year}${month}${day}`;
|
|
51
|
+
case "YYYY-MM-DD":
|
|
52
|
+
return `${year}-${month}-${day}`;
|
|
53
|
+
case "YYYY-MM":
|
|
54
|
+
return `${year}-${month}`;
|
|
55
|
+
case "YYYY/MM":
|
|
56
|
+
return `${year}/${month}`;
|
|
57
|
+
case "YYYY":
|
|
58
|
+
return String(year);
|
|
59
|
+
case "MM":
|
|
60
|
+
return month;
|
|
61
|
+
case "DD":
|
|
62
|
+
return day;
|
|
63
|
+
default:
|
|
64
|
+
return `${year}${month}${day}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function normalizeLegacyPattern(config) {
|
|
68
|
+
const pattern = config.structure.pattern;
|
|
69
|
+
if (pattern && pattern.includes("{date}") && pattern.includes("{seq}") && pattern.includes("{name}")) {
|
|
70
|
+
config.structure.pattern = "custom";
|
|
71
|
+
config.structure.groupExtractor = `{${config.structure.dateFormat}}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function resolvePrefix(prefix, dateFormat = "YYYYMMDD") {
|
|
75
|
+
const dateReplacements = {
|
|
76
|
+
"{YYYYMMDD}": () => getToday("YYYYMMDD"),
|
|
77
|
+
"{YYYY-MM-DD}": () => getToday("YYYY-MM-DD"),
|
|
78
|
+
"{YYYY-MM}": () => getToday("YYYY-MM"),
|
|
79
|
+
"{YYYY}": () => getToday("YYYY"),
|
|
80
|
+
"{MM}": () => getToday("MM"),
|
|
81
|
+
"{DD}": () => getToday("DD")
|
|
82
|
+
};
|
|
83
|
+
let result = prefix;
|
|
84
|
+
for (const [pattern, fn] of Object.entries(dateReplacements)) {
|
|
85
|
+
result = result.replace(pattern, fn());
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function extractGroup(extractor, dateFormat = "YYYYMMDD", fields, fallback) {
|
|
90
|
+
const dateReplacements = {
|
|
91
|
+
"{YYYYMMDD}": () => getToday("YYYYMMDD"),
|
|
92
|
+
"{YYYY-MM-DD}": () => getToday("YYYY-MM-DD"),
|
|
93
|
+
"{YYYY-MM}": () => getToday("YYYY-MM"),
|
|
94
|
+
"{YYYY}": () => getToday("YYYY"),
|
|
95
|
+
"{MM}": () => getToday("MM"),
|
|
96
|
+
"{DD}": () => getToday("DD")
|
|
97
|
+
};
|
|
98
|
+
let result = extractor;
|
|
99
|
+
for (const [pattern, fn] of Object.entries(dateReplacements)) {
|
|
100
|
+
result = result.replace(pattern, fn());
|
|
101
|
+
}
|
|
102
|
+
const fieldMatches = result.match(/\{([^}]+)\}/g);
|
|
103
|
+
if (fieldMatches) {
|
|
104
|
+
for (const match of fieldMatches) {
|
|
105
|
+
const fieldName = match.slice(1, -1);
|
|
106
|
+
const fieldValue = fields?.[fieldName];
|
|
107
|
+
if (fieldValue === void 0) {
|
|
108
|
+
if (!fallback) {
|
|
109
|
+
throw new Error(`Custom field '${fieldName}' required but not provided. Set structure.groupFallback in config or provide --field ${fieldName}=<value>`);
|
|
110
|
+
}
|
|
111
|
+
return fallback;
|
|
112
|
+
}
|
|
113
|
+
result = result.replace(match, String(fieldValue));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
async function loadSubFiles(specDir, options = {}) {
|
|
119
|
+
const subFiles = [];
|
|
120
|
+
try {
|
|
121
|
+
const entries = await fs2.readdir(specDir, { withFileTypes: true });
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
if (entry.name === "README.md") continue;
|
|
124
|
+
if (entry.isDirectory()) continue;
|
|
125
|
+
const filePath = path2.join(specDir, entry.name);
|
|
126
|
+
const stat2 = await fs2.stat(filePath);
|
|
127
|
+
const ext = path2.extname(entry.name).toLowerCase();
|
|
128
|
+
const isDocument = ext === ".md";
|
|
129
|
+
const subFile = {
|
|
130
|
+
name: entry.name,
|
|
131
|
+
path: filePath,
|
|
132
|
+
size: stat2.size,
|
|
133
|
+
type: isDocument ? "document" : "asset"
|
|
134
|
+
};
|
|
135
|
+
if (isDocument && options.includeContent) {
|
|
136
|
+
subFile.content = await fs2.readFile(filePath, "utf-8");
|
|
137
|
+
}
|
|
138
|
+
subFiles.push(subFile);
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
return subFiles.sort((a, b) => {
|
|
144
|
+
if (a.type !== b.type) {
|
|
145
|
+
return a.type === "document" ? -1 : 1;
|
|
146
|
+
}
|
|
147
|
+
return a.name.localeCompare(b.name);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function loadAllSpecs(options = {}) {
|
|
151
|
+
const config = await loadConfig();
|
|
152
|
+
const cwd = process.cwd();
|
|
153
|
+
const specsDir = path2.join(cwd, config.specsDir);
|
|
154
|
+
const specs = [];
|
|
155
|
+
try {
|
|
156
|
+
await fs2.access(specsDir);
|
|
157
|
+
} catch {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
const specPattern = /^(\d{2,})-/;
|
|
161
|
+
async function loadSpecsFromDir(dir, relativePath = "") {
|
|
162
|
+
try {
|
|
163
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
if (!entry.isDirectory()) continue;
|
|
166
|
+
if (entry.name === "archived" && relativePath === "") continue;
|
|
167
|
+
const entryPath = path2.join(dir, entry.name);
|
|
168
|
+
const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
169
|
+
if (specPattern.test(entry.name)) {
|
|
170
|
+
const specFile = await getSpecFile(entryPath, config.structure.defaultFile);
|
|
171
|
+
if (specFile) {
|
|
172
|
+
const frontmatter = await parseFrontmatter(specFile, config);
|
|
173
|
+
if (frontmatter) {
|
|
174
|
+
if (options.filter && !matchesFilter(frontmatter, options.filter)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const dateMatch = entryRelativePath.match(/(\d{8})/);
|
|
178
|
+
let date;
|
|
179
|
+
if (dateMatch) {
|
|
180
|
+
date = dateMatch[1];
|
|
181
|
+
} else if (typeof frontmatter.created === "string") {
|
|
182
|
+
date = frontmatter.created;
|
|
183
|
+
} else if (frontmatter.created) {
|
|
184
|
+
date = String(frontmatter.created);
|
|
185
|
+
} else {
|
|
186
|
+
date = "";
|
|
187
|
+
}
|
|
188
|
+
const specInfo = {
|
|
189
|
+
path: entryRelativePath,
|
|
190
|
+
fullPath: entryPath,
|
|
191
|
+
filePath: specFile,
|
|
192
|
+
name: entry.name,
|
|
193
|
+
date,
|
|
194
|
+
frontmatter
|
|
195
|
+
};
|
|
196
|
+
if (options.includeContent) {
|
|
197
|
+
specInfo.content = await fs2.readFile(specFile, "utf-8");
|
|
198
|
+
}
|
|
199
|
+
if (options.includeSubFiles) {
|
|
200
|
+
specInfo.subFiles = await loadSubFiles(entryPath, {
|
|
201
|
+
includeContent: options.includeContent
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
specs.push(specInfo);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
await loadSpecsFromDir(entryPath, entryRelativePath);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
await loadSpecsFromDir(specsDir);
|
|
215
|
+
if (options.includeArchived) {
|
|
216
|
+
const archivedPath = path2.join(specsDir, "archived");
|
|
217
|
+
await loadSpecsFromDir(archivedPath, "archived");
|
|
218
|
+
}
|
|
219
|
+
const sortBy = options.sortBy || "id";
|
|
220
|
+
const sortOrder = options.sortOrder || "desc";
|
|
221
|
+
specs.sort((a, b) => {
|
|
222
|
+
let comparison = 0;
|
|
223
|
+
switch (sortBy) {
|
|
224
|
+
case "id":
|
|
225
|
+
case "number": {
|
|
226
|
+
const aNum = parseInt(a.name.match(/^(\d+)/)?.[1] || "0", 10);
|
|
227
|
+
const bNum = parseInt(b.name.match(/^(\d+)/)?.[1] || "0", 10);
|
|
228
|
+
comparison = aNum - bNum;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case "created": {
|
|
232
|
+
const aDate2 = String(a.frontmatter.created || "");
|
|
233
|
+
const bDate2 = String(b.frontmatter.created || "");
|
|
234
|
+
comparison = aDate2.localeCompare(bDate2);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case "name": {
|
|
238
|
+
comparison = a.name.localeCompare(b.name);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case "status": {
|
|
242
|
+
comparison = a.frontmatter.status.localeCompare(b.frontmatter.status);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case "priority": {
|
|
246
|
+
const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
247
|
+
const aPriority = a.frontmatter.priority ? priorityOrder[a.frontmatter.priority] : 0;
|
|
248
|
+
const bPriority = b.frontmatter.priority ? priorityOrder[b.frontmatter.priority] : 0;
|
|
249
|
+
comparison = aPriority - bPriority;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
default:
|
|
253
|
+
const aDate = String(a.frontmatter.created || "");
|
|
254
|
+
const bDate = String(b.frontmatter.created || "");
|
|
255
|
+
comparison = aDate.localeCompare(bDate);
|
|
256
|
+
}
|
|
257
|
+
return sortOrder === "desc" ? -comparison : comparison;
|
|
258
|
+
});
|
|
259
|
+
return specs;
|
|
260
|
+
}
|
|
261
|
+
async function getSpec(specPath) {
|
|
262
|
+
const config = await loadConfig();
|
|
263
|
+
const cwd = process.cwd();
|
|
264
|
+
const specsDir = path2.join(cwd, config.specsDir);
|
|
265
|
+
let fullPath;
|
|
266
|
+
if (path2.isAbsolute(specPath)) {
|
|
267
|
+
fullPath = specPath;
|
|
268
|
+
} else {
|
|
269
|
+
fullPath = path2.join(specsDir, specPath);
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
await fs2.access(fullPath);
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
const specFile = await getSpecFile(fullPath, config.structure.defaultFile);
|
|
277
|
+
if (!specFile) return null;
|
|
278
|
+
const frontmatter = await parseFrontmatter(specFile, config);
|
|
279
|
+
if (!frontmatter) return null;
|
|
280
|
+
const content = await fs2.readFile(specFile, "utf-8");
|
|
281
|
+
const relativePath = path2.relative(specsDir, fullPath);
|
|
282
|
+
const parts = relativePath.split(path2.sep);
|
|
283
|
+
const date = parts[0] === "archived" ? parts[1] : parts[0];
|
|
284
|
+
const name = parts[parts.length - 1];
|
|
285
|
+
return {
|
|
286
|
+
path: relativePath,
|
|
287
|
+
fullPath,
|
|
288
|
+
filePath: specFile,
|
|
289
|
+
name,
|
|
290
|
+
date,
|
|
291
|
+
frontmatter,
|
|
292
|
+
content
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export { extractGroup, getSpec, loadAllSpecs, loadConfig, loadSubFiles, resolvePrefix, saveConfig };
|
|
297
|
+
//# sourceMappingURL=chunk-LXOJW2FE.js.map
|
|
298
|
+
//# sourceMappingURL=chunk-LXOJW2FE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/spec-loader.ts"],"names":["path","fs","stat","aDate","bDate"],"mappings":"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","file":"chunk-LXOJW2FE.js","sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\nexport interface LeanSpecConfig {\n template: string;\n templates?: Record<string, string>; // Maps template name to filename\n specsDir: string;\n autoCheck?: boolean; // Enable/disable auto-check for sequence conflicts (default: true)\n structure: {\n pattern: 'flat' | 'custom' | string; // 'flat' or 'custom', or legacy pattern string\n dateFormat: string;\n sequenceDigits: number;\n defaultFile: string;\n prefix?: string; // For flat pattern: \"{YYYYMMDD}-\" or \"spec-\" (optional, default: empty for global numbering)\n groupExtractor?: string; // For custom pattern: \"{YYYYMMDD}\" or \"milestone-{milestone}\"\n groupFallback?: string; // Fallback folder if field missing (only for non-date extractors)\n };\n features?: {\n aiAgents?: boolean;\n examples?: boolean;\n collaboration?: boolean;\n compliance?: boolean;\n approvals?: boolean;\n apiDocs?: boolean;\n };\n frontmatter?: {\n required?: string[];\n optional?: string[];\n custom?: Record<string, 'string' | 'number' | 'boolean' | 'array'>;\n };\n variables?: Record<string, string>;\n}\n\nconst DEFAULT_CONFIG: LeanSpecConfig = {\n template: 'spec-template.md',\n templates: {\n default: 'spec-template.md',\n },\n specsDir: 'specs',\n structure: {\n pattern: 'flat', // Default to flat for new projects\n prefix: '', // No prefix by default - global sequence numbers only\n dateFormat: 'YYYYMMDD',\n sequenceDigits: 3,\n defaultFile: 'README.md',\n },\n features: {\n aiAgents: true,\n examples: true,\n },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<LeanSpecConfig> {\n const configPath = path.join(cwd, '.lean-spec', 'config.json');\n\n try {\n const content = await fs.readFile(configPath, 'utf-8');\n const userConfig = JSON.parse(content);\n const merged = { ...DEFAULT_CONFIG, ...userConfig };\n \n // Normalize legacy pattern format\n normalizeLegacyPattern(merged);\n \n return merged;\n } catch {\n // No config file, use defaults\n return DEFAULT_CONFIG;\n }\n}\n\nexport async function saveConfig(\n config: LeanSpecConfig,\n cwd: string = process.cwd(),\n): Promise<void> {\n const configDir = path.join(cwd, '.lean-spec');\n const configPath = path.join(configDir, 'config.json');\n\n await fs.mkdir(configDir, { recursive: true });\n await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');\n}\n\nexport function getToday(format: string = 'YYYYMMDD'): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n\n switch (format) {\n case 'YYYYMMDD':\n return `${year}${month}${day}`;\n case 'YYYY-MM-DD':\n return `${year}-${month}-${day}`;\n case 'YYYY-MM':\n return `${year}-${month}`;\n case 'YYYY/MM':\n return `${year}/${month}`;\n case 'YYYY':\n return String(year);\n case 'MM':\n return month;\n case 'DD':\n return day;\n default:\n return `${year}${month}${day}`;\n }\n}\n\n/**\n * Detect if a config uses legacy pattern format and convert it\n */\nexport function normalizeLegacyPattern(config: LeanSpecConfig): void {\n const pattern = config.structure.pattern;\n \n // If pattern contains {date}/{seq}-{name}/, convert to custom with date grouping\n if (pattern && pattern.includes('{date}') && pattern.includes('{seq}') && pattern.includes('{name}')) {\n config.structure.pattern = 'custom';\n config.structure.groupExtractor = `{${config.structure.dateFormat}}`;\n }\n}\n\n/**\n * Resolve prefix string for flat pattern (e.g., \"{YYYYMMDD}-\" becomes \"20251103-\")\n */\nexport function resolvePrefix(\n prefix: string,\n dateFormat: string = 'YYYYMMDD'\n): string {\n const dateReplacements: Record<string, () => string> = {\n '{YYYYMMDD}': () => getToday('YYYYMMDD'),\n '{YYYY-MM-DD}': () => getToday('YYYY-MM-DD'),\n '{YYYY-MM}': () => getToday('YYYY-MM'),\n '{YYYY}': () => getToday('YYYY'),\n '{MM}': () => getToday('MM'),\n '{DD}': () => getToday('DD'),\n };\n\n let result = prefix;\n for (const [pattern, fn] of Object.entries(dateReplacements)) {\n result = result.replace(pattern, fn());\n }\n\n return result;\n}\n\n/**\n * Extract group folder from extractor pattern\n */\nexport function extractGroup(\n extractor: string,\n dateFormat: string = 'YYYYMMDD',\n fields?: Record<string, unknown>,\n fallback?: string\n): string {\n const dateReplacements: Record<string, () => string> = {\n '{YYYYMMDD}': () => getToday('YYYYMMDD'),\n '{YYYY-MM-DD}': () => getToday('YYYY-MM-DD'),\n '{YYYY-MM}': () => getToday('YYYY-MM'),\n '{YYYY}': () => getToday('YYYY'),\n '{MM}': () => getToday('MM'),\n '{DD}': () => getToday('DD'),\n };\n\n let result = extractor;\n\n // Replace date functions first\n for (const [pattern, fn] of Object.entries(dateReplacements)) {\n result = result.replace(pattern, fn());\n }\n\n // Replace frontmatter fields: {fieldname}\n const fieldMatches = result.match(/\\{([^}]+)\\}/g);\n if (fieldMatches) {\n for (const match of fieldMatches) {\n const fieldName = match.slice(1, -1); // Remove { }\n const fieldValue = fields?.[fieldName];\n\n if (fieldValue === undefined) {\n if (!fallback) {\n throw new Error(`Custom field '${fieldName}' required but not provided. Set structure.groupFallback in config or provide --field ${fieldName}=<value>`);\n }\n return fallback;\n }\n\n result = result.replace(match, String(fieldValue));\n }\n }\n\n return result;\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { loadConfig } from './config.js';\nimport { parseFrontmatter, getSpecFile, matchesFilter, type SpecFrontmatter, type SpecFilterOptions } from './frontmatter.js';\n\nexport interface SpecInfo {\n path: string; // Relative path like \"20251101/003-pm-visualization-tools\"\n fullPath: string; // Absolute path to spec directory\n filePath: string; // Absolute path to spec file (README.md)\n name: string; // Just the spec name like \"003-pm-visualization-tools\"\n date?: string; // Date folder like \"20251101\" (optional for flat/nested patterns)\n frontmatter: SpecFrontmatter;\n content?: string; // Full file content (optional, for search)\n subFiles?: SubFileInfo[]; // Sub-documents and assets\n}\n\nexport interface SubFileInfo {\n name: string; // e.g., \"TESTING.md\" or \"diagram.png\"\n path: string; // Absolute path to the file\n size: number; // File size in bytes\n type: 'document' | 'asset'; // Classification based on file type\n content?: string; // Optional content for documents\n}\n\n// Load sub-files for a spec (all files except README.md)\nexport async function loadSubFiles(\n specDir: string,\n options: { includeContent?: boolean } = {}\n): Promise<SubFileInfo[]> {\n const subFiles: SubFileInfo[] = [];\n\n try {\n const entries = await fs.readdir(specDir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip README.md (main spec file)\n if (entry.name === 'README.md') continue;\n\n // Skip directories for now (could be assets folder)\n if (entry.isDirectory()) continue;\n\n const filePath = path.join(specDir, entry.name);\n const stat = await fs.stat(filePath);\n\n // Determine type based on extension\n const ext = path.extname(entry.name).toLowerCase();\n const isDocument = ext === '.md';\n\n const subFile: SubFileInfo = {\n name: entry.name,\n path: filePath,\n size: stat.size,\n type: isDocument ? 'document' : 'asset',\n };\n\n // Load content for documents if requested\n if (isDocument && options.includeContent) {\n subFile.content = await fs.readFile(filePath, 'utf-8');\n }\n\n subFiles.push(subFile);\n }\n } catch (error) {\n // Directory doesn't exist or can't be read - return empty array\n // This is expected for specs without sub-files\n return [];\n }\n\n // Sort: documents first, then alphabetically\n return subFiles.sort((a, b) => {\n if (a.type !== b.type) {\n return a.type === 'document' ? -1 : 1;\n }\n return a.name.localeCompare(b.name);\n });\n}\n\n// Load all specs from the specs directory\nexport async function loadAllSpecs(options: {\n includeArchived?: boolean;\n includeContent?: boolean;\n includeSubFiles?: boolean;\n filter?: SpecFilterOptions;\n sortBy?: string;\n sortOrder?: 'asc' | 'desc';\n} = {}): Promise<SpecInfo[]> {\n const config = await loadConfig();\n const cwd = process.cwd();\n const specsDir = path.join(cwd, config.specsDir);\n\n const specs: SpecInfo[] = [];\n\n // Check if specs directory exists\n try {\n await fs.access(specsDir);\n } catch {\n return [];\n }\n\n // Pattern to match spec directories (2 or more digits followed by dash)\n const specPattern = /^(\\d{2,})-/;\n\n // Recursively load all specs from the directory structure\n async function loadSpecsFromDir(dir: string, relativePath: 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 // Skip archived directory in main scan (handle separately)\n if (entry.name === 'archived' && relativePath === '') continue;\n \n const entryPath = path.join(dir, entry.name);\n const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;\n \n // Check if this is a spec directory (NNN-name format)\n if (specPattern.test(entry.name)) {\n const specFile = await getSpecFile(entryPath, config.structure.defaultFile);\n \n if (specFile) {\n const frontmatter = await parseFrontmatter(specFile, config);\n \n if (frontmatter) {\n // Apply filter if provided\n if (options.filter && !matchesFilter(frontmatter, options.filter)) {\n continue;\n }\n \n // Extract date from path or frontmatter\n const dateMatch = entryRelativePath.match(/(\\d{8})/);\n let date: string;\n \n if (dateMatch) {\n date = dateMatch[1];\n } else if (typeof frontmatter.created === 'string') {\n date = frontmatter.created;\n } else if (frontmatter.created) {\n date = String(frontmatter.created);\n } else {\n date = '';\n }\n \n const specInfo: SpecInfo = {\n path: entryRelativePath,\n fullPath: entryPath,\n filePath: specFile,\n name: entry.name,\n date: date,\n frontmatter,\n };\n \n // Load content if requested\n if (options.includeContent) {\n specInfo.content = await fs.readFile(specFile, 'utf-8');\n }\n \n // Load sub-files if requested\n if (options.includeSubFiles) {\n specInfo.subFiles = await loadSubFiles(entryPath, {\n includeContent: options.includeContent,\n });\n }\n \n specs.push(specInfo);\n }\n }\n } else {\n // Not a spec directory, scan recursively for nested structure\n await loadSpecsFromDir(entryPath, entryRelativePath);\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n }\n \n // Load active specs\n await loadSpecsFromDir(specsDir);\n\n // Load archived specs if requested\n if (options.includeArchived) {\n const archivedPath = path.join(specsDir, 'archived');\n await loadSpecsFromDir(archivedPath, 'archived');\n }\n\n // Sort specs based on options (default: id desc)\n const sortBy = options.sortBy || 'id';\n const sortOrder = options.sortOrder || 'desc';\n \n specs.sort((a, b) => {\n let comparison = 0;\n \n switch (sortBy) {\n case 'id':\n case 'number': { // Keep 'number' for backwards compatibility\n // Extract leading digits from spec name\n const aNum = parseInt(a.name.match(/^(\\d+)/)?.[1] || '0', 10);\n const bNum = parseInt(b.name.match(/^(\\d+)/)?.[1] || '0', 10);\n comparison = aNum - bNum;\n break;\n }\n case 'created': {\n // Sort by created date from frontmatter\n const aDate = String(a.frontmatter.created || '');\n const bDate = String(b.frontmatter.created || '');\n comparison = aDate.localeCompare(bDate);\n break;\n }\n case 'name': {\n comparison = a.name.localeCompare(b.name);\n break;\n }\n case 'status': {\n comparison = a.frontmatter.status.localeCompare(b.frontmatter.status);\n break;\n }\n case 'priority': {\n // Priority order: critical > high > medium > low > (none)\n const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };\n const aPriority = a.frontmatter.priority ? priorityOrder[a.frontmatter.priority] : 0;\n const bPriority = b.frontmatter.priority ? priorityOrder[b.frontmatter.priority] : 0;\n comparison = aPriority - bPriority;\n break;\n }\n default:\n // Default to created date\n const aDate = String(a.frontmatter.created || '');\n const bDate = String(b.frontmatter.created || '');\n comparison = aDate.localeCompare(bDate);\n }\n \n // Apply sort order\n return sortOrder === 'desc' ? -comparison : comparison;\n });\n\n return specs;\n}\n\n// Get a specific spec by path\nexport async function getSpec(specPath: string): Promise<SpecInfo | null> {\n const config = await loadConfig();\n const cwd = process.cwd();\n const specsDir = path.join(cwd, config.specsDir);\n\n // Resolve the full path\n let fullPath: string;\n if (path.isAbsolute(specPath)) {\n fullPath = specPath;\n } else {\n fullPath = path.join(specsDir, specPath);\n }\n\n // Check if directory exists\n try {\n await fs.access(fullPath);\n } catch {\n return null;\n }\n\n const specFile = await getSpecFile(fullPath, config.structure.defaultFile);\n if (!specFile) return null;\n\n const frontmatter = await parseFrontmatter(specFile, config);\n if (!frontmatter) return null;\n\n const content = await fs.readFile(specFile, 'utf-8');\n\n // Parse path components\n const relativePath = path.relative(specsDir, fullPath);\n const parts = relativePath.split(path.sep);\n const date = parts[0] === 'archived' ? parts[1] : parts[0];\n const name = parts[parts.length - 1];\n\n return {\n path: relativePath,\n fullPath,\n filePath: specFile,\n name,\n date,\n frontmatter,\n content,\n };\n}\n"]}
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { agentCommand, analyzeCommand, archiveCommand,
|
|
1
|
+
import { agentCommand, analyzeCommand, archiveCommand, boardCommand, checkCommand, compactCommand, createCommand, depsCommand, examplesCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, viewCommand } from './chunk-JMVYXJV4.js';
|
|
2
|
+
import { backfillCommand } from './chunk-A6JDTXPV.js';
|
|
3
|
+
import { validateCommand } from './chunk-FUIUCGUV.js';
|
|
4
|
+
import './chunk-LXOJW2FE.js';
|
|
2
5
|
import './chunk-LVD7ZAVZ.js';
|
|
3
6
|
import { Command } from 'commander';
|
|
4
7
|
import { readFileSync } from 'fs';
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/registry.ts","../src/cli.ts"],"names":["program"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/registry.ts","../src/cli.ts"],"names":["program"],"mappings":"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","file":"cli.js","sourcesContent":["import { Command } from 'commander';\nimport {\n agentCommand,\n analyzeCommand,\n archiveCommand,\n backfillCommand,\n boardCommand,\n checkCommand,\n compactCommand,\n createCommand,\n depsCommand,\n examplesCommand,\n filesCommand,\n ganttCommand,\n initCommand,\n linkCommand,\n listCommand,\n mcpCommand,\n migrateCommand,\n openCommand,\n searchCommand,\n splitCommand,\n statsCommand,\n templatesCommand,\n timelineCommand,\n tokensCommand,\n uiCommand,\n unlinkCommand,\n updateCommand,\n validateCommand,\n viewCommand,\n} from './index.js';\n\n/**\n * Register all commands in alphabetical order\n */\nexport function registerCommands(program: Command): void {\n // Alphabetically sorted command registration\n program.addCommand(agentCommand());\n program.addCommand(analyzeCommand());\n program.addCommand(archiveCommand());\n program.addCommand(backfillCommand());\n program.addCommand(boardCommand());\n program.addCommand(checkCommand());\n program.addCommand(compactCommand());\n program.addCommand(createCommand());\n program.addCommand(depsCommand());\n program.addCommand(examplesCommand());\n program.addCommand(filesCommand());\n program.addCommand(ganttCommand());\n program.addCommand(initCommand());\n program.addCommand(linkCommand());\n program.addCommand(listCommand());\n program.addCommand(mcpCommand());\n program.addCommand(migrateCommand());\n program.addCommand(openCommand());\n program.addCommand(searchCommand());\n program.addCommand(splitCommand());\n program.addCommand(statsCommand());\n program.addCommand(templatesCommand());\n program.addCommand(timelineCommand());\n program.addCommand(tokensCommand());\n program.addCommand(uiCommand());\n program.addCommand(unlinkCommand());\n program.addCommand(updateCommand());\n program.addCommand(validateCommand());\n program.addCommand(viewCommand());\n}\n","import { Command } from 'commander';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { registerCommands } from './commands/registry.js';\n\n// Get version from package.json\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageJson = JSON.parse(\n readFileSync(join(__dirname, '../package.json'), 'utf-8')\n);\n\nconst program = new Command();\n\nprogram\n .name('lean-spec')\n .description('Manage LeanSpec documents')\n .version(packageJson.version);\n\n// Add custom help text with grouped commands\nprogram.addHelpText('after', `\nCommand Groups:\n\n Core Workflow:\n archive <spec> Move spec to archived/\n backfill [specs...] Backfill timestamps from git history\n create <name> Create new spec\n examples List example projects for tutorials\n init Initialize LeanSpec in current directory\n link <spec> Add relationships between specs\n migrate <input-path> Migrate specs from other SDD tools\n unlink <spec> Remove relationships between specs\n update <spec> Update spec metadata\n \n Discovery & Search:\n files <spec> List files in a spec\n list List all specs\n open <spec> Open spec in editor\n search <query> Full-text search with metadata filters\n view <spec> View spec content\n \n Project Analytics:\n board Show Kanban-style board view\n deps <spec> Show dependency graph for a spec\n gantt Show timeline with dependencies\n stats Show aggregate statistics\n timeline Show creation/completion over time\n \n Quality & Optimization:\n analyze <spec> Analyze spec complexity and structure\n check Check for sequence conflicts\n tokens [spec] Count tokens for LLM context management\n validate [specs...] Validate specs for quality issues\n \n Advanced Editing:\n compact <spec> Remove specified line ranges from spec\n split <spec> Split spec into multiple files\n \n Configuration:\n templates Manage spec templates\n \n Integration:\n agent Dispatch specs to AI coding agents\n mcp Start MCP server for AI assistants\n ui Start local web UI for spec management\n\nExamples:\n $ lean-spec init\n $ lean-spec init -y\n $ lean-spec init --example dark-theme\n $ lean-spec init --example dashboard-widgets --name my-demo\n $ lean-spec examples\n $ lean-spec create my-feature --priority high\n $ lean-spec list --status in-progress\n $ lean-spec view 042\n $ lean-spec link 085 --depends-on 042,035\n $ lean-spec link 085 --related 082\n $ lean-spec unlink 085 --depends-on 042\n $ lean-spec deps 085\n $ lean-spec backfill --dry-run\n $ lean-spec migrate ./docs/adr\n $ lean-spec migrate ./docs/rfcs --with copilot\n $ lean-spec board --tag backend\n $ lean-spec search \"authentication\"\n $ lean-spec validate\n $ lean-spec tokens 059\n $ lean-spec analyze 045 --json\n $ lean-spec split 045 --output README.md:1-150 --output DESIGN.md:151-end\n $ lean-spec agent list\n $ lean-spec agent run 045 --agent claude\n $ lean-spec agent run 045 047 048 --parallel\n $ lean-spec ui\n $ lean-spec ui --port 3001 --no-open\n $ lean-spec ui --specs ./docs/specs --dry-run\n`);\n\n// Register all commands (alphabetically ordered)\nregisterCommands(program);\n\n// Parse and execute\nprogram.parse();\n"]}
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { createMcpServer } from './chunk-
|
|
2
|
+
export { createMcpServer } from './chunk-JMVYXJV4.js';
|
|
3
|
+
import './chunk-A6JDTXPV.js';
|
|
4
|
+
import './chunk-FUIUCGUV.js';
|
|
5
|
+
import './chunk-LXOJW2FE.js';
|
|
3
6
|
import './chunk-LVD7ZAVZ.js';
|
|
4
7
|
//# sourceMappingURL=mcp-server.js.map
|
|
5
8
|
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"validate-PT6GAS57.js"}
|