@vladimir-ks/aigile 0.2.5 → 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.
- package/README.md +29 -12
- package/dist/bin/aigile.js +1462 -1411
- package/dist/bin/aigile.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/scripts/install-artifacts.js +109 -0
- package/package.json +11 -6
package/dist/bin/aigile.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
854
|
-
|
|
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
|
-
|
|
879
|
-
|
|
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
|
-
|
|
891
|
-
|
|
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
|
-
|
|
946
|
-
|
|
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(
|
|
1167
|
-
|
|
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(
|
|
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.
|
|
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
|
|
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
|
|
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-
|
|
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
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
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
|
|
1923
|
-
|
|
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
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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.
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
1469
|
+
const fullPath = join3(targetDir, template.relativePath);
|
|
1470
|
+
if (existsSync3(fullPath)) {
|
|
1471
|
+
skipped++;
|
|
1472
|
+
continue;
|
|
2039
1473
|
}
|
|
2040
|
-
|
|
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
|
-
}
|
|
2044
|
-
|
|
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,
|
|
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", "
|
|
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
|
|
2133
|
-
return "
|
|
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 "
|
|
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(
|
|
1656
|
+
aigileConfig.parent_repo_root = "../".repeat(
|
|
1657
|
+
context.relativePath.split("/").filter((p) => p).length
|
|
1658
|
+
);
|
|
2229
1659
|
}
|
|
2230
|
-
if (profile === "
|
|
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
|
-
|
|
2238
|
-
|
|
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
|
-
|
|
2244
|
-
|
|
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
|
-
|
|
2250
|
-
|
|
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
|
-
|
|
2255
|
-
|
|
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(
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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 === "
|
|
2300
|
-
console.log(" 1. Fill in 00_DOCS/
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2465
|
+
warning(`Deleting ${childCount.count} child task(s)`, opts);
|
|
3021
2466
|
run("DELETE FROM tasks WHERE story_id = ?", [story.id]);
|
|
3022
2467
|
} else {
|
|
3023
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 = [
|
|
4096
|
-
...existing.metadata.dependencies,
|
|
4097
|
-
|
|
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
|
-
|
|
4103
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
4443
|
-
return
|
|
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
|
|
4449
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
5064
|
-
|
|
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
|
-
|
|
5098
|
-
|
|
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
|
-
|
|
5168
|
-
|
|
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 {
|
|
8423
|
-
import {
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
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
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
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
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
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
|
|
7841
|
+
return relativePath;
|
|
8443
7842
|
}
|
|
8444
|
-
function
|
|
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
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
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
|
-
|
|
8459
|
-
const
|
|
8460
|
-
|
|
8461
|
-
|
|
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
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
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
|
-
|
|
8493
|
-
const opts = getOutputOptions(
|
|
8494
|
-
const
|
|
8495
|
-
|
|
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
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
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
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
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
|
-
|
|
8560
|
-
const opts = getOutputOptions(
|
|
8561
|
-
const
|
|
8562
|
-
|
|
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
|
-
|
|
8566
|
-
|
|
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
|
-
|
|
8569
|
-
} catch {
|
|
8570
|
-
error(
|
|
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
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
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
|
-
|
|
8578
|
-
|
|
8254
|
+
const absolutePath = resolve3(cwd, filePath);
|
|
8255
|
+
if (!existsSync7(absolutePath)) {
|
|
8256
|
+
throw new Error(`File not found: ${filePath}`);
|
|
8579
8257
|
}
|
|
8580
|
-
|
|
8581
|
-
|
|
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
|
-
|
|
8584
|
-
|
|
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
|
-
|
|
8587
|
-
|
|
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
|
-
|
|
8590
|
-
|
|
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
|
-
|
|
8593
|
-
|
|
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
|
-
|
|
8596
|
-
|
|
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
|
-
|
|
8599
|
-
|
|
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
|
|
8603
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
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
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
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
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
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
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
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(
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
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
|
|
8713
|
-
import { join as
|
|
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
|
|
8720
|
-
import { join as
|
|
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
|
|
8727
|
-
import { readFileSync as
|
|
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
|
-
`,
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
8984
|
-
|
|
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(
|
|
9135
|
-
|
|
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(
|
|
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.
|
|
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(
|
|
9157
|
-
|
|
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
|
|
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:
|
|
9322
|
-
logFile:
|
|
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:
|
|
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:
|
|
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 (!
|
|
9357
|
+
if (!existsSync11(paths.pidFile)) {
|
|
9340
9358
|
return { running: false };
|
|
9341
9359
|
}
|
|
9342
9360
|
try {
|
|
9343
|
-
const pid = parseInt(
|
|
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 =
|
|
9358
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 (!
|
|
9399
|
-
const stats =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
9485
|
-
if (!
|
|
9502
|
+
const plistDir = dirname4(paths.plist);
|
|
9503
|
+
if (!existsSync11(plistDir)) {
|
|
9486
9504
|
mkdirSync5(plistDir, { recursive: true });
|
|
9487
9505
|
}
|
|
9488
9506
|
const plistContent = generateLaunchAgentPlist();
|
|
9489
|
-
|
|
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 =
|
|
9496
|
-
if (!
|
|
9513
|
+
const serviceDir = dirname4(paths.service);
|
|
9514
|
+
if (!existsSync11(serviceDir)) {
|
|
9497
9515
|
mkdirSync5(serviceDir, { recursive: true });
|
|
9498
9516
|
}
|
|
9499
9517
|
const serviceContent = generateSystemdService();
|
|
9500
|
-
|
|
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
|
-
|
|
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 &&
|
|
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 &&
|
|
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 (
|
|
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 &&
|
|
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 &&
|
|
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
|
-
|
|
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 &&
|
|
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 &&
|
|
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 (
|
|
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 =
|
|
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 &&
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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
|
|
10004
|
-
import { join as
|
|
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(
|
|
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
|
-
|
|
10021
|
-
|
|
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("/") ?
|
|
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(
|
|
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(
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
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(
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
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("/") ?
|
|
10218
|
-
const fullPath =
|
|
10219
|
-
if (!
|
|
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 =
|
|
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(
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
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("/") ?
|
|
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(
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
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("/") ?
|
|
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(
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
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("/") ?
|
|
10407
|
-
|
|
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
|
|
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(
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
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) =>
|
|
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(
|
|
10503
|
-
|
|
10504
|
-
|
|
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
|
-
|
|
10577
|
+
run("DELETE FROM session_files WHERE id = ?", [existing.id]);
|
|
10542
10578
|
if (opts.json) {
|
|
10543
|
-
console.log(
|
|
10544
|
-
|
|
10545
|
-
|
|
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
|
-
|
|
10627
|
+
run(deleteQuery, deleteParams);
|
|
10590
10628
|
if (opts.json) {
|
|
10591
|
-
console.log(
|
|
10592
|
-
|
|
10593
|
-
|
|
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(
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
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(
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
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("/") ?
|
|
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(
|
|
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(
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
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
|
-
|
|
10800
|
-
|
|
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(
|
|
10817
|
-
|
|
10818
|
-
|
|
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
|
-
|
|
10858
|
-
|
|
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(
|
|
10868
|
-
|
|
10869
|
-
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
11105
|
+
warning("No chunks defined in current session.", opts);
|
|
11055
11106
|
return;
|
|
11056
11107
|
}
|
|
11057
11108
|
data(
|