@majeanson/lac 0.1.0 → 1.0.1

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,125 @@
1
+ # @majeanson/lac
2
+
3
+ **Feature provenance, from the terminal.**
4
+
5
+ `lac` gives every feature in your codebase a small, versioned document — `feature.json` — that captures *why* it exists, what decisions shaped it, and where it came from. Structured. Queryable. Committed to git.
6
+
7
+ ```bash
8
+ npm install -g @majeanson/lac
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Thirty-second tour
14
+
15
+ ```bash
16
+ # init a workspace at your repo root
17
+ lac workspace init
18
+
19
+ # scaffold a new feature
20
+ cd src/payments/checkout
21
+ lac init
22
+ # ✓ Created feature.json — feat-2026-007
23
+
24
+ # who owns this file?
25
+ lac blame src/payments/checkout/handler.ts
26
+
27
+ # Feature : feat-2026-007
28
+ # Title : Checkout flow redesign
29
+ # Status : ⊙ active
30
+ # Complete : [████████░░] 80%
31
+ # Problem:
32
+ # Cart abandonment spikes at the shipping step.
33
+
34
+ # search across everything
35
+ lac search "cart abandonment"
36
+
37
+ # open a live dashboard
38
+ lac serve
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Commands
44
+
45
+ | Command | What it does |
46
+ |---|---|
47
+ | `lac init` | Scaffold a `feature.json` interactively |
48
+ | `lac blame <path>` | Show which feature owns a file or path |
49
+ | `lac search <query>` | Full-text search across all features |
50
+ | `lac lineage <key>` | Print the parent → key → children tree |
51
+ | `lac stat` | Summary table of all features + completeness |
52
+ | `lac diff` | Features changed since a git ref |
53
+ | `lac lint` | Validate all `feature.json` files |
54
+ | `lac serve` | Start the HTTP dashboard in your browser |
55
+ | `lac spawn <key>` | Create a child feature with lineage wired |
56
+ | `lac archive <key>` | Deprecate a feature |
57
+ | `lac doctor` | Workspace health check |
58
+
59
+ Full documentation: [github.com/majeanson/lac-cli](https://github.com/majeanson/lac-cli)
60
+
61
+ ---
62
+
63
+ ## The feature.json
64
+
65
+ ```json
66
+ {
67
+ "featureKey": "feat-2026-007",
68
+ "title": "Checkout flow redesign",
69
+ "status": "active",
70
+ "problem": "Cart abandonment spikes at the shipping step.",
71
+ "decisions": [
72
+ {
73
+ "decision": "Single-page checkout, no step redirects",
74
+ "rationale": "Reduces load time and perceived friction.",
75
+ "date": "2026-02-14"
76
+ }
77
+ ],
78
+ "lineage": { "parent": "feat-2025-031" },
79
+ "tags": ["payments", "ux", "conversion"]
80
+ }
81
+ ```
82
+
83
+ Plain JSON. Committed with your code. Validated by a Zod schema. Indexed by the CLI and optionally the LSP server.
84
+
85
+ Feature keys follow the pattern `<domain>-YYYY-NNN`. The domain is yours: `feat-`, `proc-`, `goal-`, `adr-`.
86
+
87
+ ---
88
+
89
+ ## Why not tickets / ADRs / commit messages?
90
+
91
+ **Tickets** (Jira, Linear, GitHub Issues) live outside your repo. They get closed, archived, migrated. The context evaporates. `feature.json` is in git forever.
92
+
93
+ **ADRs** document architecture, not features. `lac` is complementary — it works at the folder level, with a typed lifecycle, lineage, and tooling.
94
+
95
+ **Commit messages** describe what changed, not why a thing exists. They're not queryable, not structured, and don't survive a `git squash`.
96
+
97
+ ---
98
+
99
+ ## Build on top of it
100
+
101
+ `feature.json` is plain JSON. The `lac serve` HTTP API is standard REST. Both are designed to be consumed by custom tooling.
102
+
103
+ **[lifeascode-ruddy.vercel.app](https://lifeascode-ruddy.vercel.app/)** is a companion web UI that talks to `lac serve` in real time — fork it and deploy it for your team.
104
+
105
+ **HTTP API** (start with `lac serve`, port 7474):
106
+ ```
107
+ GET /features all features as JSON
108
+ GET /features/:key single feature
109
+ PUT /features/:key write back to disk
110
+ GET /blame?path=<abs> which feature owns a file
111
+ GET /lint validation results
112
+ GET /events SSE stream of changes
113
+ ```
114
+
115
+ **Sync to a database:**
116
+ ```bash
117
+ lac export --json | curl -X POST https://your-api/features \
118
+ -H "Content-Type: application/json" -d @-
119
+ ```
120
+
121
+ This package ships with `lac` (CLI) and `lac-lsp` (LSP server + HTTP API) bundled together — one install, two binaries.
122
+
123
+ ---
124
+
125
+ MIT License
package/bin/lac-lsp.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/lsp.mjs'
package/dist/index.mjs CHANGED
@@ -2125,7 +2125,7 @@ const serveCommand = new Command("serve").description("Start the life-as-code HT
2125
2125
  let shuttingDown = false;
2126
2126
  child.on("error", (err) => {
2127
2127
  process$1.stderr.write(`Error: could not start lac-lsp — ${err.message}\n`);
2128
- process$1.stderr.write(`Make sure lac-lsp is installed: npm i -g @life-as-code/lac-lsp\n`);
2128
+ process$1.stderr.write(`Try reinstalling: npm i -g @majeanson/lac\n`);
2129
2129
  process$1.exit(1);
2130
2130
  });
2131
2131
  child.on("exit", (code) => {
@@ -2440,7 +2440,7 @@ workspaceCommand.command("status").description("Show workspace info (location, c
2440
2440
  //#endregion
2441
2441
  //#region src/index.ts
2442
2442
  const program = new Command();
2443
- program.name("lac").description("life-as-code CLI — provenance for your features").version("0.1.0");
2443
+ program.name("lac").description("life-as-code CLI — provenance for your features").version("1.0.0");
2444
2444
  program.addCommand(workspaceCommand);
2445
2445
  program.addCommand(spawnCommand);
2446
2446
  program.addCommand(initCommand);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["process","findNearestFeatureJson","process","findNearestFeatureJson","process","findLacDir","process","escapeHtml","escapeHtml","statusBadge","process","process","titleFromProblem","process","process","process","process","process","process","process","LAC_DIR","process","process","process","process"],"sources":["../src/lib/scanner.ts","../src/commands/archive.ts","../src/lib/walker.ts","../src/lib/config.ts","../src/commands/blame.ts","../src/commands/diff.ts","../src/commands/doctor.ts","../src/templates/markdown.ts","../src/templates/site-style.css.ts","../src/templates/site-feature.html.ts","../src/templates/site-index.html.ts","../src/lib/siteGenerator.ts","../src/commands/export.ts","../src/commands/hooks.ts","../src/lib/featureKey.ts","../src/commands/init.ts","../src/commands/lineage.ts","../src/commands/lint.ts","../src/commands/import.ts","../src/commands/rename.ts","../src/commands/search.ts","../src/commands/serve.ts","../src/commands/spawn.ts","../src/commands/stat.ts","../src/commands/tag.ts","../src/commands/workspace.ts","../src/index.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport type { Feature } from '@life-as-code/feature-schema'\nimport { validateFeature } from '@life-as-code/feature-schema'\n\nexport interface ScannedFeature {\n filePath: string\n feature: Feature\n}\n\n/**\n * Recursively finds all feature.json files under a directory.\n * Returns an array of { filePath, feature } for each valid feature.json found.\n * Files that fail validation are skipped with a warning printed to stderr.\n */\nexport async function scanFeatures(dir: string): Promise<ScannedFeature[]> {\n const results: ScannedFeature[] = []\n\n async function walk(currentDir: string): Promise<void> {\n let entries: Array<{ name: string; isDirectory: () => boolean; isFile: () => boolean }>\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let rawEntries: any[]\n try {\n rawEntries = await readdir(currentDir, { withFileTypes: true })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Warning: could not read directory \"${currentDir}\": ${message}\\n`)\n return\n }\n\n // Normalise to plain objects to avoid @types/node Dirent<Buffer> vs\n // Dirent<string> variance across different @types/node versions.\n entries = rawEntries.map((e) => ({\n name: String(e.name),\n isDirectory: () => e.isDirectory(),\n isFile: () => e.isFile(),\n }))\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name)\n\n if (entry.isDirectory()) {\n // Skip hidden directories and node_modules\n if (entry.name.startsWith('.') || entry.name === 'node_modules') {\n continue\n }\n await walk(fullPath)\n } else if (entry.isFile() && entry.name === 'feature.json') {\n let raw: string\n try {\n raw = await readFile(fullPath, 'utf-8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Warning: could not read \"${fullPath}\": ${message}\\n`)\n continue\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n process.stderr.write(`Warning: invalid JSON in \"${fullPath}\" — skipping\\n`)\n continue\n }\n\n const result = validateFeature(parsed)\n if (!result.success) {\n process.stderr.write(\n `Warning: \"${fullPath}\" failed validation — skipping\\n ${result.errors.join('\\n ')}\\n`,\n )\n continue\n }\n\n results.push({ filePath: fullPath, feature: result.data })\n }\n }\n }\n\n await walk(dir)\n return results\n}\n","import { readFile, writeFile } from 'node:fs/promises'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\nexport const archiveCommand = new Command('archive')\n .description('Mark a feature as deprecated (archived)')\n .argument('<key>', 'featureKey to archive (e.g. feat-2026-001)')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .action(async (key: string, options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n const features = await scanFeatures(scanDir)\n const found = features.find(f => f.feature.featureKey === key)\n\n if (!found) {\n process.stderr.write(`Error: feature \"${key}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n\n if (found.feature.status === 'deprecated') {\n process.stdout.write(`Already deprecated: ${key}\\n`)\n process.exit(0)\n }\n\n const raw = await readFile(found.filePath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n parsed['status'] = 'deprecated'\n\n const validation = validateFeature(parsed)\n if (!validation.success) {\n process.stderr.write(`Validation error: ${validation.errors.join(', ')}\\n`)\n process.exit(1)\n }\n\n await writeFile(found.filePath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n process.stdout.write(`✓ ${key} archived (status → deprecated)\\n`)\n })\n","import { existsSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\n\n/**\n * Walks up the directory tree from startDir to find the nearest feature.json.\n * Returns the absolute path or null if not found before reaching the filesystem root.\n */\nexport function findNearestFeatureJson(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, 'feature.json')\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\n/**\n * Walks up the directory tree from startDir to find the nearest .git directory.\n * Returns the absolute path to .git or null if not found.\n */\nexport function findGitDir(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, '.git')\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\n/**\n * Walks up the directory tree from startDir to find the nearest lac.config.json.\n * Returns the absolute path or null if not found.\n */\nexport function findLacConfig(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, 'lac.config.json')\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n","import { readFileSync } from 'node:fs'\nimport process from 'node:process'\n\nimport { findLacConfig } from './walker.js'\n\nexport type RequirableField =\n | 'problem'\n | 'analysis'\n | 'decisions'\n | 'implementation'\n | 'knownLimitations'\n | 'tags'\n\nexport interface LacConfig {\n version?: number\n /** Fields that must be non-empty for lint to pass. Default: ['problem'] */\n requiredFields?: RequirableField[]\n /** Completeness % (0-100) a feature must meet. Default: 0 (disabled) */\n ciThreshold?: number\n /** Only lint features with these statuses. Default: ['active', 'draft'] */\n lintStatuses?: Array<'draft' | 'active' | 'frozen' | 'deprecated'>\n /**\n * Domain prefix for generated featureKeys. Default: \"feat\".\n * Use any lowercase alphanumeric string, e.g. \"proc\", \"goal\", \"adr\", \"law\".\n * Example: `\"domain\": \"proc\"` → keys like `proc-2026-001`.\n */\n domain?: string\n}\n\nconst DEFAULTS: Required<LacConfig> = {\n version: 1,\n requiredFields: ['problem'],\n ciThreshold: 0,\n lintStatuses: ['active', 'draft'],\n domain: 'feat',\n}\n\nexport function loadConfig(fromDir?: string): Required<LacConfig> {\n const startDir = fromDir ?? process.cwd()\n const configPath = findLacConfig(startDir)\n if (!configPath) return { ...DEFAULTS }\n\n try {\n const raw = readFileSync(configPath, 'utf-8')\n const parsed = JSON.parse(raw) as LacConfig\n return {\n version: parsed.version ?? DEFAULTS.version,\n requiredFields: parsed.requiredFields ?? DEFAULTS.requiredFields,\n ciThreshold: parsed.ciThreshold ?? DEFAULTS.ciThreshold,\n lintStatuses: parsed.lintStatuses ?? DEFAULTS.lintStatuses,\n domain: parsed.domain ?? DEFAULTS.domain,\n }\n } catch {\n process.stderr.write(`Warning: could not parse lac.config.json at \"${configPath}\" — using defaults\\n`)\n return { ...DEFAULTS }\n }\n}\n\n/** The 6 optional fields used to compute completeness score (0–100) */\nexport const OPTIONAL_FIELDS: RequirableField[] = [\n 'analysis',\n 'decisions',\n 'implementation',\n 'knownLimitations',\n 'tags',\n 'annotations' as RequirableField,\n]\n\n/**\n * Returns today's date in YYYY-MM-DD format.\n * Use this as the default `date` for new annotations when no date is provided.\n */\nexport function todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nexport function computeCompleteness(feature: Record<string, unknown>): number {\n const filled = OPTIONAL_FIELDS.filter((field) => {\n const val = feature[field]\n if (val === undefined || val === null || val === '') return false\n if (Array.isArray(val)) return val.length > 0\n return typeof val === 'string' && val.trim().length > 0\n }).length\n return Math.round((filled / OPTIONAL_FIELDS.length) * 100)\n}\n","import { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { computeCompleteness } from '../lib/config.js'\nimport { findNearestFeatureJson } from '../lib/walker.js'\n\nexport const blameCommand = new Command('blame')\n .description('Show which feature owns a file or path')\n .argument('<path>', 'File path to trace (supports path:line format, line is ignored)')\n .action((rawPath: string) => {\n // Strip line number if provided (e.g. src/auth/index.ts:42 → src/auth/index.ts)\n const filePath = rawPath.replace(/:\\d+$/, '')\n const absPath = resolve(filePath)\n const startDir = dirname(absPath)\n\n const featureJsonPath = findNearestFeatureJson(startDir)\n\n if (!featureJsonPath) {\n process.stderr.write(\n `No feature.json found for \"${filePath}\".\\nRun \"lac init\" in the feature folder to create one.\\n`,\n )\n process.exit(1)\n }\n\n let raw: string\n try {\n raw = readFileSync(featureJsonPath, 'utf-8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error reading \"${featureJsonPath}\": ${message}\\n`)\n process.exit(1)\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n process.stderr.write(`Error: \"${featureJsonPath}\" contains invalid JSON.\\n`)\n process.exit(1)\n }\n\n const result = validateFeature(parsed)\n if (!result.success) {\n process.stderr.write(`Error: \"${featureJsonPath}\" failed validation:\\n ${result.errors.join('\\n ')}\\n`)\n process.exit(1)\n }\n\n const f = result.data\n\n const statusIcon: Record<string, string> = {\n active: '⊙',\n draft: '◌',\n frozen: '❄',\n deprecated: '⊘',\n }\n const icon = statusIcon[f.status] ?? '?'\n const completeness = computeCompleteness(f as unknown as Record<string, unknown>)\n const bar = '█'.repeat(Math.round(completeness / 10)) + '░'.repeat(10 - Math.round(completeness / 10))\n\n process.stdout.write('\\n')\n process.stdout.write(` Feature : ${f.featureKey}\\n`)\n process.stdout.write(` Title : ${f.title}\\n`)\n process.stdout.write(` Status : ${icon} ${f.status}\\n`)\n process.stdout.write(` Complete : [${bar}] ${completeness}%\\n`)\n process.stdout.write(` Path : ${featureJsonPath}\\n`)\n process.stdout.write('\\n')\n process.stdout.write(` Problem:\\n ${f.problem}\\n`)\n\n if (f.analysis) {\n const excerpt = f.analysis.length > 120 ? f.analysis.slice(0, 120) + '…' : f.analysis\n process.stdout.write(`\\n Analysis:\\n ${excerpt}\\n`)\n }\n\n if (f.decisions && f.decisions.length > 0) {\n process.stdout.write(`\\n Decisions (${f.decisions.length}):\\n`)\n for (const d of f.decisions) {\n process.stdout.write(` • ${d.decision}\\n Rationale: ${d.rationale}\\n`)\n }\n }\n\n if (f.knownLimitations && f.knownLimitations.length > 0) {\n process.stdout.write(`\\n Known Limitations:\\n`)\n for (const lim of f.knownLimitations) {\n process.stdout.write(` - ${lim}\\n`)\n }\n }\n\n if (f.lineage?.parent) {\n process.stdout.write(`\\n Lineage : parent → ${f.lineage.parent}\\n`)\n }\n\n process.stdout.write('\\n')\n })\n","import process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\n/**\n * Stable JSON serialisation that sorts object keys recursively so that two\n * objects with the same content but different insertion order compare equal.\n */\nfunction stableStringify(val: unknown): string {\n if (val === null || typeof val !== 'object') return JSON.stringify(val)\n if (Array.isArray(val)) return `[${val.map(stableStringify).join(',')}]`\n const sorted = Object.keys(val as Record<string, unknown>)\n .sort()\n .map((k) => `${JSON.stringify(k)}:${stableStringify((val as Record<string, unknown>)[k])}`)\n return `{${sorted.join(',')}}`\n}\n\nfunction formatValue(val: unknown): string {\n if (val === undefined || val === null) return '(empty)'\n if (typeof val === 'string') return val.length > 80 ? val.slice(0, 77) + '...' : val\n return JSON.stringify(val)\n}\n\nexport const diffCommand = new Command('diff')\n .description('Compare two features field-by-field')\n .argument('<key1>', 'First featureKey')\n .argument('<key2>', 'Second featureKey')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .action(async (key1: string, key2: string, options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n const f1 = features.find((f) => f.feature.featureKey === key1)\n const f2 = features.find((f) => f.feature.featureKey === key2)\n\n if (!f1) {\n process.stderr.write(`Error: feature \"${key1}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n if (!f2) {\n process.stderr.write(`Error: feature \"${key2}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n\n const obj1 = f1.feature as unknown as Record<string, unknown>\n const obj2 = f2.feature as unknown as Record<string, unknown>\n\n const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)])\n\n const lines: string[] = []\n lines.push(`diff ${key1} → ${key2}`)\n lines.push('─'.repeat(60))\n\n let hasDiffs = false\n for (const field of allKeys) {\n const v1 = obj1[field]\n const v2 = obj2[field]\n\n const s1 = stableStringify(v1)\n const s2 = stableStringify(v2)\n\n if (s1 === s2) continue\n hasDiffs = true\n\n if (v1 === undefined) {\n lines.push(`+ ${field}: ${formatValue(v2)}`)\n } else if (v2 === undefined) {\n lines.push(`- ${field}: ${formatValue(v1)}`)\n } else {\n lines.push(`~ ${field}:`)\n lines.push(` OLD: ${formatValue(v1)}`)\n lines.push(` NEW: ${formatValue(v2)}`)\n }\n }\n\n if (!hasDiffs) {\n lines.push('(no differences)')\n }\n\n process.stdout.write(lines.join('\\n') + '\\n')\n })\n","import { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { computeCompleteness, loadConfig } from '../lib/config.js'\nimport { scanFeatures } from '../lib/scanner.js'\nimport { findLacConfig } from '../lib/walker.js'\n\n// ─── helpers ────────────────────────────────────────────────────────────────\n\n/** Mirrors the findLacDir logic from keygen.ts */\nfunction findLacDir(fromDir: string): string | null {\n let current = path.resolve(fromDir)\n while (true) {\n const candidate = path.join(current, '.lac')\n try {\n if (statSync(candidate).isDirectory()) return candidate\n } catch { /* not found at this level */ }\n const parent = path.dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\n/** Walk a directory tree collecting all feature.json paths with validation info. */\nasync function walkFeatureFiles(\n currentDir: string,\n): Promise<{ valid: number; invalid: Array<{ filePath: string; errors: string[] }> }> {\n let validCount = 0\n const invalidFiles: Array<{ filePath: string; errors: string[] }> = []\n\n async function walk(dir: string): Promise<void> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let rawEntries: any[]\n try {\n rawEntries = await readdir(dir, { withFileTypes: true })\n } catch {\n return\n }\n\n // Normalise to plain objects to avoid @types/node Dirent variance (mirrors scanner.ts)\n const entries = rawEntries.map((e) => ({\n name: String(e.name),\n isDirectory: () => e.isDirectory() as boolean,\n isFile: () => e.isFile() as boolean,\n }))\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n\n if (entry.isDirectory()) {\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue\n await walk(fullPath)\n } else if (entry.isFile() && entry.name === 'feature.json') {\n let raw: string\n try {\n raw = await readFile(fullPath, 'utf-8')\n } catch {\n invalidFiles.push({ filePath: fullPath, errors: ['could not read file'] })\n continue\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n invalidFiles.push({ filePath: fullPath, errors: ['invalid JSON'] })\n continue\n }\n\n const result = validateFeature(parsed)\n if (!result.success) {\n invalidFiles.push({ filePath: fullPath, errors: result.errors })\n } else {\n validCount++\n }\n }\n }\n }\n\n await walk(currentDir)\n return { valid: validCount, invalid: invalidFiles }\n}\n\n// ─── command ────────────────────────────────────────────────────────────────\n\nexport const doctorCommand = new Command('doctor')\n .description('Check workspace health and report any issues')\n .argument('[dir]', 'Directory to check (default: cwd)')\n .action(async (dir: string | undefined) => {\n const checkDir = dir ? path.resolve(dir) : process.cwd()\n\n let passed = 0\n let warned = 0\n let failed = 0\n\n const output: string[] = []\n\n output.push('lac doctor — workspace diagnostics')\n output.push('===================================')\n output.push('')\n\n // ── Check 1: .lac/ workspace directory ──────────────────────────────────\n let lacDir: string | null = null\n try {\n lacDir = findLacDir(checkDir)\n } catch { /* treated as not found */ }\n\n if (lacDir) {\n output.push(`✓ Workspace found at ${lacDir}`)\n passed++\n } else {\n output.push('✗ No .lac/ workspace — run: lac workspace init')\n failed++\n }\n\n // ── Check 2: .lac/counter file ───────────────────────────────────────────\n if (lacDir) {\n const counterPath = join(lacDir, 'counter')\n let counterOk = false\n let counterYear: number | null = null\n let nextKey = ''\n\n try {\n const raw = readFileSync(counterPath, 'utf-8').trim()\n const parts = raw.split('\\n').map((l) => l.trim())\n const yr = parseInt(parts[0] ?? '', 10)\n const cnt = parseInt(parts[1] ?? '', 10)\n\n if (!isNaN(yr) && !isNaN(cnt)) {\n counterOk = true\n counterYear = yr\n nextKey = `feat-${yr}-${String(cnt + 1).padStart(3, '0')}`\n }\n } catch { /* missing or unreadable */ }\n\n if (counterOk && counterYear !== null) {\n output.push(`✓ Counter valid — next key preview: ${nextKey}`)\n passed++\n\n const currentYear = new Date().getFullYear()\n if (counterYear !== currentYear) {\n output.push(`⚠ Counter year is stale (${counterYear}) — will reset on next lac init`)\n warned++\n }\n } else {\n output.push('✗ Counter file missing or corrupt — run: lac workspace init --force')\n failed++\n }\n }\n\n // ── Check 3: feature.json files ──────────────────────────────────────────\n try {\n const { valid, invalid } = await walkFeatureFiles(checkDir)\n const total = valid + invalid.length\n output.push(`✓ Found ${total} feature.json file${total === 1 ? '' : 's'}`)\n passed++\n\n for (const inv of invalid) {\n output.push(` ✗ Invalid: ${inv.filePath} — ${inv.errors.join('; ')}`)\n failed++\n }\n } catch {\n output.push('✗ Could not scan feature.json files')\n failed++\n }\n\n // ── Check 4: lac.config.json (optional) ──────────────────────────────────\n try {\n const configPath = findLacConfig(checkDir)\n\n if (!configPath) {\n output.push(' (no lac.config.json — using defaults)')\n } else {\n try {\n const raw = readFileSync(configPath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n const domain = typeof parsed.domain === 'string' ? parsed.domain : 'feat'\n const threshold = typeof parsed.ciThreshold === 'number' ? parsed.ciThreshold : 0\n output.push(`✓ lac.config.json valid (domain: ${domain}, threshold: ${threshold})`)\n passed++\n } catch {\n output.push('✗ lac.config.json is invalid JSON — fix or delete it')\n failed++\n }\n }\n } catch {\n output.push(' (no lac.config.json — using defaults)')\n }\n\n // ── Check 5: lac-lsp availability ────────────────────────────────────────\n try {\n const result = spawnSync('lac-lsp', ['--help'], { timeout: 2000, stdio: 'ignore' })\n if (result.error || result.status === null) {\n output.push('✗ lac-lsp not found — install: npm i -g @life-as-code/lac-lsp')\n failed++\n } else {\n output.push('✓ lac-lsp found in PATH')\n passed++\n }\n } catch {\n output.push('✗ lac-lsp not found — install: npm i -g @life-as-code/lac-lsp')\n failed++\n }\n\n // ── Check 6: lint status ─────────────────────────────────────────────────\n try {\n const config = loadConfig(checkDir)\n const scanned = await scanFeatures(checkDir)\n const toCheck = scanned.filter(({ feature }) =>\n (config.lintStatuses as string[]).includes(feature.status),\n )\n\n let lintWarnCount = 0\n for (const { feature } of toCheck) {\n const raw = feature as unknown as Record<string, unknown>\n const completeness = computeCompleteness(raw)\n const missingRequired = config.requiredFields.filter((field) => {\n const val = raw[field]\n if (val === undefined || val === null || val === '') return true\n if (Array.isArray(val)) return val.length === 0\n return typeof val === 'string' && val.trim().length === 0\n })\n const belowThreshold = config.ciThreshold > 0 && completeness < config.ciThreshold\n if (missingRequired.length > 0 || belowThreshold) lintWarnCount++\n }\n\n if (lintWarnCount === 0) {\n output.push('✓ All features pass lint')\n passed++\n } else {\n output.push(`⚠ ${lintWarnCount} feature${lintWarnCount === 1 ? '' : 's'} have lint warnings`)\n warned++\n }\n } catch {\n output.push('⚠ Could not run lint check')\n warned++\n }\n\n // ── Summary ───────────────────────────────────────────────────────────────\n output.push('')\n output.push(\n `Overall: ${passed} check${passed === 1 ? '' : 's'} passed, ${warned} warning${warned === 1 ? '' : 's'}, ${failed} failure${failed === 1 ? '' : 's'}`,\n )\n\n // Next steps when all checks pass\n if (failed === 0) {\n output.push('')\n output.push('Next steps:')\n output.push(' lac serve → open dashboard at http://127.0.0.1:7474')\n output.push(' lac hooks install → lint on every commit')\n output.push(' lac init → create your first feature')\n }\n\n process.stdout.write(output.join('\\n') + '\\n')\n process.exit(failed > 0 ? 1 : 0)\n })\n","/**\n * Minimal markdown → HTML converter for use in the static site generator.\n * Handles the subset of markdown used in feature.json documentation fields:\n * headings, code blocks, inline code, bold, tables, lists, and paragraphs.\n */\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nfunction inlineMarkdown(text: string): string {\n return (\n escapeHtml(text)\n // **bold**\n .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n // `inline code`\n .replace(/`([^`]+)`/g, '<code>$1</code>')\n // [text](url) — allow balanced parentheses inside the URL so that\n // links like https://en.wikipedia.org/wiki/X_(Y) parse correctly.\n .replace(/\\[([^\\]]+)\\]\\(((?:[^()]*|\\([^()]*\\))*)\\)/g, '<a href=\"$2\">$1</a>')\n )\n}\n\nfunction isTableRow(line: string): boolean {\n return line.trim().startsWith('|') && line.trim().endsWith('|')\n}\n\nfunction isTableSeparator(line: string): boolean {\n return /^\\|[\\s\\-:|]+\\|/.test(line.trim())\n}\n\nfunction renderTableRow(line: string, isHeader: boolean): string {\n const cells = line\n .trim()\n .slice(1, -1)\n .split('|')\n .map((c) => c.trim())\n const tag = isHeader ? 'th' : 'td'\n return `<tr>${cells.map((c) => `<${tag}>${inlineMarkdown(c)}</${tag}>`).join('')}</tr>`\n}\n\nexport function markdownToHtml(md: string): string {\n const lines = md.split('\\n')\n const out: string[] = []\n let i = 0\n\n while (i < lines.length) {\n const line = lines[i] ?? ''\n\n // ── Fenced code block ────────────────────────────────────────────────────\n if (line.startsWith('```')) {\n const lang = line.slice(3).trim()\n const codeLines: string[] = []\n i++\n // Guard against an unmatched opening fence: if we reach EOF without\n // finding the closing ```, treat everything collected so far as the block.\n while (i < lines.length && !(lines[i] ?? '').startsWith('```')) {\n codeLines.push(lines[i] ?? '')\n i++\n }\n if (i < lines.length) i++ // skip closing ``` only if it exists\n const langAttr = lang ? ` class=\"language-${escapeHtml(lang)}\"` : ''\n out.push(`<pre><code${langAttr}>${escapeHtml(codeLines.join('\\n'))}</code></pre>`)\n continue\n }\n\n // ── Headings ─────────────────────────────────────────────────────────────\n if (line.startsWith('### ')) {\n out.push(`<h3>${inlineMarkdown(line.slice(4))}</h3>`)\n i++\n continue\n }\n if (line.startsWith('## ')) {\n out.push(`<h2>${inlineMarkdown(line.slice(3))}</h2>`)\n i++\n continue\n }\n if (line.startsWith('# ')) {\n out.push(`<h1>${inlineMarkdown(line.slice(2))}</h1>`)\n i++\n continue\n }\n\n // ── Horizontal rule ──────────────────────────────────────────────────────\n if (/^(-{3,}|\\*{3,}|_{3,})$/.test(line.trim())) {\n out.push('<hr />')\n i++\n continue\n }\n\n // ── Table ─────────────────────────────────────────────────────────────────\n if (isTableRow(line)) {\n const tableRows: string[] = []\n let firstRow = true\n while (i < lines.length && isTableRow(lines[i] ?? '')) {\n const current = lines[i] ?? ''\n if (isTableSeparator(current)) {\n // separator row — skip\n i++\n continue\n }\n tableRows.push(renderTableRow(current, firstRow))\n if (firstRow) firstRow = false\n i++\n }\n out.push(\n `<table class=\"md-table\"><thead>${tableRows[0] ?? ''}</thead><tbody>${tableRows.slice(1).join('')}</tbody></table>`,\n )\n continue\n }\n\n // ── Unordered list ───────────────────────────────────────────────────────\n if (/^[-*] /.test(line)) {\n const items: string[] = []\n while (i < lines.length && /^[-*] /.test(lines[i] ?? '')) {\n items.push(`<li>${inlineMarkdown((lines[i] ?? '').slice(2))}</li>`)\n i++\n }\n out.push(`<ul>${items.join('')}</ul>`)\n continue\n }\n\n // ── Ordered list ─────────────────────────────────────────────────────────\n // Require the marker at the very start of the line (no leading spaces) so\n // that a paragraph starting with e.g. \"2026. That was the year…\" is NOT\n // treated as a list item.\n if (/^[1-9]\\d*\\. /.test(line)) {\n const items: string[] = []\n while (i < lines.length && /^[1-9]\\d*\\. /.test(lines[i] ?? '')) {\n items.push(`<li>${inlineMarkdown((lines[i] ?? '').replace(/^[1-9]\\d*\\. /, ''))}</li>`)\n i++\n }\n out.push(`<ol>${items.join('')}</ol>`)\n continue\n }\n\n // ── Blank line ────────────────────────────────────────────────────────────\n if (line.trim() === '') {\n i++\n continue\n }\n\n // ── Paragraph ─────────────────────────────────────────────────────────────\n const paraLines: string[] = []\n while (\n i < lines.length &&\n (lines[i] ?? '').trim() !== '' &&\n !((lines[i] ?? '').startsWith('#')) &&\n !((lines[i] ?? '').startsWith('```')) &&\n !(/^[-*] /.test(lines[i] ?? '')) &&\n !(/^[1-9]\\d*\\. /.test(lines[i] ?? '')) &&\n !(isTableRow(lines[i] ?? ''))\n ) {\n paraLines.push(lines[i] ?? '')\n i++\n }\n if (paraLines.length > 0) {\n out.push(`<p>${inlineMarkdown(paraLines.join(' '))}</p>`)\n }\n }\n\n return out.join('\\n')\n}\n","export const css = `\n:root {\n --color-bg: #ffffff;\n --color-surface: #f8f9fa;\n --color-border: #dee2e6;\n --color-text: #212529;\n --color-text-muted: #6c757d;\n --color-link: #0d6efd;\n --color-link-hover: #0a58ca;\n --color-active: #198754;\n --color-draft: #6c757d;\n --color-frozen: #0d6efd;\n --color-deprecated: #dc3545;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --color-bg: #1a1a2e;\n --color-surface: #16213e;\n --color-border: #374151;\n --color-text: #e9ecef;\n --color-text-muted: #9ca3af;\n --color-link: #60a5fa;\n --color-link-hover: #93c5fd;\n --color-active: #4ade80;\n --color-draft: #9ca3af;\n --color-frozen: #60a5fa;\n --color-deprecated: #f87171;\n }\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-size: 16px;\n line-height: 1.6;\n}\n\nbody {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,\n Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n background-color: var(--color-bg);\n color: var(--color-text);\n margin: 0;\n padding: 0;\n}\n\n.container {\n max-width: 800px;\n margin: 0 auto;\n padding: 2rem 1rem;\n}\n\nh1 {\n font-size: 2rem;\n font-weight: 700;\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\nh2 {\n font-size: 1.25rem;\n font-weight: 600;\n margin-top: 2rem;\n margin-bottom: 0.75rem;\n border-bottom: 1px solid var(--color-border);\n padding-bottom: 0.25rem;\n}\n\na {\n color: var(--color-link);\n text-decoration: none;\n}\n\na:hover {\n color: var(--color-link-hover);\n text-decoration: underline;\n}\n\np {\n margin: 0 0 1rem;\n}\n\n.meta {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin-bottom: 1.5rem;\n color: var(--color-text-muted);\n font-size: 0.875rem;\n}\n\n.feature-key {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,\n 'Liberation Mono', 'Courier New', monospace;\n font-size: 0.875rem;\n color: var(--color-text-muted);\n}\n\n/* Status badges */\n.status-badge {\n display: inline-block;\n padding: 0.125rem 0.5rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.status-active {\n color: var(--color-active);\n background-color: color-mix(in srgb, var(--color-active) 15%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-active) 30%, transparent);\n}\n\n.status-draft {\n color: var(--color-draft);\n background-color: color-mix(in srgb, var(--color-draft) 15%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-draft) 30%, transparent);\n}\n\n.status-frozen {\n color: var(--color-frozen);\n background-color: color-mix(in srgb, var(--color-frozen) 15%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-frozen) 30%, transparent);\n}\n\n.status-deprecated {\n color: var(--color-deprecated);\n background-color: color-mix(in srgb, var(--color-deprecated) 15%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-deprecated) 30%, transparent);\n}\n\n/* Search */\n.search-wrapper {\n margin-bottom: 1.5rem;\n}\n\n.search-input {\n width: 100%;\n padding: 0.5rem 0.75rem;\n border: 1px solid var(--color-border);\n border-radius: 0.375rem;\n background-color: var(--color-surface);\n color: var(--color-text);\n font-size: 1rem;\n font-family: inherit;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.search-input:focus {\n border-color: var(--color-link);\n}\n\n/* Feature table */\n.feature-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.9375rem;\n}\n\n.feature-table th {\n text-align: left;\n padding: 0.5rem 0.75rem;\n border-bottom: 2px solid var(--color-border);\n color: var(--color-text-muted);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.feature-table td {\n padding: 0.625rem 0.75rem;\n border-bottom: 1px solid var(--color-border);\n vertical-align: top;\n}\n\n.feature-table tr:last-child td {\n border-bottom: none;\n}\n\n.feature-table tr:hover td {\n background-color: var(--color-surface);\n}\n\n.problem-excerpt {\n color: var(--color-text-muted);\n font-size: 0.875rem;\n}\n\n/* Sections */\nsection {\n margin-bottom: 2rem;\n}\n\n.problem-text {\n background-color: var(--color-surface);\n border-left: 3px solid var(--color-link);\n padding: 1rem 1.25rem;\n border-radius: 0 0.375rem 0.375rem 0;\n margin: 0;\n}\n\n/* Decisions timeline */\nol.decisions {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n\nol.decisions li {\n position: relative;\n padding: 1rem 1rem 1rem 1.5rem;\n border-left: 2px solid var(--color-border);\n margin-bottom: 1rem;\n}\n\nol.decisions li:last-child {\n margin-bottom: 0;\n}\n\nol.decisions li::before {\n content: '';\n position: absolute;\n left: -0.375rem;\n top: 1.25rem;\n width: 0.625rem;\n height: 0.625rem;\n border-radius: 50%;\n background-color: var(--color-link);\n}\n\n.decision-date {\n font-size: 0.75rem;\n color: var(--color-text-muted);\n margin-bottom: 0.25rem;\n}\n\n.decision-text {\n font-weight: 600;\n margin-bottom: 0.375rem;\n}\n\n.decision-rationale {\n color: var(--color-text-muted);\n font-size: 0.9375rem;\n margin-bottom: 0.375rem;\n}\n\n.alternatives {\n font-size: 0.875rem;\n color: var(--color-text-muted);\n}\n\n.alternatives span {\n font-weight: 500;\n}\n\n/* Implementation / limitation sections */\n.implementation-text {\n white-space: pre-wrap;\n font-size: 0.9375rem;\n line-height: 1.7;\n}\n\nul.limitations {\n padding-left: 1.5rem;\n margin: 0;\n}\n\nul.limitations li {\n margin-bottom: 0.375rem;\n color: var(--color-text-muted);\n}\n\n/* Lineage */\n.lineage-info {\n background-color: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 0.375rem;\n padding: 1rem 1.25rem;\n}\n\n.lineage-info p {\n margin-bottom: 0.5rem;\n}\n\n.lineage-info p:last-child {\n margin-bottom: 0;\n}\n\n/* Back link */\n.back-link {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n margin-bottom: 2rem;\n font-size: 0.875rem;\n}\n\n/* Empty state */\n.empty-state {\n text-align: center;\n padding: 3rem 1rem;\n color: var(--color-text-muted);\n}\n\n/* No results row */\n.no-results {\n display: none;\n padding: 1.5rem 0.75rem;\n color: var(--color-text-muted);\n font-style: italic;\n}\n\n/* Markdown-rendered content */\n.implementation-text h2,\n.analysis-text h2 {\n font-size: 1.125rem;\n font-weight: 600;\n margin-top: 1.75rem;\n margin-bottom: 0.5rem;\n border-bottom: 1px solid var(--color-border);\n padding-bottom: 0.25rem;\n}\n\n.implementation-text h3,\n.analysis-text h3 {\n font-size: 1rem;\n font-weight: 600;\n margin-top: 1.25rem;\n margin-bottom: 0.375rem;\n color: var(--color-text-muted);\n}\n\n.implementation-text pre,\n.analysis-text pre {\n background-color: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 0.375rem;\n padding: 1rem 1.25rem;\n overflow-x: auto;\n margin: 1rem 0;\n}\n\n.implementation-text code,\n.analysis-text code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 0.875em;\n}\n\n.implementation-text pre code,\n.analysis-text pre code {\n background: none;\n padding: 0;\n border-radius: 0;\n font-size: 0.875rem;\n}\n\n.implementation-text :not(pre) > code,\n.analysis-text :not(pre) > code {\n background-color: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 0.25rem;\n padding: 0.1em 0.35em;\n}\n\n.implementation-text ul,\n.analysis-text ul,\n.implementation-text ol,\n.analysis-text ol {\n padding-left: 1.5rem;\n margin: 0.5rem 0 1rem;\n}\n\n.implementation-text li,\n.analysis-text li {\n margin-bottom: 0.25rem;\n}\n\n/* Markdown tables */\n.md-table {\n width: 100%;\n border-collapse: collapse;\n margin: 1rem 0;\n font-size: 0.9rem;\n}\n\n.md-table th {\n text-align: left;\n padding: 0.5rem 0.75rem;\n border-bottom: 2px solid var(--color-border);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--color-text-muted);\n}\n\n.md-table td {\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--color-border);\n vertical-align: top;\n}\n\n.md-table tr:last-child td {\n border-bottom: none;\n}\n\n.md-table tr:hover td {\n background-color: var(--color-surface);\n}\n\n/* Analysis section */\n.analysis-text {\n font-size: 0.9375rem;\n line-height: 1.7;\n}\n`\n","import type { Feature } from '@life-as-code/feature-schema'\nimport { markdownToHtml } from './markdown.js'\nimport { css } from './site-style.css.js'\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nfunction statusBadge(status: Feature['status']): string {\n return `<span class=\"status-badge status-${escapeHtml(status)}\">${escapeHtml(status)}</span>`\n}\n\nfunction renderDecisions(decisions: NonNullable<Feature['decisions']>): string {\n if (decisions.length === 0) return ''\n const items = decisions\n .map((d) => {\n const date = d.date\n ? `<div class=\"decision-date\">${escapeHtml(d.date)}</div>`\n : ''\n const alts =\n d.alternativesConsidered && d.alternativesConsidered.length > 0\n ? `<div class=\"alternatives\"><span>Alternatives considered:</span> ${d.alternativesConsidered.map(escapeHtml).join(', ')}</div>`\n : ''\n return `\n <li>\n ${date}\n <div class=\"decision-text\">${escapeHtml(d.decision)}</div>\n <div class=\"decision-rationale\">${escapeHtml(d.rationale)}</div>\n ${alts}\n </li>`\n })\n .join('\\n')\n\n return `\n <section class=\"decisions\">\n <h2>Decisions</h2>\n <ol class=\"decisions\">\n ${items}\n </ol>\n </section>`\n}\n\nfunction renderLineage(lineage: NonNullable<Feature['lineage']>): string {\n const parts: string[] = []\n\n if (lineage.parent) {\n parts.push(\n `<p><strong>Parent:</strong> <a href=\"${escapeHtml(lineage.parent)}.html\">${escapeHtml(lineage.parent)}</a></p>`,\n )\n }\n if (lineage.children && lineage.children.length > 0) {\n const childLinks = lineage.children\n .map((c) => `<a href=\"${escapeHtml(c)}.html\">${escapeHtml(c)}</a>`)\n .join(', ')\n parts.push(`<p><strong>Children:</strong> ${childLinks}</p>`)\n }\n if (lineage.spawnReason) {\n parts.push(`<p><strong>Spawn reason:</strong> ${escapeHtml(lineage.spawnReason)}</p>`)\n }\n\n if (parts.length === 0) return ''\n\n return `\n <section class=\"lineage\">\n <h2>Lineage</h2>\n <div class=\"lineage-info\">\n ${parts.join('\\n ')}\n </div>\n </section>`\n}\n\nexport function renderFeature(feature: Feature): string {\n const decisionsSection =\n feature.decisions && feature.decisions.length > 0\n ? renderDecisions(feature.decisions)\n : ''\n\n const implementationSection = feature.implementation\n ? `\n <section class=\"implementation\">\n <h2>How it works</h2>\n <div class=\"implementation-text\">${markdownToHtml(feature.implementation)}</div>\n </section>`\n : ''\n\n const analysisSection = (feature as Record<string, unknown>)['analysis']\n ? `\n <section class=\"analysis\">\n <h2>Background &amp; Context</h2>\n <div class=\"analysis-text\">${markdownToHtml(((feature as Record<string, unknown>)['analysis'] as string))}</div>\n </section>`\n : ''\n\n const limitationsSection =\n feature.knownLimitations && feature.knownLimitations.length > 0\n ? `\n <section class=\"limitations\">\n <h2>Known Limitations</h2>\n <ul class=\"limitations\">\n ${feature.knownLimitations.map((l) => `<li>${escapeHtml(l)}</li>`).join('\\n ')}\n </ul>\n </section>`\n : ''\n\n const lineageSection =\n feature.lineage &&\n (feature.lineage.parent ||\n (feature.lineage.children && feature.lineage.children.length > 0) ||\n feature.lineage.spawnReason)\n ? renderLineage(feature.lineage)\n : ''\n\n const tagsSection =\n feature.tags && feature.tags.length > 0\n ? `<div class=\"meta\" style=\"margin-top:0.5rem;flex-wrap:wrap\">${feature.tags.map((t) => `<span style=\"font-size:0.75rem;padding:0.125rem 0.5rem;border-radius:9999px;background-color:var(--color-surface);border:1px solid var(--color-border)\">${escapeHtml(t)}</span>`).join('')}</div>`\n : ''\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${escapeHtml(feature.title)} — Feature Provenance</title>\n <style>${css}</style>\n</head>\n<body>\n <div class=\"container\">\n <a href=\"index.html\" class=\"back-link\">&#8592; All features</a>\n\n <h1>${escapeHtml(feature.title)}</h1>\n <div class=\"meta\">\n ${statusBadge(feature.status)}\n <span class=\"feature-key\">${escapeHtml(feature.featureKey)}</span>\n </div>\n ${tagsSection}\n\n <section class=\"problem\">\n <h2>Problem</h2>\n <p class=\"problem-text\">${escapeHtml(feature.problem)}</p>\n </section>\n\n ${analysisSection}\n ${decisionsSection}\n ${implementationSection}\n ${limitationsSection}\n ${lineageSection}\n </div>\n</body>\n</html>`\n}\n","import type { Feature } from '@life-as-code/feature-schema'\nimport { css } from './site-style.css.js'\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nfunction statusBadge(status: Feature['status']): string {\n return `<span class=\"status-badge status-${escapeHtml(status)}\">${escapeHtml(status)}</span>`\n}\n\nexport function renderIndex(features: Feature[], generatedAt?: Date): string {\n const timestamp = (generatedAt ?? new Date()).toISOString()\n const rows =\n features.length === 0\n ? `<tr><td colspan=\"4\" class=\"no-results\" style=\"display:table-cell\">No features found.</td></tr>`\n : features\n .map(\n (f) => `\n <tr class=\"feature-row\" data-search=\"${escapeHtml((f.featureKey + ' ' + f.title).toLowerCase())}\">\n <td><a href=\"${escapeHtml(f.featureKey)}.html\" class=\"feature-key\">${escapeHtml(f.featureKey)}</a></td>\n <td><a href=\"${escapeHtml(f.featureKey)}.html\">${escapeHtml(f.title)}</a></td>\n <td>${statusBadge(f.status)}</td>\n <td class=\"problem-excerpt\">${escapeHtml(f.problem.slice(0, 100))}${f.problem.length > 100 ? '…' : ''}</td>\n </tr>`,\n )\n .join('\\n')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Feature Provenance</title>\n <style>${css}</style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Feature Provenance</h1>\n <p style=\"color:var(--color-text-muted);margin-bottom:1.5rem\">${features.length} feature${features.length === 1 ? '' : 's'} tracked</p>\n\n <div class=\"search-wrapper\">\n <input\n type=\"search\"\n id=\"search\"\n class=\"search-input\"\n placeholder=\"Search by key or title…\"\n aria-label=\"Search features\"\n />\n </div>\n\n <table class=\"feature-table\" id=\"feature-table\">\n <thead>\n <tr>\n <th>Key</th>\n <th>Title</th>\n <th>Status</th>\n <th>Problem</th>\n </tr>\n </thead>\n <tbody id=\"feature-tbody\">\n ${rows}\n <tr id=\"no-results-row\" style=\"display:none\">\n <td colspan=\"4\" class=\"no-results\" style=\"display:table-cell;color:var(--color-text-muted);font-style:italic;padding:1.5rem 0.75rem\">No features match your search.</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <footer style=\"margin-top:2rem;padding-top:1rem;border-top:1px solid var(--color-border);color:var(--color-text-muted);font-size:0.75rem;text-align:right\">\n Generated ${escapeHtml(timestamp)}\n </footer>\n\n <script>\n (function () {\n var input = document.getElementById('search');\n var noResults = document.getElementById('no-results-row');\n if (!input) return;\n input.addEventListener('input', function () {\n var query = input.value.trim().toLowerCase();\n var rows = document.querySelectorAll('#feature-tbody .feature-row');\n var visible = 0;\n rows.forEach(function (row) {\n var search = row.getAttribute('data-search') || '';\n var match = query === '' || search.indexOf(query) !== -1;\n row.style.display = match ? '' : 'none';\n if (match) visible++;\n });\n if (noResults) {\n noResults.style.display = (visible === 0 && query !== '') ? '' : 'none';\n }\n });\n })();\n </script>\n</body>\n</html>`\n}\n","import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport type { Feature } from '@life-as-code/feature-schema'\nimport { renderFeature } from '../templates/site-feature.html.js'\nimport { renderIndex } from '../templates/site-index.html.js'\nimport { css } from '../templates/site-style.css.js'\n\nexport interface SiteFeature {\n filePath: string\n feature: Feature\n}\n\n/**\n * Generates a static HTML site for the given features.\n * Creates:\n * outDir/index.html — searchable feature list\n * outDir/{featureKey}.html — one page per feature\n * outDir/style.css — shared stylesheet\n */\nexport async function generateSite(features: SiteFeature[], outDir: string): Promise<void> {\n await mkdir(outDir, { recursive: true })\n\n // Write shared stylesheet\n await writeFile(join(outDir, 'style.css'), css.trim(), 'utf-8')\n\n // Write index page\n const allFeatures = features.map((f) => f.feature)\n await writeFile(join(outDir, 'index.html'), renderIndex(allFeatures), 'utf-8')\n\n // Write one page per feature\n for (const { feature } of features) {\n const pageHtml = renderFeature(feature)\n await writeFile(join(outDir, `${feature.featureKey}.html`), pageHtml, 'utf-8')\n }\n}\n","import { existsSync } from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\nimport { generateSite } from '../lib/siteGenerator.js'\n\n/**\n * Walks up the directory tree from `startDir` to find the nearest feature.json.\n * Returns the absolute path or null if not found.\n */\nfunction findNearestFeatureJson(startDir: string): string | null {\n let current = resolve(startDir)\n\n while (true) {\n const candidate = join(current, 'feature.json')\n if (existsSync(candidate)) {\n return candidate\n }\n const parent = dirname(current)\n if (parent === current) {\n return null\n }\n current = parent\n }\n}\n\n/** Render a feature as a Markdown document */\nfunction featureToMarkdown(feature: ReturnType<typeof JSON.parse>): string {\n const f = feature as Record<string, unknown>\n const lines: string[] = []\n\n lines.push(`# ${f['title'] as string}`)\n lines.push('')\n lines.push(`**Key:** \\`${f['featureKey'] as string}\\` `)\n lines.push(`**Status:** ${f['status'] as string}`)\n lines.push('')\n\n if (f['problem']) {\n lines.push('## Problem')\n lines.push('')\n lines.push(f['problem'] as string)\n lines.push('')\n }\n\n if (f['analysis']) {\n lines.push('## Analysis')\n lines.push('')\n lines.push(f['analysis'] as string)\n lines.push('')\n }\n\n if (f['implementation']) {\n lines.push('## Implementation')\n lines.push('')\n lines.push(f['implementation'] as string)\n lines.push('')\n }\n\n const limitations = f['knownLimitations'] as string[] | undefined\n if (limitations && limitations.length > 0) {\n lines.push('## Known Limitations')\n lines.push('')\n for (const lim of limitations) {\n lines.push(`- ${lim}`)\n }\n lines.push('')\n }\n\n const decisions = f['decisions'] as Array<Record<string, unknown>> | undefined\n if (decisions && decisions.length > 0) {\n lines.push('## Decisions')\n lines.push('')\n for (const d of decisions) {\n lines.push(`### ${d['decision'] as string}`)\n lines.push('')\n lines.push(`**Rationale:** ${d['rationale'] as string}`)\n if (d['date']) lines.push(`**Date:** ${d['date'] as string}`)\n lines.push('')\n }\n }\n\n const annotations = f['annotations'] as Array<Record<string, unknown>> | undefined\n if (annotations && annotations.length > 0) {\n lines.push('## Annotations')\n lines.push('')\n for (const a of annotations) {\n lines.push(`- **[${a['type'] as string}]** ${a['body'] as string} _(${a['author'] as string}, ${a['date'] as string})_`)\n }\n lines.push('')\n }\n\n const tags = f['tags'] as string[] | undefined\n if (tags && tags.length > 0) {\n lines.push(`**Tags:** ${tags.join(', ')}`)\n lines.push('')\n }\n\n return lines.join('\\n')\n}\n\nexport const exportCommand = new Command('export')\n .description('Export feature.json as JSON, Markdown, or generate a static HTML site')\n .option('--out <path>', 'Output file or directory path')\n .option('--site <dir>', 'Scan <dir> for feature.json files and generate a static HTML site')\n .option('--markdown', 'Output feature as a Markdown document instead of JSON')\n .action(async (options: { out?: string; site?: string; markdown?: boolean }) => {\n // ── Site generation mode ────────────────────────────────────────────────\n if (options.site !== undefined) {\n const scanDir = resolve(options.site)\n const outDir = resolve(options.out ?? './lac-site')\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n if (features.length === 0) {\n process.stdout.write(`No valid feature.json files found in \"${scanDir}\".\\n`)\n process.exit(0)\n }\n\n try {\n await generateSite(features, outDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error generating site: ${message}\\n`)\n process.exit(1)\n }\n\n // Compute a display-friendly relative path when possible\n const displayOut = options.out ?? './lac-site'\n process.stdout.write(`✓ Generated ${features.length} page${features.length === 1 ? '' : 's'} → ${displayOut}\\n`)\n return\n }\n\n // ── Plain export mode ───────────────────────────────────────────────────\n const featureJsonPath = findNearestFeatureJson(process.cwd())\n\n if (!featureJsonPath) {\n process.stderr.write(\n `Error: no feature.json found in the current directory or any of its parents.\\n`,\n )\n process.exit(1)\n }\n\n let raw: string\n try {\n raw = await readFile(featureJsonPath, 'utf-8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error reading \"${featureJsonPath}\": ${message}\\n`)\n process.exit(1)\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n process.stderr.write(`Error: \"${featureJsonPath}\" contains invalid JSON.\\n`)\n process.exit(1)\n }\n\n const result = validateFeature(parsed)\n if (!result.success) {\n process.stderr.write(\n `Error: \"${featureJsonPath}\" failed validation:\\n ${result.errors.join('\\n ')}\\n`,\n )\n process.exit(1)\n }\n\n // Markdown mode\n if (options.markdown) {\n const mdOutput = featureToMarkdown(result.data)\n if (options.out) {\n const outPath = resolve(options.out)\n try {\n await writeFile(outPath, mdOutput, 'utf-8')\n process.stdout.write(`Exported to ${outPath}\\n`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error writing to \"${outPath}\": ${message}\\n`)\n process.exit(1)\n }\n } else {\n process.stdout.write(mdOutput)\n }\n return\n }\n\n const output = JSON.stringify(result.data, null, 2) + '\\n'\n\n if (options.out) {\n const outPath = resolve(options.out)\n try {\n await writeFile(outPath, output, 'utf-8')\n process.stdout.write(`Exported to ${outPath}\\n`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error writing to \"${outPath}\": ${message}\\n`)\n process.exit(1)\n }\n } else {\n process.stdout.write(output)\n }\n })\n","import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { findGitDir } from '../lib/walker.js'\n\nconst LAC_MARKER = '# managed-by-lac'\n\nconst HOOK_SCRIPT = `#!/bin/sh\n${LAC_MARKER}\n# Provenance lint — runs lac lint before every commit.\n# To remove: run \"lac hooks uninstall\"\n\n# Find lac in PATH, or try npx as fallback\nif command -v lac >/dev/null 2>&1; then\n LAC_CMD=\"lac\"\nelse\n LAC_CMD=\"npx --yes lac\"\nfi\n\nif ! $LAC_CMD lint --quiet 2>/tmp/lac-lint-output; then\n echo \"\"\n echo \"lac pre-commit: lint failed — commit blocked\"\n # Show failing feature keys from lint output\n if [ -s /tmp/lac-lint-output ]; then\n grep -o 'feat-[a-z0-9]*-[0-9]*\\\\|[a-z][a-z0-9]*-[0-9]\\\\{4\\\\}-[0-9]*' /tmp/lac-lint-output | sort -u | while read key; do\n echo \" ✗ $key\"\n done\n fi\n echo \"\"\n echo \" Run \\\\\"lac lint\\\\\" for details.\"\n echo \"\"\n rm -f /tmp/lac-lint-output\n exit 1\nfi\nrm -f /tmp/lac-lint-output\n`\n\nconst hooksCommand = new Command('hooks')\n .description('Manage git hooks for provenance linting')\n\nhooksCommand\n .command('install')\n .description('Install a pre-commit hook that runs \"lac lint\" before each commit')\n .option('--force', 'Overwrite existing pre-commit hook even if not managed by lac')\n .action((options: { force?: boolean }) => {\n const gitDir = findGitDir(process.cwd())\n if (!gitDir) {\n process.stderr.write(`Error: no .git directory found. Are you inside a git repository?\\n`)\n process.exit(1)\n }\n\n const hooksDir = join(gitDir, 'hooks')\n const hookPath = join(hooksDir, 'pre-commit')\n\n // Create hooks dir if it doesn't exist (bare repos)\n if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true })\n\n // Check existing hook\n if (existsSync(hookPath) && !options.force) {\n const existing = readFileSync(hookPath, 'utf-8')\n if (!existing.includes(LAC_MARKER)) {\n process.stderr.write(\n `Error: a pre-commit hook already exists at \"${hookPath}\" and was not installed by lac.\\n` +\n `Use --force to overwrite it.\\n`,\n )\n process.exit(1)\n }\n }\n\n writeFileSync(hookPath, HOOK_SCRIPT, 'utf-8')\n chmodSync(hookPath, 0o755)\n\n process.stdout.write(`✓ Installed pre-commit hook at ${hookPath}\\n`)\n process.stdout.write(` \"lac lint\" will run before every commit.\\n`)\n process.stdout.write(` To remove: run \"lac hooks uninstall\"\\n`)\n })\n\nhooksCommand\n .command('uninstall')\n .description('Remove the lac-managed pre-commit hook')\n .action(() => {\n const gitDir = findGitDir(process.cwd())\n if (!gitDir) {\n process.stderr.write(`Error: no .git directory found.\\n`)\n process.exit(1)\n }\n\n const hookPath = join(gitDir, 'hooks', 'pre-commit')\n\n if (!existsSync(hookPath)) {\n process.stdout.write(`No pre-commit hook found at \"${hookPath}\".\\n`)\n return\n }\n\n const content = readFileSync(hookPath, 'utf-8')\n if (!content.includes(LAC_MARKER)) {\n process.stderr.write(\n `Error: the pre-commit hook at \"${hookPath}\" was not installed by lac.\\n` +\n `Remove it manually if you want to uninstall it.\\n`,\n )\n process.exit(1)\n }\n\n rmSync(hookPath)\n process.stdout.write(`✓ Removed lac pre-commit hook from ${hookPath}\\n`)\n })\n\nhooksCommand\n .command('status')\n .description('Show whether the lac pre-commit hook is installed')\n .action(() => {\n const gitDir = findGitDir(process.cwd())\n if (!gitDir) {\n process.stdout.write(`Not inside a git repository.\\n`)\n return\n }\n\n const hookPath = join(gitDir, 'hooks', 'pre-commit')\n if (!existsSync(hookPath)) {\n process.stdout.write(`pre-commit hook: not installed\\n`)\n return\n }\n\n const content = readFileSync(hookPath, 'utf-8')\n if (content.includes(LAC_MARKER)) {\n process.stdout.write(`pre-commit hook: ✓ installed (managed by lac)\\n`)\n } else {\n process.stdout.write(`pre-commit hook: installed (NOT managed by lac — foreign hook)\\n`)\n }\n })\n\nexport { hooksCommand }\n","import { generateFeatureKey } from '@life-as-code/feature-schema'\n\nimport { loadConfig } from './config.js'\n\n/**\n * Generates a new featureKey for the given directory, honouring the `domain`\n * field from the nearest `lac.config.json`.\n *\n * @throws {Error} If no `.lac/` directory can be found in fromDir or its parents.\n */\nexport function nextFeatureKey(fromDir: string): string {\n const config = loadConfig(fromDir)\n return generateFeatureKey(fromDir, config.domain)\n}\n","import { existsSync } from 'node:fs'\nimport { writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\nimport prompts from 'prompts'\n\nimport { nextFeatureKey } from '../lib/featureKey.js'\n\n/**\n * Derives a short title from a problem statement by taking the first 6 words\n * and appending \"...\" when truncated.\n */\nfunction titleFromProblem(problem: string): string {\n const words = problem.trim().split(/\\s+/)\n if (words.length <= 6) return problem.trim()\n return words.slice(0, 6).join(' ') + '...'\n}\n\nexport const initCommand = new Command('init')\n .description('Scaffold a feature.json in the current directory')\n .option('-f, --force', 'Overwrite existing feature.json', false)\n .action(async (options: { force: boolean }) => {\n const cwd = process.cwd()\n const featureJsonPath = join(cwd, 'feature.json')\n\n if (existsSync(featureJsonPath) && !options.force) {\n process.stderr.write(\n `Error: feature.json already exists in this directory.\\nUse --force to overwrite.\\n`,\n )\n process.exit(1)\n }\n\n // Interactive prompts\n const answers = await prompts(\n [\n {\n type: 'text',\n name: 'problem',\n message: 'What problem does this feature solve?',\n initial: 'e.g. Users cannot reset their password without contacting support',\n validate: (value: string) =>\n value.trim().length > 0 ? true : 'Problem statement is required',\n },\n {\n type: 'select',\n name: 'status',\n message: 'Status?',\n choices: [\n { title: 'draft', value: 'draft' },\n { title: 'active', value: 'active' },\n ],\n initial: 0,\n },\n ],\n {\n onCancel: () => {\n process.stderr.write('Aborted.\\n')\n process.exit(1)\n },\n },\n )\n\n const problem = (answers.problem as string).trim()\n const status = answers.status as 'draft' | 'active'\n\n // Generate featureKey — may throw if no .lac/ dir is found\n let featureKey: string\n try {\n featureKey = nextFeatureKey(cwd)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error: ${message}\\n\\nTip: run \"lac workspace init\" first to create the .lac/ workspace.\\n`)\n process.exit(1)\n }\n\n const title = titleFromProblem(problem)\n const wasTruncated = problem.trim().split(/\\s+/).length > 6\n\n let finalTitle = title\n if (wasTruncated) {\n process.stdout.write(`\\nTitle will be: \"${title}\"\\n`)\n const titleAnswer = await prompts({\n type: 'text',\n name: 'customTitle',\n message: 'Custom title? (leave blank to use the above)',\n initial: '',\n })\n if (titleAnswer.customTitle && (titleAnswer.customTitle as string).trim().length > 0) {\n finalTitle = (titleAnswer.customTitle as string).trim()\n }\n }\n\n const feature = {\n featureKey,\n title: finalTitle,\n status,\n problem,\n }\n\n // Validate before writing (belt-and-suspenders)\n const validation = validateFeature(feature)\n if (!validation.success) {\n process.stderr.write(\n `Internal error: generated feature did not pass validation:\\n ${validation.errors.join('\\n ')}\\n`,\n )\n process.exit(1)\n }\n\n await writeFile(featureJsonPath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n\n process.stdout.write(`✓ Created feature.json — ${featureKey}\\n`)\n })\n","import process from 'node:process'\n\nimport type { Feature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\ninterface FeatureNode {\n key: string\n status: string\n title: string\n children: FeatureNode[]\n}\n\n/**\n * Build a lineage tree rooted at the given featureKey.\n * Children are features that list this key as their lineage.parent.\n */\nfunction buildTree(\n key: string,\n byKey: Map<string, Feature>,\n childrenOf: Map<string, string[]>,\n visited = new Set<string>(),\n): FeatureNode {\n visited.add(key)\n const feature = byKey.get(key)\n const childKeys = childrenOf.get(key) ?? []\n\n const children: FeatureNode[] = []\n for (const ck of childKeys) {\n if (!visited.has(ck)) {\n children.push(buildTree(ck, byKey, childrenOf, visited))\n }\n }\n\n return {\n key,\n status: feature?.status ?? 'unknown',\n title: feature?.title ?? '(unknown)',\n children,\n }\n}\n\n/**\n * Render a FeatureNode tree as ASCII art.\n */\nfunction renderTree(node: FeatureNode, prefix = '', isLast = true): string[] {\n const connector = isLast ? '└── ' : '├── '\n const lines = [\n `${prefix}${prefix === '' ? '' : connector}${node.key} (${node.status}) — ${node.title}`,\n ]\n\n const childPrefix = prefix + (isLast ? ' ' : '│ ')\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]\n if (child) {\n const childLines = renderTree(child, childPrefix, i === node.children.length - 1)\n lines.push(...childLines)\n }\n }\n return lines\n}\n\nexport const lineageCommand = new Command('lineage')\n .description('Show the lineage tree (parent → key → children) for a feature')\n .argument('<key>', 'featureKey to inspect (e.g. feat-2026-001)')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .action(async (key: string, options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n const byKey = new Map<string, Feature>()\n const childrenOf = new Map<string, string[]>()\n\n for (const { feature } of features) {\n byKey.set(feature.featureKey, feature)\n const parent = feature.lineage?.parent\n if (parent) {\n const existing = childrenOf.get(parent) ?? []\n existing.push(feature.featureKey)\n childrenOf.set(parent, existing)\n }\n }\n\n if (!byKey.has(key)) {\n process.stderr.write(`Error: feature \"${key}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n\n // Find the root of the lineage chain\n let rootKey = key\n const seen = new Set<string>()\n while (true) {\n seen.add(rootKey)\n const feat = byKey.get(rootKey)\n const parent = feat?.lineage?.parent\n if (!parent || !byKey.has(parent) || seen.has(parent)) break\n rootKey = parent\n }\n\n const tree = buildTree(rootKey, byKey, childrenOf)\n const lines = renderTree(tree)\n\n process.stdout.write(lines.join('\\n') + '\\n')\n })\n","import fs from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport process from 'node:process'\n\nimport type { Feature } from '@life-as-code/feature-schema'\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { computeCompleteness, loadConfig } from '../lib/config.js'\nimport { scanFeatures } from '../lib/scanner.js'\n\ninterface LintResult {\n featureKey: string\n filePath: string\n status: string\n completeness: number\n missingRequired: string[]\n belowThreshold: boolean\n pass: boolean\n}\n\nfunction checkFeature(\n feature: Feature,\n filePath: string,\n requiredFields: string[],\n threshold: number,\n): LintResult {\n const raw = feature as unknown as Record<string, unknown>\n const completeness = computeCompleteness(raw)\n\n const missingRequired = requiredFields.filter((field) => {\n const val = raw[field]\n if (val === undefined || val === null || val === '') return true\n if (Array.isArray(val)) return val.length === 0\n return typeof val === 'string' && val.trim().length === 0\n })\n\n const belowThreshold = threshold > 0 && completeness < threshold\n\n return {\n featureKey: feature.featureKey,\n filePath,\n status: feature.status,\n completeness,\n missingRequired,\n belowThreshold,\n pass: missingRequired.length === 0 && !belowThreshold,\n }\n}\n\n/** Default placeholder values for auto-fix of missing required fields */\nconst FIELD_DEFAULTS: Record<string, unknown> = {\n problem: 'TODO: describe the problem this feature solves.',\n analysis: '',\n decisions: [],\n implementation: '',\n knownLimitations: [],\n tags: [],\n}\n\n/**\n * Auto-repair a feature file by inserting default values for missing required fields.\n * Returns the number of fields fixed, or 0 if nothing was changed.\n */\nasync function fixFeature(filePath: string, missingFields: string[]): Promise<number> {\n if (missingFields.length === 0) return 0\n\n let raw: string\n try {\n raw = await readFile(filePath, 'utf-8')\n } catch {\n return 0\n }\n\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>\n } catch {\n return 0\n }\n\n let fixed = 0\n for (const field of missingFields) {\n if (field in FIELD_DEFAULTS) {\n parsed[field] = FIELD_DEFAULTS[field]\n fixed++\n }\n }\n\n if (fixed === 0) return 0\n\n const validation = validateFeature(parsed)\n if (!validation.success) return 0\n\n await writeFile(filePath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n return fixed\n}\n\nexport const lintCommand = new Command('lint')\n .description('Check feature.json files for completeness and required fields')\n .argument('[dir]', 'Directory to scan (default: current directory)')\n .option('--require <fields>', 'Comma-separated required fields (overrides lac.config.json)')\n .option('--threshold <n>', 'Minimum completeness % required (overrides lac.config.json)', parseInt)\n .option('--quiet', 'Only print failures, suppress passing results')\n .option('--json', 'Output results as JSON')\n .option('--watch', 'Re-run lint on every feature.json change')\n .option('--fix', 'Auto-insert default values for missing required fields')\n .action(async (dir: string | undefined, options: {\n require?: string\n threshold?: number\n quiet?: boolean\n json?: boolean\n watch?: boolean\n fix?: boolean\n }) => {\n const scanDir = resolve(dir ?? process.cwd())\n const config = loadConfig(scanDir)\n\n const requiredFields = options.require\n ? options.require.split(',').map((f) => f.trim()).filter(Boolean)\n : config.requiredFields\n\n const threshold = options.threshold !== undefined ? options.threshold : config.ciThreshold\n\n async function runLint(): Promise<number> {\n // Scan\n let scanned: Awaited<ReturnType<typeof scanFeatures>>\n try {\n scanned = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n return 1\n }\n\n if (scanned.length === 0) {\n process.stdout.write(`No feature.json files found in \"${scanDir}\".\\n`)\n return 0\n }\n\n // Filter to lintable statuses\n const toCheck = scanned.filter(({ feature }) =>\n (config.lintStatuses as string[]).includes(feature.status),\n )\n\n const results = toCheck.map(({ feature, filePath }) =>\n checkFeature(feature, filePath, requiredFields, threshold),\n )\n\n // --fix: auto-repair missing required fields, then re-validate to confirm\n if (options.fix) {\n const toFix = results.filter((r) => !r.pass && r.missingRequired.length > 0)\n let totalFixed = 0\n for (const r of toFix) {\n const count = await fixFeature(r.filePath, r.missingRequired)\n if (count > 0) {\n totalFixed += count\n process.stdout.write(` 🔧 ${r.featureKey} fixed ${count} field${count === 1 ? '' : 's'} (${r.missingRequired.join(', ')})\\n`)\n }\n }\n\n if (totalFixed === 0 && toFix.length > 0) {\n process.stdout.write(` No fields could be auto-fixed (fields may not have default values).\\n`)\n return 1\n }\n if (toFix.length === 0) {\n process.stdout.write(` Nothing to fix — all required fields present.\\n`)\n return 0\n }\n\n // Re-scan and re-lint to confirm the fixes actually pass validation\n let rescanned: Awaited<ReturnType<typeof scanFeatures>>\n try {\n rescanned = await scanFeatures(scanDir)\n } catch {\n process.stdout.write(`\\n✓ Fixed ${totalFixed} field${totalFixed === 1 ? '' : 's'}. Could not re-validate — run \"lac lint\" to confirm.\\n`)\n return 0\n }\n const reFiltered = rescanned.filter(({ feature }) =>\n (config.lintStatuses as string[]).includes(feature.status),\n )\n const reResults = reFiltered.map(({ feature, filePath }) =>\n checkFeature(feature, filePath, requiredFields, threshold),\n )\n const stillFailing = reResults.filter((r) => !r.pass)\n if (stillFailing.length === 0) {\n process.stdout.write(`\\n✓ Fixed ${totalFixed} field${totalFixed === 1 ? '' : 's'} — all features now pass lint.\\n`)\n return 0\n }\n process.stdout.write(`\\n⚠ Fixed ${totalFixed} field${totalFixed === 1 ? '' : 's'} but ${stillFailing.length} feature${stillFailing.length === 1 ? '' : 's'} still fail lint:\\n`)\n for (const r of stillFailing) {\n process.stdout.write(` ✗ ${r.featureKey} missing: ${r.missingRequired.join(', ')}\\n`)\n }\n return 1\n }\n\n const failures = results.filter((r) => !r.pass)\n const passes = results.filter((r) => r.pass)\n\n if (options.json) {\n process.stdout.write(JSON.stringify({ results, failures: failures.length, passes: passes.length }, null, 2) + '\\n')\n return failures.length > 0 ? 1 : 0\n }\n\n // Human-readable output\n const col = (s: string, width: number) => s.slice(0, width).padEnd(width)\n\n if (!options.quiet || passes.length > 0) {\n if (!options.quiet) {\n for (const r of passes) {\n process.stdout.write(` ✓ ${col(r.featureKey, 18)} ${r.completeness.toString().padStart(3)}% ${r.status}\\n`)\n }\n }\n }\n\n for (const r of failures) {\n process.stdout.write(` ✗ ${col(r.featureKey, 18)} ${r.completeness.toString().padStart(3)}% ${r.status}\\n`)\n for (const field of r.missingRequired) {\n process.stdout.write(` missing required field: ${field}\\n`)\n }\n if (r.belowThreshold) {\n process.stdout.write(` completeness ${r.completeness}% is below threshold ${threshold}%\\n`)\n }\n }\n\n process.stdout.write(`\\n${passes.length} passed, ${failures.length} failed — ${results.length} features checked\\n`)\n\n if (failures.length > 0) {\n if (!options.quiet) {\n process.stdout.write(`\\nFailing features:\\n`)\n for (const r of failures) {\n process.stdout.write(` ${r.featureKey} → ${r.filePath}\\n`)\n }\n }\n return 1\n }\n\n return 0\n }\n\n if (options.watch) {\n process.stdout.write(`Watching \"${scanDir}\"...\\n\\n`)\n\n // Run immediately\n await runLint()\n\n let debounce: ReturnType<typeof setTimeout> | null = null\n fs.watch(scanDir, { recursive: true }, (_event, filename) => {\n if (!filename || !filename.toString().endsWith('feature.json')) return\n if (debounce) clearTimeout(debounce)\n debounce = setTimeout(async () => {\n process.stdout.write('\\x1Bc') // clear screen\n await runLint()\n process.stdout.write('\\nWatching for changes...\\n')\n }, 300)\n })\n\n // Keep process alive\n process.stdin.resume()\n\n process.on('SIGINT', () => {\n process.stdout.write('\\nStopping watch.\\n')\n process.exit(0)\n })\n } else {\n const exitCode = await runLint()\n process.exit(exitCode)\n }\n })\n","import { existsSync, statSync } from 'node:fs'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { join, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nexport const importCommand = new Command('import')\n .description('Import features from a JSON file (array of feature objects)')\n .argument('<file>', 'Path to a JSON file containing an array of feature objects')\n .option('-d, --dir <path>', 'Directory to create feature subdirectories in (default: cwd)')\n .option('--dry-run', 'Preview what would be created without writing files')\n .option('--skip-invalid', 'Skip invalid features instead of aborting')\n .action(\n async (\n file: string,\n options: { dir?: string; dryRun?: boolean; skipInvalid?: boolean },\n ) => {\n const filePath = resolve(file)\n const outDir = resolve(options.dir ?? process.cwd())\n const dryRun = options.dryRun ?? false\n const skipInvalid = options.skipInvalid ?? false\n\n // Read input file\n let raw: string\n try {\n raw = await readFile(filePath, 'utf-8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error reading \"${filePath}\": ${message}\\n`)\n process.exit(1)\n }\n\n // Parse JSON\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n process.stderr.write(`Error: \"${filePath}\" contains invalid JSON.\\n`)\n process.exit(1)\n }\n\n if (!Array.isArray(parsed)) {\n process.stderr.write(\n `Error: expected a JSON array of feature objects, got ${typeof parsed}.\\n`,\n )\n process.exit(1)\n }\n\n const features = parsed as unknown[]\n\n if (features.length === 0) {\n process.stdout.write(`No features in \"${filePath}\" — nothing to import.\\n`)\n process.exit(0)\n }\n\n process.stdout.write(`Found ${features.length} feature${features.length === 1 ? '' : 's'} in \"${file}\".\\n`)\n if (dryRun) {\n process.stdout.write(`Dry run — no files will be written.\\n\\n`)\n }\n\n let imported = 0\n let skipped = 0\n\n for (const item of features) {\n // Validate\n const result = validateFeature(item)\n\n if (!result.success) {\n const key = (item as Record<string, unknown>)?.['featureKey'] ?? '(unknown)'\n if (skipInvalid) {\n process.stderr.write(\n ` ⚠ ${key} — skipped (invalid): ${result.errors.join(', ')}\\n`,\n )\n skipped++\n continue\n } else {\n process.stderr.write(\n ` ✗ ${key} — validation failed:\\n` +\n result.errors.map((e) => ` ${e}`).join('\\n') +\n '\\n\\nAbort. Use --skip-invalid to continue past errors.\\n',\n )\n process.exit(1)\n }\n }\n\n const feature = result.data\n const featureDirName = feature.featureKey\n const featureDir = join(outDir, featureDirName)\n const featureFilePath = join(featureDir, 'feature.json')\n\n // Detect path conflicts before attempting to write.\n if (existsSync(featureDir) && !statSync(featureDir).isDirectory()) {\n process.stderr.write(\n ` ✗ ${feature.featureKey} — path \"${featureDirName}\" already exists as a file, not a directory.\\n`,\n )\n process.exit(1)\n }\n const alreadyExists = existsSync(featureFilePath)\n\n if (dryRun) {\n process.stdout.write(` Would ${alreadyExists ? 'overwrite' : 'create'}: ${featureDirName}/feature.json\\n`)\n imported++\n continue\n }\n\n if (alreadyExists) {\n process.stderr.write(` ⚠ ${feature.featureKey} — ${featureDirName}/feature.json already exists, overwriting\\n`)\n }\n\n try {\n await mkdir(featureDir, { recursive: true })\n await writeFile(\n featureFilePath,\n JSON.stringify(feature, null, 2) + '\\n',\n 'utf-8',\n )\n process.stdout.write(` ✓ ${feature.featureKey} → ${featureDirName}/feature.json\\n`)\n imported++\n } catch (err) {\n // Write/IO failures are never silently skipped by --skip-invalid —\n // that flag applies to schema validation errors only. An IO failure\n // (permissions, disk full, path conflict) is always fatal unless the\n // caller explicitly handles it.\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(` ✗ ${feature.featureKey} — write failed: ${message}\\n`)\n process.exit(1)\n }\n }\n\n process.stdout.write(`\\n`)\n if (dryRun) {\n process.stdout.write(`Would import ${imported} feature${imported === 1 ? '' : 's'}.\\n`)\n } else {\n const parts: string[] = []\n if (imported > 0) parts.push(`${imported} imported`)\n if (skipped > 0) parts.push(`${skipped} skipped`)\n process.stdout.write(`Done: ${parts.join(', ')}.\\n`)\n }\n },\n )\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { FEATURE_KEY_PATTERN, validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\n/**\n * Patches all featureJson files that reference oldKey in their lineage.parent\n * or lineage.children to use newKey instead.\n */\nasync function patchLineageRefs(\n features: Awaited<ReturnType<typeof scanFeatures>>,\n oldKey: string,\n newKey: string,\n): Promise<number> {\n let patched = 0\n\n for (const { feature, filePath } of features) {\n let changed = false\n const data = { ...feature } as Record<string, unknown>\n\n const lineage = data['lineage'] as\n | { parent?: string | null; children?: string[] }\n | undefined\n\n if (!lineage) continue\n\n const updatedLineage = { ...lineage }\n\n if (updatedLineage.parent === oldKey) {\n updatedLineage.parent = newKey\n changed = true\n }\n\n if (Array.isArray(updatedLineage.children) && updatedLineage.children.includes(oldKey)) {\n updatedLineage.children = updatedLineage.children.map((c) =>\n c === oldKey ? newKey : c,\n )\n changed = true\n }\n\n if (changed) {\n data['lineage'] = updatedLineage\n const validation = validateFeature(data)\n if (validation.success) {\n await writeFile(filePath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n patched++\n }\n }\n }\n\n return patched\n}\n\nexport const renameCommand = new Command('rename')\n .description('Rename a featureKey — updates the feature.json and patches all lineage references')\n .argument('<old-key>', 'Current featureKey (e.g. feat-2026-001)')\n .argument('<new-key>', 'New featureKey (e.g. feat-2026-099)')\n .option('-d, --dir <path>', 'Directory to scan for features (default: cwd)')\n .option('--dry-run', 'Preview changes without writing files')\n .action(\n async (\n oldKey: string,\n newKey: string,\n options: { dir?: string; dryRun?: boolean },\n ) => {\n // Validate new key format\n if (!FEATURE_KEY_PATTERN.test(newKey)) {\n process.stderr.write(\n `Error: \"${newKey}\" is not a valid featureKey.\\n` +\n `Keys must match the pattern <domain>-YYYY-NNN (e.g. feat-2026-099).\\n`,\n )\n process.exit(1)\n }\n\n if (oldKey === newKey) {\n process.stderr.write(`Error: old and new keys are identical (\"${oldKey}\").\\n`)\n process.exit(1)\n }\n\n const scanDir = options.dir ?? process.cwd()\n const dryRun = options.dryRun ?? false\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n // Find the target feature\n const target = features.find((f) => f.feature.featureKey === oldKey)\n if (!target) {\n process.stderr.write(`Error: feature \"${oldKey}\" not found in \"${scanDir}\".\\n`)\n process.exit(1)\n }\n\n // Check that newKey is not already taken\n const conflict = features.find((f) => f.feature.featureKey === newKey)\n if (conflict) {\n process.stderr.write(\n `Error: \"${newKey}\" is already used by \"${path.relative(scanDir, conflict.filePath)}\".\\n`,\n )\n process.exit(1)\n }\n\n // Find lineage refs that will be patched\n const lineageRefs = features.filter(({ feature }) => {\n const lineage = feature.lineage as\n | { parent?: string | null; children?: string[] }\n | undefined\n return (\n lineage?.parent === oldKey ||\n (Array.isArray(lineage?.children) && lineage.children.includes(oldKey))\n )\n })\n\n if (dryRun) {\n process.stdout.write(`Dry run — no files will be written.\\n\\n`)\n process.stdout.write(`Would rename: ${oldKey} → ${newKey}\\n`)\n process.stdout.write(` File: ${path.relative(scanDir, target.filePath)}\\n`)\n if (lineageRefs.length > 0) {\n process.stdout.write(`\\nWould patch ${lineageRefs.length} lineage reference(s):\\n`)\n for (const ref of lineageRefs) {\n process.stdout.write(` ${path.relative(scanDir, ref.filePath)}\\n`)\n }\n }\n return\n }\n\n // 1. Update the target feature.json\n const raw = await readFile(target.filePath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n parsed['featureKey'] = newKey\n\n const validation = validateFeature(parsed)\n if (!validation.success) {\n process.stderr.write(\n `Internal error: renamed feature failed validation:\\n ${validation.errors.join('\\n ')}\\n`,\n )\n process.exit(1)\n }\n\n await writeFile(\n target.filePath,\n JSON.stringify(validation.data, null, 2) + '\\n',\n 'utf-8',\n )\n process.stdout.write(`✓ Renamed ${oldKey} → ${newKey}\\n`)\n\n // 2. Patch lineage references in all other features\n const patched = await patchLineageRefs(features, oldKey, newKey)\n if (patched > 0) {\n process.stdout.write(`✓ Patched ${patched} lineage reference${patched === 1 ? '' : 's'}\\n`)\n }\n\n // 3. Update the .lac/keys registry so it stays in sync.\n // Walk up from scanDir to find the .lac/ directory.\n let lacDir: string | null = null\n let cur = path.resolve(scanDir)\n while (true) {\n const candidate = path.join(cur, '.lac')\n if (existsSync(candidate)) { lacDir = candidate; break }\n const parent = path.dirname(cur)\n if (parent === cur) break\n cur = parent\n }\n if (lacDir) {\n const keysPath = path.join(lacDir, 'keys')\n if (existsSync(keysPath)) {\n const lines = readFileSync(keysPath, 'utf-8').trim().split('\\n').filter(Boolean)\n const updated = lines.map((k) => (k === oldKey ? newKey : k))\n if (!updated.includes(newKey)) updated.push(newKey)\n writeFileSync(keysPath, updated.join('\\n') + '\\n', 'utf-8')\n process.stdout.write(`✓ Updated .lac/keys registry\\n`)\n }\n }\n },\n )\n","import process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\nexport const searchCommand = new Command('search')\n .description('Search features by keyword across key, title, problem, tags, and analysis')\n .argument('<query>', 'Search query (case-insensitive)')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .option('--json', 'Output results as JSON')\n .option('--field <fields>', 'Comma-separated fields to search (default: all)')\n .action(async (query: string, options: { dir?: string; json?: boolean; field?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n const features = await scanFeatures(scanDir)\n\n const searchFields = options.field\n ? options.field.split(',').map((f) => f.trim())\n : ['featureKey', 'title', 'problem', 'tags', 'analysis', 'implementation']\n\n const q = query.toLowerCase()\n\n const matches = features.filter(({ feature }) => {\n for (const field of searchFields) {\n const val = (feature as Record<string, unknown>)[field]\n if (val === undefined || val === null) continue\n if (typeof val === 'string' && val.toLowerCase().includes(q)) return true\n if (Array.isArray(val) && val.some((v) => typeof v === 'string' && v.toLowerCase().includes(q)))\n return true\n }\n return false\n })\n\n if (options.json) {\n process.stdout.write(JSON.stringify(matches.map((m) => m.feature), null, 2) + '\\n')\n return\n }\n\n if (matches.length === 0) {\n process.stdout.write(`No features found matching \"${query}\"\\n`)\n return\n }\n\n process.stdout.write(`Found ${matches.length} feature(s) matching \"${query}\":\\n\\n`)\n\n for (const { feature, filePath } of matches) {\n const statusIcon =\n ({ active: '⊙', draft: '◌', frozen: '❄', deprecated: '⊘' } as Record<string, string>)[\n feature.status\n ] ?? '?'\n process.stdout.write(` ${statusIcon} ${feature.featureKey.padEnd(18)} ${feature.title}\\n`)\n process.stdout.write(\n ` ${feature.problem.slice(0, 80)}${feature.problem.length > 80 ? '...' : ''}\\n`,\n )\n process.stdout.write(` ${filePath}\\n\\n`)\n }\n })\n","import { spawn } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport http from 'node:http'\nimport { resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { Command } from 'commander'\n\nfunction openBrowser(url: string): void {\n if (process.platform === 'darwin') {\n spawn('open', [url], { detached: true, stdio: 'ignore' }).unref()\n } else if (process.platform === 'win32') {\n spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' }).unref()\n } else {\n spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref()\n }\n}\n\nfunction waitForServer(port: number, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs\n return new Promise((res) => {\n function attempt() {\n const req = http.get(`http://127.0.0.1:${port}/health`, (response) => {\n res(response.statusCode === 200)\n })\n req.on('error', () => {\n if (Date.now() < deadline) {\n setTimeout(attempt, 250)\n } else {\n res(false)\n }\n })\n req.end()\n }\n attempt()\n })\n}\n\nfunction spawnServer(workspaceDir: string, port: number): ChildProcess {\n return spawn(\n 'lac-lsp',\n ['--http-only', '--workspace', workspaceDir, '--port', String(port)],\n { stdio: ['ignore', 'inherit', 'inherit'] },\n )\n}\n\nexport const serveCommand = new Command('serve')\n .description('Start the life-as-code HTTP server and open the dashboard in your browser')\n .argument('[dir]', 'Workspace root to index (default: current directory)')\n .option('-p, --port <n>', 'HTTP port (default: 7474)', '7474')\n .option('--no-open', 'Skip opening the browser automatically')\n .action(async (dir: string | undefined, options: { port: string; open: boolean }) => {\n const workspaceDir = resolve(dir ?? process.cwd())\n const port = parseInt(options.port, 10)\n const url = `http://127.0.0.1:${port}`\n\n process.stdout.write(\n `Starting lac-lsp HTTP server for workspace \"${workspaceDir}\" on port ${port}...\\n`,\n )\n\n // Mutable child ref — health monitor updates this on restart\n let child = spawnServer(workspaceDir, port)\n let shuttingDown = false\n\n child.on('error', (err) => {\n process.stderr.write(`Error: could not start lac-lsp — ${err.message}\\n`)\n process.stderr.write(\n `Make sure lac-lsp is installed: npm i -g @life-as-code/lac-lsp\\n`,\n )\n process.exit(1)\n })\n\n child.on('exit', (code) => {\n if (!shuttingDown) {\n // Will be handled by health monitor; only exit if already shutting down\n process.stderr.write(`lac-lsp exited with code ${code ?? 0}\\n`)\n }\n })\n\n // Wait up to 6 seconds for the server to respond\n const ready = await waitForServer(port, 6000)\n\n if (ready) {\n process.stdout.write(`\\nReady — ${url}\\n\\n`)\n process.stdout.write(` GET ${url}/features all indexed features\\n`)\n process.stdout.write(` GET ${url}/lint run lint against all features\\n`)\n process.stdout.write(` GET ${url}/events SSE stream of changes\\n\\n`)\n process.stdout.write(`Press Ctrl+C to stop.\\n`)\n\n if (options.open) {\n openBrowser(url)\n }\n } else {\n process.stderr.write(\n `Warning: server on port ${port} did not respond within 6 s — it may still be starting up.\\n`,\n )\n }\n\n // Health-monitor: every 15 s check if the server is still alive\n let failCount = 0\n const healthInterval = setInterval(async () => {\n if (shuttingDown) {\n clearInterval(healthInterval)\n return\n }\n\n const alive = await waitForServer(port, 2000)\n if (alive) {\n failCount = 0\n return\n }\n\n failCount++\n if (failCount >= 3) {\n process.stderr.write(\n `\\nWarning: lac-lsp appears to have crashed. Restarting...\\n`,\n )\n\n try {\n child.kill()\n } catch {\n // ignore — already dead\n }\n\n child = spawnServer(workspaceDir, port)\n failCount = 0\n\n child.on('error', (err) => {\n process.stderr.write(`Error restarting lac-lsp — ${err.message}\\n`)\n })\n\n // Wait for restart and re-open browser if needed\n const restarted = await waitForServer(port, 6000)\n if (restarted) {\n process.stdout.write(`lac-lsp restarted successfully.\\n`)\n if (options.open) {\n openBrowser(url)\n }\n } else {\n process.stderr.write(`Warning: lac-lsp did not come back up after restart.\\n`)\n }\n }\n }, 15_000)\n\n // Keep the process alive and clean up on SIGINT\n process.on('SIGINT', () => {\n shuttingDown = true\n clearInterval(healthInterval)\n process.stdout.write('\\nShutting down...\\n')\n child.kill('SIGTERM')\n process.exit(0)\n })\n })\n","import { existsSync } from 'node:fs'\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\nimport prompts from 'prompts'\n\nimport { nextFeatureKey } from '../lib/featureKey.js'\nimport { scanFeatures } from '../lib/scanner.js'\n\nconst LAC_DIR = '.lac'\n\n/**\n * Walk up from startDir looking for a .lac/ directory.\n * Returns the absolute path to the workspace root (the dir containing .lac/) or null.\n */\nfunction findWorkspaceRoot(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, LAC_DIR)\n if (existsSync(candidate)) return current\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\n/**\n * Derives a short title from a problem statement by taking the first 6 words\n * and appending \"...\" when truncated.\n */\nfunction titleFromProblem(problem: string): string {\n const words = problem.trim().split(/\\s+/)\n if (words.length <= 6) return problem.trim()\n return words.slice(0, 6).join(' ') + '...'\n}\n\n/**\n * Slugifies the first 3 words of a string into kebab-case for use as a\n * default subdirectory name.\n */\nfunction slugifyProblem(problem: string): string {\n return problem\n .trim()\n .split(/\\s+/)\n .slice(0, 3)\n .join('-')\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '')\n}\n\nexport const spawnCommand = new Command('spawn')\n .description('Spawn a child feature from an existing parent feature')\n .argument('<parent-key>', 'featureKey of the parent feature (e.g. feat-2026-001)')\n .option('--reason <text>', 'Reason for spawning (default: empty)')\n .option('--dir <name>', 'Subdirectory name under parent dir (default: slug of problem)')\n .action(async (parentKey: string, options: { reason?: string; dir?: string }) => {\n const cwd = process.cwd()\n\n // 1. Find workspace root\n const workspaceRoot = findWorkspaceRoot(cwd)\n if (!workspaceRoot) {\n process.stderr.write(\n `Error: No .lac/ workspace found in current directory or any parent.\\n` +\n `Run \"lac workspace init\" to create one.\\n`,\n )\n process.exit(1)\n }\n\n // 2. Scan workspace for the parent feature\n let scanned: Awaited<ReturnType<typeof scanFeatures>>\n try {\n scanned = await scanFeatures(workspaceRoot)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning workspace \"${workspaceRoot}\": ${message}\\n`)\n process.exit(1)\n }\n\n const parentEntry = scanned.find((s) => s.feature.featureKey === parentKey)\n if (!parentEntry) {\n process.stderr.write(\n `Error: parent feature \"${parentKey}\" not found in workspace.\\n` +\n `Run \"lac blame\" or check your .lac/keys file to see available feature keys.\\n`,\n )\n process.exit(1)\n }\n\n const parentDir = dirname(parentEntry.filePath)\n\n // 3. Prompt only for problem and status (the two required interactive prompts)\n const promptList: prompts.PromptObject[] = [\n {\n type: 'text',\n name: 'problem',\n message: 'What problem does this new feature solve?',\n validate: (value: string) =>\n value.trim().length > 0 ? true : 'Problem statement is required',\n },\n {\n type: 'select',\n name: 'status',\n message: 'Status?',\n choices: [\n { title: 'draft', value: 'draft' },\n { title: 'active', value: 'active' },\n ],\n initial: 0,\n },\n ]\n\n const answers = await prompts(promptList, {\n onCancel: () => {\n process.stderr.write('Aborted.\\n')\n process.exit(1)\n },\n })\n\n const reason = options.reason ?? ''\n const problem = (answers.problem as string).trim()\n const status = answers.status as 'draft' | 'active'\n\n // 4. Determine subdirectory for the new feature.\n // If the caller supplied --dir, use it exactly.\n // Otherwise derive from the problem slug, but detect collisions and\n // append an incrementing suffix so two features with the same first\n // 3 words never land in the same directory.\n let subdirName: string\n if (options.dir) {\n subdirName = options.dir\n } else {\n const baseSlug = slugifyProblem(problem)\n subdirName = baseSlug\n let suffix = 1\n while (existsSync(join(parentDir, subdirName))) {\n suffix++\n subdirName = `${baseSlug}-${suffix}`\n }\n }\n\n const newFeatureDir = join(parentDir, subdirName)\n\n // 5. Create the directory if it doesn't exist\n await mkdir(newFeatureDir, { recursive: true })\n\n // 6. Generate featureKey from the workspace root\n let featureKey: string\n try {\n featureKey = nextFeatureKey(newFeatureDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error: ${message}\\n\\nTip: run \"lac workspace init\" first to create the .lac/ workspace.\\n`)\n process.exit(1)\n }\n\n const title = titleFromProblem(problem)\n\n // 7. Build the feature object with lineage\n const feature = {\n featureKey,\n title,\n status,\n problem,\n lineage: {\n parent: parentKey,\n ...(reason ? { spawnReason: reason } : {}),\n },\n }\n\n // 8. Validate before writing\n const validation = validateFeature(feature)\n if (!validation.success) {\n process.stderr.write(\n `Internal error: generated feature did not pass validation:\\n ${validation.errors.join('\\n ')}\\n`,\n )\n process.exit(1)\n }\n\n const featureJsonPath = join(newFeatureDir, 'feature.json')\n await writeFile(featureJsonPath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n\n // 9. Print success\n process.stdout.write(`✓ Spawned ${featureKey} from ${parentKey} in ${newFeatureDir}\\n`)\n })\n","import process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { computeCompleteness } from '../lib/config.js'\nimport { scanFeatures } from '../lib/scanner.js'\n\nexport const statCommand = new Command('stat')\n .description('Show workspace statistics: feature counts, status breakdown, completeness, top tags')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .action(async (options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n const total = features.length\n\n if (total === 0) {\n process.stdout.write(`No features found in \"${scanDir}\".\\n`)\n process.stdout.write(`Run \"lac init\" in a subdirectory to create your first feature.\\n`)\n return\n }\n\n // Status breakdown\n const statusBreakdown: Record<string, number> = {\n active: 0,\n draft: 0,\n frozen: 0,\n deprecated: 0,\n }\n for (const { feature } of features) {\n const s = feature.status\n statusBreakdown[s] = (statusBreakdown[s] ?? 0) + 1\n }\n\n // Completeness\n const completenessValues = features.map(({ feature }) =>\n computeCompleteness(feature as unknown as Record<string, unknown>),\n )\n const avgCompleteness =\n completenessValues.length > 0\n ? Math.round(completenessValues.reduce((a, b) => a + b, 0) / completenessValues.length)\n : 0\n\n // Features with 0 decisions\n const zeroDecisions = features.filter(\n ({ feature }) => !feature.decisions || feature.decisions.length === 0,\n ).length\n\n // Features with 0 tags\n const zeroTags = features.filter(\n ({ feature }) => !feature.tags || feature.tags.length === 0,\n ).length\n\n // Top 5 tags\n const tagCounts = new Map<string, number>()\n for (const { feature } of features) {\n for (const tag of feature.tags ?? []) {\n tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1)\n }\n }\n const topTags = Array.from(tagCounts.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5)\n\n const lines: string[] = []\n lines.push('lac stat — workspace statistics')\n lines.push('================================')\n lines.push('')\n lines.push(`Total features : ${total}`)\n lines.push('')\n lines.push('By status:')\n for (const [status, count] of Object.entries(statusBreakdown)) {\n if (count > 0) {\n lines.push(` ${status.padEnd(12)}: ${count}`)\n }\n }\n lines.push('')\n lines.push(`Avg completeness : ${avgCompleteness}%`)\n lines.push(`No decisions : ${zeroDecisions}`)\n lines.push(`No tags : ${zeroTags}`)\n\n if (topTags.length > 0) {\n lines.push('')\n lines.push('Top 5 tags:')\n for (const [tag, count] of topTags) {\n lines.push(` ${tag.padEnd(20)}: ${count}`)\n }\n }\n\n process.stdout.write(lines.join('\\n') + '\\n')\n })\n","import { readFile, writeFile } from 'node:fs/promises'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\nexport const tagCommand = new Command('tag')\n .description('Add or remove tags on a feature')\n .argument('<key>', 'featureKey to tag (e.g. feat-2026-001)')\n .argument('<tags>', 'Comma-separated tags to add (prefix with - to remove, e.g. \"auth,-legacy,api\")')\n .option('-d, --dir <path>', 'Directory to scan for features (default: cwd)')\n .action(async (key: string, tags: string, options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n const features = await scanFeatures(scanDir)\n const found = features.find(f => f.feature.featureKey === key)\n\n if (!found) {\n process.stderr.write(`Error: feature \"${key}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n\n const tagList = tags.split(',').map(t => t.trim()).filter(Boolean)\n const toAdd = tagList.filter(t => !t.startsWith('-'))\n const toRemove = tagList.filter(t => t.startsWith('-')).map(t => t.slice(1))\n\n const current = found.feature.tags ?? []\n\n // Warn about tags that already exist (when adding) or don't exist (when removing)\n for (const tag of toAdd) {\n if (current.includes(tag)) {\n process.stdout.write(`Note: tag \"${tag}\" already present\\n`)\n }\n }\n for (const tag of toRemove) {\n if (!current.includes(tag)) {\n process.stdout.write(`Note: tag \"${tag}\" was not present\\n`)\n }\n }\n\n const updated = [...new Set([...current.filter(t => !toRemove.includes(t)), ...toAdd])]\n\n const raw = await readFile(found.filePath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n parsed['tags'] = updated\n\n const validation = validateFeature(parsed)\n if (!validation.success) {\n process.stderr.write(`Validation error: ${validation.errors.join(', ')}\\n`)\n process.exit(1)\n }\n\n await writeFile(found.filePath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n process.stdout.write(`✓ ${key} tags: [${updated.join(', ')}]\\n`)\n })\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { computeCompleteness } from '../lib/config.js'\nimport { scanFeatures } from '../lib/scanner.js'\n\nconst LAC_DIR = '.lac'\nconst COUNTER_FILE = 'counter'\n\n/**\n * Walk up from startDir looking for a .lac/ directory.\n * Returns the absolute path to .lac/ or null if not found.\n */\nfunction findLacDir(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, LAC_DIR)\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nexport const workspaceCommand = new Command('workspace')\n .description('Manage the lac workspace (.lac/ directory)')\n\nworkspaceCommand\n .command('init')\n .description('Initialise a lac workspace in the current directory')\n .argument('[dir]', 'Directory to initialise (default: current directory)')\n .option('--force', 'Re-initialise even if a .lac/ directory already exists')\n .action((dir: string | undefined, options: { force?: boolean }) => {\n const targetDir = resolve(dir ?? process.cwd())\n const lacDir = join(targetDir, LAC_DIR)\n const counterPath = join(lacDir, COUNTER_FILE)\n\n if (existsSync(lacDir) && !options.force) {\n process.stdout.write(\n `lac workspace already initialised at \"${lacDir}\".\\n` +\n `Run \"lac workspace init --force\" to reinitialise.\\n`,\n )\n return\n }\n\n mkdirSync(lacDir, { recursive: true })\n\n if (!existsSync(counterPath) || options.force) {\n const year = new Date().getFullYear()\n writeFileSync(counterPath, `${year}\\n0\\n`, 'utf-8')\n }\n\n process.stdout.write(`✓ Initialised lac workspace at \"${lacDir}\"\\n`)\n process.stdout.write(` Run \"lac init\" inside a feature folder to create a feature.json.\\n`)\n })\n\nworkspaceCommand\n .command('status')\n .description('Show workspace info (location, counter, next key, feature stats)')\n .action(async () => {\n const lacDir = findLacDir(process.cwd())\n\n if (!lacDir) {\n process.stdout.write(\n `No .lac/ workspace found in current directory or any parent.\\n` +\n `Run \"lac workspace init\" to create one.\\n`,\n )\n return\n }\n\n const counterPath = join(lacDir, COUNTER_FILE)\n if (!existsSync(counterPath)) {\n process.stdout.write(`Workspace : ${lacDir}\\nCounter : not initialised\\n`)\n return\n }\n\n const raw = readFileSync(counterPath, 'utf-8').trim()\n const lines = raw.split('\\n').map((l) => l.trim())\n const year = lines[0] ?? '?'\n const counterStr = lines[1] ?? '0'\n const counter = parseInt(counterStr, 10)\n const next = isNaN(counter) ? '001' : String(counter + 1).padStart(3, '0')\n\n process.stdout.write(`Workspace : ${lacDir}\\n`)\n process.stdout.write(`Counter : ${year}/${counterStr}\\n`)\n process.stdout.write(`Next key : feat-${year}-${next}\\n`)\n\n // Scan for features and show stats\n const workspaceRoot = resolve(lacDir, '..')\n try {\n const features = await scanFeatures(workspaceRoot)\n process.stdout.write(`Features : ${features.length}\\n`)\n\n if (features.length === 0) {\n process.stdout.write(`\\nNo features found. Run \"lac init\" in a subdirectory to create your first feature.\\n`)\n } else {\n const completenessValues = features.map(({ feature }) =>\n computeCompleteness(feature as unknown as Record<string, unknown>),\n )\n const avg = Math.round(\n completenessValues.reduce((a, b) => a + b, 0) / completenessValues.length,\n )\n process.stdout.write(`Avg compl.: ${avg}%\\n`)\n }\n } catch {\n // Non-fatal: just skip feature stats\n }\n })\n","import { Command } from 'commander'\nimport { archiveCommand } from './commands/archive.js'\nimport { blameCommand } from './commands/blame.js'\nimport { diffCommand } from './commands/diff.js'\nimport { doctorCommand } from './commands/doctor.js'\nimport { exportCommand } from './commands/export.js'\nimport { hooksCommand } from './commands/hooks.js'\nimport { initCommand } from './commands/init.js'\nimport { lineageCommand } from './commands/lineage.js'\nimport { lintCommand } from './commands/lint.js'\nimport { importCommand } from './commands/import.js'\nimport { renameCommand } from './commands/rename.js'\nimport { searchCommand } from './commands/search.js'\nimport { serveCommand } from './commands/serve.js'\nimport { spawnCommand } from './commands/spawn.js'\nimport { statCommand } from './commands/stat.js'\nimport { tagCommand } from './commands/tag.js'\nimport { workspaceCommand } from './commands/workspace.js'\n\nconst program = new Command()\nprogram\n .name('lac')\n .description('life-as-code CLI — provenance for your features')\n .version('0.1.0')\n\nprogram.addCommand(workspaceCommand)\nprogram.addCommand(spawnCommand)\nprogram.addCommand(initCommand)\nprogram.addCommand(exportCommand)\nprogram.addCommand(lintCommand)\nprogram.addCommand(blameCommand)\nprogram.addCommand(hooksCommand)\nprogram.addCommand(serveCommand)\nprogram.addCommand(tagCommand)\nprogram.addCommand(archiveCommand)\nprogram.addCommand(doctorCommand)\nprogram.addCommand(searchCommand)\nprogram.addCommand(statCommand)\nprogram.addCommand(lineageCommand)\nprogram.addCommand(diffCommand)\nprogram.addCommand(renameCommand)\nprogram.addCommand(importCommand)\n\nprogram.parse()\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,eAAsB,aAAa,KAAwC;CACzE,MAAM,UAA4B,EAAE;CAEpC,eAAe,KAAK,YAAmC;EACrD,IAAI;EAGJ,IAAI;AACJ,MAAI;AACF,gBAAa,MAAM,QAAQ,YAAY,EAAE,eAAe,MAAM,CAAC;WACxD,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,WAAQ,OAAO,MAAM,sCAAsC,WAAW,KAAK,QAAQ,IAAI;AACvF;;AAKF,YAAU,WAAW,KAAK,OAAO;GAC/B,MAAM,OAAO,EAAE,KAAK;GACpB,mBAAmB,EAAE,aAAa;GAClC,cAAc,EAAE,QAAQ;GACzB,EAAE;AAEH,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,WAAW,KAAK,YAAY,MAAM,KAAK;AAE7C,OAAI,MAAM,aAAa,EAAE;AAEvB,QAAI,MAAM,KAAK,WAAW,IAAI,IAAI,MAAM,SAAS,eAC/C;AAEF,UAAM,KAAK,SAAS;cACX,MAAM,QAAQ,IAAI,MAAM,SAAS,gBAAgB;IAC1D,IAAI;AACJ,QAAI;AACF,WAAM,MAAM,SAAS,UAAU,QAAQ;aAChC,KAAK;KACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,aAAQ,OAAO,MAAM,4BAA4B,SAAS,KAAK,QAAQ,IAAI;AAC3E;;IAGF,IAAI;AACJ,QAAI;AACF,cAAS,KAAK,MAAM,IAAI;YAClB;AACN,aAAQ,OAAO,MAAM,6BAA6B,SAAS,gBAAgB;AAC3E;;IAGF,MAAM,SAAS,gBAAgB,OAAO;AACtC,QAAI,CAAC,OAAO,SAAS;AACnB,aAAQ,OAAO,MACb,aAAa,SAAS,oCAAoC,OAAO,OAAO,KAAK,OAAO,CAAC,IACtF;AACD;;AAGF,YAAQ,KAAK;KAAE,UAAU;KAAU,SAAS,OAAO;KAAM,CAAC;;;;AAKhE,OAAM,KAAK,IAAI;AACf,QAAO;;;;;ACzET,MAAa,iBAAiB,IAAI,QAAQ,UAAU,CACjD,YAAY,0CAA0C,CACtD,SAAS,SAAS,6CAA6C,CAC/D,OAAO,oBAAoB,mCAAmC,CAC9D,OAAO,OAAO,KAAa,YAA8B;CACxD,MAAM,UAAU,QAAQ,OAAOA,UAAQ,KAAK;CAE5C,MAAM,SADW,MAAM,aAAa,QAAQ,EACrB,MAAK,MAAK,EAAE,QAAQ,eAAe,IAAI;AAE9D,KAAI,CAAC,OAAO;AACV,YAAQ,OAAO,MAAM,mBAAmB,IAAI,kBAAkB,QAAQ,KAAK;AAC3E,YAAQ,KAAK,EAAE;;AAGjB,KAAI,MAAM,QAAQ,WAAW,cAAc;AACzC,YAAQ,OAAO,MAAM,uBAAuB,IAAI,IAAI;AACpD,YAAQ,KAAK,EAAE;;CAGjB,MAAM,MAAM,MAAM,SAAS,MAAM,UAAU,QAAQ;CACnD,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAO,YAAY;CAEnB,MAAM,aAAa,gBAAgB,OAAO;AAC1C,KAAI,CAAC,WAAW,SAAS;AACvB,YAAQ,OAAO,MAAM,qBAAqB,WAAW,OAAO,KAAK,KAAK,CAAC,IAAI;AAC3E,YAAQ,KAAK,EAAE;;AAGjB,OAAM,UAAU,MAAM,UAAU,KAAK,UAAU,WAAW,MAAM,MAAM,EAAE,GAAG,MAAM,QAAQ;AACzF,WAAQ,OAAO,MAAM,KAAK,IAAI,mCAAmC;EACjE;;;;;;;;AChCJ,SAAgBC,yBAAuB,UAAiC;CACtE,IAAI,UAAU,QAAQ,SAAS;AAC/B,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,SAAS,eAAe;AAC/C,MAAI,WAAW,UAAU,CAAE,QAAO;EAClC,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;;;;;;AAQd,SAAgB,WAAW,UAAiC;CAC1D,IAAI,UAAU,QAAQ,SAAS;AAC/B,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,SAAS,OAAO;AACvC,MAAI,WAAW,UAAU,CAAE,QAAO;EAClC,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;;;;;;AAQd,SAAgB,cAAc,UAAiC;CAC7D,IAAI,UAAU,QAAQ,SAAS;AAC/B,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,SAAS,kBAAkB;AAClD,MAAI,WAAW,UAAU,CAAE,QAAO;EAClC,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;;;;;ACfd,MAAM,WAAgC;CACpC,SAAS;CACT,gBAAgB,CAAC,UAAU;CAC3B,aAAa;CACb,cAAc,CAAC,UAAU,QAAQ;CACjC,QAAQ;CACT;AAED,SAAgB,WAAW,SAAuC;CAEhE,MAAM,aAAa,cADF,WAAWC,UAAQ,KAAK,CACC;AAC1C,KAAI,CAAC,WAAY,QAAO,EAAE,GAAG,UAAU;AAEvC,KAAI;EACF,MAAM,MAAM,aAAa,YAAY,QAAQ;EAC7C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,SAAO;GACL,SAAS,OAAO,WAAW,SAAS;GACpC,gBAAgB,OAAO,kBAAkB,SAAS;GAClD,aAAa,OAAO,eAAe,SAAS;GAC5C,cAAc,OAAO,gBAAgB,SAAS;GAC9C,QAAQ,OAAO,UAAU,SAAS;GACnC;SACK;AACN,YAAQ,OAAO,MAAM,gDAAgD,WAAW,sBAAsB;AACtG,SAAO,EAAE,GAAG,UAAU;;;;AAK1B,MAAa,kBAAqC;CAChD;CACA;CACA;CACA;CACA;CACA;CACD;AAUD,SAAgB,oBAAoB,SAA0C;CAC5E,MAAM,SAAS,gBAAgB,QAAQ,UAAU;EAC/C,MAAM,MAAM,QAAQ;AACpB,MAAI,QAAQ,UAAa,QAAQ,QAAQ,QAAQ,GAAI,QAAO;AAC5D,MAAI,MAAM,QAAQ,IAAI,CAAE,QAAO,IAAI,SAAS;AAC5C,SAAO,OAAO,QAAQ,YAAY,IAAI,MAAM,CAAC,SAAS;GACtD,CAAC;AACH,QAAO,KAAK,MAAO,SAAS,gBAAgB,SAAU,IAAI;;;;;ACzE5D,MAAa,eAAe,IAAI,QAAQ,QAAQ,CAC7C,YAAY,yCAAyC,CACrD,SAAS,UAAU,kEAAkE,CACrF,QAAQ,YAAoB;CAE3B,MAAM,WAAW,QAAQ,QAAQ,SAAS,GAAG;CAI7C,MAAM,kBAAkBC,yBAFP,QADD,QAAQ,SAAS,CACA,CAEuB;AAExD,KAAI,CAAC,iBAAiB;AACpB,YAAQ,OAAO,MACb,8BAA8B,SAAS,2DACxC;AACD,YAAQ,KAAK,EAAE;;CAGjB,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,iBAAiB,QAAQ;UACrC,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,kBAAkB,gBAAgB,KAAK,QAAQ,IAAI;AACxE,YAAQ,KAAK,EAAE;;CAGjB,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AACN,YAAQ,OAAO,MAAM,WAAW,gBAAgB,4BAA4B;AAC5E,YAAQ,KAAK,EAAE;;CAGjB,MAAM,SAAS,gBAAgB,OAAO;AACtC,KAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,OAAO,MAAM,WAAW,gBAAgB,0BAA0B,OAAO,OAAO,KAAK,OAAO,CAAC,IAAI;AACzG,YAAQ,KAAK,EAAE;;CAGjB,MAAM,IAAI,OAAO;CAQjB,MAAM,OANqC;EACzC,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,YAAY;EACb,CACuB,EAAE,WAAW;CACrC,MAAM,eAAe,oBAAoB,EAAwC;CACjF,MAAM,MAAM,IAAI,OAAO,KAAK,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,OAAO,KAAK,KAAK,MAAM,eAAe,GAAG,CAAC;AAEtG,WAAQ,OAAO,MAAM,KAAK;AAC1B,WAAQ,OAAO,MAAM,iBAAiB,EAAE,WAAW,IAAI;AACvD,WAAQ,OAAO,MAAM,iBAAiB,EAAE,MAAM,IAAI;AAClD,WAAQ,OAAO,MAAM,iBAAiB,KAAK,IAAI,EAAE,OAAO,IAAI;AAC5D,WAAQ,OAAO,MAAM,kBAAkB,IAAI,IAAI,aAAa,KAAK;AACjE,WAAQ,OAAO,MAAM,iBAAiB,gBAAgB,IAAI;AAC1D,WAAQ,OAAO,MAAM,KAAK;AAC1B,WAAQ,OAAO,MAAM,mBAAmB,EAAE,QAAQ,IAAI;AAEtD,KAAI,EAAE,UAAU;EACd,MAAM,UAAU,EAAE,SAAS,SAAS,MAAM,EAAE,SAAS,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE;AAC7E,YAAQ,OAAO,MAAM,sBAAsB,QAAQ,IAAI;;AAGzD,KAAI,EAAE,aAAa,EAAE,UAAU,SAAS,GAAG;AACzC,YAAQ,OAAO,MAAM,kBAAkB,EAAE,UAAU,OAAO,MAAM;AAChE,OAAK,MAAM,KAAK,EAAE,UAChB,WAAQ,OAAO,MAAM,SAAS,EAAE,SAAS,qBAAqB,EAAE,UAAU,IAAI;;AAIlF,KAAI,EAAE,oBAAoB,EAAE,iBAAiB,SAAS,GAAG;AACvD,YAAQ,OAAO,MAAM,2BAA2B;AAChD,OAAK,MAAM,OAAO,EAAE,iBAClB,WAAQ,OAAO,MAAM,SAAS,IAAI,IAAI;;AAI1C,KAAI,EAAE,SAAS,OACb,WAAQ,OAAO,MAAM,4BAA4B,EAAE,QAAQ,OAAO,IAAI;AAGxE,WAAQ,OAAO,MAAM,KAAK;EAC1B;;;;;;;;ACtFJ,SAAS,gBAAgB,KAAsB;AAC7C,KAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO,KAAK,UAAU,IAAI;AACvE,KAAI,MAAM,QAAQ,IAAI,CAAE,QAAO,IAAI,IAAI,IAAI,gBAAgB,CAAC,KAAK,IAAI,CAAC;AAItE,QAAO,IAHQ,OAAO,KAAK,IAA+B,CACvD,MAAM,CACN,KAAK,MAAM,GAAG,KAAK,UAAU,EAAE,CAAC,GAAG,gBAAiB,IAAgC,GAAG,GAAG,CAC3E,KAAK,IAAI,CAAC;;AAG9B,SAAS,YAAY,KAAsB;AACzC,KAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO;AAC9C,KAAI,OAAO,QAAQ,SAAU,QAAO,IAAI,SAAS,KAAK,IAAI,MAAM,GAAG,GAAG,GAAG,QAAQ;AACjF,QAAO,KAAK,UAAU,IAAI;;AAG5B,MAAa,cAAc,IAAI,QAAQ,OAAO,CAC3C,YAAY,sCAAsC,CAClD,SAAS,UAAU,mBAAmB,CACtC,SAAS,UAAU,oBAAoB,CACvC,OAAO,oBAAoB,mCAAmC,CAC9D,OAAO,OAAO,MAAc,MAAc,YAA8B;CACvE,MAAM,UAAU,QAAQ,OAAOC,UAAQ,KAAK;CAE5C,IAAI;AACJ,KAAI;AACF,aAAW,MAAM,aAAa,QAAQ;UAC/B,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,mBAAmB,QAAQ,KAAK,QAAQ,IAAI;AACjE,YAAQ,KAAK,EAAE;;CAGjB,MAAM,KAAK,SAAS,MAAM,MAAM,EAAE,QAAQ,eAAe,KAAK;CAC9D,MAAM,KAAK,SAAS,MAAM,MAAM,EAAE,QAAQ,eAAe,KAAK;AAE9D,KAAI,CAAC,IAAI;AACP,YAAQ,OAAO,MAAM,mBAAmB,KAAK,kBAAkB,QAAQ,KAAK;AAC5E,YAAQ,KAAK,EAAE;;AAEjB,KAAI,CAAC,IAAI;AACP,YAAQ,OAAO,MAAM,mBAAmB,KAAK,kBAAkB,QAAQ,KAAK;AAC5E,YAAQ,KAAK,EAAE;;CAGjB,MAAM,OAAO,GAAG;CAChB,MAAM,OAAO,GAAG;CAEhB,MAAM,UAAU,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,KAAK,EAAE,GAAG,OAAO,KAAK,KAAK,CAAC,CAAC;CAErE,MAAM,QAAkB,EAAE;AAC1B,OAAM,KAAK,QAAQ,KAAK,KAAK,OAAO;AACpC,OAAM,KAAK,IAAI,OAAO,GAAG,CAAC;CAE1B,IAAI,WAAW;AACf,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,KAAK,KAAK;EAChB,MAAM,KAAK,KAAK;AAKhB,MAHW,gBAAgB,GAAG,KACnB,gBAAgB,GAAG,CAEf;AACf,aAAW;AAEX,MAAI,OAAO,OACT,OAAM,KAAK,KAAK,MAAM,IAAI,YAAY,GAAG,GAAG;WACnC,OAAO,OAChB,OAAM,KAAK,KAAK,MAAM,IAAI,YAAY,GAAG,GAAG;OACvC;AACL,SAAM,KAAK,KAAK,MAAM,GAAG;AACzB,SAAM,KAAK,YAAY,YAAY,GAAG,GAAG;AACzC,SAAM,KAAK,YAAY,YAAY,GAAG,GAAG;;;AAI7C,KAAI,CAAC,SACH,OAAM,KAAK,mBAAmB;AAGhC,WAAQ,OAAO,MAAM,MAAM,KAAK,KAAK,GAAG,KAAK;EAC7C;;;;;ACzEJ,SAASC,aAAW,SAAgC;CAClD,IAAI,UAAU,KAAK,QAAQ,QAAQ;AACnC,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,KAAK,SAAS,OAAO;AAC5C,MAAI;AACF,OAAI,SAAS,UAAU,CAAC,aAAa,CAAE,QAAO;UACxC;EACR,MAAM,SAAS,KAAK,QAAQ,QAAQ;AACpC,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;;;AAKd,eAAe,iBACb,YACoF;CACpF,IAAI,aAAa;CACjB,MAAM,eAA8D,EAAE;CAEtE,eAAe,KAAK,KAA4B;EAE9C,IAAI;AACJ,MAAI;AACF,gBAAa,MAAM,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;UAClD;AACN;;EAIF,MAAM,UAAU,WAAW,KAAK,OAAO;GACrC,MAAM,OAAO,EAAE,KAAK;GACpB,mBAAmB,EAAE,aAAa;GAClC,cAAc,EAAE,QAAQ;GACzB,EAAE;AAEH,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,WAAW,KAAK,KAAK,MAAM,KAAK;AAEtC,OAAI,MAAM,aAAa,EAAE;AACvB,QAAI,MAAM,KAAK,WAAW,IAAI,IAAI,MAAM,SAAS,eAAgB;AACjE,UAAM,KAAK,SAAS;cACX,MAAM,QAAQ,IAAI,MAAM,SAAS,gBAAgB;IAC1D,IAAI;AACJ,QAAI;AACF,WAAM,MAAM,SAAS,UAAU,QAAQ;YACjC;AACN,kBAAa,KAAK;MAAE,UAAU;MAAU,QAAQ,CAAC,sBAAsB;MAAE,CAAC;AAC1E;;IAGF,IAAI;AACJ,QAAI;AACF,cAAS,KAAK,MAAM,IAAI;YAClB;AACN,kBAAa,KAAK;MAAE,UAAU;MAAU,QAAQ,CAAC,eAAe;MAAE,CAAC;AACnE;;IAGF,MAAM,SAAS,gBAAgB,OAAO;AACtC,QAAI,CAAC,OAAO,QACV,cAAa,KAAK;KAAE,UAAU;KAAU,QAAQ,OAAO;KAAQ,CAAC;QAEhE;;;;AAMR,OAAM,KAAK,WAAW;AACtB,QAAO;EAAE,OAAO;EAAY,SAAS;EAAc;;AAKrD,MAAa,gBAAgB,IAAI,QAAQ,SAAS,CAC/C,YAAY,+CAA+C,CAC3D,SAAS,SAAS,oCAAoC,CACtD,OAAO,OAAO,QAA4B;CACzC,MAAM,WAAW,MAAM,KAAK,QAAQ,IAAI,GAAGC,UAAQ,KAAK;CAExD,IAAI,SAAS;CACb,IAAI,SAAS;CACb,IAAI,SAAS;CAEb,MAAM,SAAmB,EAAE;AAE3B,QAAO,KAAK,qCAAqC;AACjD,QAAO,KAAK,sCAAsC;AAClD,QAAO,KAAK,GAAG;CAGf,IAAI,SAAwB;AAC5B,KAAI;AACF,WAASD,aAAW,SAAS;SACvB;AAER,KAAI,QAAQ;AACV,SAAO,KAAK,wBAAwB,SAAS;AAC7C;QACK;AACL,SAAO,KAAK,iDAAiD;AAC7D;;AAIF,KAAI,QAAQ;EACV,MAAM,cAAc,KAAK,QAAQ,UAAU;EAC3C,IAAI,YAAY;EAChB,IAAI,cAA6B;EACjC,IAAI,UAAU;AAEd,MAAI;GAEF,MAAM,QADM,aAAa,aAAa,QAAQ,CAAC,MAAM,CACnC,MAAM,KAAK,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC;GAClD,MAAM,KAAK,SAAS,MAAM,MAAM,IAAI,GAAG;GACvC,MAAM,MAAM,SAAS,MAAM,MAAM,IAAI,GAAG;AAExC,OAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE;AAC7B,gBAAY;AACZ,kBAAc;AACd,cAAU,QAAQ,GAAG,GAAG,OAAO,MAAM,EAAE,CAAC,SAAS,GAAG,IAAI;;UAEpD;AAER,MAAI,aAAa,gBAAgB,MAAM;AACrC,UAAO,KAAK,uCAAuC,UAAU;AAC7D;GAEA,MAAM,+BAAc,IAAI,MAAM,EAAC,aAAa;AAC5C,OAAI,gBAAgB,aAAa;AAC/B,WAAO,KAAK,4BAA4B,YAAY,iCAAiC;AACrF;;SAEG;AACL,UAAO,KAAK,sEAAsE;AAClF;;;AAKJ,KAAI;EACF,MAAM,EAAE,OAAO,YAAY,MAAM,iBAAiB,SAAS;EAC3D,MAAM,QAAQ,QAAQ,QAAQ;AAC9B,SAAO,KAAK,WAAW,MAAM,oBAAoB,UAAU,IAAI,KAAK,MAAM;AAC1E;AAEA,OAAK,MAAM,OAAO,SAAS;AACzB,UAAO,KAAK,gBAAgB,IAAI,SAAS,KAAK,IAAI,OAAO,KAAK,KAAK,GAAG;AACtE;;SAEI;AACN,SAAO,KAAK,sCAAsC;AAClD;;AAIF,KAAI;EACF,MAAM,aAAa,cAAc,SAAS;AAE1C,MAAI,CAAC,WACH,QAAO,KAAK,0CAA0C;MAEtD,KAAI;GACF,MAAM,MAAM,aAAa,YAAY,QAAQ;GAC7C,MAAM,SAAS,KAAK,MAAM,IAAI;GAC9B,MAAM,SAAS,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;GACnE,MAAM,YAAY,OAAO,OAAO,gBAAgB,WAAW,OAAO,cAAc;AAChF,UAAO,KAAK,oCAAoC,OAAO,eAAe,UAAU,GAAG;AACnF;UACM;AACN,UAAO,KAAK,uDAAuD;AACnE;;SAGE;AACN,SAAO,KAAK,0CAA0C;;AAIxD,KAAI;EACF,MAAM,SAAS,UAAU,WAAW,CAAC,SAAS,EAAE;GAAE,SAAS;GAAM,OAAO;GAAU,CAAC;AACnF,MAAI,OAAO,SAAS,OAAO,WAAW,MAAM;AAC1C,UAAO,KAAK,gEAAgE;AAC5E;SACK;AACL,UAAO,KAAK,0BAA0B;AACtC;;SAEI;AACN,SAAO,KAAK,gEAAgE;AAC5E;;AAIF,KAAI;EACF,MAAM,SAAS,WAAW,SAAS;EAEnC,MAAM,WADU,MAAM,aAAa,SAAS,EACpB,QAAQ,EAAE,cAC/B,OAAO,aAA0B,SAAS,QAAQ,OAAO,CAC3D;EAED,IAAI,gBAAgB;AACpB,OAAK,MAAM,EAAE,aAAa,SAAS;GACjC,MAAM,MAAM;GACZ,MAAM,eAAe,oBAAoB,IAAI;GAC7C,MAAM,kBAAkB,OAAO,eAAe,QAAQ,UAAU;IAC9D,MAAM,MAAM,IAAI;AAChB,QAAI,QAAQ,UAAa,QAAQ,QAAQ,QAAQ,GAAI,QAAO;AAC5D,QAAI,MAAM,QAAQ,IAAI,CAAE,QAAO,IAAI,WAAW;AAC9C,WAAO,OAAO,QAAQ,YAAY,IAAI,MAAM,CAAC,WAAW;KACxD;GACF,MAAM,iBAAiB,OAAO,cAAc,KAAK,eAAe,OAAO;AACvE,OAAI,gBAAgB,SAAS,KAAK,eAAgB;;AAGpD,MAAI,kBAAkB,GAAG;AACvB,UAAO,KAAK,2BAA2B;AACvC;SACK;AACL,UAAO,KAAK,KAAK,cAAc,UAAU,kBAAkB,IAAI,KAAK,IAAI,qBAAqB;AAC7F;;SAEI;AACN,SAAO,KAAK,6BAA6B;AACzC;;AAIF,QAAO,KAAK,GAAG;AACf,QAAO,KACL,YAAY,OAAO,QAAQ,WAAW,IAAI,KAAK,IAAI,WAAW,OAAO,UAAU,WAAW,IAAI,KAAK,IAAI,IAAI,OAAO,UAAU,WAAW,IAAI,KAAK,MACjJ;AAGD,KAAI,WAAW,GAAG;AAChB,SAAO,KAAK,GAAG;AACf,SAAO,KAAK,cAAc;AAC1B,SAAO,KAAK,iEAAiE;AAC7E,SAAO,KAAK,8CAA8C;AAC1D,SAAO,KAAK,mDAAmD;;AAGjE,WAAQ,OAAO,MAAM,OAAO,KAAK,KAAK,GAAG,KAAK;AAC9C,WAAQ,KAAK,SAAS,IAAI,IAAI,EAAE;EAChC;;;;;;;;;AChQJ,SAASE,aAAW,GAAmB;AACrC,QAAO,EACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,QAAQ;;AAG3B,SAAS,eAAe,MAAsB;AAC5C,QACEA,aAAW,KAAK,CAEb,QAAQ,kBAAkB,sBAAsB,CAEhD,QAAQ,cAAc,kBAAkB,CAGxC,QAAQ,6CAA6C,wBAAsB;;AAIlF,SAAS,WAAW,MAAuB;AACzC,QAAO,KAAK,MAAM,CAAC,WAAW,IAAI,IAAI,KAAK,MAAM,CAAC,SAAS,IAAI;;AAGjE,SAAS,iBAAiB,MAAuB;AAC/C,QAAO,iBAAiB,KAAK,KAAK,MAAM,CAAC;;AAG3C,SAAS,eAAe,MAAc,UAA2B;CAC/D,MAAM,QAAQ,KACX,MAAM,CACN,MAAM,GAAG,GAAG,CACZ,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC;CACvB,MAAM,MAAM,WAAW,OAAO;AAC9B,QAAO,OAAO,MAAM,KAAK,MAAM,IAAI,IAAI,GAAG,eAAe,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC;;AAGnF,SAAgB,eAAe,IAAoB;CACjD,MAAM,QAAQ,GAAG,MAAM,KAAK;CAC5B,MAAM,MAAgB,EAAE;CACxB,IAAI,IAAI;AAER,QAAO,IAAI,MAAM,QAAQ;EACvB,MAAM,OAAO,MAAM,MAAM;AAGzB,MAAI,KAAK,WAAW,MAAM,EAAE;GAC1B,MAAM,OAAO,KAAK,MAAM,EAAE,CAAC,MAAM;GACjC,MAAM,YAAsB,EAAE;AAC9B;AAGA,UAAO,IAAI,MAAM,UAAU,EAAE,MAAM,MAAM,IAAI,WAAW,MAAM,EAAE;AAC9D,cAAU,KAAK,MAAM,MAAM,GAAG;AAC9B;;AAEF,OAAI,IAAI,MAAM,OAAQ;GACtB,MAAM,WAAW,OAAO,oBAAoBA,aAAW,KAAK,CAAC,KAAK;AAClE,OAAI,KAAK,aAAa,SAAS,GAAGA,aAAW,UAAU,KAAK,KAAK,CAAC,CAAC,eAAe;AAClF;;AAIF,MAAI,KAAK,WAAW,OAAO,EAAE;AAC3B,OAAI,KAAK,OAAO,eAAe,KAAK,MAAM,EAAE,CAAC,CAAC,OAAO;AACrD;AACA;;AAEF,MAAI,KAAK,WAAW,MAAM,EAAE;AAC1B,OAAI,KAAK,OAAO,eAAe,KAAK,MAAM,EAAE,CAAC,CAAC,OAAO;AACrD;AACA;;AAEF,MAAI,KAAK,WAAW,KAAK,EAAE;AACzB,OAAI,KAAK,OAAO,eAAe,KAAK,MAAM,EAAE,CAAC,CAAC,OAAO;AACrD;AACA;;AAIF,MAAI,yBAAyB,KAAK,KAAK,MAAM,CAAC,EAAE;AAC9C,OAAI,KAAK,SAAS;AAClB;AACA;;AAIF,MAAI,WAAW,KAAK,EAAE;GACpB,MAAM,YAAsB,EAAE;GAC9B,IAAI,WAAW;AACf,UAAO,IAAI,MAAM,UAAU,WAAW,MAAM,MAAM,GAAG,EAAE;IACrD,MAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,iBAAiB,QAAQ,EAAE;AAE7B;AACA;;AAEF,cAAU,KAAK,eAAe,SAAS,SAAS,CAAC;AACjD,QAAI,SAAU,YAAW;AACzB;;AAEF,OAAI,KACF,kCAAkC,UAAU,MAAM,GAAG,iBAAiB,UAAU,MAAM,EAAE,CAAC,KAAK,GAAG,CAAC,kBACnG;AACD;;AAIF,MAAI,SAAS,KAAK,KAAK,EAAE;GACvB,MAAM,QAAkB,EAAE;AAC1B,UAAO,IAAI,MAAM,UAAU,SAAS,KAAK,MAAM,MAAM,GAAG,EAAE;AACxD,UAAM,KAAK,OAAO,gBAAgB,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC,OAAO;AACnE;;AAEF,OAAI,KAAK,OAAO,MAAM,KAAK,GAAG,CAAC,OAAO;AACtC;;AAOF,MAAI,eAAe,KAAK,KAAK,EAAE;GAC7B,MAAM,QAAkB,EAAE;AAC1B,UAAO,IAAI,MAAM,UAAU,eAAe,KAAK,MAAM,MAAM,GAAG,EAAE;AAC9D,UAAM,KAAK,OAAO,gBAAgB,MAAM,MAAM,IAAI,QAAQ,gBAAgB,GAAG,CAAC,CAAC,OAAO;AACtF;;AAEF,OAAI,KAAK,OAAO,MAAM,KAAK,GAAG,CAAC,OAAO;AACtC;;AAIF,MAAI,KAAK,MAAM,KAAK,IAAI;AACtB;AACA;;EAIF,MAAM,YAAsB,EAAE;AAC9B,SACE,IAAI,MAAM,WACT,MAAM,MAAM,IAAI,MAAM,KAAK,MAC5B,EAAG,MAAM,MAAM,IAAI,WAAW,IAAI,IAClC,EAAG,MAAM,MAAM,IAAI,WAAW,MAAM,IACpC,CAAE,SAAS,KAAK,MAAM,MAAM,GAAG,IAC/B,CAAE,eAAe,KAAK,MAAM,MAAM,GAAG,IACrC,CAAE,WAAW,MAAM,MAAM,GAAG,EAC5B;AACA,aAAU,KAAK,MAAM,MAAM,GAAG;AAC9B;;AAEF,MAAI,UAAU,SAAS,EACrB,KAAI,KAAK,MAAM,eAAe,UAAU,KAAK,IAAI,CAAC,CAAC,MAAM;;AAI7D,QAAO,IAAI,KAAK,KAAK;;;;;ACtKvB,MAAa,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACInB,SAASC,aAAW,KAAqB;AACvC,QAAO,IACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,QAAQ;;AAG3B,SAASC,cAAY,QAAmC;AACtD,QAAO,oCAAoCD,aAAW,OAAO,CAAC,IAAIA,aAAW,OAAO,CAAC;;AAGvF,SAAS,gBAAgB,WAAsD;AAC7E,KAAI,UAAU,WAAW,EAAG,QAAO;AAoBnC,QAAO;;;;QAnBO,UACX,KAAK,MAAM;EACV,MAAM,OAAO,EAAE,OACX,8BAA8BA,aAAW,EAAE,KAAK,CAAC,UACjD;EACJ,MAAM,OACJ,EAAE,0BAA0B,EAAE,uBAAuB,SAAS,IAC1D,mEAAmE,EAAE,uBAAuB,IAAIA,aAAW,CAAC,KAAK,KAAK,CAAC,UACvH;AACN,SAAO;;UAEH,KAAK;qCACsBA,aAAW,EAAE,SAAS,CAAC;0CAClBA,aAAW,EAAE,UAAU,CAAC;UACxD,KAAK;;GAET,CACD,KAAK,KAAK,CAMD;;;;AAKd,SAAS,cAAc,SAAkD;CACvE,MAAM,QAAkB,EAAE;AAE1B,KAAI,QAAQ,OACV,OAAM,KACJ,wCAAwCA,aAAW,QAAQ,OAAO,CAAC,SAASA,aAAW,QAAQ,OAAO,CAAC,UACxG;AAEH,KAAI,QAAQ,YAAY,QAAQ,SAAS,SAAS,GAAG;EACnD,MAAM,aAAa,QAAQ,SACxB,KAAK,MAAM,YAAYA,aAAW,EAAE,CAAC,SAASA,aAAW,EAAE,CAAC,MAAM,CAClE,KAAK,KAAK;AACb,QAAM,KAAK,iCAAiC,WAAW,MAAM;;AAE/D,KAAI,QAAQ,YACV,OAAM,KAAK,qCAAqCA,aAAW,QAAQ,YAAY,CAAC,MAAM;AAGxF,KAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAO;;;;QAID,MAAM,KAAK,WAAW,CAAC;;;;AAK/B,SAAgB,cAAc,SAA0B;CACtD,MAAM,mBACJ,QAAQ,aAAa,QAAQ,UAAU,SAAS,IAC5C,gBAAgB,QAAQ,UAAU,GAClC;CAEN,MAAM,wBAAwB,QAAQ,iBAClC;;;uCAGiC,eAAe,QAAQ,eAAe,CAAC;gBAExE;CAEJ,MAAM,kBAAmB,QAAoC,cACzD;;;iCAG2B,eAAiB,QAAoC,YAAuB,CAAC;gBAExG;CAEJ,MAAM,qBACJ,QAAQ,oBAAoB,QAAQ,iBAAiB,SAAS,IAC1D;;;;QAIA,QAAQ,iBAAiB,KAAK,MAAM,OAAOA,aAAW,EAAE,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC;;gBAGlF;CAEN,MAAM,iBACJ,QAAQ,YACP,QAAQ,QAAQ,UACd,QAAQ,QAAQ,YAAY,QAAQ,QAAQ,SAAS,SAAS,KAC/D,QAAQ,QAAQ,eACd,cAAc,QAAQ,QAAQ,GAC9B;CAEN,MAAM,cACJ,QAAQ,QAAQ,QAAQ,KAAK,SAAS,IAClC,8DAA8D,QAAQ,KAAK,KAAK,MAAM,2JAA2JA,aAAW,EAAE,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,UAClR;AAEN,QAAO;;;;;WAKEA,aAAW,QAAQ,MAAM,CAAC;WAC1B,IAAI;;;;;;UAMLA,aAAW,QAAQ,MAAM,CAAC;;QAE5BC,cAAY,QAAQ,OAAO,CAAC;kCACFD,aAAW,QAAQ,WAAW,CAAC;;MAE3D,YAAY;;;;gCAIcA,aAAW,QAAQ,QAAQ,CAAC;;;MAGtD,gBAAgB;MAChB,iBAAiB;MACjB,sBAAsB;MACtB,mBAAmB;MACnB,eAAe;;;;;;;;ACnJrB,SAAS,WAAW,KAAqB;AACvC,QAAO,IACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,QAAQ;;AAG3B,SAAS,YAAY,QAAmC;AACtD,QAAO,oCAAoC,WAAW,OAAO,CAAC,IAAI,WAAW,OAAO,CAAC;;AAGvF,SAAgB,YAAY,UAAqB,aAA4B;CAC3E,MAAM,aAAa,+BAAe,IAAI,MAAM,EAAE,aAAa;CAC3D,MAAM,OACJ,SAAS,WAAW,IAChB,mGACA,SACG,KACE,MAAM;2CACwB,YAAY,EAAE,aAAa,MAAM,EAAE,OAAO,aAAa,CAAC,CAAC;qBAC/E,WAAW,EAAE,WAAW,CAAC,6BAA6B,WAAW,EAAE,WAAW,CAAC;qBAC/E,WAAW,EAAE,WAAW,CAAC,SAAS,WAAW,EAAE,MAAM,CAAC;YAC/D,YAAY,EAAE,OAAO,CAAC;oCACE,WAAW,EAAE,QAAQ,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,SAAS,MAAM,MAAM,GAAG;WAEjG,CACA,KAAK,KAAK;AAEnB,QAAO;;;;;;WAME,IAAI;;;;;oEAKqD,SAAS,OAAO,UAAU,SAAS,WAAW,IAAI,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;UAsBrH,KAAK;;;;;;;;;gBASC,WAAW,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACvDtC,eAAsB,aAAa,UAAyB,QAA+B;AACzF,OAAM,MAAM,QAAQ,EAAE,WAAW,MAAM,CAAC;AAGxC,OAAM,UAAU,KAAK,QAAQ,YAAY,EAAE,IAAI,MAAM,EAAE,QAAQ;CAG/D,MAAM,cAAc,SAAS,KAAK,MAAM,EAAE,QAAQ;AAClD,OAAM,UAAU,KAAK,QAAQ,aAAa,EAAE,YAAY,YAAY,EAAE,QAAQ;AAG9E,MAAK,MAAM,EAAE,aAAa,UAAU;EAClC,MAAM,WAAW,cAAc,QAAQ;AACvC,QAAM,UAAU,KAAK,QAAQ,GAAG,QAAQ,WAAW,OAAO,EAAE,UAAU,QAAQ;;;;;;;;;;AClBlF,SAAS,uBAAuB,UAAiC;CAC/D,IAAI,UAAU,QAAQ,SAAS;AAE/B,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,SAAS,eAAe;AAC/C,MAAI,WAAW,UAAU,CACvB,QAAO;EAET,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QACb,QAAO;AAET,YAAU;;;;AAKd,SAAS,kBAAkB,SAAgD;CACzE,MAAM,IAAI;CACV,MAAM,QAAkB,EAAE;AAE1B,OAAM,KAAK,KAAK,EAAE,WAAqB;AACvC,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,cAAc,EAAE,cAAwB,MAAM;AACzD,OAAM,KAAK,eAAe,EAAE,YAAsB;AAClD,OAAM,KAAK,GAAG;AAEd,KAAI,EAAE,YAAY;AAChB,QAAM,KAAK,aAAa;AACxB,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,EAAE,WAAqB;AAClC,QAAM,KAAK,GAAG;;AAGhB,KAAI,EAAE,aAAa;AACjB,QAAM,KAAK,cAAc;AACzB,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,EAAE,YAAsB;AACnC,QAAM,KAAK,GAAG;;AAGhB,KAAI,EAAE,mBAAmB;AACvB,QAAM,KAAK,oBAAoB;AAC/B,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,EAAE,kBAA4B;AACzC,QAAM,KAAK,GAAG;;CAGhB,MAAM,cAAc,EAAE;AACtB,KAAI,eAAe,YAAY,SAAS,GAAG;AACzC,QAAM,KAAK,uBAAuB;AAClC,QAAM,KAAK,GAAG;AACd,OAAK,MAAM,OAAO,YAChB,OAAM,KAAK,KAAK,MAAM;AAExB,QAAM,KAAK,GAAG;;CAGhB,MAAM,YAAY,EAAE;AACpB,KAAI,aAAa,UAAU,SAAS,GAAG;AACrC,QAAM,KAAK,eAAe;AAC1B,QAAM,KAAK,GAAG;AACd,OAAK,MAAM,KAAK,WAAW;AACzB,SAAM,KAAK,OAAO,EAAE,cAAwB;AAC5C,SAAM,KAAK,GAAG;AACd,SAAM,KAAK,kBAAkB,EAAE,eAAyB;AACxD,OAAI,EAAE,QAAS,OAAM,KAAK,aAAa,EAAE,UAAoB;AAC7D,SAAM,KAAK,GAAG;;;CAIlB,MAAM,cAAc,EAAE;AACtB,KAAI,eAAe,YAAY,SAAS,GAAG;AACzC,QAAM,KAAK,iBAAiB;AAC5B,QAAM,KAAK,GAAG;AACd,OAAK,MAAM,KAAK,YACd,OAAM,KAAK,QAAQ,EAAE,QAAkB,MAAM,EAAE,QAAkB,KAAK,EAAE,UAAoB,IAAI,EAAE,QAAkB,IAAI;AAE1H,QAAM,KAAK,GAAG;;CAGhB,MAAM,OAAO,EAAE;AACf,KAAI,QAAQ,KAAK,SAAS,GAAG;AAC3B,QAAM,KAAK,aAAa,KAAK,KAAK,KAAK,GAAG;AAC1C,QAAM,KAAK,GAAG;;AAGhB,QAAO,MAAM,KAAK,KAAK;;AAGzB,MAAa,gBAAgB,IAAI,QAAQ,SAAS,CAC/C,YAAY,wEAAwE,CACpF,OAAO,gBAAgB,gCAAgC,CACvD,OAAO,gBAAgB,oEAAoE,CAC3F,OAAO,cAAc,wDAAwD,CAC7E,OAAO,OAAO,YAAiE;AAE9E,KAAI,QAAQ,SAAS,QAAW;EAC9B,MAAM,UAAU,QAAQ,QAAQ,KAAK;EACrC,MAAM,SAAS,QAAQ,QAAQ,OAAO,aAAa;EAEnD,IAAI;AACJ,MAAI;AACF,cAAW,MAAM,aAAa,QAAQ;WAC/B,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,aAAQ,OAAO,MAAM,mBAAmB,QAAQ,KAAK,QAAQ,IAAI;AACjE,aAAQ,KAAK,EAAE;;AAGjB,MAAI,SAAS,WAAW,GAAG;AACzB,aAAQ,OAAO,MAAM,yCAAyC,QAAQ,MAAM;AAC5E,aAAQ,KAAK,EAAE;;AAGjB,MAAI;AACF,SAAM,aAAa,UAAU,OAAO;WAC7B,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,aAAQ,OAAO,MAAM,0BAA0B,QAAQ,IAAI;AAC3D,aAAQ,KAAK,EAAE;;EAIjB,MAAM,aAAa,QAAQ,OAAO;AAClC,YAAQ,OAAO,MAAM,eAAe,SAAS,OAAO,OAAO,SAAS,WAAW,IAAI,KAAK,IAAI,KAAK,WAAW,IAAI;AAChH;;CAIF,MAAM,kBAAkB,uBAAuBE,UAAQ,KAAK,CAAC;AAE7D,KAAI,CAAC,iBAAiB;AACpB,YAAQ,OAAO,MACb,iFACD;AACD,YAAQ,KAAK,EAAE;;CAGjB,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,iBAAiB,QAAQ;UACvC,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,kBAAkB,gBAAgB,KAAK,QAAQ,IAAI;AACxE,YAAQ,KAAK,EAAE;;CAGjB,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AACN,YAAQ,OAAO,MAAM,WAAW,gBAAgB,4BAA4B;AAC5E,YAAQ,KAAK,EAAE;;CAGjB,MAAM,SAAS,gBAAgB,OAAO;AACtC,KAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,OAAO,MACb,WAAW,gBAAgB,0BAA0B,OAAO,OAAO,KAAK,OAAO,CAAC,IACjF;AACD,YAAQ,KAAK,EAAE;;AAIjB,KAAI,QAAQ,UAAU;EACpB,MAAM,WAAW,kBAAkB,OAAO,KAAK;AAC/C,MAAI,QAAQ,KAAK;GACf,MAAM,UAAU,QAAQ,QAAQ,IAAI;AACpC,OAAI;AACF,UAAM,UAAU,SAAS,UAAU,QAAQ;AAC3C,cAAQ,OAAO,MAAM,eAAe,QAAQ,IAAI;YACzC,KAAK;IACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,cAAQ,OAAO,MAAM,qBAAqB,QAAQ,KAAK,QAAQ,IAAI;AACnE,cAAQ,KAAK,EAAE;;QAGjB,WAAQ,OAAO,MAAM,SAAS;AAEhC;;CAGF,MAAM,SAAS,KAAK,UAAU,OAAO,MAAM,MAAM,EAAE,GAAG;AAEtD,KAAI,QAAQ,KAAK;EACf,MAAM,UAAU,QAAQ,QAAQ,IAAI;AACpC,MAAI;AACF,SAAM,UAAU,SAAS,QAAQ,QAAQ;AACzC,aAAQ,OAAO,MAAM,eAAe,QAAQ,IAAI;WACzC,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,aAAQ,OAAO,MAAM,qBAAqB,QAAQ,KAAK,QAAQ,IAAI;AACnE,aAAQ,KAAK,EAAE;;OAGjB,WAAQ,OAAO,MAAM,OAAO;EAE9B;;;;AC7MJ,MAAM,aAAa;AAEnB,MAAM,cAAc;EAClB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6Bb,MAAM,eAAe,IAAI,QAAQ,QAAQ,CACtC,YAAY,0CAA0C;AAEzD,aACG,QAAQ,UAAU,CAClB,YAAY,sEAAoE,CAChF,OAAO,WAAW,gEAAgE,CAClF,QAAQ,YAAiC;CACxC,MAAM,SAAS,WAAWC,UAAQ,KAAK,CAAC;AACxC,KAAI,CAAC,QAAQ;AACX,YAAQ,OAAO,MAAM,qEAAqE;AAC1F,YAAQ,KAAK,EAAE;;CAGjB,MAAM,WAAW,KAAK,QAAQ,QAAQ;CACtC,MAAM,WAAW,KAAK,UAAU,aAAa;AAG7C,KAAI,CAAC,WAAW,SAAS,CAAE,WAAU,UAAU,EAAE,WAAW,MAAM,CAAC;AAGnE,KAAI,WAAW,SAAS,IAAI,CAAC,QAAQ,OAEnC;MAAI,CADa,aAAa,UAAU,QAAQ,CAClC,SAAS,WAAW,EAAE;AAClC,aAAQ,OAAO,MACb,+CAA+C,SAAS,iEAEzD;AACD,aAAQ,KAAK,EAAE;;;AAInB,eAAc,UAAU,aAAa,QAAQ;AAC7C,WAAU,UAAU,IAAM;AAE1B,WAAQ,OAAO,MAAM,kCAAkC,SAAS,IAAI;AACpE,WAAQ,OAAO,MAAM,+CAA+C;AACpE,WAAQ,OAAO,MAAM,2CAA2C;EAChE;AAEJ,aACG,QAAQ,YAAY,CACpB,YAAY,yCAAyC,CACrD,aAAa;CACZ,MAAM,SAAS,WAAWA,UAAQ,KAAK,CAAC;AACxC,KAAI,CAAC,QAAQ;AACX,YAAQ,OAAO,MAAM,oCAAoC;AACzD,YAAQ,KAAK,EAAE;;CAGjB,MAAM,WAAW,KAAK,QAAQ,SAAS,aAAa;AAEpD,KAAI,CAAC,WAAW,SAAS,EAAE;AACzB,YAAQ,OAAO,MAAM,gCAAgC,SAAS,MAAM;AACpE;;AAIF,KAAI,CADY,aAAa,UAAU,QAAQ,CAClC,SAAS,WAAW,EAAE;AACjC,YAAQ,OAAO,MACb,kCAAkC,SAAS,gFAE5C;AACD,YAAQ,KAAK,EAAE;;AAGjB,QAAO,SAAS;AAChB,WAAQ,OAAO,MAAM,sCAAsC,SAAS,IAAI;EACxE;AAEJ,aACG,QAAQ,SAAS,CACjB,YAAY,oDAAoD,CAChE,aAAa;CACZ,MAAM,SAAS,WAAWA,UAAQ,KAAK,CAAC;AACxC,KAAI,CAAC,QAAQ;AACX,YAAQ,OAAO,MAAM,iCAAiC;AACtD;;CAGF,MAAM,WAAW,KAAK,QAAQ,SAAS,aAAa;AACpD,KAAI,CAAC,WAAW,SAAS,EAAE;AACzB,YAAQ,OAAO,MAAM,mCAAmC;AACxD;;AAIF,KADgB,aAAa,UAAU,QAAQ,CACnC,SAAS,WAAW,CAC9B,WAAQ,OAAO,MAAM,kDAAkD;KAEvE,WAAQ,OAAO,MAAM,mEAAmE;EAE1F;;;;;;;;;;AC1HJ,SAAgB,eAAe,SAAyB;AAEtD,QAAO,mBAAmB,SADX,WAAW,QAAQ,CACQ,OAAO;;;;;;;;;ACGnD,SAASC,mBAAiB,SAAyB;CACjD,MAAM,QAAQ,QAAQ,MAAM,CAAC,MAAM,MAAM;AACzC,KAAI,MAAM,UAAU,EAAG,QAAO,QAAQ,MAAM;AAC5C,QAAO,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,GAAG;;AAGvC,MAAa,cAAc,IAAI,QAAQ,OAAO,CAC3C,YAAY,mDAAmD,CAC/D,OAAO,eAAe,mCAAmC,MAAM,CAC/D,OAAO,OAAO,YAAgC;CAC7C,MAAM,MAAMC,UAAQ,KAAK;CACzB,MAAM,kBAAkB,KAAK,KAAK,eAAe;AAEjD,KAAI,WAAW,gBAAgB,IAAI,CAAC,QAAQ,OAAO;AACjD,YAAQ,OAAO,MACb,qFACD;AACD,YAAQ,KAAK,EAAE;;CAIjB,MAAM,UAAU,MAAM,QACpB,CACE;EACE,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS;EACT,WAAW,UACT,MAAM,MAAM,CAAC,SAAS,IAAI,OAAO;EACpC,EACD;EACE,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAS,OAAO;GAAS,EAClC;GAAE,OAAO;GAAU,OAAO;GAAU,CACrC;EACD,SAAS;EACV,CACF,EACD,EACE,gBAAgB;AACd,YAAQ,OAAO,MAAM,aAAa;AAClC,YAAQ,KAAK,EAAE;IAElB,CACF;CAED,MAAM,UAAW,QAAQ,QAAmB,MAAM;CAClD,MAAM,SAAS,QAAQ;CAGvB,IAAI;AACJ,KAAI;AACF,eAAa,eAAe,IAAI;UACzB,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,UAAU,QAAQ,0EAA0E;AACjH,YAAQ,KAAK,EAAE;;CAGjB,MAAM,QAAQD,mBAAiB,QAAQ;CACvC,MAAM,eAAe,QAAQ,MAAM,CAAC,MAAM,MAAM,CAAC,SAAS;CAE1D,IAAI,aAAa;AACjB,KAAI,cAAc;AAChB,YAAQ,OAAO,MAAM,qBAAqB,MAAM,KAAK;EACrD,MAAM,cAAc,MAAM,QAAQ;GAChC,MAAM;GACN,MAAM;GACN,SAAS;GACT,SAAS;GACV,CAAC;AACF,MAAI,YAAY,eAAgB,YAAY,YAAuB,MAAM,CAAC,SAAS,EACjF,cAAc,YAAY,YAAuB,MAAM;;CAY3D,MAAM,aAAa,gBARH;EACd;EACA,OAAO;EACP;EACA;EACD,CAG0C;AAC3C,KAAI,CAAC,WAAW,SAAS;AACvB,YAAQ,OAAO,MACb,iEAAiE,WAAW,OAAO,KAAK,OAAO,CAAC,IACjG;AACD,YAAQ,KAAK,EAAE;;AAGjB,OAAM,UAAU,iBAAiB,KAAK,UAAU,WAAW,MAAM,MAAM,EAAE,GAAG,MAAM,QAAQ;AAE1F,WAAQ,OAAO,MAAM,4BAA4B,WAAW,IAAI;EAChE;;;;;;;;AChGJ,SAAS,UACP,KACA,OACA,YACA,0BAAU,IAAI,KAAa,EACd;AACb,SAAQ,IAAI,IAAI;CAChB,MAAM,UAAU,MAAM,IAAI,IAAI;CAC9B,MAAM,YAAY,WAAW,IAAI,IAAI,IAAI,EAAE;CAE3C,MAAM,WAA0B,EAAE;AAClC,MAAK,MAAM,MAAM,UACf,KAAI,CAAC,QAAQ,IAAI,GAAG,CAClB,UAAS,KAAK,UAAU,IAAI,OAAO,YAAY,QAAQ,CAAC;AAI5D,QAAO;EACL;EACA,QAAQ,SAAS,UAAU;EAC3B,OAAO,SAAS,SAAS;EACzB;EACD;;;;;AAMH,SAAS,WAAW,MAAmB,SAAS,IAAI,SAAS,MAAgB;CAE3E,MAAM,QAAQ,CACZ,GAAG,SAAS,WAAW,KAAK,KAFZ,SAAS,SAAS,SAEW,KAAK,IAAI,IAAI,KAAK,OAAO,MAAM,KAAK,QAClF;CAED,MAAM,cAAc,UAAU,SAAS,SAAS;AAChD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;EAC7C,MAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,OAAO;GACT,MAAM,aAAa,WAAW,OAAO,aAAa,MAAM,KAAK,SAAS,SAAS,EAAE;AACjF,SAAM,KAAK,GAAG,WAAW;;;AAG7B,QAAO;;AAGT,MAAa,iBAAiB,IAAI,QAAQ,UAAU,CACjD,YAAY,gEAAgE,CAC5E,SAAS,SAAS,6CAA6C,CAC/D,OAAO,oBAAoB,mCAAmC,CAC9D,OAAO,OAAO,KAAa,YAA8B;CACxD,MAAM,UAAU,QAAQ,OAAOE,UAAQ,KAAK;CAE5C,IAAI;AACJ,KAAI;AACF,aAAW,MAAM,aAAa,QAAQ;UAC/B,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,mBAAmB,QAAQ,KAAK,QAAQ,IAAI;AACjE,YAAQ,KAAK,EAAE;;CAGjB,MAAM,wBAAQ,IAAI,KAAsB;CACxC,MAAM,6BAAa,IAAI,KAAuB;AAE9C,MAAK,MAAM,EAAE,aAAa,UAAU;AAClC,QAAM,IAAI,QAAQ,YAAY,QAAQ;EACtC,MAAM,SAAS,QAAQ,SAAS;AAChC,MAAI,QAAQ;GACV,MAAM,WAAW,WAAW,IAAI,OAAO,IAAI,EAAE;AAC7C,YAAS,KAAK,QAAQ,WAAW;AACjC,cAAW,IAAI,QAAQ,SAAS;;;AAIpC,KAAI,CAAC,MAAM,IAAI,IAAI,EAAE;AACnB,YAAQ,OAAO,MAAM,mBAAmB,IAAI,kBAAkB,QAAQ,KAAK;AAC3E,YAAQ,KAAK,EAAE;;CAIjB,IAAI,UAAU;CACd,MAAM,uBAAO,IAAI,KAAa;AAC9B,QAAO,MAAM;AACX,OAAK,IAAI,QAAQ;EAEjB,MAAM,SADO,MAAM,IAAI,QAAQ,EACV,SAAS;AAC9B,MAAI,CAAC,UAAU,CAAC,MAAM,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,CAAE;AACvD,YAAU;;CAIZ,MAAM,QAAQ,WADD,UAAU,SAAS,OAAO,WAAW,CACpB;AAE9B,WAAQ,OAAO,MAAM,MAAM,KAAK,KAAK,GAAG,KAAK;EAC7C;;;;AC1FJ,SAAS,aACP,SACA,UACA,gBACA,WACY;CACZ,MAAM,MAAM;CACZ,MAAM,eAAe,oBAAoB,IAAI;CAE7C,MAAM,kBAAkB,eAAe,QAAQ,UAAU;EACvD,MAAM,MAAM,IAAI;AAChB,MAAI,QAAQ,UAAa,QAAQ,QAAQ,QAAQ,GAAI,QAAO;AAC5D,MAAI,MAAM,QAAQ,IAAI,CAAE,QAAO,IAAI,WAAW;AAC9C,SAAO,OAAO,QAAQ,YAAY,IAAI,MAAM,CAAC,WAAW;GACxD;CAEF,MAAM,iBAAiB,YAAY,KAAK,eAAe;AAEvD,QAAO;EACL,YAAY,QAAQ;EACpB;EACA,QAAQ,QAAQ;EAChB;EACA;EACA;EACA,MAAM,gBAAgB,WAAW,KAAK,CAAC;EACxC;;;AAIH,MAAM,iBAA0C;CAC9C,SAAS;CACT,UAAU;CACV,WAAW,EAAE;CACb,gBAAgB;CAChB,kBAAkB,EAAE;CACpB,MAAM,EAAE;CACT;;;;;AAMD,eAAe,WAAW,UAAkB,eAA0C;AACpF,KAAI,cAAc,WAAW,EAAG,QAAO;CAEvC,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,UAAU,QAAQ;SACjC;AACN,SAAO;;CAGT,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AACN,SAAO;;CAGT,IAAI,QAAQ;AACZ,MAAK,MAAM,SAAS,cAClB,KAAI,SAAS,gBAAgB;AAC3B,SAAO,SAAS,eAAe;AAC/B;;AAIJ,KAAI,UAAU,EAAG,QAAO;CAExB,MAAM,aAAa,gBAAgB,OAAO;AAC1C,KAAI,CAAC,WAAW,QAAS,QAAO;AAEhC,OAAM,UAAU,UAAU,KAAK,UAAU,WAAW,MAAM,MAAM,EAAE,GAAG,MAAM,QAAQ;AACnF,QAAO;;AAGT,MAAa,cAAc,IAAI,QAAQ,OAAO,CAC3C,YAAY,gEAAgE,CAC5E,SAAS,SAAS,iDAAiD,CACnE,OAAO,sBAAsB,8DAA8D,CAC3F,OAAO,mBAAmB,+DAA+D,SAAS,CAClG,OAAO,WAAW,gDAAgD,CAClE,OAAO,UAAU,yBAAyB,CAC1C,OAAO,WAAW,2CAA2C,CAC7D,OAAO,SAAS,yDAAyD,CACzE,OAAO,OAAO,KAAyB,YAOlC;CACJ,MAAM,UAAU,QAAQ,OAAOC,UAAQ,KAAK,CAAC;CAC7C,MAAM,SAAS,WAAW,QAAQ;CAElC,MAAM,iBAAiB,QAAQ,UAC3B,QAAQ,QAAQ,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,GAC/D,OAAO;CAEX,MAAM,YAAY,QAAQ,cAAc,SAAY,QAAQ,YAAY,OAAO;CAE/E,eAAe,UAA2B;EAExC,IAAI;AACJ,MAAI;AACF,aAAU,MAAM,aAAa,QAAQ;WAC9B,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,aAAQ,OAAO,MAAM,mBAAmB,QAAQ,KAAK,QAAQ,IAAI;AACjE,UAAO;;AAGT,MAAI,QAAQ,WAAW,GAAG;AACxB,aAAQ,OAAO,MAAM,mCAAmC,QAAQ,MAAM;AACtE,UAAO;;EAQT,MAAM,UAJU,QAAQ,QAAQ,EAAE,cAC/B,OAAO,aAA0B,SAAS,QAAQ,OAAO,CAC3D,CAEuB,KAAK,EAAE,SAAS,eACtC,aAAa,SAAS,UAAU,gBAAgB,UAAU,CAC3D;AAGD,MAAI,QAAQ,KAAK;GACf,MAAM,QAAQ,QAAQ,QAAQ,MAAM,CAAC,EAAE,QAAQ,EAAE,gBAAgB,SAAS,EAAE;GAC5E,IAAI,aAAa;AACjB,QAAK,MAAM,KAAK,OAAO;IACrB,MAAM,QAAQ,MAAM,WAAW,EAAE,UAAU,EAAE,gBAAgB;AAC7D,QAAI,QAAQ,GAAG;AACb,mBAAc;AACd,eAAQ,OAAO,MAAM,SAAS,EAAE,WAAW,UAAU,MAAM,QAAQ,UAAU,IAAI,KAAK,IAAI,IAAI,EAAE,gBAAgB,KAAK,KAAK,CAAC,KAAK;;;AAIpI,OAAI,eAAe,KAAK,MAAM,SAAS,GAAG;AACxC,cAAQ,OAAO,MAAM,0EAA0E;AAC/F,WAAO;;AAET,OAAI,MAAM,WAAW,GAAG;AACtB,cAAQ,OAAO,MAAM,oDAAoD;AACzE,WAAO;;GAIT,IAAI;AACJ,OAAI;AACF,gBAAY,MAAM,aAAa,QAAQ;WACjC;AACN,cAAQ,OAAO,MAAM,aAAa,WAAW,QAAQ,eAAe,IAAI,KAAK,IAAI,wDAAwD;AACzI,WAAO;;GAQT,MAAM,eANa,UAAU,QAAQ,EAAE,cACpC,OAAO,aAA0B,SAAS,QAAQ,OAAO,CAC3D,CAC4B,KAAK,EAAE,SAAS,eAC3C,aAAa,SAAS,UAAU,gBAAgB,UAAU,CAC3D,CAC8B,QAAQ,MAAM,CAAC,EAAE,KAAK;AACrD,OAAI,aAAa,WAAW,GAAG;AAC7B,cAAQ,OAAO,MAAM,aAAa,WAAW,QAAQ,eAAe,IAAI,KAAK,IAAI,kCAAkC;AACnH,WAAO;;AAET,aAAQ,OAAO,MAAM,aAAa,WAAW,QAAQ,eAAe,IAAI,KAAK,IAAI,OAAO,aAAa,OAAO,UAAU,aAAa,WAAW,IAAI,KAAK,IAAI,qBAAqB;AAChL,QAAK,MAAM,KAAK,aACd,WAAQ,OAAO,MAAM,QAAQ,EAAE,WAAW,aAAa,EAAE,gBAAgB,KAAK,KAAK,CAAC,IAAI;AAE1F,UAAO;;EAGT,MAAM,WAAW,QAAQ,QAAQ,MAAM,CAAC,EAAE,KAAK;EAC/C,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,KAAK;AAE5C,MAAI,QAAQ,MAAM;AAChB,aAAQ,OAAO,MAAM,KAAK,UAAU;IAAE;IAAS,UAAU,SAAS;IAAQ,QAAQ,OAAO;IAAQ,EAAE,MAAM,EAAE,GAAG,KAAK;AACnH,UAAO,SAAS,SAAS,IAAI,IAAI;;EAInC,MAAM,OAAO,GAAW,UAAkB,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,MAAM;AAEzE,MAAI,CAAC,QAAQ,SAAS,OAAO,SAAS,GACpC;OAAI,CAAC,QAAQ,MACX,MAAK,MAAM,KAAK,OACd,WAAQ,OAAO,MAAM,QAAQ,IAAI,EAAE,YAAY,GAAG,CAAC,GAAG,EAAE,aAAa,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,IAAI;;AAKpH,OAAK,MAAM,KAAK,UAAU;AACxB,aAAQ,OAAO,MAAM,QAAQ,IAAI,EAAE,YAAY,GAAG,CAAC,GAAG,EAAE,aAAa,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,IAAI;AAC9G,QAAK,MAAM,SAAS,EAAE,gBACpB,WAAQ,OAAO,MAAM,kCAAkC,MAAM,IAAI;AAEnE,OAAI,EAAE,eACJ,WAAQ,OAAO,MAAM,uBAAuB,EAAE,aAAa,uBAAuB,UAAU,KAAK;;AAIrG,YAAQ,OAAO,MAAM,KAAK,OAAO,OAAO,WAAW,SAAS,OAAO,YAAY,QAAQ,OAAO,qBAAqB;AAEnH,MAAI,SAAS,SAAS,GAAG;AACvB,OAAI,CAAC,QAAQ,OAAO;AAClB,cAAQ,OAAO,MAAM,wBAAwB;AAC7C,SAAK,MAAM,KAAK,SACd,WAAQ,OAAO,MAAM,KAAK,EAAE,WAAW,OAAO,EAAE,SAAS,IAAI;;AAGjE,UAAO;;AAGT,SAAO;;AAGT,KAAI,QAAQ,OAAO;AACjB,YAAQ,OAAO,MAAM,aAAa,QAAQ,UAAU;AAGpD,QAAM,SAAS;EAEf,IAAI,WAAiD;AACrD,KAAG,MAAM,SAAS,EAAE,WAAW,MAAM,GAAG,QAAQ,aAAa;AAC3D,OAAI,CAAC,YAAY,CAAC,SAAS,UAAU,CAAC,SAAS,eAAe,CAAE;AAChE,OAAI,SAAU,cAAa,SAAS;AACpC,cAAW,WAAW,YAAY;AAChC,cAAQ,OAAO,MAAM,QAAQ;AAC7B,UAAM,SAAS;AACf,cAAQ,OAAO,MAAM,8BAA8B;MAClD,IAAI;IACP;AAGF,YAAQ,MAAM,QAAQ;AAEtB,YAAQ,GAAG,gBAAgB;AACzB,aAAQ,OAAO,MAAM,sBAAsB;AAC3C,aAAQ,KAAK,EAAE;IACf;QACG;EACL,MAAM,WAAW,MAAM,SAAS;AAChC,YAAQ,KAAK,SAAS;;EAExB;;;;ACrQJ,MAAa,gBAAgB,IAAI,QAAQ,SAAS,CAC/C,YAAY,8DAA8D,CAC1E,SAAS,UAAU,6DAA6D,CAChF,OAAO,oBAAoB,+DAA+D,CAC1F,OAAO,aAAa,sDAAsD,CAC1E,OAAO,kBAAkB,4CAA4C,CACrE,OACC,OACE,MACA,YACG;CACH,MAAM,WAAW,QAAQ,KAAK;CAC9B,MAAM,SAAS,QAAQ,QAAQ,OAAOC,UAAQ,KAAK,CAAC;CACpD,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,cAAc,QAAQ,eAAe;CAG3C,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,UAAU,QAAQ;UAChC,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,kBAAkB,SAAS,KAAK,QAAQ,IAAI;AACjE,YAAQ,KAAK,EAAE;;CAIjB,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AACN,YAAQ,OAAO,MAAM,WAAW,SAAS,4BAA4B;AACrE,YAAQ,KAAK,EAAE;;AAGjB,KAAI,CAAC,MAAM,QAAQ,OAAO,EAAE;AAC1B,YAAQ,OAAO,MACb,wDAAwD,OAAO,OAAO,KACvE;AACD,YAAQ,KAAK,EAAE;;CAGjB,MAAM,WAAW;AAEjB,KAAI,SAAS,WAAW,GAAG;AACzB,YAAQ,OAAO,MAAM,mBAAmB,SAAS,0BAA0B;AAC3E,YAAQ,KAAK,EAAE;;AAGjB,WAAQ,OAAO,MAAM,SAAS,SAAS,OAAO,UAAU,SAAS,WAAW,IAAI,KAAK,IAAI,OAAO,KAAK,MAAM;AAC3G,KAAI,OACF,WAAQ,OAAO,MAAM,0CAA0C;CAGjE,IAAI,WAAW;CACf,IAAI,UAAU;AAEd,MAAK,MAAM,QAAQ,UAAU;EAE3B,MAAM,SAAS,gBAAgB,KAAK;AAEpC,MAAI,CAAC,OAAO,SAAS;GACnB,MAAM,MAAO,OAAmC,iBAAiB;AACjE,OAAI,aAAa;AACf,cAAQ,OAAO,MACb,QAAQ,IAAI,wBAAwB,OAAO,OAAO,KAAK,KAAK,CAAC,IAC9D;AACD;AACA;UACK;AACL,cAAQ,OAAO,MACb,QAAQ,IAAI,2BACV,OAAO,OAAO,KAAK,MAAM,UAAU,IAAI,CAAC,KAAK,KAAK,GAClD,2DACH;AACD,cAAQ,KAAK,EAAE;;;EAInB,MAAM,UAAU,OAAO;EACvB,MAAM,iBAAiB,QAAQ;EAC/B,MAAM,aAAa,KAAK,QAAQ,eAAe;EAC/C,MAAM,kBAAkB,KAAK,YAAY,eAAe;AAGxD,MAAI,WAAW,WAAW,IAAI,CAAC,SAAS,WAAW,CAAC,aAAa,EAAE;AACjE,aAAQ,OAAO,MACb,QAAQ,QAAQ,WAAW,WAAW,eAAe,gDACtD;AACD,aAAQ,KAAK,EAAE;;EAEjB,MAAM,gBAAgB,WAAW,gBAAgB;AAEjD,MAAI,QAAQ;AACV,aAAQ,OAAO,MAAM,WAAW,gBAAgB,cAAc,SAAS,IAAI,eAAe,iBAAiB;AAC3G;AACA;;AAGF,MAAI,cACF,WAAQ,OAAO,MAAM,QAAQ,QAAQ,WAAW,KAAK,eAAe,6CAA6C;AAGnH,MAAI;AACF,SAAM,MAAM,YAAY,EAAE,WAAW,MAAM,CAAC;AAC5C,SAAM,UACJ,iBACA,KAAK,UAAU,SAAS,MAAM,EAAE,GAAG,MACnC,QACD;AACD,aAAQ,OAAO,MAAM,QAAQ,QAAQ,WAAW,KAAK,eAAe,iBAAiB;AACrF;WACO,KAAK;GAKZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,aAAQ,OAAO,MAAM,QAAQ,QAAQ,WAAW,mBAAmB,QAAQ,IAAI;AAC/E,aAAQ,KAAK,EAAE;;;AAInB,WAAQ,OAAO,MAAM,KAAK;AAC1B,KAAI,OACF,WAAQ,OAAO,MAAM,gBAAgB,SAAS,UAAU,aAAa,IAAI,KAAK,IAAI,KAAK;MAClF;EACL,MAAM,QAAkB,EAAE;AAC1B,MAAI,WAAW,EAAG,OAAM,KAAK,GAAG,SAAS,WAAW;AACpD,MAAI,UAAU,EAAG,OAAM,KAAK,GAAG,QAAQ,UAAU;AACjD,YAAQ,OAAO,MAAM,SAAS,MAAM,KAAK,KAAK,CAAC,KAAK;;EAGzD;;;;;;;;AC/HH,eAAe,iBACb,UACA,QACA,QACiB;CACjB,IAAI,UAAU;AAEd,MAAK,MAAM,EAAE,SAAS,cAAc,UAAU;EAC5C,IAAI,UAAU;EACd,MAAM,OAAO,EAAE,GAAG,SAAS;EAE3B,MAAM,UAAU,KAAK;AAIrB,MAAI,CAAC,QAAS;EAEd,MAAM,iBAAiB,EAAE,GAAG,SAAS;AAErC,MAAI,eAAe,WAAW,QAAQ;AACpC,kBAAe,SAAS;AACxB,aAAU;;AAGZ,MAAI,MAAM,QAAQ,eAAe,SAAS,IAAI,eAAe,SAAS,SAAS,OAAO,EAAE;AACtF,kBAAe,WAAW,eAAe,SAAS,KAAK,MACrD,MAAM,SAAS,SAAS,EACzB;AACD,aAAU;;AAGZ,MAAI,SAAS;AACX,QAAK,aAAa;GAClB,MAAM,aAAa,gBAAgB,KAAK;AACxC,OAAI,WAAW,SAAS;AACtB,UAAM,UAAU,UAAU,KAAK,UAAU,WAAW,MAAM,MAAM,EAAE,GAAG,MAAM,QAAQ;AACnF;;;;AAKN,QAAO;;AAGT,MAAa,gBAAgB,IAAI,QAAQ,SAAS,CAC/C,YAAY,oFAAoF,CAChG,SAAS,aAAa,0CAA0C,CAChE,SAAS,aAAa,sCAAsC,CAC5D,OAAO,oBAAoB,gDAAgD,CAC3E,OAAO,aAAa,wCAAwC,CAC5D,OACC,OACE,QACA,QACA,YACG;AAEH,KAAI,CAAC,oBAAoB,KAAK,OAAO,EAAE;AACrC,YAAQ,OAAO,MACb,WAAW,OAAO,qGAEnB;AACD,YAAQ,KAAK,EAAE;;AAGjB,KAAI,WAAW,QAAQ;AACrB,YAAQ,OAAO,MAAM,2CAA2C,OAAO,OAAO;AAC9E,YAAQ,KAAK,EAAE;;CAGjB,MAAM,UAAU,QAAQ,OAAOC,UAAQ,KAAK;CAC5C,MAAM,SAAS,QAAQ,UAAU;CAEjC,IAAI;AACJ,KAAI;AACF,aAAW,MAAM,aAAa,QAAQ;UAC/B,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,mBAAmB,QAAQ,KAAK,QAAQ,IAAI;AACjE,YAAQ,KAAK,EAAE;;CAIjB,MAAM,SAAS,SAAS,MAAM,MAAM,EAAE,QAAQ,eAAe,OAAO;AACpE,KAAI,CAAC,QAAQ;AACX,YAAQ,OAAO,MAAM,mBAAmB,OAAO,kBAAkB,QAAQ,MAAM;AAC/E,YAAQ,KAAK,EAAE;;CAIjB,MAAM,WAAW,SAAS,MAAM,MAAM,EAAE,QAAQ,eAAe,OAAO;AACtE,KAAI,UAAU;AACZ,YAAQ,OAAO,MACb,WAAW,OAAO,wBAAwB,KAAK,SAAS,SAAS,SAAS,SAAS,CAAC,MACrF;AACD,YAAQ,KAAK,EAAE;;CAIjB,MAAM,cAAc,SAAS,QAAQ,EAAE,cAAc;EACnD,MAAM,UAAU,QAAQ;AAGxB,SACE,SAAS,WAAW,UACnB,MAAM,QAAQ,SAAS,SAAS,IAAI,QAAQ,SAAS,SAAS,OAAO;GAExE;AAEF,KAAI,QAAQ;AACV,YAAQ,OAAO,MAAM,0CAA0C;AAC/D,YAAQ,OAAO,MAAM,iBAAiB,OAAO,KAAK,OAAO,IAAI;AAC7D,YAAQ,OAAO,MAAM,WAAW,KAAK,SAAS,SAAS,OAAO,SAAS,CAAC,IAAI;AAC5E,MAAI,YAAY,SAAS,GAAG;AAC1B,aAAQ,OAAO,MAAM,iBAAiB,YAAY,OAAO,0BAA0B;AACnF,QAAK,MAAM,OAAO,YAChB,WAAQ,OAAO,MAAM,KAAK,KAAK,SAAS,SAAS,IAAI,SAAS,CAAC,IAAI;;AAGvE;;CAIF,MAAM,MAAM,MAAM,SAAS,OAAO,UAAU,QAAQ;CACpD,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAO,gBAAgB;CAEvB,MAAM,aAAa,gBAAgB,OAAO;AAC1C,KAAI,CAAC,WAAW,SAAS;AACvB,YAAQ,OAAO,MACb,yDAAyD,WAAW,OAAO,KAAK,OAAO,CAAC,IACzF;AACD,YAAQ,KAAK,EAAE;;AAGjB,OAAM,UACJ,OAAO,UACP,KAAK,UAAU,WAAW,MAAM,MAAM,EAAE,GAAG,MAC3C,QACD;AACD,WAAQ,OAAO,MAAM,aAAa,OAAO,KAAK,OAAO,IAAI;CAGzD,MAAM,UAAU,MAAM,iBAAiB,UAAU,QAAQ,OAAO;AAChE,KAAI,UAAU,EACZ,WAAQ,OAAO,MAAM,aAAa,QAAQ,oBAAoB,YAAY,IAAI,KAAK,IAAI,IAAI;CAK7F,IAAI,SAAwB;CAC5B,IAAI,MAAM,KAAK,QAAQ,QAAQ;AAC/B,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,KAAK,KAAK,OAAO;AACxC,MAAI,WAAW,UAAU,EAAE;AAAE,YAAS;AAAW;;EACjD,MAAM,SAAS,KAAK,QAAQ,IAAI;AAChC,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,KAAI,QAAQ;EACV,MAAM,WAAW,KAAK,KAAK,QAAQ,OAAO;AAC1C,MAAI,WAAW,SAAS,EAAE;GAExB,MAAM,UADQ,aAAa,UAAU,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,OAAO,QAAQ,CAC1D,KAAK,MAAO,MAAM,SAAS,SAAS,EAAG;AAC7D,OAAI,CAAC,QAAQ,SAAS,OAAO,CAAE,SAAQ,KAAK,OAAO;AACnD,iBAAc,UAAU,QAAQ,KAAK,KAAK,GAAG,MAAM,QAAQ;AAC3D,aAAQ,OAAO,MAAM,iCAAiC;;;EAI7D;;;;AClLH,MAAa,gBAAgB,IAAI,QAAQ,SAAS,CAC/C,YAAY,4EAA4E,CACxF,SAAS,WAAW,kCAAkC,CACtD,OAAO,oBAAoB,mCAAmC,CAC9D,OAAO,UAAU,yBAAyB,CAC1C,OAAO,oBAAoB,kDAAkD,CAC7E,OAAO,OAAO,OAAe,YAA8D;CAE1F,MAAM,WAAW,MAAM,aADP,QAAQ,OAAOC,UAAQ,KAAK,CACA;CAE5C,MAAM,eAAe,QAAQ,QACzB,QAAQ,MAAM,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,GAC7C;EAAC;EAAc;EAAS;EAAW;EAAQ;EAAY;EAAiB;CAE5E,MAAM,IAAI,MAAM,aAAa;CAE7B,MAAM,UAAU,SAAS,QAAQ,EAAE,cAAc;AAC/C,OAAK,MAAM,SAAS,cAAc;GAChC,MAAM,MAAO,QAAoC;AACjD,OAAI,QAAQ,UAAa,QAAQ,KAAM;AACvC,OAAI,OAAO,QAAQ,YAAY,IAAI,aAAa,CAAC,SAAS,EAAE,CAAE,QAAO;AACrE,OAAI,MAAM,QAAQ,IAAI,IAAI,IAAI,MAAM,MAAM,OAAO,MAAM,YAAY,EAAE,aAAa,CAAC,SAAS,EAAE,CAAC,CAC7F,QAAO;;AAEX,SAAO;GACP;AAEF,KAAI,QAAQ,MAAM;AAChB,YAAQ,OAAO,MAAM,KAAK,UAAU,QAAQ,KAAK,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,KAAK;AACnF;;AAGF,KAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,OAAO,MAAM,+BAA+B,MAAM,KAAK;AAC/D;;AAGF,WAAQ,OAAO,MAAM,SAAS,QAAQ,OAAO,wBAAwB,MAAM,QAAQ;AAEnF,MAAK,MAAM,EAAE,SAAS,cAAc,SAAS;EAC3C,MAAM,aACH;GAAE,QAAQ;GAAK,OAAO;GAAK,QAAQ;GAAK,YAAY;GAAK,CACxD,QAAQ,WACL;AACP,YAAQ,OAAO,MAAM,KAAK,WAAW,IAAI,QAAQ,WAAW,OAAO,GAAG,CAAC,GAAG,QAAQ,MAAM,IAAI;AAC5F,YAAQ,OAAO,MACb,QAAQ,QAAQ,QAAQ,MAAM,GAAG,GAAG,GAAG,QAAQ,QAAQ,SAAS,KAAK,QAAQ,GAAG,IACjF;AACD,YAAQ,OAAO,MAAM,QAAQ,SAAS,MAAM;;EAE9C;;;;AChDJ,SAAS,YAAY,KAAmB;AACtC,KAAIC,UAAQ,aAAa,SACvB,OAAM,QAAQ,CAAC,IAAI,EAAE;EAAE,UAAU;EAAM,OAAO;EAAU,CAAC,CAAC,OAAO;UACxDA,UAAQ,aAAa,QAC9B,OAAM,OAAO;EAAC;EAAM;EAAS;EAAI;EAAI,EAAE;EAAE,UAAU;EAAM,OAAO;EAAU,CAAC,CAAC,OAAO;KAEnF,OAAM,YAAY,CAAC,IAAI,EAAE;EAAE,UAAU;EAAM,OAAO;EAAU,CAAC,CAAC,OAAO;;AAIzE,SAAS,cAAc,MAAc,WAAqC;CACxE,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,QAAO,IAAI,SAAS,QAAQ;EAC1B,SAAS,UAAU;GACjB,MAAM,MAAM,KAAK,IAAI,oBAAoB,KAAK,WAAW,aAAa;AACpE,QAAI,SAAS,eAAe,IAAI;KAChC;AACF,OAAI,GAAG,eAAe;AACpB,QAAI,KAAK,KAAK,GAAG,SACf,YAAW,SAAS,IAAI;QAExB,KAAI,MAAM;KAEZ;AACF,OAAI,KAAK;;AAEX,WAAS;GACT;;AAGJ,SAAS,YAAY,cAAsB,MAA4B;AACrE,QAAO,MACL,WACA;EAAC;EAAe;EAAe;EAAc;EAAU,OAAO,KAAK;EAAC,EACpE,EAAE,OAAO;EAAC;EAAU;EAAW;EAAU,EAAE,CAC5C;;AAGH,MAAa,eAAe,IAAI,QAAQ,QAAQ,CAC7C,YAAY,4EAA4E,CACxF,SAAS,SAAS,uDAAuD,CACzE,OAAO,kBAAkB,6BAA6B,OAAO,CAC7D,OAAO,aAAa,yCAAyC,CAC7D,OAAO,OAAO,KAAyB,YAA6C;CACnF,MAAM,eAAe,QAAQ,OAAOA,UAAQ,KAAK,CAAC;CAClD,MAAM,OAAO,SAAS,QAAQ,MAAM,GAAG;CACvC,MAAM,MAAM,oBAAoB;AAEhC,WAAQ,OAAO,MACb,+CAA+C,aAAa,YAAY,KAAK,OAC9E;CAGD,IAAI,QAAQ,YAAY,cAAc,KAAK;CAC3C,IAAI,eAAe;AAEnB,OAAM,GAAG,UAAU,QAAQ;AACzB,YAAQ,OAAO,MAAM,oCAAoC,IAAI,QAAQ,IAAI;AACzE,YAAQ,OAAO,MACb,mEACD;AACD,YAAQ,KAAK,EAAE;GACf;AAEF,OAAM,GAAG,SAAS,SAAS;AACzB,MAAI,CAAC,aAEH,WAAQ,OAAO,MAAM,4BAA4B,QAAQ,EAAE,IAAI;GAEjE;AAKF,KAFc,MAAM,cAAc,MAAM,IAAK,EAElC;AACT,YAAQ,OAAO,MAAM,aAAa,IAAI,MAAM;AAC5C,YAAQ,OAAO,MAAM,SAAS,IAAI,sCAAsC;AACxE,YAAQ,OAAO,MAAM,SAAS,IAAI,+CAA+C;AACjF,YAAQ,OAAO,MAAM,SAAS,IAAI,yCAAyC;AAC3E,YAAQ,OAAO,MAAM,0BAA0B;AAE/C,MAAI,QAAQ,KACV,aAAY,IAAI;OAGlB,WAAQ,OAAO,MACb,2BAA2B,KAAK,8DACjC;CAIH,IAAI,YAAY;CAChB,MAAM,iBAAiB,YAAY,YAAY;AAC7C,MAAI,cAAc;AAChB,iBAAc,eAAe;AAC7B;;AAIF,MADc,MAAM,cAAc,MAAM,IAAK,EAClC;AACT,eAAY;AACZ;;AAGF;AACA,MAAI,aAAa,GAAG;AAClB,aAAQ,OAAO,MACb,8DACD;AAED,OAAI;AACF,UAAM,MAAM;WACN;AAIR,WAAQ,YAAY,cAAc,KAAK;AACvC,eAAY;AAEZ,SAAM,GAAG,UAAU,QAAQ;AACzB,cAAQ,OAAO,MAAM,8BAA8B,IAAI,QAAQ,IAAI;KACnE;AAIF,OADkB,MAAM,cAAc,MAAM,IAAK,EAClC;AACb,cAAQ,OAAO,MAAM,oCAAoC;AACzD,QAAI,QAAQ,KACV,aAAY,IAAI;SAGlB,WAAQ,OAAO,MAAM,yDAAyD;;IAGjF,KAAO;AAGV,WAAQ,GAAG,gBAAgB;AACzB,iBAAe;AACf,gBAAc,eAAe;AAC7B,YAAQ,OAAO,MAAM,uBAAuB;AAC5C,QAAM,KAAK,UAAU;AACrB,YAAQ,KAAK,EAAE;GACf;EACF;;;;AC5IJ,MAAMC,YAAU;;;;;AAMhB,SAAS,kBAAkB,UAAiC;CAC1D,IAAI,UAAU,QAAQ,SAAS;AAC/B,QAAO,MAAM;AAEX,MAAI,WADc,KAAK,SAASA,UAAQ,CACf,CAAE,QAAO;EAClC,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;;;;;;AAQd,SAAS,iBAAiB,SAAyB;CACjD,MAAM,QAAQ,QAAQ,MAAM,CAAC,MAAM,MAAM;AACzC,KAAI,MAAM,UAAU,EAAG,QAAO,QAAQ,MAAM;AAC5C,QAAO,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,GAAG;;;;;;AAOvC,SAAS,eAAe,SAAyB;AAC/C,QAAO,QACJ,MAAM,CACN,MAAM,MAAM,CACZ,MAAM,GAAG,EAAE,CACX,KAAK,IAAI,CACT,aAAa,CACb,QAAQ,eAAe,GAAG;;AAG/B,MAAa,eAAe,IAAI,QAAQ,QAAQ,CAC7C,YAAY,wDAAwD,CACpE,SAAS,gBAAgB,wDAAwD,CACjF,OAAO,mBAAmB,uCAAuC,CACjE,OAAO,gBAAgB,gEAAgE,CACvF,OAAO,OAAO,WAAmB,YAA+C;CAI/E,MAAM,gBAAgB,kBAHVC,UAAQ,KAAK,CAGmB;AAC5C,KAAI,CAAC,eAAe;AAClB,YAAQ,OAAO,MACb,mHAED;AACD,YAAQ,KAAK,EAAE;;CAIjB,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,aAAa,cAAc;UACpC,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,6BAA6B,cAAc,KAAK,QAAQ,IAAI;AACjF,YAAQ,KAAK,EAAE;;CAGjB,MAAM,cAAc,QAAQ,MAAM,MAAM,EAAE,QAAQ,eAAe,UAAU;AAC3E,KAAI,CAAC,aAAa;AAChB,YAAQ,OAAO,MACb,0BAA0B,UAAU,0GAErC;AACD,YAAQ,KAAK,EAAE;;CAGjB,MAAM,YAAY,QAAQ,YAAY,SAAS;CAuB/C,MAAM,UAAU,MAAM,QApBqB,CACzC;EACE,MAAM;EACN,MAAM;EACN,SAAS;EACT,WAAW,UACT,MAAM,MAAM,CAAC,SAAS,IAAI,OAAO;EACpC,EACD;EACE,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAS,OAAO;GAAS,EAClC;GAAE,OAAO;GAAU,OAAO;GAAU,CACrC;EACD,SAAS;EACV,CACF,EAEyC,EACxC,gBAAgB;AACd,YAAQ,OAAO,MAAM,aAAa;AAClC,YAAQ,KAAK,EAAE;IAElB,CAAC;CAEF,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,UAAW,QAAQ,QAAmB,MAAM;CAClD,MAAM,SAAS,QAAQ;CAOvB,IAAI;AACJ,KAAI,QAAQ,IACV,cAAa,QAAQ;MAChB;EACL,MAAM,WAAW,eAAe,QAAQ;AACxC,eAAa;EACb,IAAI,SAAS;AACb,SAAO,WAAW,KAAK,WAAW,WAAW,CAAC,EAAE;AAC9C;AACA,gBAAa,GAAG,SAAS,GAAG;;;CAIhC,MAAM,gBAAgB,KAAK,WAAW,WAAW;AAGjD,OAAM,MAAM,eAAe,EAAE,WAAW,MAAM,CAAC;CAG/C,IAAI;AACJ,KAAI;AACF,eAAa,eAAe,cAAc;UACnC,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,UAAU,QAAQ,0EAA0E;AACjH,YAAQ,KAAK,EAAE;;CAGjB,MAAM,QAAQ,iBAAiB,QAAQ;CAevC,MAAM,aAAa,gBAZH;EACd;EACA;EACA;EACA;EACA,SAAS;GACP,QAAQ;GACR,GAAI,SAAS,EAAE,aAAa,QAAQ,GAAG,EAAE;GAC1C;EACF,CAG0C;AAC3C,KAAI,CAAC,WAAW,SAAS;AACvB,YAAQ,OAAO,MACb,iEAAiE,WAAW,OAAO,KAAK,OAAO,CAAC,IACjG;AACD,YAAQ,KAAK,EAAE;;AAIjB,OAAM,UADkB,KAAK,eAAe,eAAe,EAC1B,KAAK,UAAU,WAAW,MAAM,MAAM,EAAE,GAAG,MAAM,QAAQ;AAG1F,WAAQ,OAAO,MAAM,aAAa,WAAW,QAAQ,UAAU,MAAM,cAAc,IAAI;EACvF;;;;AClLJ,MAAa,cAAc,IAAI,QAAQ,OAAO,CAC3C,YAAY,sFAAsF,CAClG,OAAO,oBAAoB,mCAAmC,CAC9D,OAAO,OAAO,YAA8B;CAC3C,MAAM,UAAU,QAAQ,OAAOC,UAAQ,KAAK;CAE5C,IAAI;AACJ,KAAI;AACF,aAAW,MAAM,aAAa,QAAQ;UAC/B,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,OAAO,MAAM,mBAAmB,QAAQ,KAAK,QAAQ,IAAI;AACjE,YAAQ,KAAK,EAAE;;CAGjB,MAAM,QAAQ,SAAS;AAEvB,KAAI,UAAU,GAAG;AACf,YAAQ,OAAO,MAAM,yBAAyB,QAAQ,MAAM;AAC5D,YAAQ,OAAO,MAAM,mEAAmE;AACxF;;CAIF,MAAM,kBAA0C;EAC9C,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,YAAY;EACb;AACD,MAAK,MAAM,EAAE,aAAa,UAAU;EAClC,MAAM,IAAI,QAAQ;AAClB,kBAAgB,MAAM,gBAAgB,MAAM,KAAK;;CAInD,MAAM,qBAAqB,SAAS,KAAK,EAAE,cACzC,oBAAoB,QAA8C,CACnE;CACD,MAAM,kBACJ,mBAAmB,SAAS,IACxB,KAAK,MAAM,mBAAmB,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,mBAAmB,OAAO,GACrF;CAGN,MAAM,gBAAgB,SAAS,QAC5B,EAAE,cAAc,CAAC,QAAQ,aAAa,QAAQ,UAAU,WAAW,EACrE,CAAC;CAGF,MAAM,WAAW,SAAS,QACvB,EAAE,cAAc,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,EAC3D,CAAC;CAGF,MAAM,4BAAY,IAAI,KAAqB;AAC3C,MAAK,MAAM,EAAE,aAAa,SACxB,MAAK,MAAM,OAAO,QAAQ,QAAQ,EAAE,CAClC,WAAU,IAAI,MAAM,UAAU,IAAI,IAAI,IAAI,KAAK,EAAE;CAGrD,MAAM,UAAU,MAAM,KAAK,UAAU,SAAS,CAAC,CAC5C,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG,CAC3B,MAAM,GAAG,EAAE;CAEd,MAAM,QAAkB,EAAE;AAC1B,OAAM,KAAK,kCAAkC;AAC7C,OAAM,KAAK,mCAAmC;AAC9C,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,oBAAoB,QAAQ;AACvC,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,aAAa;AACxB,MAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,gBAAgB,CAC3D,KAAI,QAAQ,EACV,OAAM,KAAK,KAAK,OAAO,OAAO,GAAG,CAAC,IAAI,QAAQ;AAGlD,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,uBAAuB,gBAAgB,GAAG;AACrD,OAAM,KAAK,uBAAuB,gBAAgB;AAClD,OAAM,KAAK,uBAAuB,WAAW;AAE7C,KAAI,QAAQ,SAAS,GAAG;AACtB,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,cAAc;AACzB,OAAK,MAAM,CAAC,KAAK,UAAU,QACzB,OAAM,KAAK,KAAK,IAAI,OAAO,GAAG,CAAC,IAAI,QAAQ;;AAI/C,WAAQ,OAAO,MAAM,MAAM,KAAK,KAAK,GAAG,KAAK;EAC7C;;;;AC1FJ,MAAa,aAAa,IAAI,QAAQ,MAAM,CACzC,YAAY,kCAAkC,CAC9C,SAAS,SAAS,yCAAyC,CAC3D,SAAS,UAAU,mFAAiF,CACpG,OAAO,oBAAoB,gDAAgD,CAC3E,OAAO,OAAO,KAAa,MAAc,YAA8B;CACtE,MAAM,UAAU,QAAQ,OAAOC,UAAQ,KAAK;CAE5C,MAAM,SADW,MAAM,aAAa,QAAQ,EACrB,MAAK,MAAK,EAAE,QAAQ,eAAe,IAAI;AAE9D,KAAI,CAAC,OAAO;AACV,YAAQ,OAAO,MAAM,mBAAmB,IAAI,kBAAkB,QAAQ,KAAK;AAC3E,YAAQ,KAAK,EAAE;;CAGjB,MAAM,UAAU,KAAK,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;CAClE,MAAM,QAAQ,QAAQ,QAAO,MAAK,CAAC,EAAE,WAAW,IAAI,CAAC;CACrD,MAAM,WAAW,QAAQ,QAAO,MAAK,EAAE,WAAW,IAAI,CAAC,CAAC,KAAI,MAAK,EAAE,MAAM,EAAE,CAAC;CAE5E,MAAM,UAAU,MAAM,QAAQ,QAAQ,EAAE;AAGxC,MAAK,MAAM,OAAO,MAChB,KAAI,QAAQ,SAAS,IAAI,CACvB,WAAQ,OAAO,MAAM,cAAc,IAAI,qBAAqB;AAGhE,MAAK,MAAM,OAAO,SAChB,KAAI,CAAC,QAAQ,SAAS,IAAI,CACxB,WAAQ,OAAO,MAAM,cAAc,IAAI,qBAAqB;CAIhE,MAAM,UAAU,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,QAAQ,QAAO,MAAK,CAAC,SAAS,SAAS,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;CAEvF,MAAM,MAAM,MAAM,SAAS,MAAM,UAAU,QAAQ;CACnD,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAO,UAAU;CAEjB,MAAM,aAAa,gBAAgB,OAAO;AAC1C,KAAI,CAAC,WAAW,SAAS;AACvB,YAAQ,OAAO,MAAM,qBAAqB,WAAW,OAAO,KAAK,KAAK,CAAC,IAAI;AAC3E,YAAQ,KAAK,EAAE;;AAGjB,OAAM,UAAU,MAAM,UAAU,KAAK,UAAU,WAAW,MAAM,MAAM,EAAE,GAAG,MAAM,QAAQ;AACzF,WAAQ,OAAO,MAAM,KAAK,IAAI,UAAU,QAAQ,KAAK,KAAK,CAAC,KAAK;EAChE;;;;AC9CJ,MAAM,UAAU;AAChB,MAAM,eAAe;;;;;AAMrB,SAAS,WAAW,UAAiC;CACnD,IAAI,UAAU,QAAQ,SAAS;AAC/B,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,SAAS,QAAQ;AACxC,MAAI,WAAW,UAAU,CAAE,QAAO;EAClC,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;;AAId,MAAa,mBAAmB,IAAI,QAAQ,YAAY,CACrD,YAAY,6CAA6C;AAE5D,iBACG,QAAQ,OAAO,CACf,YAAY,sDAAsD,CAClE,SAAS,SAAS,uDAAuD,CACzE,OAAO,WAAW,yDAAyD,CAC3E,QAAQ,KAAyB,YAAiC;CAEjE,MAAM,SAAS,KADG,QAAQ,OAAOC,UAAQ,KAAK,CAAC,EAChB,QAAQ;CACvC,MAAM,cAAc,KAAK,QAAQ,aAAa;AAE9C,KAAI,WAAW,OAAO,IAAI,CAAC,QAAQ,OAAO;AACxC,YAAQ,OAAO,MACb,yCAAyC,OAAO,yDAEjD;AACD;;AAGF,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AAEtC,KAAI,CAAC,WAAW,YAAY,IAAI,QAAQ,MAEtC,eAAc,aAAa,oBADd,IAAI,MAAM,EAAC,aAAa,CACF,QAAQ,QAAQ;AAGrD,WAAQ,OAAO,MAAM,mCAAmC,OAAO,KAAK;AACpE,WAAQ,OAAO,MAAM,uEAAuE;EAC5F;AAEJ,iBACG,QAAQ,SAAS,CACjB,YAAY,mEAAmE,CAC/E,OAAO,YAAY;CAClB,MAAM,SAAS,WAAWA,UAAQ,KAAK,CAAC;AAExC,KAAI,CAAC,QAAQ;AACX,YAAQ,OAAO,MACb,4GAED;AACD;;CAGF,MAAM,cAAc,KAAK,QAAQ,aAAa;AAC9C,KAAI,CAAC,WAAW,YAAY,EAAE;AAC5B,YAAQ,OAAO,MAAM,eAAe,OAAO,iCAAiC;AAC5E;;CAIF,MAAM,QADM,aAAa,aAAa,QAAQ,CAAC,MAAM,CACnC,MAAM,KAAK,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC;CAClD,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,aAAa,MAAM,MAAM;CAC/B,MAAM,UAAU,SAAS,YAAY,GAAG;CACxC,MAAM,OAAO,MAAM,QAAQ,GAAG,QAAQ,OAAO,UAAU,EAAE,CAAC,SAAS,GAAG,IAAI;AAE1E,WAAQ,OAAO,MAAM,eAAe,OAAO,IAAI;AAC/C,WAAQ,OAAO,MAAM,eAAe,KAAK,GAAG,WAAW,IAAI;AAC3D,WAAQ,OAAO,MAAM,oBAAoB,KAAK,GAAG,KAAK,IAAI;CAG1D,MAAM,gBAAgB,QAAQ,QAAQ,KAAK;AAC3C,KAAI;EACF,MAAM,WAAW,MAAM,aAAa,cAAc;AAClD,YAAQ,OAAO,MAAM,eAAe,SAAS,OAAO,IAAI;AAExD,MAAI,SAAS,WAAW,EACtB,WAAQ,OAAO,MAAM,wFAAwF;OACxG;GACL,MAAM,qBAAqB,SAAS,KAAK,EAAE,cACzC,oBAAoB,QAA8C,CACnE;GACD,MAAM,MAAM,KAAK,MACf,mBAAmB,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,mBAAmB,OACpE;AACD,aAAQ,OAAO,MAAM,eAAe,IAAI,KAAK;;SAEzC;EAGR;;;;AC3FJ,MAAM,UAAU,IAAI,SAAS;AAC7B,QACG,KAAK,MAAM,CACX,YAAY,kDAAkD,CAC9D,QAAQ,QAAQ;AAEnB,QAAQ,WAAW,iBAAiB;AACpC,QAAQ,WAAW,aAAa;AAChC,QAAQ,WAAW,YAAY;AAC/B,QAAQ,WAAW,cAAc;AACjC,QAAQ,WAAW,YAAY;AAC/B,QAAQ,WAAW,aAAa;AAChC,QAAQ,WAAW,aAAa;AAChC,QAAQ,WAAW,aAAa;AAChC,QAAQ,WAAW,WAAW;AAC9B,QAAQ,WAAW,eAAe;AAClC,QAAQ,WAAW,cAAc;AACjC,QAAQ,WAAW,cAAc;AACjC,QAAQ,WAAW,YAAY;AAC/B,QAAQ,WAAW,eAAe;AAClC,QAAQ,WAAW,YAAY;AAC/B,QAAQ,WAAW,cAAc;AACjC,QAAQ,WAAW,cAAc;AAEjC,QAAQ,OAAO"}
1
+ {"version":3,"file":"index.mjs","names":["process","findNearestFeatureJson","process","findNearestFeatureJson","process","findLacDir","process","escapeHtml","escapeHtml","statusBadge","process","process","titleFromProblem","process","process","process","process","process","process","process","LAC_DIR","process","process","process","process"],"sources":["../src/lib/scanner.ts","../src/commands/archive.ts","../src/lib/walker.ts","../src/lib/config.ts","../src/commands/blame.ts","../src/commands/diff.ts","../src/commands/doctor.ts","../src/templates/markdown.ts","../src/templates/site-style.css.ts","../src/templates/site-feature.html.ts","../src/templates/site-index.html.ts","../src/lib/siteGenerator.ts","../src/commands/export.ts","../src/commands/hooks.ts","../src/lib/featureKey.ts","../src/commands/init.ts","../src/commands/lineage.ts","../src/commands/lint.ts","../src/commands/import.ts","../src/commands/rename.ts","../src/commands/search.ts","../src/commands/serve.ts","../src/commands/spawn.ts","../src/commands/stat.ts","../src/commands/tag.ts","../src/commands/workspace.ts","../src/index.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport type { Feature } from '@life-as-code/feature-schema'\nimport { validateFeature } from '@life-as-code/feature-schema'\n\nexport interface ScannedFeature {\n filePath: string\n feature: Feature\n}\n\n/**\n * Recursively finds all feature.json files under a directory.\n * Returns an array of { filePath, feature } for each valid feature.json found.\n * Files that fail validation are skipped with a warning printed to stderr.\n */\nexport async function scanFeatures(dir: string): Promise<ScannedFeature[]> {\n const results: ScannedFeature[] = []\n\n async function walk(currentDir: string): Promise<void> {\n let entries: Array<{ name: string; isDirectory: () => boolean; isFile: () => boolean }>\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let rawEntries: any[]\n try {\n rawEntries = await readdir(currentDir, { withFileTypes: true })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Warning: could not read directory \"${currentDir}\": ${message}\\n`)\n return\n }\n\n // Normalise to plain objects to avoid @types/node Dirent<Buffer> vs\n // Dirent<string> variance across different @types/node versions.\n entries = rawEntries.map((e) => ({\n name: String(e.name),\n isDirectory: () => e.isDirectory(),\n isFile: () => e.isFile(),\n }))\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name)\n\n if (entry.isDirectory()) {\n // Skip hidden directories and node_modules\n if (entry.name.startsWith('.') || entry.name === 'node_modules') {\n continue\n }\n await walk(fullPath)\n } else if (entry.isFile() && entry.name === 'feature.json') {\n let raw: string\n try {\n raw = await readFile(fullPath, 'utf-8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Warning: could not read \"${fullPath}\": ${message}\\n`)\n continue\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n process.stderr.write(`Warning: invalid JSON in \"${fullPath}\" — skipping\\n`)\n continue\n }\n\n const result = validateFeature(parsed)\n if (!result.success) {\n process.stderr.write(\n `Warning: \"${fullPath}\" failed validation — skipping\\n ${result.errors.join('\\n ')}\\n`,\n )\n continue\n }\n\n results.push({ filePath: fullPath, feature: result.data })\n }\n }\n }\n\n await walk(dir)\n return results\n}\n","import { readFile, writeFile } from 'node:fs/promises'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\nexport const archiveCommand = new Command('archive')\n .description('Mark a feature as deprecated (archived)')\n .argument('<key>', 'featureKey to archive (e.g. feat-2026-001)')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .action(async (key: string, options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n const features = await scanFeatures(scanDir)\n const found = features.find(f => f.feature.featureKey === key)\n\n if (!found) {\n process.stderr.write(`Error: feature \"${key}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n\n if (found.feature.status === 'deprecated') {\n process.stdout.write(`Already deprecated: ${key}\\n`)\n process.exit(0)\n }\n\n const raw = await readFile(found.filePath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n parsed['status'] = 'deprecated'\n\n const validation = validateFeature(parsed)\n if (!validation.success) {\n process.stderr.write(`Validation error: ${validation.errors.join(', ')}\\n`)\n process.exit(1)\n }\n\n await writeFile(found.filePath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n process.stdout.write(`✓ ${key} archived (status → deprecated)\\n`)\n })\n","import { existsSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\n\n/**\n * Walks up the directory tree from startDir to find the nearest feature.json.\n * Returns the absolute path or null if not found before reaching the filesystem root.\n */\nexport function findNearestFeatureJson(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, 'feature.json')\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\n/**\n * Walks up the directory tree from startDir to find the nearest .git directory.\n * Returns the absolute path to .git or null if not found.\n */\nexport function findGitDir(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, '.git')\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\n/**\n * Walks up the directory tree from startDir to find the nearest lac.config.json.\n * Returns the absolute path or null if not found.\n */\nexport function findLacConfig(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, 'lac.config.json')\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n","import { readFileSync } from 'node:fs'\nimport process from 'node:process'\n\nimport { findLacConfig } from './walker.js'\n\nexport type RequirableField =\n | 'problem'\n | 'analysis'\n | 'decisions'\n | 'implementation'\n | 'knownLimitations'\n | 'tags'\n\nexport interface LacConfig {\n version?: number\n /** Fields that must be non-empty for lint to pass. Default: ['problem'] */\n requiredFields?: RequirableField[]\n /** Completeness % (0-100) a feature must meet. Default: 0 (disabled) */\n ciThreshold?: number\n /** Only lint features with these statuses. Default: ['active', 'draft'] */\n lintStatuses?: Array<'draft' | 'active' | 'frozen' | 'deprecated'>\n /**\n * Domain prefix for generated featureKeys. Default: \"feat\".\n * Use any lowercase alphanumeric string, e.g. \"proc\", \"goal\", \"adr\", \"law\".\n * Example: `\"domain\": \"proc\"` → keys like `proc-2026-001`.\n */\n domain?: string\n}\n\nconst DEFAULTS: Required<LacConfig> = {\n version: 1,\n requiredFields: ['problem'],\n ciThreshold: 0,\n lintStatuses: ['active', 'draft'],\n domain: 'feat',\n}\n\nexport function loadConfig(fromDir?: string): Required<LacConfig> {\n const startDir = fromDir ?? process.cwd()\n const configPath = findLacConfig(startDir)\n if (!configPath) return { ...DEFAULTS }\n\n try {\n const raw = readFileSync(configPath, 'utf-8')\n const parsed = JSON.parse(raw) as LacConfig\n return {\n version: parsed.version ?? DEFAULTS.version,\n requiredFields: parsed.requiredFields ?? DEFAULTS.requiredFields,\n ciThreshold: parsed.ciThreshold ?? DEFAULTS.ciThreshold,\n lintStatuses: parsed.lintStatuses ?? DEFAULTS.lintStatuses,\n domain: parsed.domain ?? DEFAULTS.domain,\n }\n } catch {\n process.stderr.write(`Warning: could not parse lac.config.json at \"${configPath}\" — using defaults\\n`)\n return { ...DEFAULTS }\n }\n}\n\n/** The 6 optional fields used to compute completeness score (0–100) */\nexport const OPTIONAL_FIELDS: RequirableField[] = [\n 'analysis',\n 'decisions',\n 'implementation',\n 'knownLimitations',\n 'tags',\n 'annotations' as RequirableField,\n]\n\n/**\n * Returns today's date in YYYY-MM-DD format.\n * Use this as the default `date` for new annotations when no date is provided.\n */\nexport function todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nexport function computeCompleteness(feature: Record<string, unknown>): number {\n const filled = OPTIONAL_FIELDS.filter((field) => {\n const val = feature[field]\n if (val === undefined || val === null || val === '') return false\n if (Array.isArray(val)) return val.length > 0\n return typeof val === 'string' && val.trim().length > 0\n }).length\n return Math.round((filled / OPTIONAL_FIELDS.length) * 100)\n}\n","import { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { computeCompleteness } from '../lib/config.js'\nimport { findNearestFeatureJson } from '../lib/walker.js'\n\nexport const blameCommand = new Command('blame')\n .description('Show which feature owns a file or path')\n .argument('<path>', 'File path to trace (supports path:line format, line is ignored)')\n .action((rawPath: string) => {\n // Strip line number if provided (e.g. src/auth/index.ts:42 → src/auth/index.ts)\n const filePath = rawPath.replace(/:\\d+$/, '')\n const absPath = resolve(filePath)\n const startDir = dirname(absPath)\n\n const featureJsonPath = findNearestFeatureJson(startDir)\n\n if (!featureJsonPath) {\n process.stderr.write(\n `No feature.json found for \"${filePath}\".\\nRun \"lac init\" in the feature folder to create one.\\n`,\n )\n process.exit(1)\n }\n\n let raw: string\n try {\n raw = readFileSync(featureJsonPath, 'utf-8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error reading \"${featureJsonPath}\": ${message}\\n`)\n process.exit(1)\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n process.stderr.write(`Error: \"${featureJsonPath}\" contains invalid JSON.\\n`)\n process.exit(1)\n }\n\n const result = validateFeature(parsed)\n if (!result.success) {\n process.stderr.write(`Error: \"${featureJsonPath}\" failed validation:\\n ${result.errors.join('\\n ')}\\n`)\n process.exit(1)\n }\n\n const f = result.data\n\n const statusIcon: Record<string, string> = {\n active: '⊙',\n draft: '◌',\n frozen: '❄',\n deprecated: '⊘',\n }\n const icon = statusIcon[f.status] ?? '?'\n const completeness = computeCompleteness(f as unknown as Record<string, unknown>)\n const bar = '█'.repeat(Math.round(completeness / 10)) + '░'.repeat(10 - Math.round(completeness / 10))\n\n process.stdout.write('\\n')\n process.stdout.write(` Feature : ${f.featureKey}\\n`)\n process.stdout.write(` Title : ${f.title}\\n`)\n process.stdout.write(` Status : ${icon} ${f.status}\\n`)\n process.stdout.write(` Complete : [${bar}] ${completeness}%\\n`)\n process.stdout.write(` Path : ${featureJsonPath}\\n`)\n process.stdout.write('\\n')\n process.stdout.write(` Problem:\\n ${f.problem}\\n`)\n\n if (f.analysis) {\n const excerpt = f.analysis.length > 120 ? f.analysis.slice(0, 120) + '…' : f.analysis\n process.stdout.write(`\\n Analysis:\\n ${excerpt}\\n`)\n }\n\n if (f.decisions && f.decisions.length > 0) {\n process.stdout.write(`\\n Decisions (${f.decisions.length}):\\n`)\n for (const d of f.decisions) {\n process.stdout.write(` • ${d.decision}\\n Rationale: ${d.rationale}\\n`)\n }\n }\n\n if (f.knownLimitations && f.knownLimitations.length > 0) {\n process.stdout.write(`\\n Known Limitations:\\n`)\n for (const lim of f.knownLimitations) {\n process.stdout.write(` - ${lim}\\n`)\n }\n }\n\n if (f.lineage?.parent) {\n process.stdout.write(`\\n Lineage : parent → ${f.lineage.parent}\\n`)\n }\n\n process.stdout.write('\\n')\n })\n","import process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\n/**\n * Stable JSON serialisation that sorts object keys recursively so that two\n * objects with the same content but different insertion order compare equal.\n */\nfunction stableStringify(val: unknown): string {\n if (val === null || typeof val !== 'object') return JSON.stringify(val)\n if (Array.isArray(val)) return `[${val.map(stableStringify).join(',')}]`\n const sorted = Object.keys(val as Record<string, unknown>)\n .sort()\n .map((k) => `${JSON.stringify(k)}:${stableStringify((val as Record<string, unknown>)[k])}`)\n return `{${sorted.join(',')}}`\n}\n\nfunction formatValue(val: unknown): string {\n if (val === undefined || val === null) return '(empty)'\n if (typeof val === 'string') return val.length > 80 ? val.slice(0, 77) + '...' : val\n return JSON.stringify(val)\n}\n\nexport const diffCommand = new Command('diff')\n .description('Compare two features field-by-field')\n .argument('<key1>', 'First featureKey')\n .argument('<key2>', 'Second featureKey')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .action(async (key1: string, key2: string, options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n const f1 = features.find((f) => f.feature.featureKey === key1)\n const f2 = features.find((f) => f.feature.featureKey === key2)\n\n if (!f1) {\n process.stderr.write(`Error: feature \"${key1}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n if (!f2) {\n process.stderr.write(`Error: feature \"${key2}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n\n const obj1 = f1.feature as unknown as Record<string, unknown>\n const obj2 = f2.feature as unknown as Record<string, unknown>\n\n const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)])\n\n const lines: string[] = []\n lines.push(`diff ${key1} → ${key2}`)\n lines.push('─'.repeat(60))\n\n let hasDiffs = false\n for (const field of allKeys) {\n const v1 = obj1[field]\n const v2 = obj2[field]\n\n const s1 = stableStringify(v1)\n const s2 = stableStringify(v2)\n\n if (s1 === s2) continue\n hasDiffs = true\n\n if (v1 === undefined) {\n lines.push(`+ ${field}: ${formatValue(v2)}`)\n } else if (v2 === undefined) {\n lines.push(`- ${field}: ${formatValue(v1)}`)\n } else {\n lines.push(`~ ${field}:`)\n lines.push(` OLD: ${formatValue(v1)}`)\n lines.push(` NEW: ${formatValue(v2)}`)\n }\n }\n\n if (!hasDiffs) {\n lines.push('(no differences)')\n }\n\n process.stdout.write(lines.join('\\n') + '\\n')\n })\n","import { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { computeCompleteness, loadConfig } from '../lib/config.js'\nimport { scanFeatures } from '../lib/scanner.js'\nimport { findLacConfig } from '../lib/walker.js'\n\n// ─── helpers ────────────────────────────────────────────────────────────────\n\n/** Mirrors the findLacDir logic from keygen.ts */\nfunction findLacDir(fromDir: string): string | null {\n let current = path.resolve(fromDir)\n while (true) {\n const candidate = path.join(current, '.lac')\n try {\n if (statSync(candidate).isDirectory()) return candidate\n } catch { /* not found at this level */ }\n const parent = path.dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\n/** Walk a directory tree collecting all feature.json paths with validation info. */\nasync function walkFeatureFiles(\n currentDir: string,\n): Promise<{ valid: number; invalid: Array<{ filePath: string; errors: string[] }> }> {\n let validCount = 0\n const invalidFiles: Array<{ filePath: string; errors: string[] }> = []\n\n async function walk(dir: string): Promise<void> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let rawEntries: any[]\n try {\n rawEntries = await readdir(dir, { withFileTypes: true })\n } catch {\n return\n }\n\n // Normalise to plain objects to avoid @types/node Dirent variance (mirrors scanner.ts)\n const entries = rawEntries.map((e) => ({\n name: String(e.name),\n isDirectory: () => e.isDirectory() as boolean,\n isFile: () => e.isFile() as boolean,\n }))\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n\n if (entry.isDirectory()) {\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue\n await walk(fullPath)\n } else if (entry.isFile() && entry.name === 'feature.json') {\n let raw: string\n try {\n raw = await readFile(fullPath, 'utf-8')\n } catch {\n invalidFiles.push({ filePath: fullPath, errors: ['could not read file'] })\n continue\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n invalidFiles.push({ filePath: fullPath, errors: ['invalid JSON'] })\n continue\n }\n\n const result = validateFeature(parsed)\n if (!result.success) {\n invalidFiles.push({ filePath: fullPath, errors: result.errors })\n } else {\n validCount++\n }\n }\n }\n }\n\n await walk(currentDir)\n return { valid: validCount, invalid: invalidFiles }\n}\n\n// ─── command ────────────────────────────────────────────────────────────────\n\nexport const doctorCommand = new Command('doctor')\n .description('Check workspace health and report any issues')\n .argument('[dir]', 'Directory to check (default: cwd)')\n .action(async (dir: string | undefined) => {\n const checkDir = dir ? path.resolve(dir) : process.cwd()\n\n let passed = 0\n let warned = 0\n let failed = 0\n\n const output: string[] = []\n\n output.push('lac doctor — workspace diagnostics')\n output.push('===================================')\n output.push('')\n\n // ── Check 1: .lac/ workspace directory ──────────────────────────────────\n let lacDir: string | null = null\n try {\n lacDir = findLacDir(checkDir)\n } catch { /* treated as not found */ }\n\n if (lacDir) {\n output.push(`✓ Workspace found at ${lacDir}`)\n passed++\n } else {\n output.push('✗ No .lac/ workspace — run: lac workspace init')\n failed++\n }\n\n // ── Check 2: .lac/counter file ───────────────────────────────────────────\n if (lacDir) {\n const counterPath = join(lacDir, 'counter')\n let counterOk = false\n let counterYear: number | null = null\n let nextKey = ''\n\n try {\n const raw = readFileSync(counterPath, 'utf-8').trim()\n const parts = raw.split('\\n').map((l) => l.trim())\n const yr = parseInt(parts[0] ?? '', 10)\n const cnt = parseInt(parts[1] ?? '', 10)\n\n if (!isNaN(yr) && !isNaN(cnt)) {\n counterOk = true\n counterYear = yr\n nextKey = `feat-${yr}-${String(cnt + 1).padStart(3, '0')}`\n }\n } catch { /* missing or unreadable */ }\n\n if (counterOk && counterYear !== null) {\n output.push(`✓ Counter valid — next key preview: ${nextKey}`)\n passed++\n\n const currentYear = new Date().getFullYear()\n if (counterYear !== currentYear) {\n output.push(`⚠ Counter year is stale (${counterYear}) — will reset on next lac init`)\n warned++\n }\n } else {\n output.push('✗ Counter file missing or corrupt — run: lac workspace init --force')\n failed++\n }\n }\n\n // ── Check 3: feature.json files ──────────────────────────────────────────\n try {\n const { valid, invalid } = await walkFeatureFiles(checkDir)\n const total = valid + invalid.length\n output.push(`✓ Found ${total} feature.json file${total === 1 ? '' : 's'}`)\n passed++\n\n for (const inv of invalid) {\n output.push(` ✗ Invalid: ${inv.filePath} — ${inv.errors.join('; ')}`)\n failed++\n }\n } catch {\n output.push('✗ Could not scan feature.json files')\n failed++\n }\n\n // ── Check 4: lac.config.json (optional) ──────────────────────────────────\n try {\n const configPath = findLacConfig(checkDir)\n\n if (!configPath) {\n output.push(' (no lac.config.json — using defaults)')\n } else {\n try {\n const raw = readFileSync(configPath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n const domain = typeof parsed.domain === 'string' ? parsed.domain : 'feat'\n const threshold = typeof parsed.ciThreshold === 'number' ? parsed.ciThreshold : 0\n output.push(`✓ lac.config.json valid (domain: ${domain}, threshold: ${threshold})`)\n passed++\n } catch {\n output.push('✗ lac.config.json is invalid JSON — fix or delete it')\n failed++\n }\n }\n } catch {\n output.push(' (no lac.config.json — using defaults)')\n }\n\n // ── Check 5: lac-lsp availability ────────────────────────────────────────\n try {\n const result = spawnSync('lac-lsp', ['--help'], { timeout: 2000, stdio: 'ignore' })\n if (result.error || result.status === null) {\n output.push('✗ lac-lsp not found — install: npm i -g @life-as-code/lac-lsp')\n failed++\n } else {\n output.push('✓ lac-lsp found in PATH')\n passed++\n }\n } catch {\n output.push('✗ lac-lsp not found — install: npm i -g @life-as-code/lac-lsp')\n failed++\n }\n\n // ── Check 6: lint status ─────────────────────────────────────────────────\n try {\n const config = loadConfig(checkDir)\n const scanned = await scanFeatures(checkDir)\n const toCheck = scanned.filter(({ feature }) =>\n (config.lintStatuses as string[]).includes(feature.status),\n )\n\n let lintWarnCount = 0\n for (const { feature } of toCheck) {\n const raw = feature as unknown as Record<string, unknown>\n const completeness = computeCompleteness(raw)\n const missingRequired = config.requiredFields.filter((field) => {\n const val = raw[field]\n if (val === undefined || val === null || val === '') return true\n if (Array.isArray(val)) return val.length === 0\n return typeof val === 'string' && val.trim().length === 0\n })\n const belowThreshold = config.ciThreshold > 0 && completeness < config.ciThreshold\n if (missingRequired.length > 0 || belowThreshold) lintWarnCount++\n }\n\n if (lintWarnCount === 0) {\n output.push('✓ All features pass lint')\n passed++\n } else {\n output.push(`⚠ ${lintWarnCount} feature${lintWarnCount === 1 ? '' : 's'} have lint warnings`)\n warned++\n }\n } catch {\n output.push('⚠ Could not run lint check')\n warned++\n }\n\n // ── Summary ───────────────────────────────────────────────────────────────\n output.push('')\n output.push(\n `Overall: ${passed} check${passed === 1 ? '' : 's'} passed, ${warned} warning${warned === 1 ? '' : 's'}, ${failed} failure${failed === 1 ? '' : 's'}`,\n )\n\n // Next steps when all checks pass\n if (failed === 0) {\n output.push('')\n output.push('Next steps:')\n output.push(' lac serve → open dashboard at http://127.0.0.1:7474')\n output.push(' lac hooks install → lint on every commit')\n output.push(' lac init → create your first feature')\n }\n\n process.stdout.write(output.join('\\n') + '\\n')\n process.exit(failed > 0 ? 1 : 0)\n })\n","/**\n * Minimal markdown → HTML converter for use in the static site generator.\n * Handles the subset of markdown used in feature.json documentation fields:\n * headings, code blocks, inline code, bold, tables, lists, and paragraphs.\n */\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nfunction inlineMarkdown(text: string): string {\n return (\n escapeHtml(text)\n // **bold**\n .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n // `inline code`\n .replace(/`([^`]+)`/g, '<code>$1</code>')\n // [text](url) — allow balanced parentheses inside the URL so that\n // links like https://en.wikipedia.org/wiki/X_(Y) parse correctly.\n .replace(/\\[([^\\]]+)\\]\\(((?:[^()]*|\\([^()]*\\))*)\\)/g, '<a href=\"$2\">$1</a>')\n )\n}\n\nfunction isTableRow(line: string): boolean {\n return line.trim().startsWith('|') && line.trim().endsWith('|')\n}\n\nfunction isTableSeparator(line: string): boolean {\n return /^\\|[\\s\\-:|]+\\|/.test(line.trim())\n}\n\nfunction renderTableRow(line: string, isHeader: boolean): string {\n const cells = line\n .trim()\n .slice(1, -1)\n .split('|')\n .map((c) => c.trim())\n const tag = isHeader ? 'th' : 'td'\n return `<tr>${cells.map((c) => `<${tag}>${inlineMarkdown(c)}</${tag}>`).join('')}</tr>`\n}\n\nexport function markdownToHtml(md: string): string {\n const lines = md.split('\\n')\n const out: string[] = []\n let i = 0\n\n while (i < lines.length) {\n const line = lines[i] ?? ''\n\n // ── Fenced code block ────────────────────────────────────────────────────\n if (line.startsWith('```')) {\n const lang = line.slice(3).trim()\n const codeLines: string[] = []\n i++\n // Guard against an unmatched opening fence: if we reach EOF without\n // finding the closing ```, treat everything collected so far as the block.\n while (i < lines.length && !(lines[i] ?? '').startsWith('```')) {\n codeLines.push(lines[i] ?? '')\n i++\n }\n if (i < lines.length) i++ // skip closing ``` only if it exists\n const langAttr = lang ? ` class=\"language-${escapeHtml(lang)}\"` : ''\n out.push(`<pre><code${langAttr}>${escapeHtml(codeLines.join('\\n'))}</code></pre>`)\n continue\n }\n\n // ── Headings ─────────────────────────────────────────────────────────────\n if (line.startsWith('### ')) {\n out.push(`<h3>${inlineMarkdown(line.slice(4))}</h3>`)\n i++\n continue\n }\n if (line.startsWith('## ')) {\n out.push(`<h2>${inlineMarkdown(line.slice(3))}</h2>`)\n i++\n continue\n }\n if (line.startsWith('# ')) {\n out.push(`<h1>${inlineMarkdown(line.slice(2))}</h1>`)\n i++\n continue\n }\n\n // ── Horizontal rule ──────────────────────────────────────────────────────\n if (/^(-{3,}|\\*{3,}|_{3,})$/.test(line.trim())) {\n out.push('<hr />')\n i++\n continue\n }\n\n // ── Table ─────────────────────────────────────────────────────────────────\n if (isTableRow(line)) {\n const tableRows: string[] = []\n let firstRow = true\n while (i < lines.length && isTableRow(lines[i] ?? '')) {\n const current = lines[i] ?? ''\n if (isTableSeparator(current)) {\n // separator row — skip\n i++\n continue\n }\n tableRows.push(renderTableRow(current, firstRow))\n if (firstRow) firstRow = false\n i++\n }\n out.push(\n `<table class=\"md-table\"><thead>${tableRows[0] ?? ''}</thead><tbody>${tableRows.slice(1).join('')}</tbody></table>`,\n )\n continue\n }\n\n // ── Unordered list ───────────────────────────────────────────────────────\n if (/^[-*] /.test(line)) {\n const items: string[] = []\n while (i < lines.length && /^[-*] /.test(lines[i] ?? '')) {\n items.push(`<li>${inlineMarkdown((lines[i] ?? '').slice(2))}</li>`)\n i++\n }\n out.push(`<ul>${items.join('')}</ul>`)\n continue\n }\n\n // ── Ordered list ─────────────────────────────────────────────────────────\n // Require the marker at the very start of the line (no leading spaces) so\n // that a paragraph starting with e.g. \"2026. That was the year…\" is NOT\n // treated as a list item.\n if (/^[1-9]\\d*\\. /.test(line)) {\n const items: string[] = []\n while (i < lines.length && /^[1-9]\\d*\\. /.test(lines[i] ?? '')) {\n items.push(`<li>${inlineMarkdown((lines[i] ?? '').replace(/^[1-9]\\d*\\. /, ''))}</li>`)\n i++\n }\n out.push(`<ol>${items.join('')}</ol>`)\n continue\n }\n\n // ── Blank line ────────────────────────────────────────────────────────────\n if (line.trim() === '') {\n i++\n continue\n }\n\n // ── Paragraph ─────────────────────────────────────────────────────────────\n const paraLines: string[] = []\n while (\n i < lines.length &&\n (lines[i] ?? '').trim() !== '' &&\n !((lines[i] ?? '').startsWith('#')) &&\n !((lines[i] ?? '').startsWith('```')) &&\n !(/^[-*] /.test(lines[i] ?? '')) &&\n !(/^[1-9]\\d*\\. /.test(lines[i] ?? '')) &&\n !(isTableRow(lines[i] ?? ''))\n ) {\n paraLines.push(lines[i] ?? '')\n i++\n }\n if (paraLines.length > 0) {\n out.push(`<p>${inlineMarkdown(paraLines.join(' '))}</p>`)\n }\n }\n\n return out.join('\\n')\n}\n","export const css = `\n:root {\n --color-bg: #ffffff;\n --color-surface: #f8f9fa;\n --color-border: #dee2e6;\n --color-text: #212529;\n --color-text-muted: #6c757d;\n --color-link: #0d6efd;\n --color-link-hover: #0a58ca;\n --color-active: #198754;\n --color-draft: #6c757d;\n --color-frozen: #0d6efd;\n --color-deprecated: #dc3545;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --color-bg: #1a1a2e;\n --color-surface: #16213e;\n --color-border: #374151;\n --color-text: #e9ecef;\n --color-text-muted: #9ca3af;\n --color-link: #60a5fa;\n --color-link-hover: #93c5fd;\n --color-active: #4ade80;\n --color-draft: #9ca3af;\n --color-frozen: #60a5fa;\n --color-deprecated: #f87171;\n }\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-size: 16px;\n line-height: 1.6;\n}\n\nbody {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,\n Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n background-color: var(--color-bg);\n color: var(--color-text);\n margin: 0;\n padding: 0;\n}\n\n.container {\n max-width: 800px;\n margin: 0 auto;\n padding: 2rem 1rem;\n}\n\nh1 {\n font-size: 2rem;\n font-weight: 700;\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\nh2 {\n font-size: 1.25rem;\n font-weight: 600;\n margin-top: 2rem;\n margin-bottom: 0.75rem;\n border-bottom: 1px solid var(--color-border);\n padding-bottom: 0.25rem;\n}\n\na {\n color: var(--color-link);\n text-decoration: none;\n}\n\na:hover {\n color: var(--color-link-hover);\n text-decoration: underline;\n}\n\np {\n margin: 0 0 1rem;\n}\n\n.meta {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin-bottom: 1.5rem;\n color: var(--color-text-muted);\n font-size: 0.875rem;\n}\n\n.feature-key {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,\n 'Liberation Mono', 'Courier New', monospace;\n font-size: 0.875rem;\n color: var(--color-text-muted);\n}\n\n/* Status badges */\n.status-badge {\n display: inline-block;\n padding: 0.125rem 0.5rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.status-active {\n color: var(--color-active);\n background-color: color-mix(in srgb, var(--color-active) 15%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-active) 30%, transparent);\n}\n\n.status-draft {\n color: var(--color-draft);\n background-color: color-mix(in srgb, var(--color-draft) 15%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-draft) 30%, transparent);\n}\n\n.status-frozen {\n color: var(--color-frozen);\n background-color: color-mix(in srgb, var(--color-frozen) 15%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-frozen) 30%, transparent);\n}\n\n.status-deprecated {\n color: var(--color-deprecated);\n background-color: color-mix(in srgb, var(--color-deprecated) 15%, transparent);\n border: 1px solid color-mix(in srgb, var(--color-deprecated) 30%, transparent);\n}\n\n/* Search */\n.search-wrapper {\n margin-bottom: 1.5rem;\n}\n\n.search-input {\n width: 100%;\n padding: 0.5rem 0.75rem;\n border: 1px solid var(--color-border);\n border-radius: 0.375rem;\n background-color: var(--color-surface);\n color: var(--color-text);\n font-size: 1rem;\n font-family: inherit;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.search-input:focus {\n border-color: var(--color-link);\n}\n\n/* Feature table */\n.feature-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.9375rem;\n}\n\n.feature-table th {\n text-align: left;\n padding: 0.5rem 0.75rem;\n border-bottom: 2px solid var(--color-border);\n color: var(--color-text-muted);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.feature-table td {\n padding: 0.625rem 0.75rem;\n border-bottom: 1px solid var(--color-border);\n vertical-align: top;\n}\n\n.feature-table tr:last-child td {\n border-bottom: none;\n}\n\n.feature-table tr:hover td {\n background-color: var(--color-surface);\n}\n\n.problem-excerpt {\n color: var(--color-text-muted);\n font-size: 0.875rem;\n}\n\n/* Sections */\nsection {\n margin-bottom: 2rem;\n}\n\n.problem-text {\n background-color: var(--color-surface);\n border-left: 3px solid var(--color-link);\n padding: 1rem 1.25rem;\n border-radius: 0 0.375rem 0.375rem 0;\n margin: 0;\n}\n\n/* Decisions timeline */\nol.decisions {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n\nol.decisions li {\n position: relative;\n padding: 1rem 1rem 1rem 1.5rem;\n border-left: 2px solid var(--color-border);\n margin-bottom: 1rem;\n}\n\nol.decisions li:last-child {\n margin-bottom: 0;\n}\n\nol.decisions li::before {\n content: '';\n position: absolute;\n left: -0.375rem;\n top: 1.25rem;\n width: 0.625rem;\n height: 0.625rem;\n border-radius: 50%;\n background-color: var(--color-link);\n}\n\n.decision-date {\n font-size: 0.75rem;\n color: var(--color-text-muted);\n margin-bottom: 0.25rem;\n}\n\n.decision-text {\n font-weight: 600;\n margin-bottom: 0.375rem;\n}\n\n.decision-rationale {\n color: var(--color-text-muted);\n font-size: 0.9375rem;\n margin-bottom: 0.375rem;\n}\n\n.alternatives {\n font-size: 0.875rem;\n color: var(--color-text-muted);\n}\n\n.alternatives span {\n font-weight: 500;\n}\n\n/* Implementation / limitation sections */\n.implementation-text {\n white-space: pre-wrap;\n font-size: 0.9375rem;\n line-height: 1.7;\n}\n\nul.limitations {\n padding-left: 1.5rem;\n margin: 0;\n}\n\nul.limitations li {\n margin-bottom: 0.375rem;\n color: var(--color-text-muted);\n}\n\n/* Lineage */\n.lineage-info {\n background-color: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 0.375rem;\n padding: 1rem 1.25rem;\n}\n\n.lineage-info p {\n margin-bottom: 0.5rem;\n}\n\n.lineage-info p:last-child {\n margin-bottom: 0;\n}\n\n/* Back link */\n.back-link {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n margin-bottom: 2rem;\n font-size: 0.875rem;\n}\n\n/* Empty state */\n.empty-state {\n text-align: center;\n padding: 3rem 1rem;\n color: var(--color-text-muted);\n}\n\n/* No results row */\n.no-results {\n display: none;\n padding: 1.5rem 0.75rem;\n color: var(--color-text-muted);\n font-style: italic;\n}\n\n/* Markdown-rendered content */\n.implementation-text h2,\n.analysis-text h2 {\n font-size: 1.125rem;\n font-weight: 600;\n margin-top: 1.75rem;\n margin-bottom: 0.5rem;\n border-bottom: 1px solid var(--color-border);\n padding-bottom: 0.25rem;\n}\n\n.implementation-text h3,\n.analysis-text h3 {\n font-size: 1rem;\n font-weight: 600;\n margin-top: 1.25rem;\n margin-bottom: 0.375rem;\n color: var(--color-text-muted);\n}\n\n.implementation-text pre,\n.analysis-text pre {\n background-color: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 0.375rem;\n padding: 1rem 1.25rem;\n overflow-x: auto;\n margin: 1rem 0;\n}\n\n.implementation-text code,\n.analysis-text code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 0.875em;\n}\n\n.implementation-text pre code,\n.analysis-text pre code {\n background: none;\n padding: 0;\n border-radius: 0;\n font-size: 0.875rem;\n}\n\n.implementation-text :not(pre) > code,\n.analysis-text :not(pre) > code {\n background-color: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: 0.25rem;\n padding: 0.1em 0.35em;\n}\n\n.implementation-text ul,\n.analysis-text ul,\n.implementation-text ol,\n.analysis-text ol {\n padding-left: 1.5rem;\n margin: 0.5rem 0 1rem;\n}\n\n.implementation-text li,\n.analysis-text li {\n margin-bottom: 0.25rem;\n}\n\n/* Markdown tables */\n.md-table {\n width: 100%;\n border-collapse: collapse;\n margin: 1rem 0;\n font-size: 0.9rem;\n}\n\n.md-table th {\n text-align: left;\n padding: 0.5rem 0.75rem;\n border-bottom: 2px solid var(--color-border);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--color-text-muted);\n}\n\n.md-table td {\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--color-border);\n vertical-align: top;\n}\n\n.md-table tr:last-child td {\n border-bottom: none;\n}\n\n.md-table tr:hover td {\n background-color: var(--color-surface);\n}\n\n/* Analysis section */\n.analysis-text {\n font-size: 0.9375rem;\n line-height: 1.7;\n}\n`\n","import type { Feature } from '@life-as-code/feature-schema'\nimport { markdownToHtml } from './markdown.js'\nimport { css } from './site-style.css.js'\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nfunction statusBadge(status: Feature['status']): string {\n return `<span class=\"status-badge status-${escapeHtml(status)}\">${escapeHtml(status)}</span>`\n}\n\nfunction renderDecisions(decisions: NonNullable<Feature['decisions']>): string {\n if (decisions.length === 0) return ''\n const items = decisions\n .map((d) => {\n const date = d.date\n ? `<div class=\"decision-date\">${escapeHtml(d.date)}</div>`\n : ''\n const alts =\n d.alternativesConsidered && d.alternativesConsidered.length > 0\n ? `<div class=\"alternatives\"><span>Alternatives considered:</span> ${d.alternativesConsidered.map(escapeHtml).join(', ')}</div>`\n : ''\n return `\n <li>\n ${date}\n <div class=\"decision-text\">${escapeHtml(d.decision)}</div>\n <div class=\"decision-rationale\">${escapeHtml(d.rationale)}</div>\n ${alts}\n </li>`\n })\n .join('\\n')\n\n return `\n <section class=\"decisions\">\n <h2>Decisions</h2>\n <ol class=\"decisions\">\n ${items}\n </ol>\n </section>`\n}\n\nfunction renderLineage(lineage: NonNullable<Feature['lineage']>): string {\n const parts: string[] = []\n\n if (lineage.parent) {\n parts.push(\n `<p><strong>Parent:</strong> <a href=\"${escapeHtml(lineage.parent)}.html\">${escapeHtml(lineage.parent)}</a></p>`,\n )\n }\n if (lineage.children && lineage.children.length > 0) {\n const childLinks = lineage.children\n .map((c) => `<a href=\"${escapeHtml(c)}.html\">${escapeHtml(c)}</a>`)\n .join(', ')\n parts.push(`<p><strong>Children:</strong> ${childLinks}</p>`)\n }\n if (lineage.spawnReason) {\n parts.push(`<p><strong>Spawn reason:</strong> ${escapeHtml(lineage.spawnReason)}</p>`)\n }\n\n if (parts.length === 0) return ''\n\n return `\n <section class=\"lineage\">\n <h2>Lineage</h2>\n <div class=\"lineage-info\">\n ${parts.join('\\n ')}\n </div>\n </section>`\n}\n\nexport function renderFeature(feature: Feature): string {\n const decisionsSection =\n feature.decisions && feature.decisions.length > 0\n ? renderDecisions(feature.decisions)\n : ''\n\n const implementationSection = feature.implementation\n ? `\n <section class=\"implementation\">\n <h2>How it works</h2>\n <div class=\"implementation-text\">${markdownToHtml(feature.implementation)}</div>\n </section>`\n : ''\n\n const analysisSection = (feature as Record<string, unknown>)['analysis']\n ? `\n <section class=\"analysis\">\n <h2>Background &amp; Context</h2>\n <div class=\"analysis-text\">${markdownToHtml(((feature as Record<string, unknown>)['analysis'] as string))}</div>\n </section>`\n : ''\n\n const limitationsSection =\n feature.knownLimitations && feature.knownLimitations.length > 0\n ? `\n <section class=\"limitations\">\n <h2>Known Limitations</h2>\n <ul class=\"limitations\">\n ${feature.knownLimitations.map((l) => `<li>${escapeHtml(l)}</li>`).join('\\n ')}\n </ul>\n </section>`\n : ''\n\n const lineageSection =\n feature.lineage &&\n (feature.lineage.parent ||\n (feature.lineage.children && feature.lineage.children.length > 0) ||\n feature.lineage.spawnReason)\n ? renderLineage(feature.lineage)\n : ''\n\n const tagsSection =\n feature.tags && feature.tags.length > 0\n ? `<div class=\"meta\" style=\"margin-top:0.5rem;flex-wrap:wrap\">${feature.tags.map((t) => `<span style=\"font-size:0.75rem;padding:0.125rem 0.5rem;border-radius:9999px;background-color:var(--color-surface);border:1px solid var(--color-border)\">${escapeHtml(t)}</span>`).join('')}</div>`\n : ''\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${escapeHtml(feature.title)} — Feature Provenance</title>\n <style>${css}</style>\n</head>\n<body>\n <div class=\"container\">\n <a href=\"index.html\" class=\"back-link\">&#8592; All features</a>\n\n <h1>${escapeHtml(feature.title)}</h1>\n <div class=\"meta\">\n ${statusBadge(feature.status)}\n <span class=\"feature-key\">${escapeHtml(feature.featureKey)}</span>\n </div>\n ${tagsSection}\n\n <section class=\"problem\">\n <h2>Problem</h2>\n <p class=\"problem-text\">${escapeHtml(feature.problem)}</p>\n </section>\n\n ${analysisSection}\n ${decisionsSection}\n ${implementationSection}\n ${limitationsSection}\n ${lineageSection}\n </div>\n</body>\n</html>`\n}\n","import type { Feature } from '@life-as-code/feature-schema'\nimport { css } from './site-style.css.js'\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nfunction statusBadge(status: Feature['status']): string {\n return `<span class=\"status-badge status-${escapeHtml(status)}\">${escapeHtml(status)}</span>`\n}\n\nexport function renderIndex(features: Feature[], generatedAt?: Date): string {\n const timestamp = (generatedAt ?? new Date()).toISOString()\n const rows =\n features.length === 0\n ? `<tr><td colspan=\"4\" class=\"no-results\" style=\"display:table-cell\">No features found.</td></tr>`\n : features\n .map(\n (f) => `\n <tr class=\"feature-row\" data-search=\"${escapeHtml((f.featureKey + ' ' + f.title).toLowerCase())}\">\n <td><a href=\"${escapeHtml(f.featureKey)}.html\" class=\"feature-key\">${escapeHtml(f.featureKey)}</a></td>\n <td><a href=\"${escapeHtml(f.featureKey)}.html\">${escapeHtml(f.title)}</a></td>\n <td>${statusBadge(f.status)}</td>\n <td class=\"problem-excerpt\">${escapeHtml(f.problem.slice(0, 100))}${f.problem.length > 100 ? '…' : ''}</td>\n </tr>`,\n )\n .join('\\n')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Feature Provenance</title>\n <style>${css}</style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Feature Provenance</h1>\n <p style=\"color:var(--color-text-muted);margin-bottom:1.5rem\">${features.length} feature${features.length === 1 ? '' : 's'} tracked</p>\n\n <div class=\"search-wrapper\">\n <input\n type=\"search\"\n id=\"search\"\n class=\"search-input\"\n placeholder=\"Search by key or title…\"\n aria-label=\"Search features\"\n />\n </div>\n\n <table class=\"feature-table\" id=\"feature-table\">\n <thead>\n <tr>\n <th>Key</th>\n <th>Title</th>\n <th>Status</th>\n <th>Problem</th>\n </tr>\n </thead>\n <tbody id=\"feature-tbody\">\n ${rows}\n <tr id=\"no-results-row\" style=\"display:none\">\n <td colspan=\"4\" class=\"no-results\" style=\"display:table-cell;color:var(--color-text-muted);font-style:italic;padding:1.5rem 0.75rem\">No features match your search.</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <footer style=\"margin-top:2rem;padding-top:1rem;border-top:1px solid var(--color-border);color:var(--color-text-muted);font-size:0.75rem;text-align:right\">\n Generated ${escapeHtml(timestamp)}\n </footer>\n\n <script>\n (function () {\n var input = document.getElementById('search');\n var noResults = document.getElementById('no-results-row');\n if (!input) return;\n input.addEventListener('input', function () {\n var query = input.value.trim().toLowerCase();\n var rows = document.querySelectorAll('#feature-tbody .feature-row');\n var visible = 0;\n rows.forEach(function (row) {\n var search = row.getAttribute('data-search') || '';\n var match = query === '' || search.indexOf(query) !== -1;\n row.style.display = match ? '' : 'none';\n if (match) visible++;\n });\n if (noResults) {\n noResults.style.display = (visible === 0 && query !== '') ? '' : 'none';\n }\n });\n })();\n </script>\n</body>\n</html>`\n}\n","import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport type { Feature } from '@life-as-code/feature-schema'\nimport { renderFeature } from '../templates/site-feature.html.js'\nimport { renderIndex } from '../templates/site-index.html.js'\nimport { css } from '../templates/site-style.css.js'\n\nexport interface SiteFeature {\n filePath: string\n feature: Feature\n}\n\n/**\n * Generates a static HTML site for the given features.\n * Creates:\n * outDir/index.html — searchable feature list\n * outDir/{featureKey}.html — one page per feature\n * outDir/style.css — shared stylesheet\n */\nexport async function generateSite(features: SiteFeature[], outDir: string): Promise<void> {\n await mkdir(outDir, { recursive: true })\n\n // Write shared stylesheet\n await writeFile(join(outDir, 'style.css'), css.trim(), 'utf-8')\n\n // Write index page\n const allFeatures = features.map((f) => f.feature)\n await writeFile(join(outDir, 'index.html'), renderIndex(allFeatures), 'utf-8')\n\n // Write one page per feature\n for (const { feature } of features) {\n const pageHtml = renderFeature(feature)\n await writeFile(join(outDir, `${feature.featureKey}.html`), pageHtml, 'utf-8')\n }\n}\n","import { existsSync } from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\nimport { generateSite } from '../lib/siteGenerator.js'\n\n/**\n * Walks up the directory tree from `startDir` to find the nearest feature.json.\n * Returns the absolute path or null if not found.\n */\nfunction findNearestFeatureJson(startDir: string): string | null {\n let current = resolve(startDir)\n\n while (true) {\n const candidate = join(current, 'feature.json')\n if (existsSync(candidate)) {\n return candidate\n }\n const parent = dirname(current)\n if (parent === current) {\n return null\n }\n current = parent\n }\n}\n\n/** Render a feature as a Markdown document */\nfunction featureToMarkdown(feature: ReturnType<typeof JSON.parse>): string {\n const f = feature as Record<string, unknown>\n const lines: string[] = []\n\n lines.push(`# ${f['title'] as string}`)\n lines.push('')\n lines.push(`**Key:** \\`${f['featureKey'] as string}\\` `)\n lines.push(`**Status:** ${f['status'] as string}`)\n lines.push('')\n\n if (f['problem']) {\n lines.push('## Problem')\n lines.push('')\n lines.push(f['problem'] as string)\n lines.push('')\n }\n\n if (f['analysis']) {\n lines.push('## Analysis')\n lines.push('')\n lines.push(f['analysis'] as string)\n lines.push('')\n }\n\n if (f['implementation']) {\n lines.push('## Implementation')\n lines.push('')\n lines.push(f['implementation'] as string)\n lines.push('')\n }\n\n const limitations = f['knownLimitations'] as string[] | undefined\n if (limitations && limitations.length > 0) {\n lines.push('## Known Limitations')\n lines.push('')\n for (const lim of limitations) {\n lines.push(`- ${lim}`)\n }\n lines.push('')\n }\n\n const decisions = f['decisions'] as Array<Record<string, unknown>> | undefined\n if (decisions && decisions.length > 0) {\n lines.push('## Decisions')\n lines.push('')\n for (const d of decisions) {\n lines.push(`### ${d['decision'] as string}`)\n lines.push('')\n lines.push(`**Rationale:** ${d['rationale'] as string}`)\n if (d['date']) lines.push(`**Date:** ${d['date'] as string}`)\n lines.push('')\n }\n }\n\n const annotations = f['annotations'] as Array<Record<string, unknown>> | undefined\n if (annotations && annotations.length > 0) {\n lines.push('## Annotations')\n lines.push('')\n for (const a of annotations) {\n lines.push(`- **[${a['type'] as string}]** ${a['body'] as string} _(${a['author'] as string}, ${a['date'] as string})_`)\n }\n lines.push('')\n }\n\n const tags = f['tags'] as string[] | undefined\n if (tags && tags.length > 0) {\n lines.push(`**Tags:** ${tags.join(', ')}`)\n lines.push('')\n }\n\n return lines.join('\\n')\n}\n\nexport const exportCommand = new Command('export')\n .description('Export feature.json as JSON, Markdown, or generate a static HTML site')\n .option('--out <path>', 'Output file or directory path')\n .option('--site <dir>', 'Scan <dir> for feature.json files and generate a static HTML site')\n .option('--markdown', 'Output feature as a Markdown document instead of JSON')\n .action(async (options: { out?: string; site?: string; markdown?: boolean }) => {\n // ── Site generation mode ────────────────────────────────────────────────\n if (options.site !== undefined) {\n const scanDir = resolve(options.site)\n const outDir = resolve(options.out ?? './lac-site')\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n if (features.length === 0) {\n process.stdout.write(`No valid feature.json files found in \"${scanDir}\".\\n`)\n process.exit(0)\n }\n\n try {\n await generateSite(features, outDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error generating site: ${message}\\n`)\n process.exit(1)\n }\n\n // Compute a display-friendly relative path when possible\n const displayOut = options.out ?? './lac-site'\n process.stdout.write(`✓ Generated ${features.length} page${features.length === 1 ? '' : 's'} → ${displayOut}\\n`)\n return\n }\n\n // ── Plain export mode ───────────────────────────────────────────────────\n const featureJsonPath = findNearestFeatureJson(process.cwd())\n\n if (!featureJsonPath) {\n process.stderr.write(\n `Error: no feature.json found in the current directory or any of its parents.\\n`,\n )\n process.exit(1)\n }\n\n let raw: string\n try {\n raw = await readFile(featureJsonPath, 'utf-8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error reading \"${featureJsonPath}\": ${message}\\n`)\n process.exit(1)\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n process.stderr.write(`Error: \"${featureJsonPath}\" contains invalid JSON.\\n`)\n process.exit(1)\n }\n\n const result = validateFeature(parsed)\n if (!result.success) {\n process.stderr.write(\n `Error: \"${featureJsonPath}\" failed validation:\\n ${result.errors.join('\\n ')}\\n`,\n )\n process.exit(1)\n }\n\n // Markdown mode\n if (options.markdown) {\n const mdOutput = featureToMarkdown(result.data)\n if (options.out) {\n const outPath = resolve(options.out)\n try {\n await writeFile(outPath, mdOutput, 'utf-8')\n process.stdout.write(`Exported to ${outPath}\\n`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error writing to \"${outPath}\": ${message}\\n`)\n process.exit(1)\n }\n } else {\n process.stdout.write(mdOutput)\n }\n return\n }\n\n const output = JSON.stringify(result.data, null, 2) + '\\n'\n\n if (options.out) {\n const outPath = resolve(options.out)\n try {\n await writeFile(outPath, output, 'utf-8')\n process.stdout.write(`Exported to ${outPath}\\n`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error writing to \"${outPath}\": ${message}\\n`)\n process.exit(1)\n }\n } else {\n process.stdout.write(output)\n }\n })\n","import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { findGitDir } from '../lib/walker.js'\n\nconst LAC_MARKER = '# managed-by-lac'\n\nconst HOOK_SCRIPT = `#!/bin/sh\n${LAC_MARKER}\n# Provenance lint — runs lac lint before every commit.\n# To remove: run \"lac hooks uninstall\"\n\n# Find lac in PATH, or try npx as fallback\nif command -v lac >/dev/null 2>&1; then\n LAC_CMD=\"lac\"\nelse\n LAC_CMD=\"npx --yes lac\"\nfi\n\nif ! $LAC_CMD lint --quiet 2>/tmp/lac-lint-output; then\n echo \"\"\n echo \"lac pre-commit: lint failed — commit blocked\"\n # Show failing feature keys from lint output\n if [ -s /tmp/lac-lint-output ]; then\n grep -o 'feat-[a-z0-9]*-[0-9]*\\\\|[a-z][a-z0-9]*-[0-9]\\\\{4\\\\}-[0-9]*' /tmp/lac-lint-output | sort -u | while read key; do\n echo \" ✗ $key\"\n done\n fi\n echo \"\"\n echo \" Run \\\\\"lac lint\\\\\" for details.\"\n echo \"\"\n rm -f /tmp/lac-lint-output\n exit 1\nfi\nrm -f /tmp/lac-lint-output\n`\n\nconst hooksCommand = new Command('hooks')\n .description('Manage git hooks for provenance linting')\n\nhooksCommand\n .command('install')\n .description('Install a pre-commit hook that runs \"lac lint\" before each commit')\n .option('--force', 'Overwrite existing pre-commit hook even if not managed by lac')\n .action((options: { force?: boolean }) => {\n const gitDir = findGitDir(process.cwd())\n if (!gitDir) {\n process.stderr.write(`Error: no .git directory found. Are you inside a git repository?\\n`)\n process.exit(1)\n }\n\n const hooksDir = join(gitDir, 'hooks')\n const hookPath = join(hooksDir, 'pre-commit')\n\n // Create hooks dir if it doesn't exist (bare repos)\n if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true })\n\n // Check existing hook\n if (existsSync(hookPath) && !options.force) {\n const existing = readFileSync(hookPath, 'utf-8')\n if (!existing.includes(LAC_MARKER)) {\n process.stderr.write(\n `Error: a pre-commit hook already exists at \"${hookPath}\" and was not installed by lac.\\n` +\n `Use --force to overwrite it.\\n`,\n )\n process.exit(1)\n }\n }\n\n writeFileSync(hookPath, HOOK_SCRIPT, 'utf-8')\n chmodSync(hookPath, 0o755)\n\n process.stdout.write(`✓ Installed pre-commit hook at ${hookPath}\\n`)\n process.stdout.write(` \"lac lint\" will run before every commit.\\n`)\n process.stdout.write(` To remove: run \"lac hooks uninstall\"\\n`)\n })\n\nhooksCommand\n .command('uninstall')\n .description('Remove the lac-managed pre-commit hook')\n .action(() => {\n const gitDir = findGitDir(process.cwd())\n if (!gitDir) {\n process.stderr.write(`Error: no .git directory found.\\n`)\n process.exit(1)\n }\n\n const hookPath = join(gitDir, 'hooks', 'pre-commit')\n\n if (!existsSync(hookPath)) {\n process.stdout.write(`No pre-commit hook found at \"${hookPath}\".\\n`)\n return\n }\n\n const content = readFileSync(hookPath, 'utf-8')\n if (!content.includes(LAC_MARKER)) {\n process.stderr.write(\n `Error: the pre-commit hook at \"${hookPath}\" was not installed by lac.\\n` +\n `Remove it manually if you want to uninstall it.\\n`,\n )\n process.exit(1)\n }\n\n rmSync(hookPath)\n process.stdout.write(`✓ Removed lac pre-commit hook from ${hookPath}\\n`)\n })\n\nhooksCommand\n .command('status')\n .description('Show whether the lac pre-commit hook is installed')\n .action(() => {\n const gitDir = findGitDir(process.cwd())\n if (!gitDir) {\n process.stdout.write(`Not inside a git repository.\\n`)\n return\n }\n\n const hookPath = join(gitDir, 'hooks', 'pre-commit')\n if (!existsSync(hookPath)) {\n process.stdout.write(`pre-commit hook: not installed\\n`)\n return\n }\n\n const content = readFileSync(hookPath, 'utf-8')\n if (content.includes(LAC_MARKER)) {\n process.stdout.write(`pre-commit hook: ✓ installed (managed by lac)\\n`)\n } else {\n process.stdout.write(`pre-commit hook: installed (NOT managed by lac — foreign hook)\\n`)\n }\n })\n\nexport { hooksCommand }\n","import { generateFeatureKey } from '@life-as-code/feature-schema'\n\nimport { loadConfig } from './config.js'\n\n/**\n * Generates a new featureKey for the given directory, honouring the `domain`\n * field from the nearest `lac.config.json`.\n *\n * @throws {Error} If no `.lac/` directory can be found in fromDir or its parents.\n */\nexport function nextFeatureKey(fromDir: string): string {\n const config = loadConfig(fromDir)\n return generateFeatureKey(fromDir, config.domain)\n}\n","import { existsSync } from 'node:fs'\nimport { writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\nimport prompts from 'prompts'\n\nimport { nextFeatureKey } from '../lib/featureKey.js'\n\n/**\n * Derives a short title from a problem statement by taking the first 6 words\n * and appending \"...\" when truncated.\n */\nfunction titleFromProblem(problem: string): string {\n const words = problem.trim().split(/\\s+/)\n if (words.length <= 6) return problem.trim()\n return words.slice(0, 6).join(' ') + '...'\n}\n\nexport const initCommand = new Command('init')\n .description('Scaffold a feature.json in the current directory')\n .option('-f, --force', 'Overwrite existing feature.json', false)\n .action(async (options: { force: boolean }) => {\n const cwd = process.cwd()\n const featureJsonPath = join(cwd, 'feature.json')\n\n if (existsSync(featureJsonPath) && !options.force) {\n process.stderr.write(\n `Error: feature.json already exists in this directory.\\nUse --force to overwrite.\\n`,\n )\n process.exit(1)\n }\n\n // Interactive prompts\n const answers = await prompts(\n [\n {\n type: 'text',\n name: 'problem',\n message: 'What problem does this feature solve?',\n initial: 'e.g. Users cannot reset their password without contacting support',\n validate: (value: string) =>\n value.trim().length > 0 ? true : 'Problem statement is required',\n },\n {\n type: 'select',\n name: 'status',\n message: 'Status?',\n choices: [\n { title: 'draft', value: 'draft' },\n { title: 'active', value: 'active' },\n ],\n initial: 0,\n },\n ],\n {\n onCancel: () => {\n process.stderr.write('Aborted.\\n')\n process.exit(1)\n },\n },\n )\n\n const problem = (answers.problem as string).trim()\n const status = answers.status as 'draft' | 'active'\n\n // Generate featureKey — may throw if no .lac/ dir is found\n let featureKey: string\n try {\n featureKey = nextFeatureKey(cwd)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error: ${message}\\n\\nTip: run \"lac workspace init\" first to create the .lac/ workspace.\\n`)\n process.exit(1)\n }\n\n const title = titleFromProblem(problem)\n const wasTruncated = problem.trim().split(/\\s+/).length > 6\n\n let finalTitle = title\n if (wasTruncated) {\n process.stdout.write(`\\nTitle will be: \"${title}\"\\n`)\n const titleAnswer = await prompts({\n type: 'text',\n name: 'customTitle',\n message: 'Custom title? (leave blank to use the above)',\n initial: '',\n })\n if (titleAnswer.customTitle && (titleAnswer.customTitle as string).trim().length > 0) {\n finalTitle = (titleAnswer.customTitle as string).trim()\n }\n }\n\n const feature = {\n featureKey,\n title: finalTitle,\n status,\n problem,\n }\n\n // Validate before writing (belt-and-suspenders)\n const validation = validateFeature(feature)\n if (!validation.success) {\n process.stderr.write(\n `Internal error: generated feature did not pass validation:\\n ${validation.errors.join('\\n ')}\\n`,\n )\n process.exit(1)\n }\n\n await writeFile(featureJsonPath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n\n process.stdout.write(`✓ Created feature.json — ${featureKey}\\n`)\n })\n","import process from 'node:process'\n\nimport type { Feature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\ninterface FeatureNode {\n key: string\n status: string\n title: string\n children: FeatureNode[]\n}\n\n/**\n * Build a lineage tree rooted at the given featureKey.\n * Children are features that list this key as their lineage.parent.\n */\nfunction buildTree(\n key: string,\n byKey: Map<string, Feature>,\n childrenOf: Map<string, string[]>,\n visited = new Set<string>(),\n): FeatureNode {\n visited.add(key)\n const feature = byKey.get(key)\n const childKeys = childrenOf.get(key) ?? []\n\n const children: FeatureNode[] = []\n for (const ck of childKeys) {\n if (!visited.has(ck)) {\n children.push(buildTree(ck, byKey, childrenOf, visited))\n }\n }\n\n return {\n key,\n status: feature?.status ?? 'unknown',\n title: feature?.title ?? '(unknown)',\n children,\n }\n}\n\n/**\n * Render a FeatureNode tree as ASCII art.\n */\nfunction renderTree(node: FeatureNode, prefix = '', isLast = true): string[] {\n const connector = isLast ? '└── ' : '├── '\n const lines = [\n `${prefix}${prefix === '' ? '' : connector}${node.key} (${node.status}) — ${node.title}`,\n ]\n\n const childPrefix = prefix + (isLast ? ' ' : '│ ')\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i]\n if (child) {\n const childLines = renderTree(child, childPrefix, i === node.children.length - 1)\n lines.push(...childLines)\n }\n }\n return lines\n}\n\nexport const lineageCommand = new Command('lineage')\n .description('Show the lineage tree (parent → key → children) for a feature')\n .argument('<key>', 'featureKey to inspect (e.g. feat-2026-001)')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .action(async (key: string, options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n const byKey = new Map<string, Feature>()\n const childrenOf = new Map<string, string[]>()\n\n for (const { feature } of features) {\n byKey.set(feature.featureKey, feature)\n const parent = feature.lineage?.parent\n if (parent) {\n const existing = childrenOf.get(parent) ?? []\n existing.push(feature.featureKey)\n childrenOf.set(parent, existing)\n }\n }\n\n if (!byKey.has(key)) {\n process.stderr.write(`Error: feature \"${key}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n\n // Find the root of the lineage chain\n let rootKey = key\n const seen = new Set<string>()\n while (true) {\n seen.add(rootKey)\n const feat = byKey.get(rootKey)\n const parent = feat?.lineage?.parent\n if (!parent || !byKey.has(parent) || seen.has(parent)) break\n rootKey = parent\n }\n\n const tree = buildTree(rootKey, byKey, childrenOf)\n const lines = renderTree(tree)\n\n process.stdout.write(lines.join('\\n') + '\\n')\n })\n","import fs from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport process from 'node:process'\n\nimport type { Feature } from '@life-as-code/feature-schema'\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { computeCompleteness, loadConfig } from '../lib/config.js'\nimport { scanFeatures } from '../lib/scanner.js'\n\ninterface LintResult {\n featureKey: string\n filePath: string\n status: string\n completeness: number\n missingRequired: string[]\n belowThreshold: boolean\n pass: boolean\n}\n\nfunction checkFeature(\n feature: Feature,\n filePath: string,\n requiredFields: string[],\n threshold: number,\n): LintResult {\n const raw = feature as unknown as Record<string, unknown>\n const completeness = computeCompleteness(raw)\n\n const missingRequired = requiredFields.filter((field) => {\n const val = raw[field]\n if (val === undefined || val === null || val === '') return true\n if (Array.isArray(val)) return val.length === 0\n return typeof val === 'string' && val.trim().length === 0\n })\n\n const belowThreshold = threshold > 0 && completeness < threshold\n\n return {\n featureKey: feature.featureKey,\n filePath,\n status: feature.status,\n completeness,\n missingRequired,\n belowThreshold,\n pass: missingRequired.length === 0 && !belowThreshold,\n }\n}\n\n/** Default placeholder values for auto-fix of missing required fields */\nconst FIELD_DEFAULTS: Record<string, unknown> = {\n problem: 'TODO: describe the problem this feature solves.',\n analysis: '',\n decisions: [],\n implementation: '',\n knownLimitations: [],\n tags: [],\n}\n\n/**\n * Auto-repair a feature file by inserting default values for missing required fields.\n * Returns the number of fields fixed, or 0 if nothing was changed.\n */\nasync function fixFeature(filePath: string, missingFields: string[]): Promise<number> {\n if (missingFields.length === 0) return 0\n\n let raw: string\n try {\n raw = await readFile(filePath, 'utf-8')\n } catch {\n return 0\n }\n\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>\n } catch {\n return 0\n }\n\n let fixed = 0\n for (const field of missingFields) {\n if (field in FIELD_DEFAULTS) {\n parsed[field] = FIELD_DEFAULTS[field]\n fixed++\n }\n }\n\n if (fixed === 0) return 0\n\n const validation = validateFeature(parsed)\n if (!validation.success) return 0\n\n await writeFile(filePath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n return fixed\n}\n\nexport const lintCommand = new Command('lint')\n .description('Check feature.json files for completeness and required fields')\n .argument('[dir]', 'Directory to scan (default: current directory)')\n .option('--require <fields>', 'Comma-separated required fields (overrides lac.config.json)')\n .option('--threshold <n>', 'Minimum completeness % required (overrides lac.config.json)', parseInt)\n .option('--quiet', 'Only print failures, suppress passing results')\n .option('--json', 'Output results as JSON')\n .option('--watch', 'Re-run lint on every feature.json change')\n .option('--fix', 'Auto-insert default values for missing required fields')\n .action(async (dir: string | undefined, options: {\n require?: string\n threshold?: number\n quiet?: boolean\n json?: boolean\n watch?: boolean\n fix?: boolean\n }) => {\n const scanDir = resolve(dir ?? process.cwd())\n const config = loadConfig(scanDir)\n\n const requiredFields = options.require\n ? options.require.split(',').map((f) => f.trim()).filter(Boolean)\n : config.requiredFields\n\n const threshold = options.threshold !== undefined ? options.threshold : config.ciThreshold\n\n async function runLint(): Promise<number> {\n // Scan\n let scanned: Awaited<ReturnType<typeof scanFeatures>>\n try {\n scanned = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n return 1\n }\n\n if (scanned.length === 0) {\n process.stdout.write(`No feature.json files found in \"${scanDir}\".\\n`)\n return 0\n }\n\n // Filter to lintable statuses\n const toCheck = scanned.filter(({ feature }) =>\n (config.lintStatuses as string[]).includes(feature.status),\n )\n\n const results = toCheck.map(({ feature, filePath }) =>\n checkFeature(feature, filePath, requiredFields, threshold),\n )\n\n // --fix: auto-repair missing required fields, then re-validate to confirm\n if (options.fix) {\n const toFix = results.filter((r) => !r.pass && r.missingRequired.length > 0)\n let totalFixed = 0\n for (const r of toFix) {\n const count = await fixFeature(r.filePath, r.missingRequired)\n if (count > 0) {\n totalFixed += count\n process.stdout.write(` 🔧 ${r.featureKey} fixed ${count} field${count === 1 ? '' : 's'} (${r.missingRequired.join(', ')})\\n`)\n }\n }\n\n if (totalFixed === 0 && toFix.length > 0) {\n process.stdout.write(` No fields could be auto-fixed (fields may not have default values).\\n`)\n return 1\n }\n if (toFix.length === 0) {\n process.stdout.write(` Nothing to fix — all required fields present.\\n`)\n return 0\n }\n\n // Re-scan and re-lint to confirm the fixes actually pass validation\n let rescanned: Awaited<ReturnType<typeof scanFeatures>>\n try {\n rescanned = await scanFeatures(scanDir)\n } catch {\n process.stdout.write(`\\n✓ Fixed ${totalFixed} field${totalFixed === 1 ? '' : 's'}. Could not re-validate — run \"lac lint\" to confirm.\\n`)\n return 0\n }\n const reFiltered = rescanned.filter(({ feature }) =>\n (config.lintStatuses as string[]).includes(feature.status),\n )\n const reResults = reFiltered.map(({ feature, filePath }) =>\n checkFeature(feature, filePath, requiredFields, threshold),\n )\n const stillFailing = reResults.filter((r) => !r.pass)\n if (stillFailing.length === 0) {\n process.stdout.write(`\\n✓ Fixed ${totalFixed} field${totalFixed === 1 ? '' : 's'} — all features now pass lint.\\n`)\n return 0\n }\n process.stdout.write(`\\n⚠ Fixed ${totalFixed} field${totalFixed === 1 ? '' : 's'} but ${stillFailing.length} feature${stillFailing.length === 1 ? '' : 's'} still fail lint:\\n`)\n for (const r of stillFailing) {\n process.stdout.write(` ✗ ${r.featureKey} missing: ${r.missingRequired.join(', ')}\\n`)\n }\n return 1\n }\n\n const failures = results.filter((r) => !r.pass)\n const passes = results.filter((r) => r.pass)\n\n if (options.json) {\n process.stdout.write(JSON.stringify({ results, failures: failures.length, passes: passes.length }, null, 2) + '\\n')\n return failures.length > 0 ? 1 : 0\n }\n\n // Human-readable output\n const col = (s: string, width: number) => s.slice(0, width).padEnd(width)\n\n if (!options.quiet || passes.length > 0) {\n if (!options.quiet) {\n for (const r of passes) {\n process.stdout.write(` ✓ ${col(r.featureKey, 18)} ${r.completeness.toString().padStart(3)}% ${r.status}\\n`)\n }\n }\n }\n\n for (const r of failures) {\n process.stdout.write(` ✗ ${col(r.featureKey, 18)} ${r.completeness.toString().padStart(3)}% ${r.status}\\n`)\n for (const field of r.missingRequired) {\n process.stdout.write(` missing required field: ${field}\\n`)\n }\n if (r.belowThreshold) {\n process.stdout.write(` completeness ${r.completeness}% is below threshold ${threshold}%\\n`)\n }\n }\n\n process.stdout.write(`\\n${passes.length} passed, ${failures.length} failed — ${results.length} features checked\\n`)\n\n if (failures.length > 0) {\n if (!options.quiet) {\n process.stdout.write(`\\nFailing features:\\n`)\n for (const r of failures) {\n process.stdout.write(` ${r.featureKey} → ${r.filePath}\\n`)\n }\n }\n return 1\n }\n\n return 0\n }\n\n if (options.watch) {\n process.stdout.write(`Watching \"${scanDir}\"...\\n\\n`)\n\n // Run immediately\n await runLint()\n\n let debounce: ReturnType<typeof setTimeout> | null = null\n fs.watch(scanDir, { recursive: true }, (_event, filename) => {\n if (!filename || !filename.toString().endsWith('feature.json')) return\n if (debounce) clearTimeout(debounce)\n debounce = setTimeout(async () => {\n process.stdout.write('\\x1Bc') // clear screen\n await runLint()\n process.stdout.write('\\nWatching for changes...\\n')\n }, 300)\n })\n\n // Keep process alive\n process.stdin.resume()\n\n process.on('SIGINT', () => {\n process.stdout.write('\\nStopping watch.\\n')\n process.exit(0)\n })\n } else {\n const exitCode = await runLint()\n process.exit(exitCode)\n }\n })\n","import { existsSync, statSync } from 'node:fs'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { join, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nexport const importCommand = new Command('import')\n .description('Import features from a JSON file (array of feature objects)')\n .argument('<file>', 'Path to a JSON file containing an array of feature objects')\n .option('-d, --dir <path>', 'Directory to create feature subdirectories in (default: cwd)')\n .option('--dry-run', 'Preview what would be created without writing files')\n .option('--skip-invalid', 'Skip invalid features instead of aborting')\n .action(\n async (\n file: string,\n options: { dir?: string; dryRun?: boolean; skipInvalid?: boolean },\n ) => {\n const filePath = resolve(file)\n const outDir = resolve(options.dir ?? process.cwd())\n const dryRun = options.dryRun ?? false\n const skipInvalid = options.skipInvalid ?? false\n\n // Read input file\n let raw: string\n try {\n raw = await readFile(filePath, 'utf-8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error reading \"${filePath}\": ${message}\\n`)\n process.exit(1)\n }\n\n // Parse JSON\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n process.stderr.write(`Error: \"${filePath}\" contains invalid JSON.\\n`)\n process.exit(1)\n }\n\n if (!Array.isArray(parsed)) {\n process.stderr.write(\n `Error: expected a JSON array of feature objects, got ${typeof parsed}.\\n`,\n )\n process.exit(1)\n }\n\n const features = parsed as unknown[]\n\n if (features.length === 0) {\n process.stdout.write(`No features in \"${filePath}\" — nothing to import.\\n`)\n process.exit(0)\n }\n\n process.stdout.write(`Found ${features.length} feature${features.length === 1 ? '' : 's'} in \"${file}\".\\n`)\n if (dryRun) {\n process.stdout.write(`Dry run — no files will be written.\\n\\n`)\n }\n\n let imported = 0\n let skipped = 0\n\n for (const item of features) {\n // Validate\n const result = validateFeature(item)\n\n if (!result.success) {\n const key = (item as Record<string, unknown>)?.['featureKey'] ?? '(unknown)'\n if (skipInvalid) {\n process.stderr.write(\n ` ⚠ ${key} — skipped (invalid): ${result.errors.join(', ')}\\n`,\n )\n skipped++\n continue\n } else {\n process.stderr.write(\n ` ✗ ${key} — validation failed:\\n` +\n result.errors.map((e) => ` ${e}`).join('\\n') +\n '\\n\\nAbort. Use --skip-invalid to continue past errors.\\n',\n )\n process.exit(1)\n }\n }\n\n const feature = result.data\n const featureDirName = feature.featureKey\n const featureDir = join(outDir, featureDirName)\n const featureFilePath = join(featureDir, 'feature.json')\n\n // Detect path conflicts before attempting to write.\n if (existsSync(featureDir) && !statSync(featureDir).isDirectory()) {\n process.stderr.write(\n ` ✗ ${feature.featureKey} — path \"${featureDirName}\" already exists as a file, not a directory.\\n`,\n )\n process.exit(1)\n }\n const alreadyExists = existsSync(featureFilePath)\n\n if (dryRun) {\n process.stdout.write(` Would ${alreadyExists ? 'overwrite' : 'create'}: ${featureDirName}/feature.json\\n`)\n imported++\n continue\n }\n\n if (alreadyExists) {\n process.stderr.write(` ⚠ ${feature.featureKey} — ${featureDirName}/feature.json already exists, overwriting\\n`)\n }\n\n try {\n await mkdir(featureDir, { recursive: true })\n await writeFile(\n featureFilePath,\n JSON.stringify(feature, null, 2) + '\\n',\n 'utf-8',\n )\n process.stdout.write(` ✓ ${feature.featureKey} → ${featureDirName}/feature.json\\n`)\n imported++\n } catch (err) {\n // Write/IO failures are never silently skipped by --skip-invalid —\n // that flag applies to schema validation errors only. An IO failure\n // (permissions, disk full, path conflict) is always fatal unless the\n // caller explicitly handles it.\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(` ✗ ${feature.featureKey} — write failed: ${message}\\n`)\n process.exit(1)\n }\n }\n\n process.stdout.write(`\\n`)\n if (dryRun) {\n process.stdout.write(`Would import ${imported} feature${imported === 1 ? '' : 's'}.\\n`)\n } else {\n const parts: string[] = []\n if (imported > 0) parts.push(`${imported} imported`)\n if (skipped > 0) parts.push(`${skipped} skipped`)\n process.stdout.write(`Done: ${parts.join(', ')}.\\n`)\n }\n },\n )\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { FEATURE_KEY_PATTERN, validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\n/**\n * Patches all featureJson files that reference oldKey in their lineage.parent\n * or lineage.children to use newKey instead.\n */\nasync function patchLineageRefs(\n features: Awaited<ReturnType<typeof scanFeatures>>,\n oldKey: string,\n newKey: string,\n): Promise<number> {\n let patched = 0\n\n for (const { feature, filePath } of features) {\n let changed = false\n const data = { ...feature } as Record<string, unknown>\n\n const lineage = data['lineage'] as\n | { parent?: string | null; children?: string[] }\n | undefined\n\n if (!lineage) continue\n\n const updatedLineage = { ...lineage }\n\n if (updatedLineage.parent === oldKey) {\n updatedLineage.parent = newKey\n changed = true\n }\n\n if (Array.isArray(updatedLineage.children) && updatedLineage.children.includes(oldKey)) {\n updatedLineage.children = updatedLineage.children.map((c) =>\n c === oldKey ? newKey : c,\n )\n changed = true\n }\n\n if (changed) {\n data['lineage'] = updatedLineage\n const validation = validateFeature(data)\n if (validation.success) {\n await writeFile(filePath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n patched++\n }\n }\n }\n\n return patched\n}\n\nexport const renameCommand = new Command('rename')\n .description('Rename a featureKey — updates the feature.json and patches all lineage references')\n .argument('<old-key>', 'Current featureKey (e.g. feat-2026-001)')\n .argument('<new-key>', 'New featureKey (e.g. feat-2026-099)')\n .option('-d, --dir <path>', 'Directory to scan for features (default: cwd)')\n .option('--dry-run', 'Preview changes without writing files')\n .action(\n async (\n oldKey: string,\n newKey: string,\n options: { dir?: string; dryRun?: boolean },\n ) => {\n // Validate new key format\n if (!FEATURE_KEY_PATTERN.test(newKey)) {\n process.stderr.write(\n `Error: \"${newKey}\" is not a valid featureKey.\\n` +\n `Keys must match the pattern <domain>-YYYY-NNN (e.g. feat-2026-099).\\n`,\n )\n process.exit(1)\n }\n\n if (oldKey === newKey) {\n process.stderr.write(`Error: old and new keys are identical (\"${oldKey}\").\\n`)\n process.exit(1)\n }\n\n const scanDir = options.dir ?? process.cwd()\n const dryRun = options.dryRun ?? false\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n // Find the target feature\n const target = features.find((f) => f.feature.featureKey === oldKey)\n if (!target) {\n process.stderr.write(`Error: feature \"${oldKey}\" not found in \"${scanDir}\".\\n`)\n process.exit(1)\n }\n\n // Check that newKey is not already taken\n const conflict = features.find((f) => f.feature.featureKey === newKey)\n if (conflict) {\n process.stderr.write(\n `Error: \"${newKey}\" is already used by \"${path.relative(scanDir, conflict.filePath)}\".\\n`,\n )\n process.exit(1)\n }\n\n // Find lineage refs that will be patched\n const lineageRefs = features.filter(({ feature }) => {\n const lineage = feature.lineage as\n | { parent?: string | null; children?: string[] }\n | undefined\n return (\n lineage?.parent === oldKey ||\n (Array.isArray(lineage?.children) && lineage.children.includes(oldKey))\n )\n })\n\n if (dryRun) {\n process.stdout.write(`Dry run — no files will be written.\\n\\n`)\n process.stdout.write(`Would rename: ${oldKey} → ${newKey}\\n`)\n process.stdout.write(` File: ${path.relative(scanDir, target.filePath)}\\n`)\n if (lineageRefs.length > 0) {\n process.stdout.write(`\\nWould patch ${lineageRefs.length} lineage reference(s):\\n`)\n for (const ref of lineageRefs) {\n process.stdout.write(` ${path.relative(scanDir, ref.filePath)}\\n`)\n }\n }\n return\n }\n\n // 1. Update the target feature.json\n const raw = await readFile(target.filePath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n parsed['featureKey'] = newKey\n\n const validation = validateFeature(parsed)\n if (!validation.success) {\n process.stderr.write(\n `Internal error: renamed feature failed validation:\\n ${validation.errors.join('\\n ')}\\n`,\n )\n process.exit(1)\n }\n\n await writeFile(\n target.filePath,\n JSON.stringify(validation.data, null, 2) + '\\n',\n 'utf-8',\n )\n process.stdout.write(`✓ Renamed ${oldKey} → ${newKey}\\n`)\n\n // 2. Patch lineage references in all other features\n const patched = await patchLineageRefs(features, oldKey, newKey)\n if (patched > 0) {\n process.stdout.write(`✓ Patched ${patched} lineage reference${patched === 1 ? '' : 's'}\\n`)\n }\n\n // 3. Update the .lac/keys registry so it stays in sync.\n // Walk up from scanDir to find the .lac/ directory.\n let lacDir: string | null = null\n let cur = path.resolve(scanDir)\n while (true) {\n const candidate = path.join(cur, '.lac')\n if (existsSync(candidate)) { lacDir = candidate; break }\n const parent = path.dirname(cur)\n if (parent === cur) break\n cur = parent\n }\n if (lacDir) {\n const keysPath = path.join(lacDir, 'keys')\n if (existsSync(keysPath)) {\n const lines = readFileSync(keysPath, 'utf-8').trim().split('\\n').filter(Boolean)\n const updated = lines.map((k) => (k === oldKey ? newKey : k))\n if (!updated.includes(newKey)) updated.push(newKey)\n writeFileSync(keysPath, updated.join('\\n') + '\\n', 'utf-8')\n process.stdout.write(`✓ Updated .lac/keys registry\\n`)\n }\n }\n },\n )\n","import process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\nexport const searchCommand = new Command('search')\n .description('Search features by keyword across key, title, problem, tags, and analysis')\n .argument('<query>', 'Search query (case-insensitive)')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .option('--json', 'Output results as JSON')\n .option('--field <fields>', 'Comma-separated fields to search (default: all)')\n .action(async (query: string, options: { dir?: string; json?: boolean; field?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n const features = await scanFeatures(scanDir)\n\n const searchFields = options.field\n ? options.field.split(',').map((f) => f.trim())\n : ['featureKey', 'title', 'problem', 'tags', 'analysis', 'implementation']\n\n const q = query.toLowerCase()\n\n const matches = features.filter(({ feature }) => {\n for (const field of searchFields) {\n const val = (feature as Record<string, unknown>)[field]\n if (val === undefined || val === null) continue\n if (typeof val === 'string' && val.toLowerCase().includes(q)) return true\n if (Array.isArray(val) && val.some((v) => typeof v === 'string' && v.toLowerCase().includes(q)))\n return true\n }\n return false\n })\n\n if (options.json) {\n process.stdout.write(JSON.stringify(matches.map((m) => m.feature), null, 2) + '\\n')\n return\n }\n\n if (matches.length === 0) {\n process.stdout.write(`No features found matching \"${query}\"\\n`)\n return\n }\n\n process.stdout.write(`Found ${matches.length} feature(s) matching \"${query}\":\\n\\n`)\n\n for (const { feature, filePath } of matches) {\n const statusIcon =\n ({ active: '⊙', draft: '◌', frozen: '❄', deprecated: '⊘' } as Record<string, string>)[\n feature.status\n ] ?? '?'\n process.stdout.write(` ${statusIcon} ${feature.featureKey.padEnd(18)} ${feature.title}\\n`)\n process.stdout.write(\n ` ${feature.problem.slice(0, 80)}${feature.problem.length > 80 ? '...' : ''}\\n`,\n )\n process.stdout.write(` ${filePath}\\n\\n`)\n }\n })\n","import { spawn } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport http from 'node:http'\nimport { resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { Command } from 'commander'\n\nfunction openBrowser(url: string): void {\n if (process.platform === 'darwin') {\n spawn('open', [url], { detached: true, stdio: 'ignore' }).unref()\n } else if (process.platform === 'win32') {\n spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' }).unref()\n } else {\n spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref()\n }\n}\n\nfunction waitForServer(port: number, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs\n return new Promise((res) => {\n function attempt() {\n const req = http.get(`http://127.0.0.1:${port}/health`, (response) => {\n res(response.statusCode === 200)\n })\n req.on('error', () => {\n if (Date.now() < deadline) {\n setTimeout(attempt, 250)\n } else {\n res(false)\n }\n })\n req.end()\n }\n attempt()\n })\n}\n\nfunction spawnServer(workspaceDir: string, port: number): ChildProcess {\n return spawn(\n 'lac-lsp',\n ['--http-only', '--workspace', workspaceDir, '--port', String(port)],\n { stdio: ['ignore', 'inherit', 'inherit'] },\n )\n}\n\nexport const serveCommand = new Command('serve')\n .description('Start the life-as-code HTTP server and open the dashboard in your browser')\n .argument('[dir]', 'Workspace root to index (default: current directory)')\n .option('-p, --port <n>', 'HTTP port (default: 7474)', '7474')\n .option('--no-open', 'Skip opening the browser automatically')\n .action(async (dir: string | undefined, options: { port: string; open: boolean }) => {\n const workspaceDir = resolve(dir ?? process.cwd())\n const port = parseInt(options.port, 10)\n const url = `http://127.0.0.1:${port}`\n\n process.stdout.write(\n `Starting lac-lsp HTTP server for workspace \"${workspaceDir}\" on port ${port}...\\n`,\n )\n\n // Mutable child ref — health monitor updates this on restart\n let child = spawnServer(workspaceDir, port)\n let shuttingDown = false\n\n child.on('error', (err) => {\n process.stderr.write(`Error: could not start lac-lsp — ${err.message}\\n`)\n process.stderr.write(\n `Try reinstalling: npm i -g @majeanson/lac\\n`,\n )\n process.exit(1)\n })\n\n child.on('exit', (code) => {\n if (!shuttingDown) {\n // Will be handled by health monitor; only exit if already shutting down\n process.stderr.write(`lac-lsp exited with code ${code ?? 0}\\n`)\n }\n })\n\n // Wait up to 6 seconds for the server to respond\n const ready = await waitForServer(port, 6000)\n\n if (ready) {\n process.stdout.write(`\\nReady — ${url}\\n\\n`)\n process.stdout.write(` GET ${url}/features all indexed features\\n`)\n process.stdout.write(` GET ${url}/lint run lint against all features\\n`)\n process.stdout.write(` GET ${url}/events SSE stream of changes\\n\\n`)\n process.stdout.write(`Press Ctrl+C to stop.\\n`)\n\n if (options.open) {\n openBrowser(url)\n }\n } else {\n process.stderr.write(\n `Warning: server on port ${port} did not respond within 6 s — it may still be starting up.\\n`,\n )\n }\n\n // Health-monitor: every 15 s check if the server is still alive\n let failCount = 0\n const healthInterval = setInterval(async () => {\n if (shuttingDown) {\n clearInterval(healthInterval)\n return\n }\n\n const alive = await waitForServer(port, 2000)\n if (alive) {\n failCount = 0\n return\n }\n\n failCount++\n if (failCount >= 3) {\n process.stderr.write(\n `\\nWarning: lac-lsp appears to have crashed. Restarting...\\n`,\n )\n\n try {\n child.kill()\n } catch {\n // ignore — already dead\n }\n\n child = spawnServer(workspaceDir, port)\n failCount = 0\n\n child.on('error', (err) => {\n process.stderr.write(`Error restarting lac-lsp — ${err.message}\\n`)\n })\n\n // Wait for restart and re-open browser if needed\n const restarted = await waitForServer(port, 6000)\n if (restarted) {\n process.stdout.write(`lac-lsp restarted successfully.\\n`)\n if (options.open) {\n openBrowser(url)\n }\n } else {\n process.stderr.write(`Warning: lac-lsp did not come back up after restart.\\n`)\n }\n }\n }, 15_000)\n\n // Keep the process alive and clean up on SIGINT\n process.on('SIGINT', () => {\n shuttingDown = true\n clearInterval(healthInterval)\n process.stdout.write('\\nShutting down...\\n')\n child.kill('SIGTERM')\n process.exit(0)\n })\n })\n","import { existsSync } from 'node:fs'\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\nimport prompts from 'prompts'\n\nimport { nextFeatureKey } from '../lib/featureKey.js'\nimport { scanFeatures } from '../lib/scanner.js'\n\nconst LAC_DIR = '.lac'\n\n/**\n * Walk up from startDir looking for a .lac/ directory.\n * Returns the absolute path to the workspace root (the dir containing .lac/) or null.\n */\nfunction findWorkspaceRoot(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, LAC_DIR)\n if (existsSync(candidate)) return current\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\n/**\n * Derives a short title from a problem statement by taking the first 6 words\n * and appending \"...\" when truncated.\n */\nfunction titleFromProblem(problem: string): string {\n const words = problem.trim().split(/\\s+/)\n if (words.length <= 6) return problem.trim()\n return words.slice(0, 6).join(' ') + '...'\n}\n\n/**\n * Slugifies the first 3 words of a string into kebab-case for use as a\n * default subdirectory name.\n */\nfunction slugifyProblem(problem: string): string {\n return problem\n .trim()\n .split(/\\s+/)\n .slice(0, 3)\n .join('-')\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '')\n}\n\nexport const spawnCommand = new Command('spawn')\n .description('Spawn a child feature from an existing parent feature')\n .argument('<parent-key>', 'featureKey of the parent feature (e.g. feat-2026-001)')\n .option('--reason <text>', 'Reason for spawning (default: empty)')\n .option('--dir <name>', 'Subdirectory name under parent dir (default: slug of problem)')\n .action(async (parentKey: string, options: { reason?: string; dir?: string }) => {\n const cwd = process.cwd()\n\n // 1. Find workspace root\n const workspaceRoot = findWorkspaceRoot(cwd)\n if (!workspaceRoot) {\n process.stderr.write(\n `Error: No .lac/ workspace found in current directory or any parent.\\n` +\n `Run \"lac workspace init\" to create one.\\n`,\n )\n process.exit(1)\n }\n\n // 2. Scan workspace for the parent feature\n let scanned: Awaited<ReturnType<typeof scanFeatures>>\n try {\n scanned = await scanFeatures(workspaceRoot)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning workspace \"${workspaceRoot}\": ${message}\\n`)\n process.exit(1)\n }\n\n const parentEntry = scanned.find((s) => s.feature.featureKey === parentKey)\n if (!parentEntry) {\n process.stderr.write(\n `Error: parent feature \"${parentKey}\" not found in workspace.\\n` +\n `Run \"lac blame\" or check your .lac/keys file to see available feature keys.\\n`,\n )\n process.exit(1)\n }\n\n const parentDir = dirname(parentEntry.filePath)\n\n // 3. Prompt only for problem and status (the two required interactive prompts)\n const promptList: prompts.PromptObject[] = [\n {\n type: 'text',\n name: 'problem',\n message: 'What problem does this new feature solve?',\n validate: (value: string) =>\n value.trim().length > 0 ? true : 'Problem statement is required',\n },\n {\n type: 'select',\n name: 'status',\n message: 'Status?',\n choices: [\n { title: 'draft', value: 'draft' },\n { title: 'active', value: 'active' },\n ],\n initial: 0,\n },\n ]\n\n const answers = await prompts(promptList, {\n onCancel: () => {\n process.stderr.write('Aborted.\\n')\n process.exit(1)\n },\n })\n\n const reason = options.reason ?? ''\n const problem = (answers.problem as string).trim()\n const status = answers.status as 'draft' | 'active'\n\n // 4. Determine subdirectory for the new feature.\n // If the caller supplied --dir, use it exactly.\n // Otherwise derive from the problem slug, but detect collisions and\n // append an incrementing suffix so two features with the same first\n // 3 words never land in the same directory.\n let subdirName: string\n if (options.dir) {\n subdirName = options.dir\n } else {\n const baseSlug = slugifyProblem(problem)\n subdirName = baseSlug\n let suffix = 1\n while (existsSync(join(parentDir, subdirName))) {\n suffix++\n subdirName = `${baseSlug}-${suffix}`\n }\n }\n\n const newFeatureDir = join(parentDir, subdirName)\n\n // 5. Create the directory if it doesn't exist\n await mkdir(newFeatureDir, { recursive: true })\n\n // 6. Generate featureKey from the workspace root\n let featureKey: string\n try {\n featureKey = nextFeatureKey(newFeatureDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error: ${message}\\n\\nTip: run \"lac workspace init\" first to create the .lac/ workspace.\\n`)\n process.exit(1)\n }\n\n const title = titleFromProblem(problem)\n\n // 7. Build the feature object with lineage\n const feature = {\n featureKey,\n title,\n status,\n problem,\n lineage: {\n parent: parentKey,\n ...(reason ? { spawnReason: reason } : {}),\n },\n }\n\n // 8. Validate before writing\n const validation = validateFeature(feature)\n if (!validation.success) {\n process.stderr.write(\n `Internal error: generated feature did not pass validation:\\n ${validation.errors.join('\\n ')}\\n`,\n )\n process.exit(1)\n }\n\n const featureJsonPath = join(newFeatureDir, 'feature.json')\n await writeFile(featureJsonPath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n\n // 9. Print success\n process.stdout.write(`✓ Spawned ${featureKey} from ${parentKey} in ${newFeatureDir}\\n`)\n })\n","import process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { computeCompleteness } from '../lib/config.js'\nimport { scanFeatures } from '../lib/scanner.js'\n\nexport const statCommand = new Command('stat')\n .description('Show workspace statistics: feature counts, status breakdown, completeness, top tags')\n .option('-d, --dir <path>', 'Directory to scan (default: cwd)')\n .action(async (options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n\n let features: Awaited<ReturnType<typeof scanFeatures>>\n try {\n features = await scanFeatures(scanDir)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`Error scanning \"${scanDir}\": ${message}\\n`)\n process.exit(1)\n }\n\n const total = features.length\n\n if (total === 0) {\n process.stdout.write(`No features found in \"${scanDir}\".\\n`)\n process.stdout.write(`Run \"lac init\" in a subdirectory to create your first feature.\\n`)\n return\n }\n\n // Status breakdown\n const statusBreakdown: Record<string, number> = {\n active: 0,\n draft: 0,\n frozen: 0,\n deprecated: 0,\n }\n for (const { feature } of features) {\n const s = feature.status\n statusBreakdown[s] = (statusBreakdown[s] ?? 0) + 1\n }\n\n // Completeness\n const completenessValues = features.map(({ feature }) =>\n computeCompleteness(feature as unknown as Record<string, unknown>),\n )\n const avgCompleteness =\n completenessValues.length > 0\n ? Math.round(completenessValues.reduce((a, b) => a + b, 0) / completenessValues.length)\n : 0\n\n // Features with 0 decisions\n const zeroDecisions = features.filter(\n ({ feature }) => !feature.decisions || feature.decisions.length === 0,\n ).length\n\n // Features with 0 tags\n const zeroTags = features.filter(\n ({ feature }) => !feature.tags || feature.tags.length === 0,\n ).length\n\n // Top 5 tags\n const tagCounts = new Map<string, number>()\n for (const { feature } of features) {\n for (const tag of feature.tags ?? []) {\n tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1)\n }\n }\n const topTags = Array.from(tagCounts.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5)\n\n const lines: string[] = []\n lines.push('lac stat — workspace statistics')\n lines.push('================================')\n lines.push('')\n lines.push(`Total features : ${total}`)\n lines.push('')\n lines.push('By status:')\n for (const [status, count] of Object.entries(statusBreakdown)) {\n if (count > 0) {\n lines.push(` ${status.padEnd(12)}: ${count}`)\n }\n }\n lines.push('')\n lines.push(`Avg completeness : ${avgCompleteness}%`)\n lines.push(`No decisions : ${zeroDecisions}`)\n lines.push(`No tags : ${zeroTags}`)\n\n if (topTags.length > 0) {\n lines.push('')\n lines.push('Top 5 tags:')\n for (const [tag, count] of topTags) {\n lines.push(` ${tag.padEnd(20)}: ${count}`)\n }\n }\n\n process.stdout.write(lines.join('\\n') + '\\n')\n })\n","import { readFile, writeFile } from 'node:fs/promises'\nimport process from 'node:process'\n\nimport { validateFeature } from '@life-as-code/feature-schema'\nimport { Command } from 'commander'\n\nimport { scanFeatures } from '../lib/scanner.js'\n\nexport const tagCommand = new Command('tag')\n .description('Add or remove tags on a feature')\n .argument('<key>', 'featureKey to tag (e.g. feat-2026-001)')\n .argument('<tags>', 'Comma-separated tags to add (prefix with - to remove, e.g. \"auth,-legacy,api\")')\n .option('-d, --dir <path>', 'Directory to scan for features (default: cwd)')\n .action(async (key: string, tags: string, options: { dir?: string }) => {\n const scanDir = options.dir ?? process.cwd()\n const features = await scanFeatures(scanDir)\n const found = features.find(f => f.feature.featureKey === key)\n\n if (!found) {\n process.stderr.write(`Error: feature \"${key}\" not found in \"${scanDir}\"\\n`)\n process.exit(1)\n }\n\n const tagList = tags.split(',').map(t => t.trim()).filter(Boolean)\n const toAdd = tagList.filter(t => !t.startsWith('-'))\n const toRemove = tagList.filter(t => t.startsWith('-')).map(t => t.slice(1))\n\n const current = found.feature.tags ?? []\n\n // Warn about tags that already exist (when adding) or don't exist (when removing)\n for (const tag of toAdd) {\n if (current.includes(tag)) {\n process.stdout.write(`Note: tag \"${tag}\" already present\\n`)\n }\n }\n for (const tag of toRemove) {\n if (!current.includes(tag)) {\n process.stdout.write(`Note: tag \"${tag}\" was not present\\n`)\n }\n }\n\n const updated = [...new Set([...current.filter(t => !toRemove.includes(t)), ...toAdd])]\n\n const raw = await readFile(found.filePath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n parsed['tags'] = updated\n\n const validation = validateFeature(parsed)\n if (!validation.success) {\n process.stderr.write(`Validation error: ${validation.errors.join(', ')}\\n`)\n process.exit(1)\n }\n\n await writeFile(found.filePath, JSON.stringify(validation.data, null, 2) + '\\n', 'utf-8')\n process.stdout.write(`✓ ${key} tags: [${updated.join(', ')}]\\n`)\n })\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport process from 'node:process'\n\nimport { Command } from 'commander'\n\nimport { computeCompleteness } from '../lib/config.js'\nimport { scanFeatures } from '../lib/scanner.js'\n\nconst LAC_DIR = '.lac'\nconst COUNTER_FILE = 'counter'\n\n/**\n * Walk up from startDir looking for a .lac/ directory.\n * Returns the absolute path to .lac/ or null if not found.\n */\nfunction findLacDir(startDir: string): string | null {\n let current = resolve(startDir)\n while (true) {\n const candidate = join(current, LAC_DIR)\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nexport const workspaceCommand = new Command('workspace')\n .description('Manage the lac workspace (.lac/ directory)')\n\nworkspaceCommand\n .command('init')\n .description('Initialise a lac workspace in the current directory')\n .argument('[dir]', 'Directory to initialise (default: current directory)')\n .option('--force', 'Re-initialise even if a .lac/ directory already exists')\n .action((dir: string | undefined, options: { force?: boolean }) => {\n const targetDir = resolve(dir ?? process.cwd())\n const lacDir = join(targetDir, LAC_DIR)\n const counterPath = join(lacDir, COUNTER_FILE)\n\n if (existsSync(lacDir) && !options.force) {\n process.stdout.write(\n `lac workspace already initialised at \"${lacDir}\".\\n` +\n `Run \"lac workspace init --force\" to reinitialise.\\n`,\n )\n return\n }\n\n mkdirSync(lacDir, { recursive: true })\n\n if (!existsSync(counterPath) || options.force) {\n const year = new Date().getFullYear()\n writeFileSync(counterPath, `${year}\\n0\\n`, 'utf-8')\n }\n\n process.stdout.write(`✓ Initialised lac workspace at \"${lacDir}\"\\n`)\n process.stdout.write(` Run \"lac init\" inside a feature folder to create a feature.json.\\n`)\n })\n\nworkspaceCommand\n .command('status')\n .description('Show workspace info (location, counter, next key, feature stats)')\n .action(async () => {\n const lacDir = findLacDir(process.cwd())\n\n if (!lacDir) {\n process.stdout.write(\n `No .lac/ workspace found in current directory or any parent.\\n` +\n `Run \"lac workspace init\" to create one.\\n`,\n )\n return\n }\n\n const counterPath = join(lacDir, COUNTER_FILE)\n if (!existsSync(counterPath)) {\n process.stdout.write(`Workspace : ${lacDir}\\nCounter : not initialised\\n`)\n return\n }\n\n const raw = readFileSync(counterPath, 'utf-8').trim()\n const lines = raw.split('\\n').map((l) => l.trim())\n const year = lines[0] ?? '?'\n const counterStr = lines[1] ?? '0'\n const counter = parseInt(counterStr, 10)\n const next = isNaN(counter) ? '001' : String(counter + 1).padStart(3, '0')\n\n process.stdout.write(`Workspace : ${lacDir}\\n`)\n process.stdout.write(`Counter : ${year}/${counterStr}\\n`)\n process.stdout.write(`Next key : feat-${year}-${next}\\n`)\n\n // Scan for features and show stats\n const workspaceRoot = resolve(lacDir, '..')\n try {\n const features = await scanFeatures(workspaceRoot)\n process.stdout.write(`Features : ${features.length}\\n`)\n\n if (features.length === 0) {\n process.stdout.write(`\\nNo features found. Run \"lac init\" in a subdirectory to create your first feature.\\n`)\n } else {\n const completenessValues = features.map(({ feature }) =>\n computeCompleteness(feature as unknown as Record<string, unknown>),\n )\n const avg = Math.round(\n completenessValues.reduce((a, b) => a + b, 0) / completenessValues.length,\n )\n process.stdout.write(`Avg compl.: ${avg}%\\n`)\n }\n } catch {\n // Non-fatal: just skip feature stats\n }\n })\n","import { Command } from 'commander'\nimport { archiveCommand } from './commands/archive.js'\nimport { blameCommand } from './commands/blame.js'\nimport { diffCommand } from './commands/diff.js'\nimport { doctorCommand } from './commands/doctor.js'\nimport { exportCommand } from './commands/export.js'\nimport { hooksCommand } from './commands/hooks.js'\nimport { initCommand } from './commands/init.js'\nimport { lineageCommand } from './commands/lineage.js'\nimport { lintCommand } from './commands/lint.js'\nimport { importCommand } from './commands/import.js'\nimport { renameCommand } from './commands/rename.js'\nimport { searchCommand } from './commands/search.js'\nimport { serveCommand } from './commands/serve.js'\nimport { spawnCommand } from './commands/spawn.js'\nimport { statCommand } from './commands/stat.js'\nimport { tagCommand } from './commands/tag.js'\nimport { workspaceCommand } from './commands/workspace.js'\n\nconst program = new Command()\nprogram\n .name('lac')\n .description('life-as-code CLI — provenance for your features')\n .version('1.0.0')\n\nprogram.addCommand(workspaceCommand)\nprogram.addCommand(spawnCommand)\nprogram.addCommand(initCommand)\nprogram.addCommand(exportCommand)\nprogram.addCommand(lintCommand)\nprogram.addCommand(blameCommand)\nprogram.addCommand(hooksCommand)\nprogram.addCommand(serveCommand)\nprogram.addCommand(tagCommand)\nprogram.addCommand(archiveCommand)\nprogram.addCommand(doctorCommand)\nprogram.addCommand(searchCommand)\nprogram.addCommand(statCommand)\nprogram.addCommand(lineageCommand)\nprogram.addCommand(diffCommand)\nprogram.addCommand(renameCommand)\nprogram.addCommand(importCommand)\n\nprogram.parse()\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majeanson/lac",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "description": "CLI for life-as-code — feature provenance tracking from the terminal",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,11 +10,13 @@
10
10
  "keywords": ["cli", "feature", "provenance", "lac", "life-as-code"],
11
11
  "type": "module",
12
12
  "bin": {
13
- "lac": "./bin/lac.js"
13
+ "lac": "./bin/lac.js",
14
+ "lac-lsp": "./bin/lac-lsp.js"
14
15
  },
15
16
  "files": [
16
17
  "bin",
17
- "dist"
18
+ "dist",
19
+ "README.md"
18
20
  ],
19
21
  "scripts": {
20
22
  "build": "tsdown src/index.ts --format esm --dts --out-dir dist",
@@ -26,8 +28,11 @@
26
28
  "typecheck": "tsc --noEmit"
27
29
  },
28
30
  "dependencies": {
31
+ "chokidar": "^3.6.0",
29
32
  "commander": "^12.0.0",
30
- "prompts": "^2.4.2"
33
+ "prompts": "^2.4.2",
34
+ "vscode-languageserver": "^9.0.1",
35
+ "vscode-languageserver-textdocument": "^1.0.12"
31
36
  },
32
37
  "devDependencies": {
33
38
  "@life-as-code/tsconfig": "workspace:*",