@vladimir-ks/aigile 0.2.6 → 0.2.7

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.
@@ -426,7 +426,8 @@ __export(connection_exports, {
426
426
  run: () => run,
427
427
  runMigrations: () => runMigrations,
428
428
  saveDatabase: () => saveDatabase,
429
- tagFileReviewed: () => tagFileReviewed
429
+ tagFileReviewed: () => tagFileReviewed,
430
+ transaction: () => transaction
430
431
  });
431
432
  import initSqlJs from "sql.js";
432
433
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
@@ -460,16 +461,33 @@ function getDatabase() {
460
461
  }
461
462
  return db;
462
463
  }
463
- function saveDatabase() {
464
- if (db && dbPath) {
464
+ function saveDatabase(retries = 3) {
465
+ if (!db || !dbPath) {
466
+ return;
467
+ }
468
+ let lastError = null;
469
+ for (let attempt = 0; attempt < retries; attempt++) {
465
470
  try {
466
471
  const data2 = db.export();
467
472
  const buffer = Buffer.from(data2);
468
473
  writeFileSync2(dbPath, buffer);
474
+ return;
469
475
  } catch (err) {
470
- console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Database save error: ${err}`);
476
+ lastError = err instanceof Error ? err : new Error(String(err));
477
+ console.error(
478
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] Database save attempt ${attempt + 1}/${retries} failed: ${lastError.message}`
479
+ );
480
+ if (attempt < retries - 1) {
481
+ const delay = Math.min(100 * Math.pow(2, attempt), 1e3);
482
+ const start = Date.now();
483
+ while (Date.now() - start < delay) {
484
+ }
485
+ }
471
486
  }
472
487
  }
488
+ throw new Error(
489
+ `Failed to save database after ${retries} attempts: ${lastError?.message || "Unknown error"}`
490
+ );
473
491
  }
474
492
  function closeDatabase() {
475
493
  if (db) {
@@ -497,7 +515,36 @@ function queryOne(sql, params = []) {
497
515
  function run(sql, params = []) {
498
516
  const database = getDatabase();
499
517
  database.run(sql, params);
500
- saveDatabase();
518
+ if (transactionDepth === 0) {
519
+ saveDatabase();
520
+ }
521
+ }
522
+ async function transaction(callback) {
523
+ const database = getDatabase();
524
+ const isOutermost = transactionDepth === 0;
525
+ transactionDepth++;
526
+ try {
527
+ if (isOutermost) {
528
+ database.run("BEGIN TRANSACTION");
529
+ }
530
+ const result = await callback();
531
+ if (isOutermost) {
532
+ database.run("COMMIT");
533
+ saveDatabase();
534
+ }
535
+ return result;
536
+ } catch (err) {
537
+ if (isOutermost) {
538
+ try {
539
+ database.run("ROLLBACK");
540
+ } catch (rollbackErr) {
541
+ console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Rollback failed: ${rollbackErr}`);
542
+ }
543
+ }
544
+ throw err;
545
+ } finally {
546
+ transactionDepth--;
547
+ }
501
548
  }
502
549
  function initializeSchema(database) {
503
550
  database.run(`
@@ -829,7 +876,9 @@ function initializeSchema(database) {
829
876
  )
830
877
  `);
831
878
  database.run(`CREATE INDEX IF NOT EXISTS idx_session_files_session ON session_files(session_id)`);
832
- database.run(`CREATE INDEX IF NOT EXISTS idx_session_files_document ON session_files(document_id)`);
879
+ database.run(
880
+ `CREATE INDEX IF NOT EXISTS idx_session_files_document ON session_files(document_id)`
881
+ );
833
882
  database.run(`CREATE INDEX IF NOT EXISTS idx_session_files_chunk ON session_files(chunk_id)`);
834
883
  database.run(`CREATE INDEX IF NOT EXISTS idx_session_files_report ON session_files(report_path)`);
835
884
  database.run(`CREATE INDEX IF NOT EXISTS idx_chunks_session ON chunks(session_id)`);
@@ -849,10 +898,11 @@ function getNextKey(projectKey) {
849
898
  [nextValue, projectKey]
850
899
  );
851
900
  } else {
852
- run(
853
- `INSERT INTO key_sequences (id, prefix, current_value) VALUES (?, ?, ?)`,
854
- [generateId(), projectKey, nextValue]
855
- );
901
+ run(`INSERT INTO key_sequences (id, prefix, current_value) VALUES (?, ?, ?)`, [
902
+ generateId(),
903
+ projectKey,
904
+ nextValue
905
+ ]);
856
906
  }
857
907
  return `${projectKey}-${nextValue}`;
858
908
  }
@@ -874,10 +924,9 @@ function getChunk(chunkId) {
874
924
  return queryOne("SELECT * FROM chunks WHERE id = ?", [chunkId]);
875
925
  }
876
926
  function getSessionChunks(sessionId) {
877
- return queryAll(
878
- "SELECT * FROM chunks WHERE session_id = ? ORDER BY created_at",
879
- [sessionId]
880
- );
927
+ return queryAll("SELECT * FROM chunks WHERE session_id = ? ORDER BY created_at", [
928
+ sessionId
929
+ ]);
881
930
  }
882
931
  function assignFilesToChunk(chunkId, files) {
883
932
  const chunk = getChunk(chunkId);
@@ -886,10 +935,10 @@ function assignFilesToChunk(chunkId, files) {
886
935
  }
887
936
  const existing = chunk.assigned_files ? JSON.parse(chunk.assigned_files) : [];
888
937
  const merged = [.../* @__PURE__ */ new Set([...existing, ...files])];
889
- run(
890
- `UPDATE chunks SET assigned_files = ?, updated_at = datetime('now') WHERE id = ?`,
891
- [JSON.stringify(merged), chunkId]
892
- );
938
+ run(`UPDATE chunks SET assigned_files = ?, updated_at = datetime('now') WHERE id = ?`, [
939
+ JSON.stringify(merged),
940
+ chunkId
941
+ ]);
893
942
  }
894
943
  function tagFileReviewed(sessionId, documentId, options = {}) {
895
944
  const existing = queryOne(
@@ -941,10 +990,10 @@ function flagFileQualityIssue(sessionFileId, issues) {
941
990
  );
942
991
  const current = existing?.quality_issues ? JSON.parse(existing.quality_issues) : [];
943
992
  const merged = [.../* @__PURE__ */ new Set([...current, ...issues])];
944
- run(
945
- `UPDATE session_files SET quality_issues = ? WHERE id = ?`,
946
- [JSON.stringify(merged), sessionFileId]
947
- );
993
+ run(`UPDATE session_files SET quality_issues = ? WHERE id = ?`, [
994
+ JSON.stringify(merged),
995
+ sessionFileId
996
+ ]);
948
997
  }
949
998
  function getSessionFiles(sessionId, options = {}) {
950
999
  let sql = "SELECT * FROM session_files WHERE session_id = ?";
@@ -1066,9 +1115,7 @@ function getCoverageStats(sessionId, chunkId) {
1066
1115
  }
1067
1116
  function runMigrations() {
1068
1117
  const database = getDatabase();
1069
- const columns = queryAll(
1070
- `PRAGMA table_info(documents)`
1071
- );
1118
+ const columns = queryAll(`PRAGMA table_info(documents)`);
1072
1119
  const columnNames = new Set(columns.map((c) => c.name));
1073
1120
  const newColumns = [
1074
1121
  { name: "meta_status", type: "TEXT" },
@@ -1094,7 +1141,13 @@ function runMigrations() {
1094
1141
  // Tri-state monitoring columns
1095
1142
  { name: "monitoring_category", type: 'TEXT DEFAULT "unknown"' },
1096
1143
  { name: "needs_review", type: "INTEGER DEFAULT 0" },
1097
- { name: "reviewed_at", type: "TEXT" }
1144
+ { name: "reviewed_at", type: "TEXT" },
1145
+ // Template system columns (v3)
1146
+ { name: "last_approved_at", type: "TEXT" },
1147
+ { name: "last_modified_at", type: "TEXT" },
1148
+ { name: "is_external", type: "INTEGER DEFAULT 0" },
1149
+ { name: "meta_related", type: "TEXT" },
1150
+ { name: "meta_exception", type: "TEXT" }
1098
1151
  ];
1099
1152
  for (const col of newColumns) {
1100
1153
  if (!columnNames.has(col.name)) {
@@ -1163,22 +1216,29 @@ function runMigrations() {
1163
1216
  )
1164
1217
  `);
1165
1218
  try {
1166
- database.run(`CREATE INDEX IF NOT EXISTS idx_session_files_session ON session_files(session_id)`);
1167
- database.run(`CREATE INDEX IF NOT EXISTS idx_session_files_document ON session_files(document_id)`);
1219
+ database.run(
1220
+ `CREATE INDEX IF NOT EXISTS idx_session_files_session ON session_files(session_id)`
1221
+ );
1222
+ database.run(
1223
+ `CREATE INDEX IF NOT EXISTS idx_session_files_document ON session_files(document_id)`
1224
+ );
1168
1225
  database.run(`CREATE INDEX IF NOT EXISTS idx_session_files_chunk ON session_files(chunk_id)`);
1169
- database.run(`CREATE INDEX IF NOT EXISTS idx_session_files_report ON session_files(report_path)`);
1226
+ database.run(
1227
+ `CREATE INDEX IF NOT EXISTS idx_session_files_report ON session_files(report_path)`
1228
+ );
1170
1229
  database.run(`CREATE INDEX IF NOT EXISTS idx_chunks_session ON chunks(session_id)`);
1171
1230
  } catch {
1172
1231
  }
1173
1232
  saveDatabase();
1174
1233
  }
1175
- var db, dbPath;
1234
+ var db, dbPath, transactionDepth;
1176
1235
  var init_connection = __esm({
1177
1236
  "src/db/connection.ts"() {
1178
1237
  "use strict";
1179
1238
  init_config();
1180
1239
  db = null;
1181
1240
  dbPath = "";
1241
+ transactionDepth = 0;
1182
1242
  }
1183
1243
  });
1184
1244
 
@@ -1186,14 +1246,14 @@ var init_connection = __esm({
1186
1246
  import { Command as Command23 } from "commander";
1187
1247
 
1188
1248
  // src/index.ts
1189
- var VERSION = true ? "0.2.6" : "0.0.0-dev";
1249
+ var VERSION = true ? "0.2.7" : "0.0.0-dev";
1190
1250
 
1191
1251
  // src/bin/aigile.ts
1192
1252
  init_connection();
1193
1253
 
1194
1254
  // src/commands/init.ts
1195
1255
  import { Command } from "commander";
1196
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, appendFileSync, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
1256
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, appendFileSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
1197
1257
  import { resolve, join as join4, relative, basename as basename2 } from "path";
1198
1258
 
1199
1259
  // src/utils/git.ts
@@ -1290,7 +1350,7 @@ function error(message, opts = {}) {
1290
1350
  console.error(`${prefix} ${message}`);
1291
1351
  }
1292
1352
  }
1293
- function warning2(message, opts = {}) {
1353
+ function warning(message, opts = {}) {
1294
1354
  if (opts.json) {
1295
1355
  console.log(JSON.stringify({ warning: message }));
1296
1356
  } else {
@@ -1351,747 +1411,88 @@ function header(title, opts = {}) {
1351
1411
  console.log(formatted);
1352
1412
  }
1353
1413
 
1354
- // src/services/template-packs.ts
1355
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
1414
+ // src/services/template-loader.ts
1415
+ import { readdirSync, statSync, readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
1356
1416
  import { join as join3, dirname as dirname2 } from "path";
1357
- function createFrontmatter(title, tldr, modules = []) {
1358
- return `---
1359
- metadata:
1360
- status: TEMPLATE
1361
- version: "1.0"
1362
- tldr: "${tldr}"
1363
- title: "${title}"
1364
- modules: [${modules.map((m) => `"${m}"`).join(", ")}]
1365
- authors: []
1366
- dependencies: []
1367
- ---
1368
-
1369
- `;
1370
- }
1371
- function getFullRepoTemplates() {
1372
- return [
1373
- // 00_DOCS hierarchy
1374
- {
1375
- path: "00_DOCS/00_vision/01_mission-vision.md",
1376
- content: createFrontmatter(
1377
- "Mission & Vision",
1378
- "Define company mission, vision, values, and elevator pitch",
1379
- ["vision"]
1380
- ) + `# Mission & Vision
1381
-
1382
- ## Mission Statement
1383
- <!-- Why does this company/product exist? -->
1384
-
1385
- ## Vision Statement
1386
- <!-- What future are we creating? -->
1387
-
1388
- ## Core Values
1389
- <!-- What principles guide our decisions? -->
1390
-
1391
- ## Elevator Pitch
1392
- <!-- One sentence: What do we do? -->
1393
- `
1394
- },
1395
- {
1396
- path: "00_DOCS/01_strategy/01_strategic-analysis.md",
1397
- content: createFrontmatter(
1398
- "Strategic Analysis",
1399
- "SWOT analysis and strategic pillars",
1400
- ["strategy"]
1401
- ) + `# Strategic Analysis
1402
-
1403
- ## SWOT Analysis
1404
-
1405
- ### Strengths
1406
- -
1407
-
1408
- ### Weaknesses
1409
- -
1410
-
1411
- ### Opportunities
1412
- -
1413
-
1414
- ### Threats
1415
- -
1416
-
1417
- ## Strategic Pillars
1418
- <!-- Key focus areas for the next 1-3 years -->
1419
-
1420
- `
1421
- },
1422
- {
1423
- path: "00_DOCS/02_target-audience/01_personas.md",
1424
- content: createFrontmatter(
1425
- "User Personas",
1426
- "Define ideal customer profile and user personas",
1427
- ["personas", "ux"]
1428
- ) + `# User Personas
1429
-
1430
- ## Ideal Customer Profile (ICP)
1431
- <!-- Who is our primary customer? -->
1432
-
1433
- ## Primary Personas
1434
-
1435
- ### Persona 1: [Name]
1436
- - **Role:**
1437
- - **Goals:**
1438
- - **Frustrations:**
1439
- - **Key Tasks:**
1440
-
1441
- `
1442
- },
1443
- {
1444
- path: "00_DOCS/03_finance-legal/01_model.md",
1445
- content: createFrontmatter(
1446
- "Business Model",
1447
- "Revenue model and financial overview",
1448
- ["finance"]
1449
- ) + `# Business Model
1450
-
1451
- ## Revenue Model
1452
- <!-- How do we make money? -->
1453
-
1454
- ## Pricing Strategy
1455
- <!-- How do we price our offerings? -->
1456
-
1457
- ## Key Metrics
1458
- <!-- What metrics matter most? -->
1459
-
1460
- `
1461
- },
1462
- {
1463
- path: "00_DOCS/04_raw-inputs/README.md",
1464
- content: createFrontmatter(
1465
- "Raw Inputs",
1466
- "Repository for raw user feedback, interviews, and research data",
1467
- ["research"]
1468
- ) + `# Raw Inputs
1469
-
1470
- This directory contains raw, unprocessed input from users, stakeholders, and research.
1471
-
1472
- ## Contents
1473
- - User interviews
1474
- - Survey responses
1475
- - Support tickets
1476
- - Feature requests
1477
- - Competitive intelligence
1478
-
1479
- `
1480
- },
1481
- {
1482
- path: "00_DOCS/05_research/README.md",
1483
- content: createFrontmatter(
1484
- "Research",
1485
- "Market research and analysis documents",
1486
- ["research"]
1487
- ) + `# Research
1488
-
1489
- This directory contains research and analysis documents.
1490
-
1491
- ## Contents
1492
- - Market analysis
1493
- - User research
1494
- - Technical research
1495
- - Competitive analysis
1496
-
1497
- `
1498
- },
1499
- {
1500
- path: "00_DOCS/06_product-specs/01_prd.md",
1501
- content: createFrontmatter(
1502
- "Product Requirements Document",
1503
- "Main PRD template for product features",
1504
- ["product", "specs"]
1505
- ) + `# Product Requirements Document (PRD)
1506
-
1507
- ## Overview
1508
- <!-- Brief description of the product/feature -->
1509
-
1510
- ## Problem Statement
1511
- <!-- What problem are we solving? -->
1512
-
1513
- ## Goals & Success Metrics
1514
- <!-- How will we measure success? -->
1515
-
1516
- ## User Stories
1517
- <!-- Link to user stories -->
1518
-
1519
- ## Requirements
1520
- ### Functional Requirements
1521
- -
1522
-
1523
- ### Non-Functional Requirements
1524
- -
1525
-
1526
- ## Out of Scope
1527
- -
1528
-
1529
- `
1530
- },
1531
- {
1532
- path: "00_DOCS/06_product-specs/epics/README.md",
1533
- content: createFrontmatter(
1534
- "Epics Directory",
1535
- "Contains epic-level product specifications",
1536
- ["product", "epics"]
1537
- ) + `# Epics
1538
-
1539
- This directory contains epic-level specifications.
1540
-
1541
- `
1542
- },
1543
- {
1544
- path: "00_DOCS/06_product-specs/stories/README.md",
1545
- content: createFrontmatter(
1546
- "User Stories Directory",
1547
- "Contains user story specifications",
1548
- ["product", "stories"]
1549
- ) + `# User Stories
1550
-
1551
- This directory contains user story specifications.
1552
-
1553
- `
1554
- },
1555
- {
1556
- path: "00_DOCS/07_ux-design/00_README.md",
1557
- content: createFrontmatter(
1558
- "UX Design",
1559
- "UX design documentation and journey maps",
1560
- ["ux"]
1561
- ) + `# UX Design
1562
-
1563
- ## Contents
1564
- - User journeys
1565
- - Wireframes
1566
- - Design system references
1567
- - Interaction patterns
1568
-
1569
- `
1570
- },
1571
- {
1572
- path: "00_DOCS/07_ux-design/journeys/README.md",
1573
- content: createFrontmatter(
1574
- "User Journeys",
1575
- "User journey maps and flow documentation",
1576
- ["ux", "journeys"]
1577
- ) + `# User Journeys
1578
-
1579
- This directory contains user journey maps.
1580
-
1581
- `
1582
- },
1583
- {
1584
- path: "00_DOCS/08_go-to-market/01_launch-plan.md",
1585
- content: createFrontmatter(
1586
- "Launch Plan",
1587
- "Go-to-market strategy and launch checklist",
1588
- ["gtm"]
1589
- ) + `# Launch Plan
1590
-
1591
- ## Launch Goals
1592
- -
1593
-
1594
- ## Target Audience
1595
- -
1596
-
1597
- ## Channels
1598
- -
1599
-
1600
- ## Timeline
1601
- -
1602
-
1603
- ## Checklist
1604
- - [ ] Marketing materials ready
1605
- - [ ] Documentation complete
1606
- - [ ] Support trained
1607
- - [ ] Analytics configured
1608
-
1609
- `
1610
- },
1611
- {
1612
- path: "00_DOCS/11_people-ops/ai-agents/README.md",
1613
- content: createFrontmatter(
1614
- "AI Agents",
1615
- "AI agent configurations and prompts",
1616
- ["ai", "agents"]
1617
- ) + `# AI Agents
1618
-
1619
- This directory contains AI agent configurations and prompts.
1620
-
1621
- `
1622
- },
1623
- {
1624
- path: "00_DOCS/11_people-ops/sops/README.md",
1625
- content: createFrontmatter(
1626
- "Standard Operating Procedures",
1627
- "Team SOPs and process documentation",
1628
- ["sops"]
1629
- ) + `# Standard Operating Procedures
1630
-
1631
- This directory contains team SOPs and process documentation.
1632
-
1633
- `
1634
- },
1635
- {
1636
- path: "00_DOCS/12_customer-success/installation.md",
1637
- content: createFrontmatter(
1638
- "Installation Guide",
1639
- "Installation and setup instructions",
1640
- ["docs", "installation"]
1641
- ) + `# Installation Guide
1642
-
1643
- ## Prerequisites
1644
- -
1645
-
1646
- ## Installation Steps
1647
- 1.
1648
- 2.
1649
- 3.
1650
-
1651
- ## Verification
1652
- -
1653
-
1654
- `
1655
- },
1656
- {
1657
- path: "00_DOCS/12_customer-success/quick-start.md",
1658
- content: createFrontmatter(
1659
- "Quick Start Guide",
1660
- "Getting started guide for new users",
1661
- ["docs", "quickstart"]
1662
- ) + `# Quick Start Guide
1663
-
1664
- ## Overview
1665
- <!-- Brief intro -->
1666
-
1667
- ## Step 1: Setup
1668
- <!-- First step -->
1669
-
1670
- ## Step 2: First Action
1671
- <!-- Second step -->
1672
-
1673
- ## Next Steps
1674
- <!-- What to do next -->
1675
-
1676
- `
1677
- },
1678
- {
1679
- path: "00_DOCS/12_customer-success/troubleshooting.md",
1680
- content: createFrontmatter(
1681
- "Troubleshooting Guide",
1682
- "Common issues and solutions",
1683
- ["docs", "troubleshooting"]
1684
- ) + `# Troubleshooting
1685
-
1686
- ## Common Issues
1687
-
1688
- ### Issue: [Description]
1689
- **Symptoms:**
1690
- **Solution:**
1691
-
1692
- `
1693
- },
1694
- {
1695
- path: "00_DOCS/99_archive/00_README.md",
1696
- content: createFrontmatter(
1697
- "Archive",
1698
- "Archived documents no longer in active use",
1699
- ["archive"]
1700
- ) + `# Archive
1701
-
1702
- This directory contains archived documents that are no longer in active use but are kept for reference.
1703
-
1704
- `
1705
- },
1706
- // 01_SPECS hierarchy
1707
- {
1708
- path: "01_SPECS/00_adr/README.md",
1709
- content: createFrontmatter(
1710
- "Architecture Decision Records",
1711
- "ADRs documenting key technical decisions",
1712
- ["specs", "adr"]
1713
- ) + `# Architecture Decision Records (ADRs)
1714
-
1715
- This directory contains ADRs for documenting key technical decisions.
1716
-
1717
- ## Template
1718
- Use the template in \`template.md\` for new ADRs.
1719
-
1720
- `
1721
- },
1722
- {
1723
- path: "01_SPECS/01_domain-models/README.md",
1724
- content: createFrontmatter(
1725
- "Domain Models",
1726
- "Domain model specifications",
1727
- ["specs", "domain"]
1728
- ) + `# Domain Models
1729
-
1730
- This directory contains domain model specifications.
1731
-
1732
- `
1733
- },
1734
- {
1735
- path: "01_SPECS/02_api-contracts/README.md",
1736
- content: createFrontmatter(
1737
- "API Contracts",
1738
- "API specifications and contracts",
1739
- ["specs", "api"]
1740
- ) + `# API Contracts
1741
-
1742
- This directory contains API specifications and contracts.
1743
-
1744
- `
1745
- },
1746
- {
1747
- path: "01_SPECS/03_c4-components/README.md",
1748
- content: createFrontmatter(
1749
- "C4 Architecture",
1750
- "C4 model component diagrams",
1751
- ["specs", "architecture"]
1752
- ) + `# C4 Architecture Components
1753
-
1754
- This directory contains C4 model architecture diagrams.
1755
-
1756
- `
1757
- },
1758
- {
1759
- path: "01_SPECS/04_ui-ux-specs/README.md",
1760
- content: createFrontmatter(
1761
- "UI/UX Specifications",
1762
- "UI/UX technical specifications",
1763
- ["specs", "ux"]
1764
- ) + `# UI/UX Specifications
1765
-
1766
- This directory contains UI/UX technical specifications.
1767
-
1768
- `
1769
- },
1770
- {
1771
- path: "01_SPECS/05_infrastructure/README.md",
1772
- content: createFrontmatter(
1773
- "Infrastructure",
1774
- "Infrastructure and deployment specifications",
1775
- ["specs", "infra"]
1776
- ) + `# Infrastructure
1777
-
1778
- This directory contains infrastructure and deployment specifications.
1779
-
1780
- `
1781
- },
1782
- // 02_FEATURES hierarchy
1783
- {
1784
- path: "02_FEATURES/00_smoke-tests/README.md",
1785
- content: createFrontmatter(
1786
- "Smoke Tests",
1787
- "Critical path smoke test scenarios",
1788
- ["features", "tests"]
1789
- ) + `# Smoke Tests
1790
-
1791
- This directory contains smoke test feature files for critical paths.
1792
-
1793
- `
1794
- },
1795
- {
1796
- path: "02_FEATURES/01_epics/README.md",
1797
- content: createFrontmatter(
1798
- "Epic Features",
1799
- "BDD feature files organized by epic",
1800
- ["features", "epics"]
1801
- ) + `# Epic Features
1802
-
1803
- This directory contains BDD feature files organized by epic.
1804
-
1805
- `
1806
- },
1807
- {
1808
- path: "02_FEATURES/02_end-to-end/README.md",
1809
- content: createFrontmatter(
1810
- "End-to-End Features",
1811
- "End-to-end scenario feature files",
1812
- ["features", "e2e"]
1813
- ) + `# End-to-End Features
1814
-
1815
- This directory contains end-to-end scenario feature files.
1816
-
1817
- `
1818
- },
1819
- {
1820
- path: "02_FEATURES/03_nfr/README.md",
1821
- content: createFrontmatter(
1822
- "Non-Functional Requirements",
1823
- "NFR feature files (performance, security, etc.)",
1824
- ["features", "nfr"]
1825
- ) + `# Non-Functional Requirements
1826
-
1827
- This directory contains NFR feature files:
1828
- - Performance
1829
- - Security
1830
- - Scalability
1831
- - Accessibility
1832
-
1833
- `
1834
- },
1835
- // 03_TESTING_INFRA hierarchy
1836
- {
1837
- path: "03_TESTING_INFRA/00_governance/README.md",
1838
- content: createFrontmatter(
1839
- "Test Governance",
1840
- "Testing standards and policies",
1841
- ["testing", "governance"]
1842
- ) + `# Test Governance
1843
-
1844
- This directory contains testing standards and policies.
1845
-
1846
- `
1847
- },
1848
- {
1849
- path: "03_TESTING_INFRA/01_unit-layer/README.md",
1850
- content: createFrontmatter(
1851
- "Unit Testing Layer",
1852
- "Unit test infrastructure and guides",
1853
- ["testing", "unit"]
1854
- ) + `# Unit Testing Layer
1855
-
1856
- This directory contains unit test infrastructure.
1857
-
1858
- `
1859
- },
1860
- {
1861
- path: "03_TESTING_INFRA/02_integration-layer/README.md",
1862
- content: createFrontmatter(
1863
- "Integration Testing Layer",
1864
- "Integration test infrastructure and guides",
1865
- ["testing", "integration"]
1866
- ) + `# Integration Testing Layer
1867
-
1868
- This directory contains integration test infrastructure.
1869
-
1870
- `
1871
- },
1872
- {
1873
- path: "03_TESTING_INFRA/03_e2e-layer/README.md",
1874
- content: createFrontmatter(
1875
- "E2E Testing Layer",
1876
- "End-to-end test infrastructure and guides",
1877
- ["testing", "e2e"]
1878
- ) + `# E2E Testing Layer
1879
-
1880
- This directory contains E2E test infrastructure.
1881
-
1882
- `
1883
- },
1884
- {
1885
- path: "03_TESTING_INFRA/04_manual-qa-layer/README.md",
1886
- content: createFrontmatter(
1887
- "Manual QA Layer",
1888
- "Manual QA procedures and checklists",
1889
- ["testing", "qa"]
1890
- ) + `# Manual QA Layer
1891
-
1892
- This directory contains manual QA procedures and checklists.
1893
-
1894
- `
1895
- },
1896
- {
1897
- path: "03_TESTING_INFRA/qa/README.md",
1898
- content: createFrontmatter(
1899
- "QA Documentation",
1900
- "QA procedures and documentation",
1901
- ["testing", "qa"]
1902
- ) + `# QA Documentation
1903
-
1904
- This directory contains QA procedures and documentation.
1905
-
1906
- `
1907
- },
1908
- {
1909
- path: "03_TESTING_INFRA/reports/README.md",
1910
- content: createFrontmatter(
1911
- "Test Reports",
1912
- "Test execution reports",
1913
- ["testing", "reports"]
1914
- ) + `# Test Reports
1915
-
1916
- This directory contains test execution reports.
1917
-
1918
- `
1919
- }
1920
- ];
1417
+ import { fileURLToPath } from "url";
1418
+ var __filename = fileURLToPath(import.meta.url);
1419
+ var __dirname = dirname2(__filename);
1420
+ function getTemplateDir(profile) {
1421
+ const isDev = process.env.NODE_ENV === "development" || !process.pkg;
1422
+ let baseDir;
1423
+ if (isDev) {
1424
+ baseDir = join3(__dirname, "..", "..", "artifacts", "templates");
1425
+ } else {
1426
+ baseDir = join3(__dirname, "..", "artifacts", "templates");
1427
+ }
1428
+ return join3(baseDir, profile);
1921
1429
  }
1922
- function getSubrepoTemplates() {
1923
- return getFullRepoTemplates();
1430
+ function loadTemplates(profile) {
1431
+ const templateDir = getTemplateDir(profile);
1432
+ if (!existsSync3(templateDir)) {
1433
+ throw new Error(`Template directory not found: ${templateDir}`);
1434
+ }
1435
+ return recursiveReadTemplates(templateDir, "");
1924
1436
  }
1925
- function getModuleTemplates(moduleName) {
1926
- return [
1927
- {
1928
- path: "docs/01_module-overview.md",
1929
- content: createFrontmatter(
1930
- `${moduleName} Overview`,
1931
- `Overview documentation for ${moduleName} module`,
1932
- [moduleName]
1933
- ) + `# ${moduleName} Module Overview
1934
-
1935
- ## Purpose
1936
- <!-- What does this module do? -->
1937
-
1938
- ## Key Concepts
1939
- <!-- Core concepts and terminology -->
1940
-
1941
- ## Dependencies
1942
- <!-- What does this module depend on? -->
1943
-
1944
- ## API
1945
- <!-- Public API surface -->
1946
-
1947
- `
1948
- },
1949
- {
1950
- path: "docs/02_mini-prd.md",
1951
- content: createFrontmatter(
1952
- `${moduleName} Requirements`,
1953
- `Module-level requirements for ${moduleName}`,
1954
- [moduleName, "specs"]
1955
- ) + `# ${moduleName} Module Requirements
1956
-
1957
- ## Goals
1958
- <!-- What should this module achieve? -->
1959
-
1960
- ## Requirements
1961
- ### Functional
1962
- -
1963
-
1964
- ### Non-Functional
1965
- -
1966
-
1967
- ## Out of Scope
1968
- -
1969
-
1970
- `
1971
- },
1972
- {
1973
- path: "specs/01_spec-template.md",
1974
- content: createFrontmatter(
1975
- `${moduleName} Spec Template`,
1976
- `Specification template for ${moduleName}`,
1977
- [moduleName, "specs"]
1978
- ) + `# ${moduleName} Specification
1979
-
1980
- ## Overview
1981
- <!-- Brief description -->
1982
-
1983
- ## Design
1984
- <!-- Technical design -->
1985
-
1986
- ## Implementation Notes
1987
- <!-- Key implementation details -->
1988
-
1989
- `
1990
- },
1991
- {
1992
- path: "features/01_happy-path.feature",
1993
- content: `Feature: ${moduleName} Happy Path
1994
- As a user
1995
- I want to use ${moduleName}
1996
- So that I can achieve my goals
1997
-
1998
- Scenario: Basic usage
1999
- Given I have configured ${moduleName}
2000
- When I perform the main action
2001
- Then I should see the expected result
2002
- `
2003
- },
2004
- {
2005
- path: "features/02_edge-cases.feature",
2006
- content: `Feature: ${moduleName} Edge Cases
2007
- As a user
2008
- I want ${moduleName} to handle edge cases gracefully
2009
- So that I don't encounter errors
2010
-
2011
- Scenario: Empty input
2012
- Given I have configured ${moduleName}
2013
- When I provide empty input
2014
- Then I should see a helpful error message
2015
- `
1437
+ function recursiveReadTemplates(baseDir, relativePath) {
1438
+ const fullPath = join3(baseDir, relativePath);
1439
+ if (!existsSync3(fullPath)) {
1440
+ return [];
1441
+ }
1442
+ const entries = readdirSync(fullPath);
1443
+ const files = [];
1444
+ for (const entry of entries) {
1445
+ const entryPath = join3(fullPath, entry);
1446
+ const entryRelative = relativePath ? join3(relativePath, entry) : entry;
1447
+ try {
1448
+ const stat = statSync(entryPath);
1449
+ if (stat.isDirectory()) {
1450
+ files.push(...recursiveReadTemplates(baseDir, entryRelative));
1451
+ } else if (stat.isFile()) {
1452
+ const content = readFileSync3(entryPath, "utf-8");
1453
+ files.push({
1454
+ relativePath: entryRelative,
1455
+ content
1456
+ });
1457
+ }
1458
+ } catch (err) {
1459
+ console.warn(`Warning: Could not read ${entryPath}: ${err}`);
2016
1460
  }
2017
- ];
2018
- }
2019
- function getTemplatesForProfile(profile, moduleName) {
2020
- switch (profile) {
2021
- case "full-repo":
2022
- return getFullRepoTemplates();
2023
- case "subrepo":
2024
- return getSubrepoTemplates();
2025
- case "module":
2026
- return getModuleTemplates(moduleName ?? "module");
2027
- default:
2028
- return [];
2029
1461
  }
1462
+ return files;
2030
1463
  }
2031
1464
  function writeTemplates(targetDir, templates) {
2032
1465
  let written = 0;
2033
1466
  let skipped = 0;
1467
+ const errors = [];
2034
1468
  for (const template of templates) {
2035
- const fullPath = join3(targetDir, template.path);
2036
- const dir = dirname2(fullPath);
2037
- if (!existsSync3(dir)) {
2038
- mkdirSync3(dir, { recursive: true });
1469
+ const fullPath = join3(targetDir, template.relativePath);
1470
+ if (existsSync3(fullPath)) {
1471
+ skipped++;
1472
+ continue;
2039
1473
  }
2040
- if (!existsSync3(fullPath)) {
1474
+ try {
1475
+ const dir = dirname2(fullPath);
1476
+ if (!existsSync3(dir)) {
1477
+ mkdirSync3(dir, { recursive: true });
1478
+ }
2041
1479
  writeFileSync3(fullPath, template.content, "utf-8");
2042
1480
  written++;
2043
- } else {
2044
- skipped++;
1481
+ } catch (err) {
1482
+ errors.push({
1483
+ path: template.relativePath,
1484
+ error: err instanceof Error ? err.message : String(err)
1485
+ });
2045
1486
  }
2046
1487
  }
2047
- return { written, skipped };
2048
- }
2049
- function generateConfigYaml(config, projectKey, projectName) {
2050
- let yaml = `# AIGILE Project Configuration
2051
- # Auto-generated by aigile init
2052
-
2053
- db:
2054
- mode: ${config.db.mode}
2055
- path: ${config.db.path}
2056
-
2057
- profile: ${config.profile}
2058
- repo_root: ${config.repo_root}
2059
- `;
2060
- if (config.parent_repo_root) {
2061
- yaml += `parent_repo_root: ${config.parent_repo_root}
2062
- `;
2063
- }
2064
- if (config.module) {
2065
- yaml += `
2066
- module:
2067
- name: ${config.module.name}
2068
- kind: ${config.module.kind}
2069
- path: ${config.module.path}
2070
- `;
2071
- }
2072
- yaml += `
2073
- project:
2074
- key: ${projectKey}
2075
- name: ${projectName}
2076
-
2077
- sync:
2078
- enabled: true
2079
- patterns:
2080
- - "**/*.md"
2081
- - "**/*.feature"
2082
- - "**/*.yaml"
2083
- - "**/*.yml"
2084
- ignore:
2085
- - node_modules
2086
- - dist
2087
- - .git
2088
- - coverage
2089
- `;
2090
- return yaml;
1488
+ return { written, skipped, errors };
2091
1489
  }
2092
1490
 
2093
1491
  // src/commands/init.ts
2094
- var initCommand = new Command("init").description("Initialize AIGILE in current or specified directory").argument("[path]", "Directory path (defaults to current directory)").option("-k, --key <key>", "Project key (auto-generated if not specified)").option("-n, --name <name>", "Project name (auto-detected if not specified)").option("-p, --profile <profile>", "Init profile: full-repo, subrepo, module").option("--db-mode <mode>", "Database mode: local, shared (default based on profile)").option("--module-kind <kind>", "Module kind: library, service, ui, cli, other (for module profile)").option("--skip-templates", "Skip template file creation").option("-f, --force", "Reinitialize existing project").action(async (pathArg, options) => {
1492
+ var initCommand = new Command("init").description("Initialize AIGILE in current or specified directory").argument("[path]", "Directory path (defaults to current directory)").option("-k, --key <key>", "Project key (auto-generated if not specified)").option("-n, --name <name>", "Project name (auto-detected if not specified)").option("-p, --profile <profile>", "Init profile: full-repo, minor-project, module").option("--db-mode <mode>", "Database mode: local, shared (default based on profile)").option(
1493
+ "--module-kind <kind>",
1494
+ "Module kind: library, service, ui, cli, other (for module profile)"
1495
+ ).option("--skip-templates", "Skip template file creation").option("-f, --force", "Reinitialize existing project").action(async (pathArg, options) => {
2095
1496
  const opts = getOutputOptions(initCommand);
2096
1497
  const targetPath = resolve(pathArg ?? process.cwd());
2097
1498
  try {
@@ -2118,7 +1519,7 @@ function detectGitContext(targetPath) {
2118
1519
  relativePath
2119
1520
  };
2120
1521
  }
2121
- var VALID_PROFILES = ["full-repo", "subrepo", "module"];
1522
+ var VALID_PROFILES = ["full-repo", "minor-project", "module"];
2122
1523
  function determineProfile(context, options, opts) {
2123
1524
  if (options.profile) {
2124
1525
  if (!VALID_PROFILES.includes(options.profile)) {
@@ -2129,8 +1530,8 @@ function determineProfile(context, options, opts) {
2129
1530
  return options.profile;
2130
1531
  }
2131
1532
  if (context.isSubmodule) {
2132
- info("Detected git submodule - using subrepo profile", opts);
2133
- return "subrepo";
1533
+ info("Detected git submodule - using minor-project profile", opts);
1534
+ return "minor-project";
2134
1535
  }
2135
1536
  if (context.isSubdirectory) {
2136
1537
  const parentAigile = join4(context.gitRoot, ".aigile");
@@ -2153,7 +1554,7 @@ function determineDbMode(profile, context, options) {
2153
1554
  switch (profile) {
2154
1555
  case "full-repo":
2155
1556
  return { mode: "local", path: ".aigile/aigile.db" };
2156
- case "subrepo":
1557
+ case "minor-project":
2157
1558
  return { mode: "local", path: ".aigile/aigile.db" };
2158
1559
  case "module":
2159
1560
  const parentDbPath = findParentDb(context);
@@ -2176,6 +1577,33 @@ function findParentDb(context) {
2176
1577
  }
2177
1578
  return ".aigile/aigile.db";
2178
1579
  }
1580
+ function generateConfigYaml(config, projectKey, projectName) {
1581
+ const lines = ["# AIGILE Project Configuration", ""];
1582
+ lines.push("project:");
1583
+ lines.push(` key: "${projectKey}"`);
1584
+ lines.push(` name: "${projectName}"`);
1585
+ lines.push("");
1586
+ lines.push("db:");
1587
+ lines.push(` mode: ${config.db.mode}`);
1588
+ lines.push(` path: ${config.db.path}`);
1589
+ lines.push("");
1590
+ lines.push(`profile: ${config.profile}`);
1591
+ lines.push("");
1592
+ lines.push(`repo_root: ${config.repo_root}`);
1593
+ if (config.module) {
1594
+ lines.push("");
1595
+ lines.push("module:");
1596
+ lines.push(` name: ${config.module.name}`);
1597
+ lines.push(` kind: ${config.module.kind}`);
1598
+ lines.push(` path: ${config.module.path}`);
1599
+ }
1600
+ if (config.parent_repo_root) {
1601
+ lines.push("");
1602
+ lines.push(`parent_repo_root: ${config.parent_repo_root}`);
1603
+ }
1604
+ lines.push("");
1605
+ return lines.join("\n");
1606
+ }
2179
1607
  async function initProject(targetPath, options, opts) {
2180
1608
  if (!isGitRepo(targetPath)) {
2181
1609
  throw new Error('AIGILE requires a git repository. Run "git init" first.');
@@ -2225,35 +1653,47 @@ async function initProject(targetPath, options, opts) {
2225
1653
  kind: options.moduleKind ?? "other",
2226
1654
  path: context.relativePath
2227
1655
  };
2228
- aigileConfig.parent_repo_root = "../".repeat(context.relativePath.split("/").filter((p) => p).length);
1656
+ aigileConfig.parent_repo_root = "../".repeat(
1657
+ context.relativePath.split("/").filter((p) => p).length
1658
+ );
2229
1659
  }
2230
- if (profile === "subrepo" && context.superprojectRoot) {
1660
+ if (profile === "minor-project" && context.superprojectRoot) {
2231
1661
  aigileConfig.parent_repo_root = "..";
2232
1662
  }
2233
1663
  const configYaml = generateConfigYaml(aigileConfig, projectKey, projectName);
2234
1664
  writeFileSync4(join4(aigileDir, "config.yaml"), configYaml, "utf-8");
2235
- let templatesResult = { written: 0, skipped: 0 };
1665
+ let templatesResult = { written: 0, skipped: 0, errors: [] };
2236
1666
  if (!options.skipTemplates) {
2237
- const templates = getTemplatesForProfile(profile, moduleName);
2238
- templatesResult = writeTemplates(aigileDir, templates);
1667
+ try {
1668
+ const templates = loadTemplates(profile);
1669
+ templatesResult = writeTemplates(targetPath, templates);
1670
+ } catch (err) {
1671
+ warning(
1672
+ `Could not load templates: ${err instanceof Error ? err.message : String(err)}`,
1673
+ opts
1674
+ );
1675
+ }
2239
1676
  }
2240
1677
  if (dbConfig.mode === "local") {
2241
1678
  registerProject(targetPath, (projectId) => {
2242
- const existingProject = queryOne(
2243
- "SELECT id FROM projects WHERE path = ?",
2244
- [targetPath]
2245
- );
1679
+ const existingProject = queryOne("SELECT id FROM projects WHERE path = ?", [
1680
+ targetPath
1681
+ ]);
2246
1682
  const id = existingProject?.id ?? generateId();
2247
1683
  if (existingProject) {
2248
- run(
2249
- `UPDATE projects SET key = ?, name = ?, updated_at = datetime('now') WHERE id = ?`,
2250
- [projectKey, projectName, id]
2251
- );
1684
+ run(`UPDATE projects SET key = ?, name = ?, updated_at = datetime('now') WHERE id = ?`, [
1685
+ projectKey,
1686
+ projectName,
1687
+ id
1688
+ ]);
2252
1689
  } else {
2253
- run(
2254
- `INSERT INTO projects (id, key, name, path, is_default) VALUES (?, ?, ?, ?, ?)`,
2255
- [id, projectKey, projectName, targetPath, 0]
2256
- );
1690
+ run(`INSERT INTO projects (id, key, name, path, is_default) VALUES (?, ?, ?, ?, ?)`, [
1691
+ id,
1692
+ projectKey,
1693
+ projectName,
1694
+ targetPath,
1695
+ 0
1696
+ ]);
2257
1697
  }
2258
1698
  const defaultProject = queryOne(
2259
1699
  "SELECT id FROM projects WHERE is_default = 1",
@@ -2267,17 +1707,19 @@ async function initProject(targetPath, options, opts) {
2267
1707
  }
2268
1708
  updateGitignore(targetPath, opts);
2269
1709
  if (opts.json) {
2270
- console.log(JSON.stringify({
2271
- success: true,
2272
- project: {
2273
- key: projectKey,
2274
- name: projectName,
2275
- path: targetPath,
2276
- profile,
2277
- dbMode: dbConfig.mode
2278
- },
2279
- templates: templatesResult
2280
- }));
1710
+ console.log(
1711
+ JSON.stringify({
1712
+ success: true,
1713
+ project: {
1714
+ key: projectKey,
1715
+ name: projectName,
1716
+ path: targetPath,
1717
+ profile,
1718
+ dbMode: dbConfig.mode
1719
+ },
1720
+ templates: templatesResult
1721
+ })
1722
+ );
2281
1723
  } else {
2282
1724
  blank();
2283
1725
  success(`AIGILE initialized with ${profile} profile`, opts);
@@ -2293,11 +1735,14 @@ async function initProject(targetPath, options, opts) {
2293
1735
  header("Templates:", opts);
2294
1736
  console.log(` Written: ${templatesResult.written} files`);
2295
1737
  console.log(` Skipped: ${templatesResult.skipped} files (already exist)`);
1738
+ if (templatesResult.errors.length > 0) {
1739
+ console.log(` Errors: ${templatesResult.errors.length} files`);
1740
+ }
2296
1741
  }
2297
1742
  blank();
2298
1743
  header("Next steps:", opts);
2299
- if (profile === "full-repo" || profile === "subrepo") {
2300
- console.log(" 1. Fill in 00_DOCS/00_vision/01_mission-vision.md");
1744
+ if (profile === "full-repo" || profile === "minor-project") {
1745
+ console.log(" 1. Fill in 00_DOCS/01_vision-foundations/01_mission-vision-values.md");
2301
1746
  console.log(' 2. Run "aigile sync scan" to index your files');
2302
1747
  console.log(' 3. Run "aigile daemon install && aigile daemon start" for auto-sync');
2303
1748
  } else {
@@ -2317,7 +1762,7 @@ function updateGitignore(repoPath, opts) {
2317
1762
  info('No .gitignore found. Consider adding ".aigile/" to ignore local config.', opts);
2318
1763
  return;
2319
1764
  }
2320
- const content = readFileSync3(gitignorePath, "utf-8");
1765
+ const content = readFileSync4(gitignorePath, "utf-8");
2321
1766
  const lines = content.split("\n");
2322
1767
  const hasPattern = lines.some((line) => {
2323
1768
  const trimmed = line.trim();
@@ -2399,7 +1844,7 @@ projectCommand.command("list").alias("ls").description("List all registered proj
2399
1844
  console.log(" \u2713 = valid path, \u2717 = missing/invalid path");
2400
1845
  if (invalidCount > 0) {
2401
1846
  blank();
2402
- warning2(`${invalidCount} project(s) have invalid paths. Run "aigile project cleanup" to remove.`, opts);
1847
+ warning(`${invalidCount} project(s) have invalid paths. Run "aigile project cleanup" to remove.`, opts);
2403
1848
  }
2404
1849
  });
2405
1850
  projectCommand.command("show").argument("[key]", "Project key (uses default if not specified)").description("Show project details").action((key) => {
@@ -2808,7 +2253,7 @@ epicCommand.command("delete").alias("rm").argument("<key>", "Epic key").option("
2808
2253
  (SELECT id FROM user_stories WHERE epic_id = ?)`,
2809
2254
  [epic.id]
2810
2255
  );
2811
- warning2(
2256
+ warning(
2812
2257
  `Deleting ${childCount.count} child story(s) and ${taskCount?.count || 0} task(s)`,
2813
2258
  opts
2814
2259
  );
@@ -2818,7 +2263,7 @@ epicCommand.command("delete").alias("rm").argument("<key>", "Epic key").option("
2818
2263
  );
2819
2264
  run("DELETE FROM user_stories WHERE epic_id = ?", [epic.id]);
2820
2265
  } else {
2821
- warning2(`Orphaning ${childCount.count} child story(s)`, opts);
2266
+ warning(`Orphaning ${childCount.count} child story(s)`, opts);
2822
2267
  run("UPDATE user_stories SET epic_id = NULL WHERE epic_id = ?", [epic.id]);
2823
2268
  }
2824
2269
  }
@@ -3017,10 +2462,10 @@ storyCommand.command("delete").alias("rm").argument("<key>", "Story key").option
3017
2462
  process.exit(1);
3018
2463
  }
3019
2464
  if (options.cascade) {
3020
- warning2(`Deleting ${childCount.count} child task(s)`, opts);
2465
+ warning(`Deleting ${childCount.count} child task(s)`, opts);
3021
2466
  run("DELETE FROM tasks WHERE story_id = ?", [story.id]);
3022
2467
  } else {
3023
- warning2(`Orphaning ${childCount.count} child task(s)`, opts);
2468
+ warning(`Orphaning ${childCount.count} child task(s)`, opts);
3024
2469
  run("UPDATE tasks SET story_id = NULL WHERE story_id = ?", [story.id]);
3025
2470
  }
3026
2471
  }
@@ -3983,11 +3428,11 @@ init_config();
3983
3428
  // src/services/file-scanner.ts
3984
3429
  init_connection();
3985
3430
  import { createHash } from "crypto";
3986
- import { readFileSync as readFileSync5, statSync, readdirSync, existsSync as existsSync6 } from "fs";
3431
+ import { readFileSync as readFileSync6, statSync as statSync2, readdirSync as readdirSync2, existsSync as existsSync6 } from "fs";
3987
3432
  import { join as join6, relative as relative2, extname } from "path";
3988
3433
 
3989
3434
  // src/services/frontmatter-parser.ts
3990
- import { readFileSync as readFileSync4 } from "fs";
3435
+ import { readFileSync as readFileSync5 } from "fs";
3991
3436
  import { parse as parseYaml2 } from "yaml";
3992
3437
  var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
3993
3438
  function extractFrontmatter(content) {
@@ -4009,9 +3454,11 @@ function extractFrontmatter(content) {
4009
3454
  tldr: data2?.tldr,
4010
3455
  modules: data2?.modules,
4011
3456
  dependencies: data2?.dependencies,
3457
+ related: data2?.related,
4012
3458
  code_refs: data2?.code_refs,
4013
3459
  authors: data2?.authors,
4014
- title: data2?.title
3460
+ title: data2?.title,
3461
+ exception: data2?.exception
4015
3462
  };
4016
3463
  }
4017
3464
  if (metadata.modules && !Array.isArray(metadata.modules)) {
@@ -4020,6 +3467,9 @@ function extractFrontmatter(content) {
4020
3467
  if (metadata.dependencies && !Array.isArray(metadata.dependencies)) {
4021
3468
  metadata.dependencies = [String(metadata.dependencies)];
4022
3469
  }
3470
+ if (metadata.related && !Array.isArray(metadata.related)) {
3471
+ metadata.related = [String(metadata.related)];
3472
+ }
4023
3473
  if (metadata.code_refs && !Array.isArray(metadata.code_refs)) {
4024
3474
  metadata.code_refs = [String(metadata.code_refs)];
4025
3475
  }
@@ -4039,7 +3489,7 @@ function extractFrontmatter(content) {
4039
3489
  }
4040
3490
  function parseFrontmatterFromFile(filePath) {
4041
3491
  try {
4042
- const content = readFileSync4(filePath, "utf-8");
3492
+ const content = readFileSync5(filePath, "utf-8");
4043
3493
  return extractFrontmatter(content);
4044
3494
  } catch {
4045
3495
  return null;
@@ -4068,9 +3518,15 @@ function serializeMetadata(metadata) {
4068
3518
  if (metadata.dependencies && metadata.dependencies.length > 0) {
4069
3519
  lines.push(` dependencies: [${metadata.dependencies.join(", ")}]`);
4070
3520
  }
3521
+ if (metadata.related && metadata.related.length > 0) {
3522
+ lines.push(` related: [${metadata.related.join(", ")}]`);
3523
+ }
4071
3524
  if (metadata.code_refs && metadata.code_refs.length > 0) {
4072
3525
  lines.push(` code_refs: [${metadata.code_refs.join(", ")}]`);
4073
3526
  }
3527
+ if (metadata.exception) {
3528
+ lines.push(` exception: "${metadata.exception.replace(/"/g, '\\"')}"`);
3529
+ }
4074
3530
  lines.push("---");
4075
3531
  return lines.join("\n");
4076
3532
  }
@@ -4092,22 +3548,18 @@ function updateFrontmatterContent(content, updates) {
4092
3548
  ...updates
4093
3549
  };
4094
3550
  if (updates.dependencies && existing.metadata.dependencies) {
4095
- merged.dependencies = [.../* @__PURE__ */ new Set([
4096
- ...existing.metadata.dependencies,
4097
- ...updates.dependencies
4098
- ])];
3551
+ merged.dependencies = [
3552
+ .../* @__PURE__ */ new Set([...existing.metadata.dependencies, ...updates.dependencies])
3553
+ ];
4099
3554
  }
4100
3555
  if (updates.modules && existing.metadata.modules) {
4101
- merged.modules = [.../* @__PURE__ */ new Set([
4102
- ...existing.metadata.modules,
4103
- ...updates.modules
4104
- ])];
3556
+ merged.modules = [.../* @__PURE__ */ new Set([...existing.metadata.modules, ...updates.modules])];
3557
+ }
3558
+ if (updates.related && existing.metadata.related) {
3559
+ merged.related = [.../* @__PURE__ */ new Set([...existing.metadata.related, ...updates.related])];
4105
3560
  }
4106
3561
  if (updates.code_refs && existing.metadata.code_refs) {
4107
- merged.code_refs = [.../* @__PURE__ */ new Set([
4108
- ...existing.metadata.code_refs,
4109
- ...updates.code_refs
4110
- ])];
3562
+ merged.code_refs = [.../* @__PURE__ */ new Set([...existing.metadata.code_refs, ...updates.code_refs])];
4111
3563
  }
4112
3564
  const newFrontmatter = serializeMetadata(merged);
4113
3565
  return content.replace(FRONTMATTER_REGEX, newFrontmatter + "\n\n");
@@ -4118,7 +3570,7 @@ init_monitoring_patterns();
4118
3570
  init_config();
4119
3571
  import picomatch from "picomatch";
4120
3572
  function computeFileHash(filePath) {
4121
- const content = readFileSync5(filePath);
3573
+ const content = readFileSync6(filePath);
4122
3574
  return createHash("sha256").update(content).digest("hex");
4123
3575
  }
4124
3576
  var FileClassifier = class {
@@ -4156,7 +3608,7 @@ function collectFiles(dir, rootDir, classifier, trackUnknown, files = []) {
4156
3608
  if (!existsSync6(dir)) {
4157
3609
  return files;
4158
3610
  }
4159
- const entries = readdirSync(dir, { withFileTypes: true });
3611
+ const entries = readdirSync2(dir, { withFileTypes: true });
4160
3612
  for (const entry of entries) {
4161
3613
  const fullPath = join6(dir, entry.name);
4162
3614
  const relativePath = relative2(rootDir, fullPath);
@@ -4178,7 +3630,7 @@ function collectFiles(dir, rootDir, classifier, trackUnknown, files = []) {
4178
3630
  continue;
4179
3631
  }
4180
3632
  try {
4181
- const stats = statSync(fullPath);
3633
+ const stats = statSync2(fullPath);
4182
3634
  const ext = extname(entry.name).toLowerCase().slice(1);
4183
3635
  const isBinary = isBinaryExtension(ext);
4184
3636
  const shouldComputeHash = category === "allow" || !isBinary && stats.size < 10 * 1024 * 1024;
@@ -4372,82 +3824,16 @@ function getDocuments(projectId, status) {
4372
3824
  params.push(status);
4373
3825
  }
4374
3826
  query += " ORDER BY path";
4375
- return queryAll(query, params);
4376
- }
4377
- function getDocumentsByMetaStatus(projectId, metaStatus) {
4378
- return queryAll(
4379
- `SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
4380
- has_frontmatter, meta_status, meta_version, meta_tldr, meta_title,
4381
- meta_modules, meta_dependencies, meta_code_refs, meta_authors
4382
- FROM documents
4383
- WHERE project_id = ? AND meta_status = ? AND status != 'deleted'
4384
- ORDER BY path`,
4385
- [projectId, metaStatus]
4386
- );
4387
- }
4388
- function getDocumentsByModule(projectId, module) {
4389
- return queryAll(
4390
- `SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
4391
- has_frontmatter, meta_status, meta_version, meta_tldr, meta_title,
4392
- meta_modules, meta_dependencies, meta_code_refs, meta_authors
4393
- FROM documents
4394
- WHERE project_id = ? AND meta_modules LIKE ? AND status != 'deleted'
4395
- ORDER BY path`,
4396
- [projectId, `%"${module}"%`]
4397
- );
4398
- }
4399
- function getDocumentsWithFrontmatter(projectId) {
4400
- return queryAll(
4401
- `SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
4402
- has_frontmatter, meta_status, meta_version, meta_tldr, meta_title,
4403
- meta_modules, meta_dependencies, meta_code_refs, meta_authors
4404
- FROM documents
4405
- WHERE project_id = ? AND has_frontmatter = 1 AND status != 'deleted'
4406
- ORDER BY path`,
4407
- [projectId]
4408
- );
4409
- }
4410
- function getDocumentsWithoutFrontmatter(projectId) {
4411
- return queryAll(
4412
- `SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
4413
- has_frontmatter, meta_status, meta_version, meta_tldr, meta_title,
4414
- meta_modules, meta_dependencies, meta_code_refs, meta_authors
4415
- FROM documents
4416
- WHERE project_id = ? AND (has_frontmatter = 0 OR has_frontmatter IS NULL) AND status != 'deleted'
4417
- ORDER BY path`,
4418
- [projectId]
4419
- );
4420
- }
4421
- function getDocumentByPath(projectId, filePath) {
4422
- return queryOne(
4423
- `SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
4424
- has_frontmatter, meta_status, meta_version, meta_tldr, meta_title,
4425
- meta_modules, meta_dependencies, meta_code_refs, meta_authors
4426
- FROM documents
4427
- WHERE project_id = ? AND path = ?`,
4428
- [projectId, filePath]
4429
- );
4430
- }
4431
- function getTemplateDocuments(projectId) {
4432
- return queryAll(
4433
- `SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
4434
- has_frontmatter, meta_status, meta_version, meta_tldr, meta_title,
4435
- meta_modules, meta_dependencies, meta_code_refs, meta_authors
4436
- FROM documents
4437
- WHERE project_id = ? AND meta_status = 'TEMPLATE' AND status != 'deleted'
4438
- ORDER BY path`,
4439
- [projectId]
4440
- );
3827
+ return queryAll(query, params);
4441
3828
  }
4442
- function searchDocumentsByTldr(projectId, searchTerm) {
4443
- return queryAll(
3829
+ function getDocumentByPath(projectId, filePath) {
3830
+ return queryOne(
4444
3831
  `SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
4445
3832
  has_frontmatter, meta_status, meta_version, meta_tldr, meta_title,
4446
3833
  meta_modules, meta_dependencies, meta_code_refs, meta_authors
4447
3834
  FROM documents
4448
- WHERE project_id = ? AND meta_tldr LIKE ? AND status != 'deleted'
4449
- ORDER BY path`,
4450
- [projectId, `%${searchTerm}%`]
3835
+ WHERE project_id = ? AND path = ?`,
3836
+ [projectId, filePath]
4451
3837
  );
4452
3838
  }
4453
3839
  function getUnanalyzedDocuments(projectId, limit, offset) {
@@ -4673,7 +4059,7 @@ function trackShadowFile(projectId, projectPath, filePath) {
4673
4059
  return getDocumentWithAnalysis(projectId, filePath) ?? null;
4674
4060
  }
4675
4061
  try {
4676
- const stats = statSync(fullPath);
4062
+ const stats = statSync2(fullPath);
4677
4063
  const hash = computeFileHash(fullPath);
4678
4064
  const filename = filePath.split("/").pop() ?? filePath;
4679
4065
  const ext = extname(filename).slice(1);
@@ -4694,11 +4080,11 @@ function trackShadowFile(projectId, projectPath, filePath) {
4694
4080
 
4695
4081
  // src/services/comment-parser.ts
4696
4082
  init_connection();
4697
- import { readFileSync as readFileSync6 } from "fs";
4083
+ import { readFileSync as readFileSync7 } from "fs";
4698
4084
  var USER_COMMENT_PATTERN = /\[\[!\s*([\s\S]*?)\s*\]\]/g;
4699
4085
  var AI_COMMENT_PATTERN = /\[\{!\s*([\s\S]*?)\s*\}]/g;
4700
4086
  function parseComments(filePath) {
4701
- const content = readFileSync6(filePath, "utf-8");
4087
+ const content = readFileSync7(filePath, "utf-8");
4702
4088
  const lines = content.split("\n");
4703
4089
  const comments = [];
4704
4090
  const positionToLine = [];
@@ -5059,10 +4445,9 @@ function startSession(projectId, name) {
5059
4445
  [projectId, "active"]
5060
4446
  );
5061
4447
  if (existing) {
5062
- run(
5063
- `UPDATE sessions SET status = 'abandoned', ended_at = datetime('now') WHERE id = ?`,
5064
- [existing.id]
5065
- );
4448
+ run(`UPDATE sessions SET status = 'abandoned', ended_at = datetime('now') WHERE id = ?`, [
4449
+ existing.id
4450
+ ]);
5066
4451
  }
5067
4452
  const sessionId = generateId();
5068
4453
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5086,20 +4471,22 @@ function startSession(projectId, name) {
5086
4471
  }
5087
4472
  function endSession(projectId, summary) {
5088
4473
  const session = queryOne(
5089
- "SELECT id, started_at, entities_modified, files_modified FROM sessions WHERE project_id = ? AND status = ?",
4474
+ "SELECT id, name, started_at, entities_modified, files_modified FROM sessions WHERE project_id = ? AND status = ?",
5090
4475
  [projectId, "active"]
5091
4476
  );
5092
4477
  if (!session) {
5093
4478
  return null;
5094
4479
  }
5095
4480
  const now = (/* @__PURE__ */ new Date()).toISOString();
5096
- run(
5097
- `UPDATE sessions SET status = 'completed', ended_at = ?, summary = ? WHERE id = ?`,
5098
- [now, summary ?? null, session.id]
5099
- );
4481
+ run(`UPDATE sessions SET status = 'completed', ended_at = ?, summary = ? WHERE id = ?`, [
4482
+ now,
4483
+ summary ?? null,
4484
+ session.id
4485
+ ]);
5100
4486
  return {
5101
4487
  id: session.id,
5102
4488
  projectId,
4489
+ name: session.name,
5103
4490
  startedAt: session.started_at,
5104
4491
  endedAt: now,
5105
4492
  summary: summary ?? null,
@@ -5109,16 +4496,14 @@ function endSession(projectId, summary) {
5109
4496
  };
5110
4497
  }
5111
4498
  function getActiveSession(projectId) {
5112
- const session = queryOne(
5113
- "SELECT * FROM sessions WHERE project_id = ? AND status = ?",
5114
- [projectId, "active"]
5115
- );
4499
+ const session = queryOne("SELECT * FROM sessions WHERE project_id = ? AND status = ?", [projectId, "active"]);
5116
4500
  if (!session) {
5117
4501
  return null;
5118
4502
  }
5119
4503
  return {
5120
4504
  id: session.id,
5121
4505
  projectId: session.project_id,
4506
+ name: session.name,
5122
4507
  startedAt: session.started_at,
5123
4508
  endedAt: session.ended_at,
5124
4509
  summary: session.summary,
@@ -5128,10 +4513,7 @@ function getActiveSession(projectId) {
5128
4513
  };
5129
4514
  }
5130
4515
  function getSessionByName(projectId, name) {
5131
- const session = queryOne(
5132
- "SELECT * FROM sessions WHERE project_id = ? AND name = ?",
5133
- [projectId, name]
5134
- );
4516
+ const session = queryOne("SELECT * FROM sessions WHERE project_id = ? AND name = ?", [projectId, name]);
5135
4517
  if (!session) {
5136
4518
  return null;
5137
4519
  }
@@ -5148,10 +4530,7 @@ function getSessionByName(projectId, name) {
5148
4530
  };
5149
4531
  }
5150
4532
  function resumeSession(projectId, sessionId) {
5151
- const session = queryOne(
5152
- "SELECT * FROM sessions WHERE id = ? AND project_id = ?",
5153
- [sessionId, projectId]
5154
- );
4533
+ const session = queryOne("SELECT * FROM sessions WHERE id = ? AND project_id = ?", [sessionId, projectId]);
5155
4534
  if (!session) {
5156
4535
  return null;
5157
4536
  }
@@ -5163,15 +4542,11 @@ function resumeSession(projectId, sessionId) {
5163
4542
  [projectId, "active", sessionId]
5164
4543
  );
5165
4544
  if (existing) {
5166
- run(
5167
- `UPDATE sessions SET status = 'abandoned', ended_at = datetime('now') WHERE id = ?`,
5168
- [existing.id]
5169
- );
4545
+ run(`UPDATE sessions SET status = 'abandoned', ended_at = datetime('now') WHERE id = ?`, [
4546
+ existing.id
4547
+ ]);
5170
4548
  }
5171
- run(
5172
- `UPDATE sessions SET status = 'active', ended_at = NULL WHERE id = ?`,
5173
- [sessionId]
5174
- );
4549
+ run(`UPDATE sessions SET status = 'active', ended_at = NULL WHERE id = ?`, [sessionId]);
5175
4550
  return {
5176
4551
  id: session.id,
5177
4552
  projectId: session.project_id,
@@ -5249,6 +4624,7 @@ function getSession(sessionId) {
5249
4624
  return {
5250
4625
  id: session.id,
5251
4626
  projectId: session.project_id,
4627
+ name: session.name,
5252
4628
  startedAt: session.started_at,
5253
4629
  endedAt: session.ended_at,
5254
4630
  summary: session.summary,
@@ -8419,321 +7795,863 @@ uxJourneyCommand.command("delete").alias("rm").argument("<key>", "Journey key").
8419
7795
  // src/commands/doc.ts
8420
7796
  init_connection();
8421
7797
  import { Command as Command19 } from "commander";
8422
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
8423
- import { join as join8 } from "path";
8424
- init_config();
8425
- var docCommand = new Command19("doc").description("Document management and frontmatter operations");
8426
- function getProjectId(opts) {
8427
- const projectRoot = findProjectRoot();
8428
- if (!projectRoot) {
8429
- error('Not in an AIGILE project. Run "aigile init" first.', opts);
8430
- return null;
7798
+ import { existsSync as existsSync7 } from "fs";
7799
+ import { resolve as resolve3, relative as relative4 } from "path";
7800
+
7801
+ // src/services/dependency-graph.ts
7802
+ init_connection();
7803
+ import { resolve as resolve2, relative as relative3, dirname as dirname3 } from "path";
7804
+ function buildDependencyGraph(projectId) {
7805
+ const docs = queryAll(
7806
+ `SELECT id, path, meta_status, last_approved_at, last_modified_at, meta_dependencies
7807
+ FROM documents
7808
+ WHERE project_id = ?`,
7809
+ [projectId]
7810
+ );
7811
+ const graph = /* @__PURE__ */ new Map();
7812
+ for (const doc of docs) {
7813
+ const dependencies = doc.meta_dependencies ? JSON.parse(doc.meta_dependencies) : [];
7814
+ graph.set(doc.path, {
7815
+ id: doc.id,
7816
+ path: doc.path,
7817
+ status: doc.meta_status || "UNKNOWN",
7818
+ lastApprovedAt: doc.last_approved_at || void 0,
7819
+ lastModifiedAt: doc.last_modified_at || void 0,
7820
+ dependencies,
7821
+ dependents: []
7822
+ });
8431
7823
  }
8432
- const config = loadProjectConfig(projectRoot);
8433
- if (!config) {
8434
- error("Could not load project config.", opts);
8435
- return null;
7824
+ for (const [path, node] of graph.entries()) {
7825
+ for (const depPath of node.dependencies) {
7826
+ const absoluteDep = resolveDocPath(path, depPath);
7827
+ const depNode = graph.get(absoluteDep);
7828
+ if (depNode) {
7829
+ depNode.dependents.push(path);
7830
+ }
7831
+ }
8436
7832
  }
8437
- const project = queryOne("SELECT id FROM projects WHERE key = ?", [config.project.key]);
8438
- if (!project) {
8439
- error(`Project "${config.project.key}" not found in database.`, opts);
8440
- return null;
7833
+ return graph;
7834
+ }
7835
+ function resolveDocPath(fromPath, relativePath) {
7836
+ if (relativePath.startsWith("./") || relativePath.startsWith("../")) {
7837
+ const fromDir = dirname3(fromPath);
7838
+ const resolved = resolve2(fromDir, relativePath);
7839
+ return relative3(".", resolved);
8441
7840
  }
8442
- return { projectId: project.id, projectRoot };
7841
+ return relativePath;
8443
7842
  }
8444
- function formatDocForDisplay(doc) {
7843
+ function checkStaleness(docPath, graph) {
7844
+ const doc = graph.get(docPath);
7845
+ if (!doc) {
7846
+ return {
7847
+ isStale: false,
7848
+ staleDependencies: []
7849
+ };
7850
+ }
7851
+ if (!doc.lastApprovedAt) {
7852
+ return {
7853
+ isStale: false,
7854
+ staleDependencies: []
7855
+ };
7856
+ }
7857
+ const staleDeps = [];
7858
+ for (const depRelPath of doc.dependencies) {
7859
+ const depAbsPath = resolveDocPath(docPath, depRelPath);
7860
+ const depNode = graph.get(depAbsPath);
7861
+ if (!depNode || !depNode.lastModifiedAt) {
7862
+ continue;
7863
+ }
7864
+ if (depNode.lastModifiedAt > doc.lastApprovedAt) {
7865
+ staleDeps.push({
7866
+ path: depRelPath,
7867
+ lastModified: depNode.lastModifiedAt,
7868
+ parentApproved: doc.lastApprovedAt
7869
+ });
7870
+ }
7871
+ }
7872
+ const isStale = staleDeps.length > 0;
8445
7873
  return {
8446
- path: doc.path,
8447
- filename: doc.filename,
8448
- extension: doc.extension,
8449
- sync_status: doc.status,
8450
- has_frontmatter: doc.has_frontmatter ? "Yes" : "No",
8451
- meta_status: doc.meta_status ?? "-",
8452
- meta_version: doc.meta_version ?? "-",
8453
- meta_tldr: doc.meta_tldr ?? "-",
8454
- meta_modules: doc.meta_modules ? JSON.parse(doc.meta_modules).join(", ") : "-",
8455
- meta_dependencies: doc.meta_dependencies ? JSON.parse(doc.meta_dependencies).join(", ") : "-"
7874
+ isStale,
7875
+ reason: isStale ? `${staleDeps.length} dependencies modified since approval` : void 0,
7876
+ staleDependencies: staleDeps
8456
7877
  };
8457
7878
  }
8458
- docCommand.command("list").alias("ls").option("--meta-status <status>", "Filter by frontmatter status (DRAFT, IN-REVIEW, APPROVED, TEMPLATE)").option("--module <module>", "Filter by module").option("--with-frontmatter", "Only show documents with frontmatter").option("--without-frontmatter", "Only show documents without frontmatter (shadow mode)").option("--templates", "Only show TEMPLATE documents").option("--search <term>", "Search by tldr content").description("List documents with metadata filters").action((options) => {
8459
- const opts = getOutputOptions(docCommand);
8460
- const ctx = getProjectId(opts);
8461
- if (!ctx) {
7879
+ function checkCascadeApproval(docPath, graph) {
7880
+ const doc = graph.get(docPath);
7881
+ if (!doc) {
7882
+ return {
7883
+ canCascade: false,
7884
+ affectedDocs: [],
7885
+ blockedBy: []
7886
+ };
7887
+ }
7888
+ const canApprove = [];
7889
+ const blocked = [];
7890
+ for (const dependentPath of doc.dependents) {
7891
+ const dependent = graph.get(dependentPath);
7892
+ if (!dependent) {
7893
+ continue;
7894
+ }
7895
+ const allDepsApproved = dependent.dependencies.every((depRelPath) => {
7896
+ const depAbsPath = resolveDocPath(dependentPath, depRelPath);
7897
+ const depNode = graph.get(depAbsPath);
7898
+ if (depAbsPath === docPath) {
7899
+ return true;
7900
+ }
7901
+ return depNode && depNode.status === "APPROVED";
7902
+ });
7903
+ if (allDepsApproved) {
7904
+ canApprove.push(dependentPath);
7905
+ } else {
7906
+ blocked.push(dependentPath);
7907
+ }
7908
+ }
7909
+ return {
7910
+ canCascade: canApprove.length > 0,
7911
+ affectedDocs: canApprove,
7912
+ blockedBy: blocked
7913
+ };
7914
+ }
7915
+ function getTopologicalOrder(graph) {
7916
+ const visited = /* @__PURE__ */ new Set();
7917
+ const result = [];
7918
+ function visit(path) {
7919
+ if (visited.has(path)) {
7920
+ return;
7921
+ }
7922
+ visited.add(path);
7923
+ const node = graph.get(path);
7924
+ if (!node) {
7925
+ return;
7926
+ }
7927
+ for (const depRelPath of node.dependencies) {
7928
+ const depAbsPath = resolveDocPath(path, depRelPath);
7929
+ visit(depAbsPath);
7930
+ }
7931
+ result.push(path);
7932
+ }
7933
+ for (const path of graph.keys()) {
7934
+ visit(path);
7935
+ }
7936
+ return result;
7937
+ }
7938
+ function getFoundationalDocs(graph) {
7939
+ const foundational = [];
7940
+ for (const [path, node] of graph.entries()) {
7941
+ if (node.dependencies.length === 0) {
7942
+ foundational.push(path);
7943
+ }
7944
+ }
7945
+ return foundational;
7946
+ }
7947
+ function getDirectDependents(docPath, graph) {
7948
+ const doc = graph.get(docPath);
7949
+ return doc ? [...doc.dependents] : [];
7950
+ }
7951
+ function getTransitiveDependents(docPath, graph) {
7952
+ const visited = /* @__PURE__ */ new Set();
7953
+ const result = [];
7954
+ function visit(path) {
7955
+ if (visited.has(path)) {
7956
+ return;
7957
+ }
7958
+ visited.add(path);
7959
+ const node = graph.get(path);
7960
+ if (!node) {
7961
+ return;
7962
+ }
7963
+ for (const dependentPath of node.dependents) {
7964
+ result.push(dependentPath);
7965
+ visit(dependentPath);
7966
+ }
7967
+ }
7968
+ visit(docPath);
7969
+ return result;
7970
+ }
7971
+
7972
+ // src/commands/doc.ts
7973
+ init_config();
7974
+ init_config();
7975
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync8 } from "fs";
7976
+ async function ensureProjectContext() {
7977
+ const projectPath = findProjectRoot();
7978
+ if (!projectPath) {
7979
+ throw new Error('Not in an AIGILE project. Run "aigile init" first.');
7980
+ }
7981
+ const config = loadProjectConfig(projectPath);
7982
+ if (!config) {
7983
+ throw new Error("Could not load project config.");
7984
+ }
7985
+ const project = queryOne("SELECT id FROM projects WHERE key = ?", [
7986
+ config.project.key
7987
+ ]);
7988
+ if (!project) {
7989
+ throw new Error(`Project "${config.project.key}" not found in database.`);
7990
+ }
7991
+ return { projectId: project.id, projectPath, projectKey: config.project.key };
7992
+ }
7993
+ var integrityCommand = new Command19("integrity").description("Check document integrity and dependencies").option("--fix", "Auto-fix missing dependencies in frontmatter").option("--all", "Show all documents, not just problems").action(async (options) => {
7994
+ const opts = getOutputOptions(integrityCommand);
7995
+ const cwd = process.cwd();
7996
+ try {
7997
+ await runIntegrityCheck(cwd, options, opts);
7998
+ } catch (err) {
7999
+ error(err instanceof Error ? err.message : String(err), opts);
8462
8000
  process.exit(1);
8463
8001
  }
8464
- let documents = [];
8465
- if (options.metaStatus) {
8466
- documents = getDocumentsByMetaStatus(ctx.projectId, options.metaStatus);
8467
- } else if (options.module) {
8468
- documents = getDocumentsByModule(ctx.projectId, options.module);
8469
- } else if (options.withFrontmatter) {
8470
- documents = getDocumentsWithFrontmatter(ctx.projectId);
8471
- } else if (options.withoutFrontmatter) {
8472
- documents = getDocumentsWithoutFrontmatter(ctx.projectId);
8473
- } else if (options.templates) {
8474
- documents = getTemplateDocuments(ctx.projectId);
8475
- } else if (options.search) {
8476
- documents = searchDocumentsByTldr(ctx.projectId, options.search);
8477
- } else {
8478
- documents = getDocumentsWithFrontmatter(ctx.projectId);
8002
+ });
8003
+ var approveCommand = new Command19("approve").description("Approve a document").argument("<file>", "File path to approve").option("--cascade", "Auto-approve dependents if all their dependencies are approved").action(async (filePath, options) => {
8004
+ const opts = getOutputOptions(approveCommand);
8005
+ const cwd = process.cwd();
8006
+ try {
8007
+ await approveDocument(cwd, filePath, options, opts);
8008
+ } catch (err) {
8009
+ error(err instanceof Error ? err.message : String(err), opts);
8010
+ process.exit(1);
8479
8011
  }
8480
- const displayDocs = documents.map(formatDocForDisplay);
8481
- data(
8482
- displayDocs,
8483
- [
8484
- { header: "Path", key: "path", width: 45 },
8485
- { header: "Status", key: "meta_status", width: 12 },
8486
- { header: "Version", key: "meta_version", width: 8 },
8487
- { header: "Modules", key: "meta_modules", width: 20 }
8488
- ],
8489
- opts
8490
- );
8491
8012
  });
8492
- docCommand.command("show <path>").description("Show document details including frontmatter metadata").action((filePath) => {
8493
- const opts = getOutputOptions(docCommand);
8494
- const ctx = getProjectId(opts);
8495
- if (!ctx) {
8013
+ var ackCommand = new Command19("ack").description("Acknowledge document changes (clears STALE)").argument("<file>", "File path to acknowledge").action(async (filePath) => {
8014
+ const opts = getOutputOptions(ackCommand);
8015
+ const cwd = process.cwd();
8016
+ try {
8017
+ await acknowledgeDocument(cwd, filePath, opts);
8018
+ } catch (err) {
8019
+ error(err instanceof Error ? err.message : String(err), opts);
8496
8020
  process.exit(1);
8497
8021
  }
8498
- const doc = getDocumentByPath(ctx.projectId, filePath);
8499
- if (!doc) {
8500
- error(`Document not found: ${filePath}`, opts);
8022
+ });
8023
+ var readyCommand = new Command19("ready").description("Mark document as ready for review").argument("<file>", "File path to mark ready").action(async (filePath) => {
8024
+ const opts = getOutputOptions(readyCommand);
8025
+ const cwd = process.cwd();
8026
+ try {
8027
+ await markReady(cwd, filePath, opts);
8028
+ } catch (err) {
8029
+ error(err instanceof Error ? err.message : String(err), opts);
8501
8030
  process.exit(1);
8502
8031
  }
8503
- const fullPath = join8(ctx.projectRoot, filePath);
8504
- const parsed = parseFrontmatterFromFile(fullPath);
8505
- if (opts.json) {
8506
- console.log(JSON.stringify({
8507
- success: true,
8508
- data: {
8509
- database: doc,
8510
- frontmatter: parsed ? {
8511
- raw: parsed.raw,
8512
- metadata: parsed.metadata
8513
- } : null
8514
- }
8515
- }));
8516
- } else {
8517
- const displayData = {
8518
- path: doc.path,
8519
- filename: doc.filename,
8520
- extension: doc.extension,
8521
- sync_status: doc.status,
8522
- size_bytes: doc.size_bytes,
8523
- last_scanned: doc.last_scanned_at,
8524
- has_frontmatter: doc.has_frontmatter ? "Yes" : "No"
8525
- };
8526
- if (parsed) {
8527
- displayData.meta_status = parsed.metadata.status ?? "-";
8528
- displayData.meta_version = parsed.metadata.version ?? "-";
8529
- displayData.meta_tldr = parsed.metadata.tldr ?? "-";
8530
- displayData.meta_title = parsed.metadata.title ?? "-";
8531
- displayData.meta_modules = parsed.metadata.modules?.join(", ") ?? "-";
8532
- displayData.meta_dependencies = parsed.metadata.dependencies?.join(", ") ?? "-";
8533
- displayData.meta_code_refs = parsed.metadata.code_refs?.join(", ") ?? "-";
8534
- displayData.meta_authors = parsed.metadata.authors?.join(", ") ?? "-";
8535
- }
8536
- details(
8537
- displayData,
8538
- [
8539
- { label: "Path", key: "path" },
8540
- { label: "Filename", key: "filename" },
8541
- { label: "Extension", key: "extension" },
8542
- { label: "Sync Status", key: "sync_status" },
8543
- { label: "Size (bytes)", key: "size_bytes" },
8544
- { label: "Last Scanned", key: "last_scanned" },
8545
- { label: "Has Frontmatter", key: "has_frontmatter" },
8546
- { label: "Meta Status", key: "meta_status" },
8547
- { label: "Meta Version", key: "meta_version" },
8548
- { label: "Meta Title", key: "meta_title" },
8549
- { label: "Meta TLDR", key: "meta_tldr" },
8550
- { label: "Meta Modules", key: "meta_modules" },
8551
- { label: "Meta Dependencies", key: "meta_dependencies" },
8552
- { label: "Meta Code Refs", key: "meta_code_refs" },
8553
- { label: "Meta Authors", key: "meta_authors" }
8554
- ],
8555
- opts
8556
- );
8032
+ });
8033
+ var reviewCommand = new Command19("review").description("Interactive document review (TUI)").action(async () => {
8034
+ const opts = getOutputOptions(reviewCommand);
8035
+ warning("Interactive review TUI not yet implemented", opts);
8036
+ info("Use 'aigile doc integrity' to see document status", opts);
8037
+ });
8038
+ var exceptionCommand = new Command19("exception").description("Mark document with structural exception").argument("<file>", "File path").argument("<reason>", "Reason for exception").action(async (filePath, reason) => {
8039
+ const opts = getOutputOptions(exceptionCommand);
8040
+ const cwd = process.cwd();
8041
+ try {
8042
+ await markException(cwd, filePath, reason, opts);
8043
+ } catch (err) {
8044
+ error(err instanceof Error ? err.message : String(err), opts);
8045
+ process.exit(1);
8557
8046
  }
8558
8047
  });
8559
- docCommand.command("update <path>").option("--status <status>", "Set metadata status (DRAFT, IN-REVIEW, APPROVED, TEMPLATE, PRODUCTION)").option("--version <version>", "Set metadata version").option("--tldr <tldr>", "Set one-sentence summary").option("--title <title>", "Set document title").option("--add-module <module>", "Add a module to the modules list").option("--add-dependency <dep>", "Add a dependency path").option("--add-code-ref <ref>", "Add a code reference path").option("--add-author <author>", "Add an author").option("--dry-run", "Show what would be changed without modifying file").description("Update frontmatter metadata in a document").action((filePath, options) => {
8560
- const opts = getOutputOptions(docCommand);
8561
- const ctx = getProjectId(opts);
8562
- if (!ctx) {
8048
+ var refactorCommand = new Command19("refactor").description("Mark document for refactoring").argument("<file>", "File path").action(async (filePath) => {
8049
+ const opts = getOutputOptions(refactorCommand);
8050
+ const cwd = process.cwd();
8051
+ try {
8052
+ await markRefactoring(cwd, filePath, opts);
8053
+ } catch (err) {
8054
+ error(err instanceof Error ? err.message : String(err), opts);
8563
8055
  process.exit(1);
8564
8056
  }
8565
- const fullPath = join8(ctx.projectRoot, filePath);
8566
- let content;
8057
+ });
8058
+ var archiveCommand = new Command19("archive").description("Archive a document").argument("<file>", "File path to archive").action(async (filePath) => {
8059
+ const opts = getOutputOptions(archiveCommand);
8060
+ const cwd = process.cwd();
8567
8061
  try {
8568
- content = readFileSync7(fullPath, "utf-8");
8569
- } catch {
8570
- error(`Could not read file: ${filePath}`, opts);
8062
+ await archiveDocument(cwd, filePath, opts);
8063
+ } catch (err) {
8064
+ error(err instanceof Error ? err.message : String(err), opts);
8571
8065
  process.exit(1);
8572
8066
  }
8573
- const updates = {};
8574
- if (options.status) {
8575
- updates.status = options.status;
8067
+ });
8068
+ async function runIntegrityCheck(cwd, options, opts) {
8069
+ const config = loadProjectConfig(cwd);
8070
+ if (!config?.project?.key) {
8071
+ throw new Error("AIGILE not initialized in this directory");
8072
+ }
8073
+ const projectId = config.project.key;
8074
+ const graph = buildDependencyGraph(projectId);
8075
+ blank();
8076
+ header("Document Integrity Check", opts);
8077
+ blank();
8078
+ const orderedDocs = getTopologicalOrder(graph);
8079
+ let issueCount = 0;
8080
+ const stale = [];
8081
+ const needsReview = [];
8082
+ const templates = [];
8083
+ for (const docPath of orderedDocs) {
8084
+ const node = graph.get(docPath);
8085
+ if (!node) continue;
8086
+ const stalenessCheck = checkStaleness(docPath, graph);
8087
+ const hasIssues = stalenessCheck.isStale || node.status === "NEEDS_REVIEW" || node.status === "TEMPLATE";
8088
+ if (!hasIssues && !options.all) {
8089
+ continue;
8090
+ }
8091
+ console.log(`\u{1F4C4} ${docPath}`);
8092
+ console.log(` Status: ${node.status}`);
8093
+ if (stalenessCheck.isStale) {
8094
+ console.log(` \u26A0\uFE0F STALE: ${stalenessCheck.reason}`);
8095
+ stale.push(docPath);
8096
+ issueCount++;
8097
+ }
8098
+ if (node.status === "NEEDS_REVIEW") {
8099
+ console.log(` \u{1F4DD} Needs review`);
8100
+ needsReview.push(docPath);
8101
+ issueCount++;
8102
+ }
8103
+ if (node.status === "TEMPLATE") {
8104
+ console.log(` \u{1F4CB} Template (needs filling)`);
8105
+ templates.push(docPath);
8106
+ issueCount++;
8107
+ }
8108
+ if (node.dependencies.length > 0) {
8109
+ console.log(` Dependencies: ${node.dependencies.join(", ")}`);
8110
+ }
8111
+ if (node.dependents.length > 0) {
8112
+ console.log(` Dependents: ${node.dependents.length} files`);
8113
+ }
8114
+ blank();
8115
+ }
8116
+ blank();
8117
+ header("Summary", opts);
8118
+ console.log(` Total documents: ${orderedDocs.length}`);
8119
+ console.log(` Issues found: ${issueCount}`);
8120
+ if (stale.length > 0) {
8121
+ console.log(` - Stale: ${stale.length}`);
8122
+ }
8123
+ if (needsReview.length > 0) {
8124
+ console.log(` - Needs review: ${needsReview.length}`);
8125
+ }
8126
+ if (templates.length > 0) {
8127
+ console.log(` - Templates: ${templates.length}`);
8128
+ }
8129
+ blank();
8130
+ if (issueCount === 0) {
8131
+ success("\u2713 All documents are up to date", opts);
8132
+ } else {
8133
+ info("Run 'aigile doc approve <file>' to approve documents", opts);
8134
+ info("Run 'aigile doc ack <file>' to acknowledge changes", opts);
8135
+ }
8136
+ }
8137
+ async function approveDocument(cwd, filePath, options, opts) {
8138
+ const config = loadProjectConfig(cwd);
8139
+ if (!config?.project?.key) {
8140
+ throw new Error("AIGILE not initialized in this directory");
8141
+ }
8142
+ const absolutePath = resolve3(cwd, filePath);
8143
+ if (!existsSync7(absolutePath)) {
8144
+ throw new Error(`File not found: ${filePath}`);
8145
+ }
8146
+ const projectId = config.project.key;
8147
+ const relativePath = relative4(cwd, absolutePath);
8148
+ const doc = queryOne(
8149
+ `SELECT id, meta_status FROM documents WHERE project_id = ? AND path = ?`,
8150
+ [projectId, relativePath]
8151
+ );
8152
+ if (!doc) {
8153
+ throw new Error(`Document not found in database: ${filePath}`);
8154
+ }
8155
+ const content = readFileSync8(absolutePath, "utf-8");
8156
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8157
+ const updatedContent = updateFrontmatterContent(content, {
8158
+ status: "APPROVED",
8159
+ reviewed: now
8160
+ });
8161
+ writeFileSync5(absolutePath, updatedContent, "utf-8");
8162
+ run(`UPDATE documents SET meta_status = ?, last_approved_at = ?, reviewed_at = ? WHERE id = ?`, [
8163
+ "APPROVED",
8164
+ now,
8165
+ now,
8166
+ doc.id
8167
+ ]);
8168
+ success(`\u2713 Approved: ${filePath}`, opts);
8169
+ if (options.cascade) {
8170
+ const graph = buildDependencyGraph(projectId);
8171
+ const cascadeResult = checkCascadeApproval(relativePath, graph);
8172
+ if (cascadeResult.canCascade && cascadeResult.affectedDocs.length > 0) {
8173
+ blank();
8174
+ info(`Cascade approving ${cascadeResult.affectedDocs.length} dependents...`, opts);
8175
+ for (const depPath of cascadeResult.affectedDocs) {
8176
+ const depAbsolute = resolve3(cwd, depPath);
8177
+ const depContent = readFileSync8(depAbsolute, "utf-8");
8178
+ const updatedDep = updateFrontmatterContent(depContent, {
8179
+ status: "APPROVED",
8180
+ reviewed: now
8181
+ });
8182
+ writeFileSync5(depAbsolute, updatedDep, "utf-8");
8183
+ const depDoc = queryOne(
8184
+ `SELECT id FROM documents WHERE project_id = ? AND path = ?`,
8185
+ [projectId, depPath]
8186
+ );
8187
+ if (depDoc) {
8188
+ run(
8189
+ `UPDATE documents SET meta_status = ?, last_approved_at = ?, reviewed_at = ? WHERE id = ?`,
8190
+ ["APPROVED", now, now, depDoc.id]
8191
+ );
8192
+ }
8193
+ success(` \u2713 ${depPath}`, opts);
8194
+ }
8195
+ }
8196
+ }
8197
+ }
8198
+ async function acknowledgeDocument(cwd, filePath, opts) {
8199
+ const config = loadProjectConfig(cwd);
8200
+ if (!config?.project?.key) {
8201
+ throw new Error("AIGILE not initialized in this directory");
8202
+ }
8203
+ const absolutePath = resolve3(cwd, filePath);
8204
+ if (!existsSync7(absolutePath)) {
8205
+ throw new Error(`File not found: ${filePath}`);
8206
+ }
8207
+ const projectId = config.project.key;
8208
+ const relativePath = relative4(cwd, absolutePath);
8209
+ const doc = queryOne(
8210
+ `SELECT id FROM documents WHERE project_id = ? AND path = ?`,
8211
+ [projectId, relativePath]
8212
+ );
8213
+ if (!doc) {
8214
+ throw new Error(`Document not found in database: ${filePath}`);
8215
+ }
8216
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8217
+ run(`UPDATE documents SET last_approved_at = ? WHERE id = ?`, [now, doc.id]);
8218
+ success(`\u2713 Acknowledged: ${filePath}`, opts);
8219
+ info("Document is no longer marked as stale", opts);
8220
+ }
8221
+ async function markReady(cwd, filePath, opts) {
8222
+ const config = loadProjectConfig(cwd);
8223
+ if (!config?.project?.key) {
8224
+ throw new Error("AIGILE not initialized in this directory");
8225
+ }
8226
+ const absolutePath = resolve3(cwd, filePath);
8227
+ if (!existsSync7(absolutePath)) {
8228
+ throw new Error(`File not found: ${filePath}`);
8229
+ }
8230
+ const projectId = config.project.key;
8231
+ const relativePath = relative4(cwd, absolutePath);
8232
+ const content = readFileSync8(absolutePath, "utf-8");
8233
+ const updatedContent = updateFrontmatterContent(content, {
8234
+ status: "NEEDS_REVIEW"
8235
+ });
8236
+ writeFileSync5(absolutePath, updatedContent, "utf-8");
8237
+ const doc = queryOne(
8238
+ `SELECT id FROM documents WHERE project_id = ? AND path = ?`,
8239
+ [projectId, relativePath]
8240
+ );
8241
+ if (doc) {
8242
+ run(`UPDATE documents SET meta_status = ?, needs_review = 1 WHERE id = ?`, [
8243
+ "NEEDS_REVIEW",
8244
+ doc.id
8245
+ ]);
8246
+ }
8247
+ success(`\u2713 Marked ready for review: ${filePath}`, opts);
8248
+ }
8249
+ async function markException(cwd, filePath, reason, opts) {
8250
+ const config = loadProjectConfig(cwd);
8251
+ if (!config?.project?.key) {
8252
+ throw new Error("AIGILE not initialized in this directory");
8576
8253
  }
8577
- if (options.version) {
8578
- updates.version = options.version;
8254
+ const absolutePath = resolve3(cwd, filePath);
8255
+ if (!existsSync7(absolutePath)) {
8256
+ throw new Error(`File not found: ${filePath}`);
8579
8257
  }
8580
- if (options.tldr) {
8581
- updates.tldr = options.tldr;
8258
+ const projectId = config.project.key;
8259
+ const relativePath = relative4(cwd, absolutePath);
8260
+ const content = readFileSync8(absolutePath, "utf-8");
8261
+ const updatedContent = updateFrontmatterContent(content, {
8262
+ exception: reason
8263
+ });
8264
+ writeFileSync5(absolutePath, updatedContent, "utf-8");
8265
+ const doc = queryOne(
8266
+ `SELECT id FROM documents WHERE project_id = ? AND path = ?`,
8267
+ [projectId, relativePath]
8268
+ );
8269
+ if (doc) {
8270
+ run(`UPDATE documents SET meta_exception = ? WHERE id = ?`, [reason, doc.id]);
8271
+ }
8272
+ success(`\u2713 Marked exception: ${filePath}`, opts);
8273
+ info(`Reason: ${reason}`, opts);
8274
+ }
8275
+ async function markRefactoring(cwd, filePath, opts) {
8276
+ const config = loadProjectConfig(cwd);
8277
+ if (!config?.project?.key) {
8278
+ throw new Error("AIGILE not initialized in this directory");
8279
+ }
8280
+ const absolutePath = resolve3(cwd, filePath);
8281
+ if (!existsSync7(absolutePath)) {
8282
+ throw new Error(`File not found: ${filePath}`);
8582
8283
  }
8583
- if (options.title) {
8584
- updates.title = options.title;
8284
+ const projectId = config.project.key;
8285
+ const relativePath = relative4(cwd, absolutePath);
8286
+ const doc = queryOne(
8287
+ `SELECT id FROM documents WHERE project_id = ? AND path = ?`,
8288
+ [projectId, relativePath]
8289
+ );
8290
+ if (!doc) {
8291
+ throw new Error(`Document not found in database: ${filePath}`);
8292
+ }
8293
+ run(`UPDATE documents SET monitoring_category = ? WHERE id = ?`, [
8294
+ "REFACTORING_REQUIRED",
8295
+ doc.id
8296
+ ]);
8297
+ success(`\u2713 Marked for refactoring: ${filePath}`, opts);
8298
+ info("This file needs to be moved or restructured", opts);
8299
+ }
8300
+ async function archiveDocument(cwd, filePath, opts) {
8301
+ const config = loadProjectConfig(cwd);
8302
+ if (!config?.project?.key) {
8303
+ throw new Error("AIGILE not initialized in this directory");
8304
+ }
8305
+ const absolutePath = resolve3(cwd, filePath);
8306
+ if (!existsSync7(absolutePath)) {
8307
+ throw new Error(`File not found: ${filePath}`);
8308
+ }
8309
+ const projectId = config.project.key;
8310
+ const relativePath = relative4(cwd, absolutePath);
8311
+ const content = readFileSync8(absolutePath, "utf-8");
8312
+ const updatedContent = updateFrontmatterContent(content, {
8313
+ status: "ARCHIVED"
8314
+ });
8315
+ writeFileSync5(absolutePath, updatedContent, "utf-8");
8316
+ const doc = queryOne(
8317
+ `SELECT id FROM documents WHERE project_id = ? AND path = ?`,
8318
+ [projectId, relativePath]
8319
+ );
8320
+ if (doc) {
8321
+ run(`UPDATE documents SET meta_status = ? WHERE id = ?`, ["ARCHIVED", doc.id]);
8585
8322
  }
8586
- if (options.addModule) {
8587
- updates.modules = [options.addModule];
8323
+ success(`\u2713 Archived: ${filePath}`, opts);
8324
+ info("Document moved to archived status", opts);
8325
+ }
8326
+ var dependentsCommand = new Command19("dependents").description("Show documents that depend on the specified file").argument("<file>", "Document path (relative to project root)").option("--depth <n>", "Maximum depth to traverse (default: unlimited)", parseInt).option("--json", "Output as JSON").action(async (file, opts) => {
8327
+ const { projectId, projectPath } = await ensureProjectContext();
8328
+ const filePath = file.startsWith(".aigile/") ? file : `.aigile/${file}`;
8329
+ const graph = buildDependencyGraph(projectId);
8330
+ const doc = graph.get(filePath);
8331
+ if (!doc) {
8332
+ error(`Document not found: ${filePath}`);
8333
+ process.exit(1);
8588
8334
  }
8589
- if (options.addDependency) {
8590
- updates.dependencies = [options.addDependency];
8335
+ const directDependents = getDirectDependents(filePath, graph);
8336
+ const allDependents = opts.depth === 1 ? directDependents : getTransitiveDependents(filePath, graph);
8337
+ if (opts.json) {
8338
+ console.log(
8339
+ JSON.stringify(
8340
+ {
8341
+ document: filePath,
8342
+ directDependents,
8343
+ allDependents,
8344
+ count: allDependents.length
8345
+ },
8346
+ null,
8347
+ 2
8348
+ )
8349
+ );
8350
+ return;
8591
8351
  }
8592
- if (options.addCodeRef) {
8593
- updates.code_refs = [options.addCodeRef];
8352
+ success(`Dependents of: ${filePath}`, opts);
8353
+ console.log();
8354
+ if (allDependents.length === 0) {
8355
+ info("No dependents found (this is a leaf document)", opts);
8356
+ return;
8594
8357
  }
8595
- if (options.addAuthor) {
8596
- updates.authors = [options.addAuthor];
8358
+ console.log(`Total: ${allDependents.length} dependent(s)`);
8359
+ console.log();
8360
+ for (const depPath of allDependents) {
8361
+ const depDoc = graph.get(depPath);
8362
+ const tldr = depDoc?.status ? `[${depDoc.status}]` : "";
8363
+ console.log(` \u2022 ${depPath} ${tldr}`);
8597
8364
  }
8598
- if (Object.keys(updates).length === 0) {
8599
- error("No updates specified. Use --status, --version, --tldr, etc.", opts);
8365
+ });
8366
+ var dependenciesCommand = new Command19("dependencies").description("Show documents that the specified file depends on").argument("<file>", "Document path (relative to project root)").option("--json", "Output as JSON").action(async (file, opts) => {
8367
+ const { projectId, projectPath } = await ensureProjectContext();
8368
+ const filePath = file.startsWith(".aigile/") ? file : `.aigile/${file}`;
8369
+ const graph = buildDependencyGraph(projectId);
8370
+ const doc = graph.get(filePath);
8371
+ if (!doc) {
8372
+ error(`Document not found: ${filePath}`);
8600
8373
  process.exit(1);
8601
8374
  }
8602
- const newContent = updateFrontmatterContent(content, updates);
8603
- if (options.dryRun) {
8375
+ const dependencies = doc.dependencies || [];
8376
+ if (opts.json) {
8377
+ console.log(
8378
+ JSON.stringify(
8379
+ {
8380
+ document: filePath,
8381
+ dependencies,
8382
+ count: dependencies.length
8383
+ },
8384
+ null,
8385
+ 2
8386
+ )
8387
+ );
8388
+ return;
8389
+ }
8390
+ success(`Dependencies of: ${filePath}`, opts);
8391
+ console.log();
8392
+ if (dependencies.length === 0) {
8393
+ info("No dependencies (this is a foundational document)", opts);
8394
+ return;
8395
+ }
8396
+ console.log(
8397
+ `Total: ${dependencies.length} dependenc${dependencies.length === 1 ? "y" : "ies"}`
8398
+ );
8399
+ console.log();
8400
+ for (const depPath of dependencies) {
8401
+ const depDoc = graph.get(depPath);
8402
+ const tldr = depDoc?.status ? `[${depDoc.status}]` : "";
8403
+ console.log(` \u2022 ${depPath} ${tldr}`);
8404
+ }
8405
+ });
8406
+ var nextTemplateCommand = new Command19("next-template").description("Find the next template document to fill (foundation-first)").option("--json", "Output as JSON").action(async (opts) => {
8407
+ const { projectId } = await ensureProjectContext();
8408
+ const templates = queryAll(
8409
+ `SELECT path, meta_tldr, meta_dependencies
8410
+ FROM documents
8411
+ WHERE project_id = ? AND meta_status = 'TEMPLATE'
8412
+ ORDER BY path ASC`,
8413
+ [projectId]
8414
+ );
8415
+ if (templates.length === 0) {
8604
8416
  if (opts.json) {
8605
- console.log(JSON.stringify({
8606
- success: true,
8607
- dryRun: true,
8608
- updates,
8609
- preview: newContent.substring(0, 500) + (newContent.length > 500 ? "..." : "")
8610
- }));
8611
- } else {
8612
- info("Dry run - would update frontmatter with:", opts);
8613
- console.log(JSON.stringify(updates, null, 2));
8614
- console.log("\nNew frontmatter preview:");
8615
- const lines = newContent.split("\n");
8616
- const endIndex = lines.findIndex((l, i) => i > 0 && l === "---");
8617
- lines.slice(0, endIndex + 1).forEach((l) => console.log(l));
8417
+ console.log(JSON.stringify({ nextTemplate: null, remaining: 0 }, null, 2));
8418
+ return;
8618
8419
  }
8420
+ success("\u2713 No templates remaining - all documents filled!", opts);
8619
8421
  return;
8620
8422
  }
8621
- try {
8622
- writeFileSync5(fullPath, newContent, "utf-8");
8623
- success(`Updated frontmatter in ${filePath}`, opts);
8624
- if (!opts.json) {
8625
- info('Run "aigile sync scan" to update the database.', opts);
8626
- }
8627
- } catch (err) {
8628
- error(`Could not write file: ${filePath}`, opts);
8629
- process.exit(1);
8630
- }
8631
- });
8632
- docCommand.command("init-frontmatter <path>").option("--status <status>", "Initial status (default: DRAFT)").option("--version <version>", "Initial version (default: 0.1)").option("--tldr <tldr>", "One-sentence summary").option("--title <title>", "Document title").description("Add frontmatter to a file that does not have it").action((filePath, options) => {
8633
- const opts = getOutputOptions(docCommand);
8634
- const ctx = getProjectId(opts);
8635
- if (!ctx) {
8636
- process.exit(1);
8423
+ const graph = buildDependencyGraph(projectId);
8424
+ const foundational = getFoundationalDocs(graph).filter((path) => {
8425
+ const doc = graph.get(path);
8426
+ return doc?.status === "TEMPLATE";
8427
+ });
8428
+ const nextTemplate = foundational[0] || templates[0].path;
8429
+ const nextDoc = graph.get(nextTemplate);
8430
+ if (opts.json) {
8431
+ console.log(
8432
+ JSON.stringify(
8433
+ {
8434
+ nextTemplate,
8435
+ tldr: nextDoc?.status || null,
8436
+ remaining: templates.length,
8437
+ foundational: foundational.length
8438
+ },
8439
+ null,
8440
+ 2
8441
+ )
8442
+ );
8443
+ return;
8637
8444
  }
8638
- const fullPath = join8(ctx.projectRoot, filePath);
8639
- let content;
8640
- try {
8641
- content = readFileSync7(fullPath, "utf-8");
8642
- } catch {
8643
- error(`Could not read file: ${filePath}`, opts);
8644
- process.exit(1);
8445
+ success(`Next template to fill: ${nextTemplate}`, opts);
8446
+ if (nextDoc?.status) {
8447
+ info(`TLDR: ${nextDoc.status}`, opts);
8645
8448
  }
8646
- const existing = parseFrontmatterFromFile(fullPath);
8647
- if (existing) {
8648
- error('File already has frontmatter. Use "aigile doc update" instead.', opts);
8649
- process.exit(1);
8449
+ console.log();
8450
+ info(`Remaining templates: ${templates.length}`, opts);
8451
+ info(`Foundational templates: ${foundational.length}`, opts);
8452
+ });
8453
+ var statsCommand = new Command19("stats").description("Show document statistics and progress").option("--json", "Output as JSON").action(async (opts) => {
8454
+ const { projectId } = await ensureProjectContext();
8455
+ const statusCounts = queryAll(
8456
+ `SELECT meta_status, COUNT(*) as count
8457
+ FROM documents
8458
+ WHERE project_id = ? AND meta_status IS NOT NULL
8459
+ GROUP BY meta_status`,
8460
+ [projectId]
8461
+ );
8462
+ const stats = {};
8463
+ let total = 0;
8464
+ for (const row of statusCounts) {
8465
+ stats[row.meta_status || "UNKNOWN"] = row.count;
8466
+ total += row.count;
8650
8467
  }
8651
- const metadata = {
8652
- status: options.status ?? "DRAFT",
8653
- version: options.version ?? "0.1",
8654
- tldr: options.tldr ?? "",
8655
- title: options.title
8656
- };
8657
- const newContent = updateFrontmatterContent(content, metadata);
8658
- try {
8659
- writeFileSync5(fullPath, newContent, "utf-8");
8660
- success(`Added frontmatter to ${filePath}`, opts);
8661
- if (!opts.json) {
8662
- info('Run "aigile sync scan" to update the database.', opts);
8468
+ const graph = buildDependencyGraph(projectId);
8469
+ let staleCount = 0;
8470
+ for (const [path] of graph) {
8471
+ const staleness = checkStaleness(path, graph);
8472
+ if (staleness.isStale) {
8473
+ staleCount++;
8663
8474
  }
8664
- } catch {
8665
- error(`Could not write file: ${filePath}`, opts);
8666
- process.exit(1);
8667
- }
8668
- });
8669
- docCommand.command("stats").description("Show frontmatter statistics for the project").action(() => {
8670
- const opts = getOutputOptions(docCommand);
8671
- const ctx = getProjectId(opts);
8672
- if (!ctx) {
8673
- process.exit(1);
8674
8475
  }
8675
- const withFrontmatter = getDocumentsWithFrontmatter(ctx.projectId);
8676
- const withoutFrontmatter = getDocumentsWithoutFrontmatter(ctx.projectId);
8677
- const templates = getTemplateDocuments(ctx.projectId);
8678
- const drafts = getDocumentsByMetaStatus(ctx.projectId, "DRAFT");
8679
- const inReview = getDocumentsByMetaStatus(ctx.projectId, "IN-REVIEW");
8680
- const approved = getDocumentsByMetaStatus(ctx.projectId, "APPROVED");
8681
- const stats = {
8682
- total_documents: withFrontmatter.length + withoutFrontmatter.length,
8683
- with_frontmatter: withFrontmatter.length,
8684
- without_frontmatter: withoutFrontmatter.length,
8685
- templates: templates.length,
8686
- drafts: drafts.length,
8687
- in_review: inReview.length,
8688
- approved: approved.length
8689
- };
8690
8476
  if (opts.json) {
8691
- console.log(JSON.stringify({ success: true, data: stats }));
8692
- } else {
8693
- details(
8694
- stats,
8695
- [
8696
- { label: "Total Documents", key: "total_documents" },
8697
- { label: "With Frontmatter", key: "with_frontmatter" },
8698
- { label: "Without Frontmatter", key: "without_frontmatter" },
8699
- { label: "TEMPLATE", key: "templates" },
8700
- { label: "DRAFT", key: "drafts" },
8701
- { label: "IN-REVIEW", key: "in_review" },
8702
- { label: "APPROVED", key: "approved" }
8703
- ],
8704
- opts
8477
+ console.log(
8478
+ JSON.stringify(
8479
+ {
8480
+ total,
8481
+ byStatus: stats,
8482
+ stale: staleCount,
8483
+ completion: total > 0 ? Math.round((stats.APPROVED || 0) / total * 100) : 0
8484
+ },
8485
+ null,
8486
+ 2
8487
+ )
8705
8488
  );
8489
+ return;
8490
+ }
8491
+ success("Document Statistics", opts);
8492
+ console.log();
8493
+ console.log(`Total Documents: ${total}`);
8494
+ console.log();
8495
+ if (Object.keys(stats).length > 0) {
8496
+ console.log("By Status:");
8497
+ for (const [status, count] of Object.entries(stats)) {
8498
+ const pct = total > 0 ? (count / total * 100).toFixed(1) : "0.0";
8499
+ console.log(` ${status.padEnd(15)} ${count.toString().padStart(4)} (${pct}%)`);
8500
+ }
8501
+ console.log();
8502
+ }
8503
+ if (staleCount > 0) {
8504
+ warning(`\u26A0 ${staleCount} document(s) are stale (dependencies changed)`, opts);
8706
8505
  }
8506
+ const approved = stats.APPROVED || 0;
8507
+ const completion = total > 0 ? (approved / total * 100).toFixed(1) : "0.0";
8508
+ info(`Completion: ${completion}% (${approved}/${total} approved)`, opts);
8707
8509
  });
8510
+ var docCommand = new Command19("doc").description("Document integrity and status management commands").addCommand(integrityCommand).addCommand(approveCommand).addCommand(ackCommand).addCommand(readyCommand).addCommand(reviewCommand).addCommand(exceptionCommand).addCommand(refactorCommand).addCommand(archiveCommand).addCommand(dependentsCommand).addCommand(dependenciesCommand).addCommand(nextTemplateCommand).addCommand(statsCommand);
8708
8511
 
8709
8512
  // src/commands/daemon.ts
8710
8513
  init_connection();
8711
8514
  import { Command as Command20 } from "commander";
8712
- import { existsSync as existsSync9, writeFileSync as writeFileSync6, unlinkSync, readFileSync as readFileSync9, mkdirSync as mkdirSync5, statSync as statSync3, renameSync, readdirSync as readdirSync2 } from "fs";
8713
- import { join as join11, dirname as dirname3 } from "path";
8515
+ import { existsSync as existsSync11, writeFileSync as writeFileSync7, unlinkSync, readFileSync as readFileSync11, mkdirSync as mkdirSync5, statSync as statSync5, renameSync, readdirSync as readdirSync3 } from "fs";
8516
+ import { join as join12, dirname as dirname4 } from "path";
8714
8517
  import { homedir as homedir2, platform } from "os";
8715
8518
  import { spawn, execSync as execSync2 } from "child_process";
8716
8519
  init_config();
8717
8520
 
8718
8521
  // src/services/daemon-manager.ts
8719
- import { existsSync as existsSync8 } from "fs";
8720
- import { join as join10 } from "path";
8522
+ import { existsSync as existsSync10 } from "fs";
8523
+ import { join as join11 } from "path";
8721
8524
  import { EventEmitter as EventEmitter2 } from "events";
8722
8525
 
8723
8526
  // src/services/file-watcher.ts
8724
8527
  init_connection();
8725
8528
  import { watch } from "chokidar";
8726
- import { relative as relative3, extname as extname2, basename as basename4 } from "path";
8727
- import { readFileSync as readFileSync8, statSync as statSync2 } from "fs";
8529
+ import { relative as relative5, extname as extname2, basename as basename4 } from "path";
8530
+ import { existsSync as existsSync9, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
8728
8531
  import { EventEmitter } from "events";
8729
8532
  init_monitoring_patterns();
8730
8533
  init_config();
8534
+
8535
+ // src/services/document-status-transitions.ts
8536
+ init_connection();
8537
+ import { writeFileSync as writeFileSync6, readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
8538
+ import { resolve as resolve4 } from "path";
8539
+ import { createHash as createHash2 } from "crypto";
8540
+ async function handleDocumentEdit(projectId, docPath, absolutePath) {
8541
+ if (!existsSync8(absolutePath)) {
8542
+ return false;
8543
+ }
8544
+ const doc = queryOne(
8545
+ `SELECT id, meta_status, content_hash FROM documents WHERE project_id = ? AND path = ?`,
8546
+ [projectId, docPath]
8547
+ );
8548
+ if (!doc) {
8549
+ return false;
8550
+ }
8551
+ const currentStatus = doc.meta_status;
8552
+ if (!currentStatus || currentStatus === "ARCHIVED") {
8553
+ return false;
8554
+ }
8555
+ try {
8556
+ const parsed = parseFrontmatterFromFile(absolutePath);
8557
+ const fileStatus = parsed?.metadata?.status;
8558
+ const content = readFileSync9(absolutePath, "utf-8");
8559
+ const contentHash = createHash2("sha256").update(content).digest("hex");
8560
+ if (contentHash === doc.content_hash) {
8561
+ return false;
8562
+ }
8563
+ let newStatus = null;
8564
+ switch (currentStatus) {
8565
+ case "TEMPLATE":
8566
+ newStatus = "DRAFT";
8567
+ break;
8568
+ case "APPROVED":
8569
+ newStatus = "NEEDS_REVIEW";
8570
+ break;
8571
+ case "NEEDS_REVIEW":
8572
+ case "DRAFT":
8573
+ break;
8574
+ }
8575
+ if (!newStatus) {
8576
+ return false;
8577
+ }
8578
+ const updatedContent = updateFrontmatterContent(content, {
8579
+ status: newStatus
8580
+ });
8581
+ writeFileSync6(absolutePath, updatedContent, "utf-8");
8582
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8583
+ run(
8584
+ `UPDATE documents
8585
+ SET meta_status = ?, content_hash = ?, last_modified_at = ?, needs_review = ?
8586
+ WHERE id = ?`,
8587
+ [
8588
+ newStatus,
8589
+ contentHash,
8590
+ now,
8591
+ newStatus === "NEEDS_REVIEW" ? 1 : 0,
8592
+ doc.id
8593
+ ]
8594
+ );
8595
+ console.log(
8596
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] Status transition: ${docPath} (${currentStatus} \u2192 ${newStatus})`
8597
+ );
8598
+ return true;
8599
+ } catch (err) {
8600
+ console.error(
8601
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] Error in status transition for ${docPath}: ${err}`
8602
+ );
8603
+ return false;
8604
+ }
8605
+ }
8606
+ async function markDependentsStale(projectId, docPath) {
8607
+ const dependents = queryAll(
8608
+ `SELECT id, path, meta_dependencies FROM documents WHERE project_id = ?`,
8609
+ [projectId]
8610
+ );
8611
+ let markedCount = 0;
8612
+ for (const dep of dependents) {
8613
+ try {
8614
+ const dependencies = dep.meta_dependencies ? JSON.parse(dep.meta_dependencies) : [];
8615
+ if (dependencies.includes(docPath)) {
8616
+ run(`UPDATE documents SET needs_review = 1 WHERE id = ?`, [dep.id]);
8617
+ markedCount++;
8618
+ }
8619
+ } catch {
8620
+ }
8621
+ }
8622
+ if (markedCount > 0) {
8623
+ console.log(
8624
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] Marked ${markedCount} dependents as stale for ${docPath}`
8625
+ );
8626
+ }
8627
+ return markedCount;
8628
+ }
8629
+ function isAigileDoc(docPath) {
8630
+ return docPath.startsWith(".aigile/") && (docPath.endsWith(".md") || docPath.endsWith(".feature"));
8631
+ }
8632
+ async function onFileChanged(projectId, projectPath, relativeDocPath) {
8633
+ if (!isAigileDoc(relativeDocPath)) {
8634
+ return;
8635
+ }
8636
+ const absolutePath = resolve4(projectPath, relativeDocPath);
8637
+ const transitioned = await handleDocumentEdit(
8638
+ projectId,
8639
+ relativeDocPath,
8640
+ absolutePath
8641
+ );
8642
+ if (transitioned || existsSync8(absolutePath)) {
8643
+ await markDependentsStale(projectId, relativeDocPath);
8644
+ }
8645
+ }
8646
+
8647
+ // src/services/file-watcher.ts
8731
8648
  import picomatch2 from "picomatch";
8732
8649
  var FileWatcher = class extends EventEmitter {
8733
8650
  watcher = null;
8734
8651
  config;
8735
8652
  stats;
8736
8653
  debounceTimers = /* @__PURE__ */ new Map();
8654
+ cleanupInterval = null;
8737
8655
  // Tri-state pattern matchers
8738
8656
  allowMatcher = null;
8739
8657
  denyMatcher = null;
@@ -8817,6 +8735,12 @@ var FileWatcher = class extends EventEmitter {
8817
8735
  this.updateCategoryCounts();
8818
8736
  this.emit("ready", this.stats);
8819
8737
  });
8738
+ this.cleanupInterval = setInterval(
8739
+ () => {
8740
+ this.cleanupStaleTimers();
8741
+ },
8742
+ 5 * 60 * 1e3
8743
+ );
8820
8744
  }
8821
8745
  /**
8822
8746
  * Update category counts from database
@@ -8824,12 +8748,15 @@ var FileWatcher = class extends EventEmitter {
8824
8748
  */
8825
8749
  updateCategoryCounts() {
8826
8750
  try {
8827
- const counts = queryAll(`
8751
+ const counts = queryAll(
8752
+ `
8828
8753
  SELECT monitoring_category, COUNT(*) as count
8829
8754
  FROM documents
8830
8755
  WHERE project_id = ? AND status != 'deleted'
8831
8756
  GROUP BY monitoring_category
8832
- `, [this.config.projectId]);
8757
+ `,
8758
+ [this.config.projectId]
8759
+ );
8833
8760
  this.stats.categoryCounts = { allow: 0, deny: 0, unknown: 0 };
8834
8761
  for (const row of counts) {
8835
8762
  const cat = row.monitoring_category;
@@ -8847,6 +8774,10 @@ var FileWatcher = class extends EventEmitter {
8847
8774
  if (!this.watcher) {
8848
8775
  return;
8849
8776
  }
8777
+ if (this.cleanupInterval) {
8778
+ clearInterval(this.cleanupInterval);
8779
+ this.cleanupInterval = null;
8780
+ }
8850
8781
  for (const timer of this.debounceTimers.values()) {
8851
8782
  clearTimeout(timer);
8852
8783
  }
@@ -8856,6 +8787,28 @@ var FileWatcher = class extends EventEmitter {
8856
8787
  this.stats.isRunning = false;
8857
8788
  this.emit("stopped");
8858
8789
  }
8790
+ /**
8791
+ * Periodic cleanup of stale debounce timers
8792
+ * Removes timers for files that no longer exist
8793
+ */
8794
+ cleanupStaleTimers() {
8795
+ const staleKeys = [];
8796
+ for (const [path] of this.debounceTimers) {
8797
+ if (!existsSync9(path)) {
8798
+ staleKeys.push(path);
8799
+ }
8800
+ }
8801
+ for (const key of staleKeys) {
8802
+ const timer = this.debounceTimers.get(key);
8803
+ if (timer) {
8804
+ clearTimeout(timer);
8805
+ this.debounceTimers.delete(key);
8806
+ }
8807
+ }
8808
+ if (staleKeys.length > 0) {
8809
+ console.log(`[FileWatcher] Cleaned up ${staleKeys.length} stale debounce timers`);
8810
+ }
8811
+ }
8859
8812
  /**
8860
8813
  * Get current watcher statistics
8861
8814
  */
@@ -8881,6 +8834,11 @@ var FileWatcher = class extends EventEmitter {
8881
8834
  const existingTimer = this.debounceTimers.get(absolutePath);
8882
8835
  if (existingTimer) {
8883
8836
  clearTimeout(existingTimer);
8837
+ this.debounceTimers.delete(absolutePath);
8838
+ }
8839
+ if (type === "unlink") {
8840
+ this.processFileEvent(type, absolutePath);
8841
+ return;
8884
8842
  }
8885
8843
  const timer = setTimeout(() => {
8886
8844
  this.debounceTimers.delete(absolutePath);
@@ -8892,7 +8850,7 @@ var FileWatcher = class extends EventEmitter {
8892
8850
  * Process a file event (after debounce)
8893
8851
  */
8894
8852
  processFileEvent(type, absolutePath) {
8895
- const relativePath = relative3(this.config.projectPath, absolutePath);
8853
+ const relativePath = relative5(this.config.projectPath, absolutePath);
8896
8854
  const category = this.classifyFile(relativePath);
8897
8855
  if (category === "deny" && type !== "unlink") {
8898
8856
  return;
@@ -8915,6 +8873,14 @@ var FileWatcher = class extends EventEmitter {
8915
8873
  break;
8916
8874
  case "change":
8917
8875
  this.syncFileChange(absolutePath, relativePath, category);
8876
+ onFileChanged(this.config.projectId, this.config.projectPath, relativePath).catch((err) => {
8877
+ this.emit("transitionError", {
8878
+ path: relativePath,
8879
+ error: err,
8880
+ timestamp: /* @__PURE__ */ new Date()
8881
+ });
8882
+ console.error(`[FileWatcher] Error in onFileChanged for ${relativePath}:`, err);
8883
+ });
8918
8884
  break;
8919
8885
  case "unlink":
8920
8886
  this.syncFileDelete(relativePath);
@@ -8933,7 +8899,7 @@ var FileWatcher = class extends EventEmitter {
8933
8899
  const filename = basename4(relativePath);
8934
8900
  const isBinary = isBinaryExtension(ext);
8935
8901
  try {
8936
- const stats = statSync2(absolutePath);
8902
+ const stats = statSync4(absolutePath);
8937
8903
  const shouldComputeHash = category === "allow" || !isBinary && stats.size < 10 * 1024 * 1024;
8938
8904
  const hash = shouldComputeHash ? computeFileHash(absolutePath) : null;
8939
8905
  let hasFrontmatter = false;
@@ -8952,16 +8918,36 @@ var FileWatcher = class extends EventEmitter {
8952
8918
  [this.config.projectId, relativePath]
8953
8919
  );
8954
8920
  if (existing) {
8955
- this.updateDocument(existing.id, hash, stats.size, hasFrontmatter, frontmatterRaw, metadata, category);
8921
+ this.updateDocument(
8922
+ existing.id,
8923
+ hash,
8924
+ stats.size,
8925
+ hasFrontmatter,
8926
+ frontmatterRaw,
8927
+ metadata,
8928
+ category
8929
+ );
8956
8930
  } else {
8957
- this.insertDocument(relativePath, filename, ext, hash, stats.size, hasFrontmatter, frontmatterRaw, metadata, category);
8931
+ this.insertDocument(
8932
+ relativePath,
8933
+ filename,
8934
+ ext,
8935
+ hash,
8936
+ stats.size,
8937
+ hasFrontmatter,
8938
+ frontmatterRaw,
8939
+ metadata,
8940
+ category
8941
+ );
8958
8942
  }
8959
8943
  } catch (err) {
8960
8944
  const errMsg = err instanceof Error ? err.message : String(err);
8961
8945
  if (errMsg.includes("Database") || errMsg.includes("database")) {
8962
8946
  throw err;
8963
8947
  }
8964
- console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] syncFileAdd error for ${relativePath}: ${errMsg}`);
8948
+ console.error(
8949
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] syncFileAdd error for ${relativePath}: ${errMsg}`
8950
+ );
8965
8951
  }
8966
8952
  }
8967
8953
  /**
@@ -8979,10 +8965,9 @@ var FileWatcher = class extends EventEmitter {
8979
8965
  [this.config.projectId, relativePath]
8980
8966
  );
8981
8967
  if (doc) {
8982
- run(
8983
- `UPDATE documents SET status = 'deleted', updated_at = datetime('now') WHERE id = ?`,
8984
- [doc.id]
8985
- );
8968
+ run(`UPDATE documents SET status = 'deleted', updated_at = datetime('now') WHERE id = ?`, [
8969
+ doc.id
8970
+ ]);
8986
8971
  }
8987
8972
  }
8988
8973
  /**
@@ -9076,6 +9061,7 @@ var INITIAL_RETRY_DELAY_MS = 5e3;
9076
9061
  var DaemonManager = class extends EventEmitter2 {
9077
9062
  watchers = /* @__PURE__ */ new Map();
9078
9063
  watcherRetries = /* @__PURE__ */ new Map();
9064
+ retryTimeouts = /* @__PURE__ */ new Map();
9079
9065
  running = false;
9080
9066
  startedAt = null;
9081
9067
  /**
@@ -9131,21 +9117,37 @@ var DaemonManager = class extends EventEmitter2 {
9131
9117
  if (currentRetries < MAX_WATCHER_RETRIES) {
9132
9118
  this.watcherRetries.set(project.key, currentRetries + 1);
9133
9119
  const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, currentRetries);
9134
- console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Will retry in ${delay}ms (attempt ${currentRetries + 1}/${MAX_WATCHER_RETRIES})`);
9135
- setTimeout(async () => {
9120
+ console.log(
9121
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Will retry in ${delay}ms (attempt ${currentRetries + 1}/${MAX_WATCHER_RETRIES})`
9122
+ );
9123
+ const existingTimeout2 = this.retryTimeouts.get(project.key);
9124
+ if (existingTimeout2) {
9125
+ clearTimeout(existingTimeout2);
9126
+ }
9127
+ const timeout = setTimeout(async () => {
9128
+ this.retryTimeouts.delete(project.key);
9136
9129
  if (this.running) {
9137
9130
  console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Attempting restart...`);
9138
9131
  await this.startWatcherWithRetry(project);
9139
9132
  }
9140
9133
  }, delay);
9134
+ this.retryTimeouts.set(project.key, timeout);
9141
9135
  } else {
9142
- console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Max retries (${MAX_WATCHER_RETRIES}) exceeded - watcher disabled`);
9136
+ console.error(
9137
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Max retries (${MAX_WATCHER_RETRIES}) exceeded - watcher disabled`
9138
+ );
9139
+ this.watcherRetries.delete(project.key);
9143
9140
  this.emit("watcherDisabled", { project: project.key });
9144
9141
  }
9145
9142
  });
9146
9143
  watcher.start();
9147
9144
  this.watchers.set(project.key, watcher);
9148
- this.watcherRetries.set(project.key, 0);
9145
+ this.watcherRetries.delete(project.key);
9146
+ const existingTimeout = this.retryTimeouts.get(project.key);
9147
+ if (existingTimeout) {
9148
+ clearTimeout(existingTimeout);
9149
+ this.retryTimeouts.delete(project.key);
9150
+ }
9149
9151
  console.log(` \u2713 ${project.key}: ${project.path}`);
9150
9152
  } catch (error2) {
9151
9153
  console.error(` \u2717 ${project.key}: Failed to start watcher - ${error2}`);
@@ -9153,12 +9155,22 @@ var DaemonManager = class extends EventEmitter2 {
9153
9155
  if (retryCount < MAX_WATCHER_RETRIES) {
9154
9156
  this.watcherRetries.set(project.key, retryCount + 1);
9155
9157
  const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
9156
- console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Will retry in ${delay}ms (attempt ${retryCount + 1}/${MAX_WATCHER_RETRIES})`);
9157
- setTimeout(async () => {
9158
+ console.log(
9159
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Will retry in ${delay}ms (attempt ${retryCount + 1}/${MAX_WATCHER_RETRIES})`
9160
+ );
9161
+ const existingTimeout = this.retryTimeouts.get(project.key);
9162
+ if (existingTimeout) {
9163
+ clearTimeout(existingTimeout);
9164
+ }
9165
+ const timeout = setTimeout(async () => {
9166
+ this.retryTimeouts.delete(project.key);
9158
9167
  if (this.running) {
9159
9168
  await this.startWatcherWithRetry(project);
9160
9169
  }
9161
9170
  }, delay);
9171
+ this.retryTimeouts.set(project.key, timeout);
9172
+ } else {
9173
+ this.watcherRetries.delete(project.key);
9162
9174
  }
9163
9175
  }
9164
9176
  }
@@ -9170,6 +9182,11 @@ var DaemonManager = class extends EventEmitter2 {
9170
9182
  return;
9171
9183
  }
9172
9184
  console.log("Stopping all watchers...");
9185
+ for (const [key, timeout] of this.retryTimeouts) {
9186
+ clearTimeout(timeout);
9187
+ console.log(` \u2713 Cleared retry timeout: ${key}`);
9188
+ }
9189
+ this.retryTimeouts.clear();
9173
9190
  for (const [key, watcher] of this.watchers) {
9174
9191
  try {
9175
9192
  await watcher.stop();
@@ -9179,6 +9196,7 @@ var DaemonManager = class extends EventEmitter2 {
9179
9196
  }
9180
9197
  }
9181
9198
  this.watchers.clear();
9199
+ this.watcherRetries.clear();
9182
9200
  this.running = false;
9183
9201
  this.startedAt = null;
9184
9202
  this.emit("stopped");
@@ -9295,7 +9313,7 @@ var DaemonManager = class extends EventEmitter2 {
9295
9313
  * Check if a project path is valid
9296
9314
  */
9297
9315
  isValidProject(path) {
9298
- return existsSync8(path) && existsSync8(join10(path, ".aigile"));
9316
+ return existsSync10(path) && existsSync10(join11(path, ".aigile"));
9299
9317
  }
9300
9318
  };
9301
9319
  var daemonManagerInstance = null;
@@ -9318,29 +9336,29 @@ var DAEMON_NAME = "com.aigile.watcher";
9318
9336
  function getDaemonPaths() {
9319
9337
  const aigileHome = getAigileHome();
9320
9338
  const basePaths = {
9321
- pidFile: join11(aigileHome, "daemon.pid"),
9322
- logFile: join11(aigileHome, "daemon.log")
9339
+ pidFile: join12(aigileHome, "daemon.pid"),
9340
+ logFile: join12(aigileHome, "daemon.log")
9323
9341
  };
9324
9342
  if (PLATFORM === "darwin") {
9325
9343
  return {
9326
9344
  ...basePaths,
9327
- plist: join11(homedir2(), "Library", "LaunchAgents", `${DAEMON_NAME}.plist`)
9345
+ plist: join12(homedir2(), "Library", "LaunchAgents", `${DAEMON_NAME}.plist`)
9328
9346
  };
9329
9347
  } else if (PLATFORM === "linux") {
9330
9348
  return {
9331
9349
  ...basePaths,
9332
- service: join11(homedir2(), ".config", "systemd", "user", `${DAEMON_NAME}.service`)
9350
+ service: join12(homedir2(), ".config", "systemd", "user", `${DAEMON_NAME}.service`)
9333
9351
  };
9334
9352
  }
9335
9353
  return basePaths;
9336
9354
  }
9337
9355
  function isDaemonRunning() {
9338
9356
  const paths = getDaemonPaths();
9339
- if (!existsSync9(paths.pidFile)) {
9357
+ if (!existsSync11(paths.pidFile)) {
9340
9358
  return { running: false };
9341
9359
  }
9342
9360
  try {
9343
- const pid = parseInt(readFileSync9(paths.pidFile, "utf-8").trim(), 10);
9361
+ const pid = parseInt(readFileSync11(paths.pidFile, "utf-8").trim(), 10);
9344
9362
  try {
9345
9363
  process.kill(pid, 0);
9346
9364
  return { running: true, pid };
@@ -9354,12 +9372,12 @@ function isDaemonRunning() {
9354
9372
  }
9355
9373
  function writeCrashReport(error2) {
9356
9374
  try {
9357
- const crashDir = join11(getAigileHome(), CRASH_DIR_NAME);
9358
- if (!existsSync9(crashDir)) {
9375
+ const crashDir = join12(getAigileHome(), CRASH_DIR_NAME);
9376
+ if (!existsSync11(crashDir)) {
9359
9377
  mkdirSync5(crashDir, { recursive: true });
9360
9378
  }
9361
9379
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9362
- const crashFile = join11(crashDir, `crash-${timestamp}.log`);
9380
+ const crashFile = join12(crashDir, `crash-${timestamp}.log`);
9363
9381
  const report = [
9364
9382
  `AIGILE Daemon Crash Report`,
9365
9383
  `==========================`,
@@ -9372,7 +9390,7 @@ function writeCrashReport(error2) {
9372
9390
  `Error:`,
9373
9391
  error2 instanceof Error ? error2.stack || error2.message : String(error2)
9374
9392
  ].join("\n");
9375
- writeFileSync6(crashFile, report);
9393
+ writeFileSync7(crashFile, report);
9376
9394
  console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Crash report saved: ${crashFile}`);
9377
9395
  cleanupOldCrashReports(crashDir);
9378
9396
  } catch (writeErr) {
@@ -9381,7 +9399,7 @@ function writeCrashReport(error2) {
9381
9399
  }
9382
9400
  function cleanupOldCrashReports(crashDir) {
9383
9401
  try {
9384
- const files = readdirSync2(crashDir).filter((f) => f.startsWith("crash-") && f.endsWith(".log")).map((f) => ({ name: f, path: join11(crashDir, f) })).sort((a, b) => b.name.localeCompare(a.name));
9402
+ const files = readdirSync3(crashDir).filter((f) => f.startsWith("crash-") && f.endsWith(".log")).map((f) => ({ name: f, path: join12(crashDir, f) })).sort((a, b) => b.name.localeCompare(a.name));
9385
9403
  for (let i = MAX_CRASH_REPORTS; i < files.length; i++) {
9386
9404
  try {
9387
9405
  unlinkSync(files[i].path);
@@ -9395,21 +9413,21 @@ function rotateLogIfNeeded() {
9395
9413
  const paths = getDaemonPaths();
9396
9414
  const logPath = paths.logFile;
9397
9415
  try {
9398
- if (!existsSync9(logPath)) return;
9399
- const stats = statSync3(logPath);
9416
+ if (!existsSync11(logPath)) return;
9417
+ const stats = statSync5(logPath);
9400
9418
  if (stats.size < MAX_LOG_SIZE) return;
9401
9419
  const timestamp = Date.now();
9402
9420
  const rotatedPath = `${logPath}.${timestamp}`;
9403
9421
  renameSync(logPath, rotatedPath);
9404
9422
  console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Log rotated: ${rotatedPath}`);
9405
- cleanupOldLogs(dirname3(logPath));
9423
+ cleanupOldLogs(dirname4(logPath));
9406
9424
  } catch (err) {
9407
9425
  console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Log rotation error: ${err}`);
9408
9426
  }
9409
9427
  }
9410
9428
  function cleanupOldLogs(logDir) {
9411
9429
  try {
9412
- const files = readdirSync2(logDir).filter((f) => f.startsWith("daemon.log.")).map((f) => ({ name: f, path: join11(logDir, f) })).sort((a, b) => b.name.localeCompare(a.name));
9430
+ const files = readdirSync3(logDir).filter((f) => f.startsWith("daemon.log.")).map((f) => ({ name: f, path: join12(logDir, f) })).sort((a, b) => b.name.localeCompare(a.name));
9413
9431
  for (let i = MAX_LOG_FILES; i < files.length; i++) {
9414
9432
  try {
9415
9433
  unlinkSync(files[i].path);
@@ -9423,7 +9441,7 @@ function cleanupOldLogs(logDir) {
9423
9441
  function generateLaunchAgentPlist() {
9424
9442
  const paths = getDaemonPaths();
9425
9443
  const nodePath = process.execPath;
9426
- const aigilePath = join11(dirname3(dirname3(import.meta.url.replace("file://", ""))), "bin", "aigile.js");
9444
+ const aigilePath = join12(dirname4(dirname4(import.meta.url.replace("file://", ""))), "bin", "aigile.js");
9427
9445
  return `<?xml version="1.0" encoding="UTF-8"?>
9428
9446
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
9429
9447
  <plist version="1.0">
@@ -9458,7 +9476,7 @@ function generateLaunchAgentPlist() {
9458
9476
  function generateSystemdService() {
9459
9477
  const paths = getDaemonPaths();
9460
9478
  const nodePath = process.execPath;
9461
- const aigilePath = join11(dirname3(dirname3(import.meta.url.replace("file://", ""))), "bin", "aigile.js");
9479
+ const aigilePath = join12(dirname4(dirname4(import.meta.url.replace("file://", ""))), "bin", "aigile.js");
9462
9480
  return `[Unit]
9463
9481
  Description=AIGILE File Watcher Daemon
9464
9482
  After=network.target
@@ -9481,23 +9499,23 @@ daemonCommand.command("install").description("Install daemon to start automatica
9481
9499
  const opts = getOutputOptions(daemonCommand);
9482
9500
  const paths = getDaemonPaths();
9483
9501
  if (PLATFORM === "darwin") {
9484
- const plistDir = dirname3(paths.plist);
9485
- if (!existsSync9(plistDir)) {
9502
+ const plistDir = dirname4(paths.plist);
9503
+ if (!existsSync11(plistDir)) {
9486
9504
  mkdirSync5(plistDir, { recursive: true });
9487
9505
  }
9488
9506
  const plistContent = generateLaunchAgentPlist();
9489
- writeFileSync6(paths.plist, plistContent);
9507
+ writeFileSync7(paths.plist, plistContent);
9490
9508
  success("Installed macOS LaunchAgent", opts);
9491
9509
  info(`Plist location: ${paths.plist}`, opts);
9492
9510
  info("Daemon will watch ALL registered projects", opts);
9493
9511
  info('Run "aigile daemon start" to start the watcher', opts);
9494
9512
  } else if (PLATFORM === "linux") {
9495
- const serviceDir = dirname3(paths.service);
9496
- if (!existsSync9(serviceDir)) {
9513
+ const serviceDir = dirname4(paths.service);
9514
+ if (!existsSync11(serviceDir)) {
9497
9515
  mkdirSync5(serviceDir, { recursive: true });
9498
9516
  }
9499
9517
  const serviceContent = generateSystemdService();
9500
- writeFileSync6(paths.service, serviceContent);
9518
+ writeFileSync7(paths.service, serviceContent);
9501
9519
  try {
9502
9520
  execSync2("systemctl --user daemon-reload");
9503
9521
  execSync2(`systemctl --user enable ${DAEMON_NAME}`);
@@ -9506,7 +9524,7 @@ daemonCommand.command("install").description("Install daemon to start automatica
9506
9524
  info("Daemon will watch ALL registered projects", opts);
9507
9525
  info('Run "aigile daemon start" to start the watcher', opts);
9508
9526
  } catch (err) {
9509
- warning2("Service file created but could not enable. You may need to run:", opts);
9527
+ warning("Service file created but could not enable. You may need to run:", opts);
9510
9528
  console.log(` systemctl --user daemon-reload`);
9511
9529
  console.log(` systemctl --user enable ${DAEMON_NAME}`);
9512
9530
  }
@@ -9527,14 +9545,14 @@ daemonCommand.command("uninstall").description("Remove daemon from auto-start").
9527
9545
  } catch {
9528
9546
  }
9529
9547
  }
9530
- if (PLATFORM === "darwin" && paths.plist && existsSync9(paths.plist)) {
9548
+ if (PLATFORM === "darwin" && paths.plist && existsSync11(paths.plist)) {
9531
9549
  try {
9532
9550
  execSync2(`launchctl unload ${paths.plist}`);
9533
9551
  } catch {
9534
9552
  }
9535
9553
  unlinkSync(paths.plist);
9536
9554
  success("Removed macOS LaunchAgent", opts);
9537
- } else if (PLATFORM === "linux" && paths.service && existsSync9(paths.service)) {
9555
+ } else if (PLATFORM === "linux" && paths.service && existsSync11(paths.service)) {
9538
9556
  try {
9539
9557
  execSync2(`systemctl --user stop ${DAEMON_NAME}`);
9540
9558
  execSync2(`systemctl --user disable ${DAEMON_NAME}`);
@@ -9549,7 +9567,7 @@ daemonCommand.command("uninstall").description("Remove daemon from auto-start").
9549
9567
  } else {
9550
9568
  info("No daemon installation found", opts);
9551
9569
  }
9552
- if (existsSync9(paths.pidFile)) {
9570
+ if (existsSync11(paths.pidFile)) {
9553
9571
  unlinkSync(paths.pidFile);
9554
9572
  }
9555
9573
  });
@@ -9561,7 +9579,7 @@ daemonCommand.command("start").description("Start the file watcher daemon (watch
9561
9579
  info(`Daemon already running (PID: ${status.pid})`, opts);
9562
9580
  return;
9563
9581
  }
9564
- if (PLATFORM === "darwin" && paths.plist && existsSync9(paths.plist)) {
9582
+ if (PLATFORM === "darwin" && paths.plist && existsSync11(paths.plist)) {
9565
9583
  try {
9566
9584
  execSync2(`launchctl load ${paths.plist}`);
9567
9585
  success("Started daemon via launchctl (watching all projects)", opts);
@@ -9569,7 +9587,7 @@ daemonCommand.command("start").description("Start the file watcher daemon (watch
9569
9587
  error("Failed to start daemon via launchctl", opts);
9570
9588
  process.exit(1);
9571
9589
  }
9572
- } else if (PLATFORM === "linux" && paths.service && existsSync9(paths.service)) {
9590
+ } else if (PLATFORM === "linux" && paths.service && existsSync11(paths.service)) {
9573
9591
  try {
9574
9592
  execSync2(`systemctl --user start ${DAEMON_NAME}`);
9575
9593
  success("Started daemon via systemctl (watching all projects)", opts);
@@ -9586,7 +9604,7 @@ daemonCommand.command("start").description("Start the file watcher daemon (watch
9586
9604
  if (child.pid) {
9587
9605
  try {
9588
9606
  process.kill(child.pid, 0);
9589
- writeFileSync6(paths.pidFile, String(child.pid));
9607
+ writeFileSync7(paths.pidFile, String(child.pid));
9590
9608
  success(`Started daemon (PID: ${child.pid}) - watching all projects`, opts);
9591
9609
  } catch {
9592
9610
  error("Failed to start daemon - process died immediately", opts);
@@ -9601,14 +9619,14 @@ daemonCommand.command("start").description("Start the file watcher daemon (watch
9601
9619
  daemonCommand.command("stop").description("Stop the file watcher daemon").action(() => {
9602
9620
  const opts = getOutputOptions(daemonCommand);
9603
9621
  const paths = getDaemonPaths();
9604
- if (PLATFORM === "darwin" && paths.plist && existsSync9(paths.plist)) {
9622
+ if (PLATFORM === "darwin" && paths.plist && existsSync11(paths.plist)) {
9605
9623
  try {
9606
9624
  execSync2(`launchctl unload ${paths.plist}`);
9607
9625
  success("Stopped daemon via launchctl", opts);
9608
9626
  } catch {
9609
9627
  info("Daemon was not running", opts);
9610
9628
  }
9611
- } else if (PLATFORM === "linux" && paths.service && existsSync9(paths.service)) {
9629
+ } else if (PLATFORM === "linux" && paths.service && existsSync11(paths.service)) {
9612
9630
  try {
9613
9631
  execSync2(`systemctl --user stop ${DAEMON_NAME}`);
9614
9632
  success("Stopped daemon via systemctl", opts);
@@ -9620,7 +9638,7 @@ daemonCommand.command("stop").description("Stop the file watcher daemon").action
9620
9638
  if (status.running && status.pid) {
9621
9639
  try {
9622
9640
  process.kill(status.pid, "SIGTERM");
9623
- if (existsSync9(paths.pidFile)) {
9641
+ if (existsSync11(paths.pidFile)) {
9624
9642
  unlinkSync(paths.pidFile);
9625
9643
  }
9626
9644
  success(`Stopped daemon (PID: ${status.pid})`, opts);
@@ -9641,7 +9659,7 @@ daemonCommand.command("status").description("Show daemon status for ALL register
9641
9659
  const projectStats = [];
9642
9660
  let totalFiles = { allow: 0, unknown: 0, total: 0 };
9643
9661
  for (const project of projects) {
9644
- const valid = existsSync9(project.path) && existsSync9(join11(project.path, ".aigile"));
9662
+ const valid = existsSync11(project.path) && existsSync11(join12(project.path, ".aigile"));
9645
9663
  const counts = queryAll(`
9646
9664
  SELECT COALESCE(monitoring_category, 'unknown') as monitoring_category, COUNT(*) as count
9647
9665
  FROM documents
@@ -9677,7 +9695,7 @@ daemonCommand.command("status").description("Show daemon status for ALL register
9677
9695
  running: status.running,
9678
9696
  pid: status.pid ?? null,
9679
9697
  platform: PLATFORM,
9680
- installed: PLATFORM === "darwin" ? paths.plist && existsSync9(paths.plist) : PLATFORM === "linux" ? paths.service && existsSync9(paths.service) : false,
9698
+ installed: PLATFORM === "darwin" ? paths.plist && existsSync11(paths.plist) : PLATFORM === "linux" ? paths.service && existsSync11(paths.service) : false,
9681
9699
  projectCount: projects.length,
9682
9700
  validProjectCount: validCount,
9683
9701
  projects: projectStats,
@@ -9694,7 +9712,7 @@ daemonCommand.command("status").description("Show daemon status for ALL register
9694
9712
  console.log("\n\u{1F4CA} Daemon Status\n");
9695
9713
  console.log(`\u251C\u2500\u2500 Running: ${status.running ? "\u2705 Yes" : "\u274C No"}${status.pid ? ` (PID: ${status.pid})` : ""}`);
9696
9714
  console.log(`\u251C\u2500\u2500 Platform: ${PLATFORM}`);
9697
- const installed = PLATFORM === "darwin" ? paths.plist && existsSync9(paths.plist) : PLATFORM === "linux" ? paths.service && existsSync9(paths.service) : false;
9715
+ const installed = PLATFORM === "darwin" ? paths.plist && existsSync11(paths.plist) : PLATFORM === "linux" ? paths.service && existsSync11(paths.service) : false;
9698
9716
  console.log(`\u251C\u2500\u2500 Installed: ${installed ? "\u2705 Yes" : "\u274C No"}`);
9699
9717
  console.log(`\u251C\u2500\u2500 Projects: ${validCount}/${projects.length} valid`);
9700
9718
  if (projectStats.length > 0) {
@@ -9742,7 +9760,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
9742
9760
  console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] FATAL: Uncaught exception:`);
9743
9761
  console.error(err.stack || err.message);
9744
9762
  writeCrashReport(err);
9745
- if (existsSync9(paths.pidFile)) {
9763
+ if (existsSync11(paths.pidFile)) {
9746
9764
  try {
9747
9765
  unlinkSync(paths.pidFile);
9748
9766
  } catch {
@@ -9754,7 +9772,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
9754
9772
  console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] FATAL: Unhandled promise rejection:`);
9755
9773
  console.error(reason);
9756
9774
  writeCrashReport(reason);
9757
- if (existsSync9(paths.pidFile)) {
9775
+ if (existsSync11(paths.pidFile)) {
9758
9776
  try {
9759
9777
  unlinkSync(paths.pidFile);
9760
9778
  } catch {
@@ -9763,7 +9781,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
9763
9781
  process.exit(1);
9764
9782
  });
9765
9783
  rotateLogIfNeeded();
9766
- writeFileSync6(paths.pidFile, String(process.pid));
9784
+ writeFileSync7(paths.pidFile, String(process.pid));
9767
9785
  console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Starting AIGILE daemon for all registered projects...`);
9768
9786
  console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] PID: ${process.pid}, Node: ${process.version}, Platform: ${platform()}`);
9769
9787
  const manager = getDaemonManager();
@@ -9794,7 +9812,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
9794
9812
  console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Shutting down...`);
9795
9813
  const forceExitTimeout = setTimeout(() => {
9796
9814
  console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Shutdown timeout (${SHUTDOWN_TIMEOUT_MS}ms) - forcing exit`);
9797
- if (existsSync9(paths.pidFile)) {
9815
+ if (existsSync11(paths.pidFile)) {
9798
9816
  try {
9799
9817
  unlinkSync(paths.pidFile);
9800
9818
  } catch {
@@ -9808,7 +9826,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
9808
9826
  console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Error during shutdown: ${err}`);
9809
9827
  }
9810
9828
  clearTimeout(forceExitTimeout);
9811
- if (existsSync9(paths.pidFile)) {
9829
+ if (existsSync11(paths.pidFile)) {
9812
9830
  unlinkSync(paths.pidFile);
9813
9831
  }
9814
9832
  console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Daemon stopped gracefully`);
@@ -9832,7 +9850,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
9832
9850
  }
9833
9851
  } catch (err) {
9834
9852
  console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Failed to start daemon: ${err}`);
9835
- if (existsSync9(paths.pidFile)) {
9853
+ if (existsSync11(paths.pidFile)) {
9836
9854
  unlinkSync(paths.pidFile);
9837
9855
  }
9838
9856
  process.exit(1);
@@ -9841,7 +9859,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
9841
9859
  daemonCommand.command("logs").option("-n, --lines <number>", "Number of lines to show", "50").option("-f, --follow", "Follow log output").description("Show daemon logs").action((options) => {
9842
9860
  const opts = getOutputOptions(daemonCommand);
9843
9861
  const paths = getDaemonPaths();
9844
- if (!existsSync9(paths.logFile)) {
9862
+ if (!existsSync11(paths.logFile)) {
9845
9863
  info("No log file found. Daemon may not have run yet.", opts);
9846
9864
  return;
9847
9865
  }
@@ -10000,11 +10018,13 @@ function formatBytes(bytes) {
10000
10018
  init_connection();
10001
10019
  init_connection();
10002
10020
  import { Command as Command21 } from "commander";
10003
- import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
10004
- import { join as join12, relative as relative4 } from "path";
10021
+ import { readFileSync as readFileSync12, existsSync as existsSync12 } from "fs";
10022
+ import { join as join13, relative as relative6 } from "path";
10005
10023
  import { glob } from "glob";
10006
10024
  init_config();
10007
- var fileCommand = new Command21("file").description("Shadow mode file analysis and management for brownfield projects");
10025
+ var fileCommand = new Command21("file").description(
10026
+ "Shadow mode file analysis and management for brownfield projects"
10027
+ );
10008
10028
  function getProjectContext(opts) {
10009
10029
  const projectRoot = findProjectRoot();
10010
10030
  if (!projectRoot) {
@@ -10016,10 +10036,9 @@ function getProjectContext(opts) {
10016
10036
  error("Could not load project config.", opts);
10017
10037
  return null;
10018
10038
  }
10019
- const project = queryOne(
10020
- "SELECT id FROM projects WHERE key = ?",
10021
- [config.project.key]
10022
- );
10039
+ const project = queryOne("SELECT id FROM projects WHERE key = ?", [
10040
+ config.project.key
10041
+ ]);
10023
10042
  if (!project) {
10024
10043
  error(`Project "${config.project.key}" not found in database.`, opts);
10025
10044
  return null;
@@ -10042,12 +10061,15 @@ fileCommand.command("analyze <path>").option("--tldr <tldr>", "One-line summary
10042
10061
  if (!ctx) {
10043
10062
  process.exit(1);
10044
10063
  }
10045
- const normalizedPath = filePath.startsWith("/") ? relative4(ctx.projectRoot, filePath) : filePath;
10064
+ const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
10046
10065
  const doc = getDocumentWithAnalysis(ctx.projectId, normalizedPath);
10047
10066
  if (!doc) {
10048
10067
  const tracked = trackShadowFile(ctx.projectId, ctx.projectRoot, normalizedPath);
10049
10068
  if (!tracked) {
10050
- error(`File not tracked. Run "aigile sync scan" or track with "aigile file track ${normalizedPath}"`, opts);
10069
+ error(
10070
+ `File not tracked. Run "aigile sync scan" or track with "aigile file track ${normalizedPath}"`,
10071
+ opts
10072
+ );
10051
10073
  process.exit(1);
10052
10074
  }
10053
10075
  }
@@ -10072,24 +10094,26 @@ fileCommand.command("analyze <path>").option("--tldr <tldr>", "One-line summary
10072
10094
  }
10073
10095
  const updatedDoc = getDocumentWithAnalysis(ctx.projectId, normalizedPath);
10074
10096
  if (opts.json) {
10075
- console.log(JSON.stringify({
10076
- success: true,
10077
- data: {
10078
- path: normalizedPath,
10079
- analyzed: true,
10080
- analyzedAt: updatedDoc?.analyzed_at,
10081
- metadata: {
10082
- tldr: updatedDoc?.meta_tldr,
10083
- module: updatedDoc?.inferred_module,
10084
- component: updatedDoc?.inferred_component,
10085
- type: updatedDoc?.file_type,
10086
- dependencies: updatedDoc?.meta_dependencies ? JSON.parse(updatedDoc.meta_dependencies) : null,
10087
- exports: updatedDoc?.exports ? JSON.parse(updatedDoc.exports) : null,
10088
- complexity: updatedDoc?.complexity_score,
10089
- confidence: updatedDoc?.analysis_confidence
10097
+ console.log(
10098
+ JSON.stringify({
10099
+ success: true,
10100
+ data: {
10101
+ path: normalizedPath,
10102
+ analyzed: true,
10103
+ analyzedAt: updatedDoc?.analyzed_at,
10104
+ metadata: {
10105
+ tldr: updatedDoc?.meta_tldr,
10106
+ module: updatedDoc?.inferred_module,
10107
+ component: updatedDoc?.inferred_component,
10108
+ type: updatedDoc?.file_type,
10109
+ dependencies: updatedDoc?.meta_dependencies ? JSON.parse(updatedDoc.meta_dependencies) : null,
10110
+ exports: updatedDoc?.exports ? JSON.parse(updatedDoc.exports) : null,
10111
+ complexity: updatedDoc?.complexity_score,
10112
+ confidence: updatedDoc?.analysis_confidence
10113
+ }
10090
10114
  }
10091
- }
10092
- }));
10115
+ })
10116
+ );
10093
10117
  } else {
10094
10118
  success(`Analysis added for: ${normalizedPath}`, opts);
10095
10119
  if (updatedDoc?.meta_tldr) {
@@ -10138,24 +10162,26 @@ fileCommand.command("list").alias("ls").option("--unanalyzed", "Only files witho
10138
10162
  documents = getUnanalyzedDocuments(ctx.projectId, limit, offset);
10139
10163
  }
10140
10164
  if (opts.json || options.format === "json") {
10141
- console.log(JSON.stringify({
10142
- success: true,
10143
- data: documents.map((d) => ({
10144
- path: d.path,
10145
- filename: d.filename,
10146
- extension: d.extension,
10147
- module: d.inferred_module,
10148
- component: d.inferred_component,
10149
- fileType: d.file_type,
10150
- analyzed: !!d.analyzed_at,
10151
- analyzedAt: d.analyzed_at,
10152
- confidence: d.analysis_confidence,
10153
- tldr: d.meta_tldr,
10154
- dependencies: d.meta_dependencies ? JSON.parse(d.meta_dependencies) : null,
10155
- exports: d.exports ? JSON.parse(d.exports) : null,
10156
- complexity: d.complexity_score
10157
- }))
10158
- }));
10165
+ console.log(
10166
+ JSON.stringify({
10167
+ success: true,
10168
+ data: documents.map((d) => ({
10169
+ path: d.path,
10170
+ filename: d.filename,
10171
+ extension: d.extension,
10172
+ module: d.inferred_module,
10173
+ component: d.inferred_component,
10174
+ fileType: d.file_type,
10175
+ analyzed: !!d.analyzed_at,
10176
+ analyzedAt: d.analyzed_at,
10177
+ confidence: d.analysis_confidence,
10178
+ tldr: d.meta_tldr,
10179
+ dependencies: d.meta_dependencies ? JSON.parse(d.meta_dependencies) : null,
10180
+ exports: d.exports ? JSON.parse(d.exports) : null,
10181
+ complexity: d.complexity_score
10182
+ }))
10183
+ })
10184
+ );
10159
10185
  } else if (options.format === "paths") {
10160
10186
  documents.forEach((d) => console.log(d.path));
10161
10187
  } else {
@@ -10214,15 +10240,15 @@ fileCommand.command("read <path>").option("--with-metadata", "Include existing D
10214
10240
  if (!ctx) {
10215
10241
  process.exit(1);
10216
10242
  }
10217
- const normalizedPath = filePath.startsWith("/") ? relative4(ctx.projectRoot, filePath) : filePath;
10218
- const fullPath = join12(ctx.projectRoot, normalizedPath);
10219
- if (!existsSync10(fullPath)) {
10243
+ const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
10244
+ const fullPath = join13(ctx.projectRoot, normalizedPath);
10245
+ if (!existsSync12(fullPath)) {
10220
10246
  error(`File not found: ${normalizedPath}`, opts);
10221
10247
  process.exit(1);
10222
10248
  }
10223
10249
  let content;
10224
10250
  try {
10225
- content = readFileSync10(fullPath, "utf-8");
10251
+ content = readFileSync12(fullPath, "utf-8");
10226
10252
  } catch {
10227
10253
  error(`Could not read file: ${normalizedPath}`, opts);
10228
10254
  process.exit(1);
@@ -10236,22 +10262,24 @@ fileCommand.command("read <path>").option("--with-metadata", "Include existing D
10236
10262
  lines = lines.slice(0, options.limit);
10237
10263
  }
10238
10264
  if (opts.json) {
10239
- console.log(JSON.stringify({
10240
- success: true,
10241
- data: {
10242
- path: normalizedPath,
10243
- content: lines.join("\n"),
10244
- lineCount: lines.length,
10245
- metadata: metadata ? {
10246
- tldr: metadata.meta_tldr,
10247
- module: metadata.inferred_module,
10248
- component: metadata.inferred_component,
10249
- fileType: metadata.file_type,
10250
- analyzed: !!metadata.analyzed_at,
10251
- confidence: metadata.analysis_confidence
10252
- } : null
10253
- }
10254
- }));
10265
+ console.log(
10266
+ JSON.stringify({
10267
+ success: true,
10268
+ data: {
10269
+ path: normalizedPath,
10270
+ content: lines.join("\n"),
10271
+ lineCount: lines.length,
10272
+ metadata: metadata ? {
10273
+ tldr: metadata.meta_tldr,
10274
+ module: metadata.inferred_module,
10275
+ component: metadata.inferred_component,
10276
+ fileType: metadata.file_type,
10277
+ analyzed: !!metadata.analyzed_at,
10278
+ confidence: metadata.analysis_confidence
10279
+ } : null
10280
+ }
10281
+ })
10282
+ );
10255
10283
  } else {
10256
10284
  if (options.withMetadata && metadata) {
10257
10285
  console.log(`# File: ${normalizedPath}`);
@@ -10275,7 +10303,7 @@ fileCommand.command("track <path>").option("--type <type>", "File type classific
10275
10303
  if (!ctx) {
10276
10304
  process.exit(1);
10277
10305
  }
10278
- const normalizedPath = filePath.startsWith("/") ? relative4(ctx.projectRoot, filePath) : filePath;
10306
+ const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
10279
10307
  const tracked = trackShadowFile(ctx.projectId, ctx.projectRoot, normalizedPath);
10280
10308
  if (!tracked) {
10281
10309
  error(`Could not track file: ${normalizedPath}`, opts);
@@ -10290,18 +10318,20 @@ fileCommand.command("track <path>").option("--type <type>", "File type classific
10290
10318
  }
10291
10319
  const doc = getDocumentWithAnalysis(ctx.projectId, normalizedPath);
10292
10320
  if (opts.json) {
10293
- console.log(JSON.stringify({
10294
- success: true,
10295
- data: {
10296
- path: normalizedPath,
10297
- tracked: true,
10298
- shadowMode: true,
10299
- metadata: doc ? {
10300
- fileType: doc.file_type,
10301
- module: doc.inferred_module
10302
- } : null
10303
- }
10304
- }));
10321
+ console.log(
10322
+ JSON.stringify({
10323
+ success: true,
10324
+ data: {
10325
+ path: normalizedPath,
10326
+ tracked: true,
10327
+ shadowMode: true,
10328
+ metadata: doc ? {
10329
+ fileType: doc.file_type,
10330
+ module: doc.inferred_module
10331
+ } : null
10332
+ }
10333
+ })
10334
+ );
10305
10335
  } else {
10306
10336
  success(`Tracked in shadow mode: ${normalizedPath}`, opts);
10307
10337
  }
@@ -10312,39 +10342,41 @@ fileCommand.command("show <path>").description("Show detailed file analysis").ac
10312
10342
  if (!ctx) {
10313
10343
  process.exit(1);
10314
10344
  }
10315
- const normalizedPath = filePath.startsWith("/") ? relative4(ctx.projectRoot, filePath) : filePath;
10345
+ const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
10316
10346
  const doc = getDocumentWithAnalysis(ctx.projectId, normalizedPath);
10317
10347
  if (!doc) {
10318
10348
  error(`File not tracked: ${normalizedPath}`, opts);
10319
10349
  process.exit(1);
10320
10350
  }
10321
10351
  if (opts.json) {
10322
- console.log(JSON.stringify({
10323
- success: true,
10324
- data: {
10325
- path: doc.path,
10326
- filename: doc.filename,
10327
- extension: doc.extension,
10328
- status: doc.status,
10329
- sizeBytes: doc.size_bytes,
10330
- lastScanned: doc.last_scanned_at,
10331
- hasFrontmatter: !!doc.has_frontmatter,
10332
- shadowMode: !!doc.shadow_mode,
10333
- analysis: {
10334
- analyzed: !!doc.analyzed_at,
10335
- analyzedAt: doc.analyzed_at,
10336
- confidence: doc.analysis_confidence,
10337
- tldr: doc.meta_tldr,
10338
- module: doc.inferred_module,
10339
- component: doc.inferred_component,
10340
- fileType: doc.file_type,
10341
- complexity: doc.complexity_score,
10342
- dependencies: doc.meta_dependencies ? JSON.parse(doc.meta_dependencies) : null,
10343
- exports: doc.exports ? JSON.parse(doc.exports) : null,
10344
- notes: doc.analysis_notes
10352
+ console.log(
10353
+ JSON.stringify({
10354
+ success: true,
10355
+ data: {
10356
+ path: doc.path,
10357
+ filename: doc.filename,
10358
+ extension: doc.extension,
10359
+ status: doc.status,
10360
+ sizeBytes: doc.size_bytes,
10361
+ lastScanned: doc.last_scanned_at,
10362
+ hasFrontmatter: !!doc.has_frontmatter,
10363
+ shadowMode: !!doc.shadow_mode,
10364
+ analysis: {
10365
+ analyzed: !!doc.analyzed_at,
10366
+ analyzedAt: doc.analyzed_at,
10367
+ confidence: doc.analysis_confidence,
10368
+ tldr: doc.meta_tldr,
10369
+ module: doc.inferred_module,
10370
+ component: doc.inferred_component,
10371
+ fileType: doc.file_type,
10372
+ complexity: doc.complexity_score,
10373
+ dependencies: doc.meta_dependencies ? JSON.parse(doc.meta_dependencies) : null,
10374
+ exports: doc.exports ? JSON.parse(doc.exports) : null,
10375
+ notes: doc.analysis_notes
10376
+ }
10345
10377
  }
10346
- }
10347
- }));
10378
+ })
10379
+ );
10348
10380
  } else {
10349
10381
  const displayData = {
10350
10382
  path: doc.path,
@@ -10403,8 +10435,8 @@ fileCommand.command("tag").argument("<path>", "File path to tag").option("--chun
10403
10435
  error('No active session. Start one with "aigile session start".', opts);
10404
10436
  process.exit(1);
10405
10437
  }
10406
- const normalizedPath = filePath.startsWith("/") ? relative4(ctx.projectRoot, filePath) : filePath;
10407
- const doc = queryOne(
10438
+ const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
10439
+ let doc = queryOne(
10408
10440
  "SELECT id FROM documents WHERE project_id = ? AND path = ?",
10409
10441
  [ctx.projectId, normalizedPath]
10410
10442
  );
@@ -10422,7 +10454,7 @@ fileCommand.command("tag").argument("<path>", "File path to tag").option("--chun
10422
10454
  error(`Could not track file: ${normalizedPath}`, opts);
10423
10455
  process.exit(1);
10424
10456
  }
10425
- doc.id = newDoc.id;
10457
+ doc = newDoc;
10426
10458
  }
10427
10459
  const validTypes = ["assigned", "explored", "skipped"];
10428
10460
  if (!validTypes.includes(options.type)) {
@@ -10437,17 +10469,19 @@ fileCommand.command("tag").argument("<path>", "File path to tag").option("--chun
10437
10469
  isFoundational: options.foundational ?? false
10438
10470
  });
10439
10471
  if (opts.json) {
10440
- console.log(JSON.stringify({
10441
- success: true,
10442
- data: {
10443
- session_file_id: sessionFileId,
10444
- path: normalizedPath,
10445
- session_id: session.id,
10446
- chunk_id: options.chunk ?? null,
10447
- review_type: options.type,
10448
- is_foundational: options.foundational ?? false
10449
- }
10450
- }));
10472
+ console.log(
10473
+ JSON.stringify({
10474
+ success: true,
10475
+ data: {
10476
+ session_file_id: sessionFileId,
10477
+ path: normalizedPath,
10478
+ session_id: session.id,
10479
+ chunk_id: options.chunk ?? null,
10480
+ review_type: options.type,
10481
+ is_foundational: options.foundational ?? false
10482
+ }
10483
+ })
10484
+ );
10451
10485
  } else {
10452
10486
  success(`Tagged: ${normalizedPath}`, opts);
10453
10487
  if (options.chunk) {
@@ -10470,7 +10504,7 @@ fileCommand.command("tag-batch").option("--chunk <id>", "Chunk ID for all files"
10470
10504
  let filesToTag = [];
10471
10505
  if (options.glob) {
10472
10506
  const matches = await glob(options.glob, { cwd: ctx.projectRoot, nodir: true });
10473
- filesToTag = matches.map((f) => relative4(ctx.projectRoot, join12(ctx.projectRoot, f)));
10507
+ filesToTag = matches.map((f) => relative6(ctx.projectRoot, join13(ctx.projectRoot, f)));
10474
10508
  } else {
10475
10509
  error("Please provide --glob pattern. Stdin not supported yet.", opts);
10476
10510
  process.exit(1);
@@ -10499,10 +10533,12 @@ fileCommand.command("tag-batch").option("--chunk <id>", "Chunk ID for all files"
10499
10533
  tagged++;
10500
10534
  }
10501
10535
  if (opts.json) {
10502
- console.log(JSON.stringify({
10503
- success: true,
10504
- data: { tagged, skipped, total: filesToTag.length }
10505
- }));
10536
+ console.log(
10537
+ JSON.stringify({
10538
+ success: true,
10539
+ data: { tagged, skipped, total: filesToTag.length }
10540
+ })
10541
+ );
10506
10542
  } else {
10507
10543
  success(`Tagged ${tagged} files (${skipped} skipped - not tracked)`, opts);
10508
10544
  }
@@ -10538,12 +10574,14 @@ fileCommand.command("untag").argument("<path>", "File path to untag").option("--
10538
10574
  warning(`File "${filePath}" is not tagged in this session.`, opts);
10539
10575
  return;
10540
10576
  }
10541
- queryOne("DELETE FROM session_files WHERE id = ?", [existing.id]);
10577
+ run("DELETE FROM session_files WHERE id = ?", [existing.id]);
10542
10578
  if (opts.json) {
10543
- console.log(JSON.stringify({
10544
- success: true,
10545
- data: { path: filePath, untagged: true }
10546
- }));
10579
+ console.log(
10580
+ JSON.stringify({
10581
+ success: true,
10582
+ data: { path: filePath, untagged: true }
10583
+ })
10584
+ );
10547
10585
  } else {
10548
10586
  success(`Untagged: ${filePath}`, opts);
10549
10587
  }
@@ -10586,12 +10624,14 @@ fileCommand.command("clear-tags").option("--session <id>", "Session ID (default:
10586
10624
  deleteQuery += " AND chunk_id = ?";
10587
10625
  deleteParams.push(options.chunk);
10588
10626
  }
10589
- queryOne(deleteQuery, deleteParams);
10627
+ run(deleteQuery, deleteParams);
10590
10628
  if (opts.json) {
10591
- console.log(JSON.stringify({
10592
- success: true,
10593
- data: { session_id: sessionId, cleared: count }
10594
- }));
10629
+ console.log(
10630
+ JSON.stringify({
10631
+ success: true,
10632
+ data: { session_id: sessionId, cleared: count }
10633
+ })
10634
+ );
10595
10635
  } else {
10596
10636
  success(`Cleared ${count} tag(s)`, opts);
10597
10637
  }
@@ -10616,15 +10656,17 @@ fileCommand.command("untagged").option("--session <id>", "Session ID (default: c
10616
10656
  assignedOnly: options.assignedOnly
10617
10657
  });
10618
10658
  if (opts.json) {
10619
- console.log(JSON.stringify({
10620
- success: true,
10621
- data: {
10622
- session_id: sessionId,
10623
- chunk_id: options.chunk ?? null,
10624
- count: untagged.length,
10625
- files: untagged.map((f) => f.path)
10626
- }
10627
- }));
10659
+ console.log(
10660
+ JSON.stringify({
10661
+ success: true,
10662
+ data: {
10663
+ session_id: sessionId,
10664
+ chunk_id: options.chunk ?? null,
10665
+ count: untagged.length,
10666
+ files: untagged.map((f) => f.path)
10667
+ }
10668
+ })
10669
+ );
10628
10670
  } else {
10629
10671
  if (untagged.length === 0) {
10630
10672
  success("All files have been tagged!", opts);
@@ -10692,22 +10734,24 @@ fileCommand.command("coverage").option("--session <id>", "Session ID (default: c
10692
10734
  const total = totalTagged + untagged.length;
10693
10735
  const pct = total > 0 ? Math.round(totalTagged / total * 100) : 100;
10694
10736
  if (opts.json) {
10695
- console.log(JSON.stringify({
10696
- success: true,
10697
- data: {
10698
- session_id: sessionId,
10699
- total_files: total,
10700
- tagged: totalTagged,
10701
- untagged: untagged.length,
10702
- coverage_percent: pct,
10703
- by_type: {
10704
- assigned: stats.assigned.reviewed,
10705
- explored: stats.explored,
10706
- foundational: stats.foundational,
10707
- skipped: stats.skipped
10737
+ console.log(
10738
+ JSON.stringify({
10739
+ success: true,
10740
+ data: {
10741
+ session_id: sessionId,
10742
+ total_files: total,
10743
+ tagged: totalTagged,
10744
+ untagged: untagged.length,
10745
+ coverage_percent: pct,
10746
+ by_type: {
10747
+ assigned: stats.assigned.reviewed,
10748
+ explored: stats.explored,
10749
+ foundational: stats.foundational,
10750
+ skipped: stats.skipped
10751
+ }
10708
10752
  }
10709
- }
10710
- }));
10753
+ })
10754
+ );
10711
10755
  } else {
10712
10756
  console.log(`
10713
10757
  Coverage for session ${sessionId.slice(0, 8)}...`);
@@ -10734,7 +10778,7 @@ fileCommand.command("flag").argument("<path>", "File path to flag").option("--du
10734
10778
  error('No active session. Start one with "aigile session start".', opts);
10735
10779
  process.exit(1);
10736
10780
  }
10737
- const normalizedPath = filePath.startsWith("/") ? relative4(ctx.projectRoot, filePath) : filePath;
10781
+ const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
10738
10782
  const sessionFile = queryOne(
10739
10783
  `SELECT sf.id FROM session_files sf
10740
10784
  JOIN documents d ON sf.document_id = d.id
@@ -10742,7 +10786,10 @@ fileCommand.command("flag").argument("<path>", "File path to flag").option("--du
10742
10786
  [session.id, normalizedPath]
10743
10787
  );
10744
10788
  if (!sessionFile) {
10745
- error(`File not tagged in this session: ${normalizedPath}. Tag it first with "aigile file tag".`, opts);
10789
+ error(
10790
+ `File not tagged in this session: ${normalizedPath}. Tag it first with "aigile file tag".`,
10791
+ opts
10792
+ );
10746
10793
  process.exit(1);
10747
10794
  }
10748
10795
  const issues = [];
@@ -10761,13 +10808,15 @@ fileCommand.command("flag").argument("<path>", "File path to flag").option("--du
10761
10808
  }
10762
10809
  flagFileQualityIssue(sessionFile.id, issues);
10763
10810
  if (opts.json) {
10764
- console.log(JSON.stringify({
10765
- success: true,
10766
- data: {
10767
- path: normalizedPath,
10768
- issues
10769
- }
10770
- }));
10811
+ console.log(
10812
+ JSON.stringify({
10813
+ success: true,
10814
+ data: {
10815
+ path: normalizedPath,
10816
+ issues
10817
+ }
10818
+ })
10819
+ );
10771
10820
  } else {
10772
10821
  success(`Flagged: ${normalizedPath}`, opts);
10773
10822
  for (const issue of issues) {
@@ -10795,10 +10844,9 @@ fileCommand.command("duplicates").option("--session <id>", "Session ID (default:
10795
10844
  for (const sf of filesWithIssues) {
10796
10845
  if (!sf.quality_issues) continue;
10797
10846
  const issues = JSON.parse(sf.quality_issues);
10798
- const doc = queryOne(
10799
- "SELECT path FROM documents WHERE id = ?",
10800
- [sf.document_id]
10801
- );
10847
+ const doc = queryOne("SELECT path FROM documents WHERE id = ?", [
10848
+ sf.document_id
10849
+ ]);
10802
10850
  if (!doc) continue;
10803
10851
  for (const issue of issues) {
10804
10852
  if (issue.startsWith("duplicate:")) {
@@ -10813,10 +10861,12 @@ fileCommand.command("duplicates").option("--session <id>", "Session ID (default:
10813
10861
  }
10814
10862
  }
10815
10863
  if (opts.json) {
10816
- console.log(JSON.stringify({
10817
- success: true,
10818
- data: { duplicates }
10819
- }));
10864
+ console.log(
10865
+ JSON.stringify({
10866
+ success: true,
10867
+ data: { duplicates }
10868
+ })
10869
+ );
10820
10870
  } else {
10821
10871
  if (duplicates.length === 0) {
10822
10872
  info("No duplicates flagged.", opts);
@@ -10853,10 +10903,9 @@ fileCommand.command("issues").option("--session <id>", "Session ID (default: cur
10853
10903
  }
10854
10904
  const issueList = [];
10855
10905
  for (const sf of filesWithIssues) {
10856
- const doc = queryOne(
10857
- "SELECT path FROM documents WHERE id = ?",
10858
- [sf.document_id]
10859
- );
10906
+ const doc = queryOne("SELECT path FROM documents WHERE id = ?", [
10907
+ sf.document_id
10908
+ ]);
10860
10909
  if (!doc) continue;
10861
10910
  issueList.push({
10862
10911
  path: doc.path,
@@ -10864,10 +10913,12 @@ fileCommand.command("issues").option("--session <id>", "Session ID (default: cur
10864
10913
  });
10865
10914
  }
10866
10915
  if (opts.json) {
10867
- console.log(JSON.stringify({
10868
- success: true,
10869
- data: { files_with_issues: issueList }
10870
- }));
10916
+ console.log(
10917
+ JSON.stringify({
10918
+ success: true,
10919
+ data: { files_with_issues: issueList }
10920
+ })
10921
+ );
10871
10922
  } else {
10872
10923
  console.log(`Files with quality issues (${issueList.length}):`);
10873
10924
  for (const file of issueList) {
@@ -10885,7 +10936,7 @@ init_connection();
10885
10936
  init_connection();
10886
10937
  import { Command as Command22 } from "commander";
10887
10938
  import { glob as glob2 } from "glob";
10888
- import { relative as relative5, resolve as resolve2 } from "path";
10939
+ import { relative as relative7, resolve as resolve5 } from "path";
10889
10940
  init_config();
10890
10941
  function safeParseArray(json) {
10891
10942
  if (!json) return [];
@@ -10932,7 +10983,7 @@ chunkCommand.command("create").argument("<id>", "Chunk ID (e.g., chunk-001)").op
10932
10983
  if (options.pattern) {
10933
10984
  for (const pattern of options.pattern) {
10934
10985
  const matches = await glob2(pattern, { cwd: projectRoot, nodir: true });
10935
- assignedFiles.push(...matches.map((f) => relative5(projectRoot, resolve2(projectRoot, f))));
10986
+ assignedFiles.push(...matches.map((f) => relative7(projectRoot, resolve5(projectRoot, f))));
10936
10987
  }
10937
10988
  }
10938
10989
  if (options.assign) {
@@ -11046,12 +11097,12 @@ chunkCommand.command("list").alias("ls").description("List all chunks in current
11046
11097
  }
11047
11098
  const session = getActiveSession(project.id);
11048
11099
  if (!session) {
11049
- warning2("No active session.", opts);
11100
+ warning("No active session.", opts);
11050
11101
  return;
11051
11102
  }
11052
11103
  const chunks = getSessionChunks(session.id);
11053
11104
  if (chunks.length === 0) {
11054
- warning2("No chunks defined in current session.", opts);
11105
+ warning("No chunks defined in current session.", opts);
11055
11106
  return;
11056
11107
  }
11057
11108
  data(