@vladimir-ks/aigile 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/aigile.js +1613 -1482
- 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 +4 -1
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,15 +1246,15 @@ 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.8" : "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
|
|
1197
|
-
import { resolve, join as join4, relative, basename as basename2 } from "path";
|
|
1256
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, appendFileSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1257
|
+
import { resolve as resolve2, join as join4, relative, basename as basename2 } from "path";
|
|
1198
1258
|
|
|
1199
1259
|
// src/utils/git.ts
|
|
1200
1260
|
import { execSync } from "child_process";
|
|
@@ -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,749 +1411,99 @@ 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";
|
|
1356
|
-
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
|
-
];
|
|
1414
|
+
// src/services/template-loader.ts
|
|
1415
|
+
import { readdirSync, statSync, readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1416
|
+
import { join as join3, dirname as dirname2, resolve, sep } from "path";
|
|
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 = [];
|
|
1468
|
+
const resolvedTargetDir = resolve(targetDir);
|
|
2034
1469
|
for (const template of templates) {
|
|
2035
|
-
const fullPath = join3(targetDir, template.
|
|
2036
|
-
const
|
|
2037
|
-
if (!
|
|
2038
|
-
|
|
1470
|
+
const fullPath = join3(targetDir, template.relativePath);
|
|
1471
|
+
const resolvedFullPath = resolve(fullPath);
|
|
1472
|
+
if (!resolvedFullPath.startsWith(resolvedTargetDir + sep) && resolvedFullPath !== resolvedTargetDir) {
|
|
1473
|
+
errors.push({
|
|
1474
|
+
path: template.relativePath,
|
|
1475
|
+
error: "Path traversal detected - file path escapes target directory"
|
|
1476
|
+
});
|
|
1477
|
+
continue;
|
|
2039
1478
|
}
|
|
2040
|
-
if (
|
|
2041
|
-
writeFileSync3(fullPath, template.content, "utf-8");
|
|
2042
|
-
written++;
|
|
2043
|
-
} else {
|
|
1479
|
+
if (existsSync3(fullPath)) {
|
|
2044
1480
|
skipped++;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
try {
|
|
1484
|
+
const dir = dirname2(fullPath);
|
|
1485
|
+
if (!existsSync3(dir)) {
|
|
1486
|
+
mkdirSync3(dir, { recursive: true });
|
|
1487
|
+
}
|
|
1488
|
+
writeFileSync3(fullPath, template.content, "utf-8");
|
|
1489
|
+
written++;
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
errors.push({
|
|
1492
|
+
path: template.relativePath,
|
|
1493
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1494
|
+
});
|
|
2045
1495
|
}
|
|
2046
1496
|
}
|
|
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;
|
|
1497
|
+
return { written, skipped, errors };
|
|
2091
1498
|
}
|
|
2092
1499
|
|
|
2093
1500
|
// 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,
|
|
1501
|
+
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(
|
|
1502
|
+
"--module-kind <kind>",
|
|
1503
|
+
"Module kind: library, service, ui, cli, other (for module profile)"
|
|
1504
|
+
).option("--skip-templates", "Skip template file creation").option("-f, --force", "Reinitialize existing project").action(async (pathArg, options) => {
|
|
2095
1505
|
const opts = getOutputOptions(initCommand);
|
|
2096
|
-
const targetPath =
|
|
1506
|
+
const targetPath = resolve2(pathArg ?? process.cwd());
|
|
2097
1507
|
try {
|
|
2098
1508
|
await initProject(targetPath, options, opts);
|
|
2099
1509
|
} catch (err) {
|
|
@@ -2118,7 +1528,7 @@ function detectGitContext(targetPath) {
|
|
|
2118
1528
|
relativePath
|
|
2119
1529
|
};
|
|
2120
1530
|
}
|
|
2121
|
-
var VALID_PROFILES = ["full-repo", "
|
|
1531
|
+
var VALID_PROFILES = ["full-repo", "minor-project", "module"];
|
|
2122
1532
|
function determineProfile(context, options, opts) {
|
|
2123
1533
|
if (options.profile) {
|
|
2124
1534
|
if (!VALID_PROFILES.includes(options.profile)) {
|
|
@@ -2129,8 +1539,8 @@ function determineProfile(context, options, opts) {
|
|
|
2129
1539
|
return options.profile;
|
|
2130
1540
|
}
|
|
2131
1541
|
if (context.isSubmodule) {
|
|
2132
|
-
info("Detected git submodule - using
|
|
2133
|
-
return "
|
|
1542
|
+
info("Detected git submodule - using minor-project profile", opts);
|
|
1543
|
+
return "minor-project";
|
|
2134
1544
|
}
|
|
2135
1545
|
if (context.isSubdirectory) {
|
|
2136
1546
|
const parentAigile = join4(context.gitRoot, ".aigile");
|
|
@@ -2153,7 +1563,7 @@ function determineDbMode(profile, context, options) {
|
|
|
2153
1563
|
switch (profile) {
|
|
2154
1564
|
case "full-repo":
|
|
2155
1565
|
return { mode: "local", path: ".aigile/aigile.db" };
|
|
2156
|
-
case "
|
|
1566
|
+
case "minor-project":
|
|
2157
1567
|
return { mode: "local", path: ".aigile/aigile.db" };
|
|
2158
1568
|
case "module":
|
|
2159
1569
|
const parentDbPath = findParentDb(context);
|
|
@@ -2176,6 +1586,33 @@ function findParentDb(context) {
|
|
|
2176
1586
|
}
|
|
2177
1587
|
return ".aigile/aigile.db";
|
|
2178
1588
|
}
|
|
1589
|
+
function generateConfigYaml(config, projectKey, projectName) {
|
|
1590
|
+
const lines = ["# AIGILE Project Configuration", ""];
|
|
1591
|
+
lines.push("project:");
|
|
1592
|
+
lines.push(` key: "${projectKey}"`);
|
|
1593
|
+
lines.push(` name: "${projectName}"`);
|
|
1594
|
+
lines.push("");
|
|
1595
|
+
lines.push("db:");
|
|
1596
|
+
lines.push(` mode: ${config.db.mode}`);
|
|
1597
|
+
lines.push(` path: ${config.db.path}`);
|
|
1598
|
+
lines.push("");
|
|
1599
|
+
lines.push(`profile: ${config.profile}`);
|
|
1600
|
+
lines.push("");
|
|
1601
|
+
lines.push(`repo_root: ${config.repo_root}`);
|
|
1602
|
+
if (config.module) {
|
|
1603
|
+
lines.push("");
|
|
1604
|
+
lines.push("module:");
|
|
1605
|
+
lines.push(` name: ${config.module.name}`);
|
|
1606
|
+
lines.push(` kind: ${config.module.kind}`);
|
|
1607
|
+
lines.push(` path: ${config.module.path}`);
|
|
1608
|
+
}
|
|
1609
|
+
if (config.parent_repo_root) {
|
|
1610
|
+
lines.push("");
|
|
1611
|
+
lines.push(`parent_repo_root: ${config.parent_repo_root}`);
|
|
1612
|
+
}
|
|
1613
|
+
lines.push("");
|
|
1614
|
+
return lines.join("\n");
|
|
1615
|
+
}
|
|
2179
1616
|
async function initProject(targetPath, options, opts) {
|
|
2180
1617
|
if (!isGitRepo(targetPath)) {
|
|
2181
1618
|
throw new Error('AIGILE requires a git repository. Run "git init" first.');
|
|
@@ -2225,35 +1662,47 @@ async function initProject(targetPath, options, opts) {
|
|
|
2225
1662
|
kind: options.moduleKind ?? "other",
|
|
2226
1663
|
path: context.relativePath
|
|
2227
1664
|
};
|
|
2228
|
-
aigileConfig.parent_repo_root = "../".repeat(
|
|
1665
|
+
aigileConfig.parent_repo_root = "../".repeat(
|
|
1666
|
+
context.relativePath.split("/").filter((p) => p).length
|
|
1667
|
+
);
|
|
2229
1668
|
}
|
|
2230
|
-
if (profile === "
|
|
1669
|
+
if (profile === "minor-project" && context.superprojectRoot) {
|
|
2231
1670
|
aigileConfig.parent_repo_root = "..";
|
|
2232
1671
|
}
|
|
2233
1672
|
const configYaml = generateConfigYaml(aigileConfig, projectKey, projectName);
|
|
2234
1673
|
writeFileSync4(join4(aigileDir, "config.yaml"), configYaml, "utf-8");
|
|
2235
|
-
let templatesResult = { written: 0, skipped: 0 };
|
|
1674
|
+
let templatesResult = { written: 0, skipped: 0, errors: [] };
|
|
2236
1675
|
if (!options.skipTemplates) {
|
|
2237
|
-
|
|
2238
|
-
|
|
1676
|
+
try {
|
|
1677
|
+
const templates = loadTemplates(profile);
|
|
1678
|
+
templatesResult = writeTemplates(targetPath, templates);
|
|
1679
|
+
} catch (err) {
|
|
1680
|
+
warning(
|
|
1681
|
+
`Could not load templates: ${err instanceof Error ? err.message : String(err)}`,
|
|
1682
|
+
opts
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
2239
1685
|
}
|
|
2240
1686
|
if (dbConfig.mode === "local") {
|
|
2241
1687
|
registerProject(targetPath, (projectId) => {
|
|
2242
|
-
const existingProject = queryOne(
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
);
|
|
1688
|
+
const existingProject = queryOne("SELECT id FROM projects WHERE path = ?", [
|
|
1689
|
+
targetPath
|
|
1690
|
+
]);
|
|
2246
1691
|
const id = existingProject?.id ?? generateId();
|
|
2247
1692
|
if (existingProject) {
|
|
2248
|
-
run(
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
1693
|
+
run(`UPDATE projects SET key = ?, name = ?, updated_at = datetime('now') WHERE id = ?`, [
|
|
1694
|
+
projectKey,
|
|
1695
|
+
projectName,
|
|
1696
|
+
id
|
|
1697
|
+
]);
|
|
2252
1698
|
} else {
|
|
2253
|
-
run(
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
1699
|
+
run(`INSERT INTO projects (id, key, name, path, is_default) VALUES (?, ?, ?, ?, ?)`, [
|
|
1700
|
+
id,
|
|
1701
|
+
projectKey,
|
|
1702
|
+
projectName,
|
|
1703
|
+
targetPath,
|
|
1704
|
+
0
|
|
1705
|
+
]);
|
|
2257
1706
|
}
|
|
2258
1707
|
const defaultProject = queryOne(
|
|
2259
1708
|
"SELECT id FROM projects WHERE is_default = 1",
|
|
@@ -2267,17 +1716,19 @@ async function initProject(targetPath, options, opts) {
|
|
|
2267
1716
|
}
|
|
2268
1717
|
updateGitignore(targetPath, opts);
|
|
2269
1718
|
if (opts.json) {
|
|
2270
|
-
console.log(
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
1719
|
+
console.log(
|
|
1720
|
+
JSON.stringify({
|
|
1721
|
+
success: true,
|
|
1722
|
+
project: {
|
|
1723
|
+
key: projectKey,
|
|
1724
|
+
name: projectName,
|
|
1725
|
+
path: targetPath,
|
|
1726
|
+
profile,
|
|
1727
|
+
dbMode: dbConfig.mode
|
|
1728
|
+
},
|
|
1729
|
+
templates: templatesResult
|
|
1730
|
+
})
|
|
1731
|
+
);
|
|
2281
1732
|
} else {
|
|
2282
1733
|
blank();
|
|
2283
1734
|
success(`AIGILE initialized with ${profile} profile`, opts);
|
|
@@ -2293,11 +1744,14 @@ async function initProject(targetPath, options, opts) {
|
|
|
2293
1744
|
header("Templates:", opts);
|
|
2294
1745
|
console.log(` Written: ${templatesResult.written} files`);
|
|
2295
1746
|
console.log(` Skipped: ${templatesResult.skipped} files (already exist)`);
|
|
1747
|
+
if (templatesResult.errors.length > 0) {
|
|
1748
|
+
console.log(` Errors: ${templatesResult.errors.length} files`);
|
|
1749
|
+
}
|
|
2296
1750
|
}
|
|
2297
1751
|
blank();
|
|
2298
1752
|
header("Next steps:", opts);
|
|
2299
|
-
if (profile === "full-repo" || profile === "
|
|
2300
|
-
console.log(" 1. Fill in 00_DOCS/
|
|
1753
|
+
if (profile === "full-repo" || profile === "minor-project") {
|
|
1754
|
+
console.log(" 1. Fill in 00_DOCS/01_vision-foundations/01_mission-vision-values.md");
|
|
2301
1755
|
console.log(' 2. Run "aigile sync scan" to index your files');
|
|
2302
1756
|
console.log(' 3. Run "aigile daemon install && aigile daemon start" for auto-sync');
|
|
2303
1757
|
} else {
|
|
@@ -2317,7 +1771,7 @@ function updateGitignore(repoPath, opts) {
|
|
|
2317
1771
|
info('No .gitignore found. Consider adding ".aigile/" to ignore local config.', opts);
|
|
2318
1772
|
return;
|
|
2319
1773
|
}
|
|
2320
|
-
const content =
|
|
1774
|
+
const content = readFileSync4(gitignorePath, "utf-8");
|
|
2321
1775
|
const lines = content.split("\n");
|
|
2322
1776
|
const hasPattern = lines.some((line) => {
|
|
2323
1777
|
const trimmed = line.trim();
|
|
@@ -2399,7 +1853,7 @@ projectCommand.command("list").alias("ls").description("List all registered proj
|
|
|
2399
1853
|
console.log(" \u2713 = valid path, \u2717 = missing/invalid path");
|
|
2400
1854
|
if (invalidCount > 0) {
|
|
2401
1855
|
blank();
|
|
2402
|
-
|
|
1856
|
+
warning(`${invalidCount} project(s) have invalid paths. Run "aigile project cleanup" to remove.`, opts);
|
|
2403
1857
|
}
|
|
2404
1858
|
});
|
|
2405
1859
|
projectCommand.command("show").argument("[key]", "Project key (uses default if not specified)").description("Show project details").action((key) => {
|
|
@@ -2808,7 +2262,7 @@ epicCommand.command("delete").alias("rm").argument("<key>", "Epic key").option("
|
|
|
2808
2262
|
(SELECT id FROM user_stories WHERE epic_id = ?)`,
|
|
2809
2263
|
[epic.id]
|
|
2810
2264
|
);
|
|
2811
|
-
|
|
2265
|
+
warning(
|
|
2812
2266
|
`Deleting ${childCount.count} child story(s) and ${taskCount?.count || 0} task(s)`,
|
|
2813
2267
|
opts
|
|
2814
2268
|
);
|
|
@@ -2818,7 +2272,7 @@ epicCommand.command("delete").alias("rm").argument("<key>", "Epic key").option("
|
|
|
2818
2272
|
);
|
|
2819
2273
|
run("DELETE FROM user_stories WHERE epic_id = ?", [epic.id]);
|
|
2820
2274
|
} else {
|
|
2821
|
-
|
|
2275
|
+
warning(`Orphaning ${childCount.count} child story(s)`, opts);
|
|
2822
2276
|
run("UPDATE user_stories SET epic_id = NULL WHERE epic_id = ?", [epic.id]);
|
|
2823
2277
|
}
|
|
2824
2278
|
}
|
|
@@ -3017,10 +2471,10 @@ storyCommand.command("delete").alias("rm").argument("<key>", "Story key").option
|
|
|
3017
2471
|
process.exit(1);
|
|
3018
2472
|
}
|
|
3019
2473
|
if (options.cascade) {
|
|
3020
|
-
|
|
2474
|
+
warning(`Deleting ${childCount.count} child task(s)`, opts);
|
|
3021
2475
|
run("DELETE FROM tasks WHERE story_id = ?", [story.id]);
|
|
3022
2476
|
} else {
|
|
3023
|
-
|
|
2477
|
+
warning(`Orphaning ${childCount.count} child task(s)`, opts);
|
|
3024
2478
|
run("UPDATE tasks SET story_id = NULL WHERE story_id = ?", [story.id]);
|
|
3025
2479
|
}
|
|
3026
2480
|
}
|
|
@@ -3983,11 +3437,11 @@ init_config();
|
|
|
3983
3437
|
// src/services/file-scanner.ts
|
|
3984
3438
|
init_connection();
|
|
3985
3439
|
import { createHash } from "crypto";
|
|
3986
|
-
import { readFileSync as
|
|
3440
|
+
import { readFileSync as readFileSync6, statSync as statSync2, readdirSync as readdirSync2, existsSync as existsSync6 } from "fs";
|
|
3987
3441
|
import { join as join6, relative as relative2, extname } from "path";
|
|
3988
3442
|
|
|
3989
3443
|
// src/services/frontmatter-parser.ts
|
|
3990
|
-
import { readFileSync as
|
|
3444
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
3991
3445
|
import { parse as parseYaml2 } from "yaml";
|
|
3992
3446
|
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
3993
3447
|
function extractFrontmatter(content) {
|
|
@@ -4009,9 +3463,11 @@ function extractFrontmatter(content) {
|
|
|
4009
3463
|
tldr: data2?.tldr,
|
|
4010
3464
|
modules: data2?.modules,
|
|
4011
3465
|
dependencies: data2?.dependencies,
|
|
3466
|
+
related: data2?.related,
|
|
4012
3467
|
code_refs: data2?.code_refs,
|
|
4013
3468
|
authors: data2?.authors,
|
|
4014
|
-
title: data2?.title
|
|
3469
|
+
title: data2?.title,
|
|
3470
|
+
exception: data2?.exception
|
|
4015
3471
|
};
|
|
4016
3472
|
}
|
|
4017
3473
|
if (metadata.modules && !Array.isArray(metadata.modules)) {
|
|
@@ -4020,6 +3476,9 @@ function extractFrontmatter(content) {
|
|
|
4020
3476
|
if (metadata.dependencies && !Array.isArray(metadata.dependencies)) {
|
|
4021
3477
|
metadata.dependencies = [String(metadata.dependencies)];
|
|
4022
3478
|
}
|
|
3479
|
+
if (metadata.related && !Array.isArray(metadata.related)) {
|
|
3480
|
+
metadata.related = [String(metadata.related)];
|
|
3481
|
+
}
|
|
4023
3482
|
if (metadata.code_refs && !Array.isArray(metadata.code_refs)) {
|
|
4024
3483
|
metadata.code_refs = [String(metadata.code_refs)];
|
|
4025
3484
|
}
|
|
@@ -4039,7 +3498,7 @@ function extractFrontmatter(content) {
|
|
|
4039
3498
|
}
|
|
4040
3499
|
function parseFrontmatterFromFile(filePath) {
|
|
4041
3500
|
try {
|
|
4042
|
-
const content =
|
|
3501
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
4043
3502
|
return extractFrontmatter(content);
|
|
4044
3503
|
} catch {
|
|
4045
3504
|
return null;
|
|
@@ -4068,9 +3527,15 @@ function serializeMetadata(metadata) {
|
|
|
4068
3527
|
if (metadata.dependencies && metadata.dependencies.length > 0) {
|
|
4069
3528
|
lines.push(` dependencies: [${metadata.dependencies.join(", ")}]`);
|
|
4070
3529
|
}
|
|
3530
|
+
if (metadata.related && metadata.related.length > 0) {
|
|
3531
|
+
lines.push(` related: [${metadata.related.join(", ")}]`);
|
|
3532
|
+
}
|
|
4071
3533
|
if (metadata.code_refs && metadata.code_refs.length > 0) {
|
|
4072
3534
|
lines.push(` code_refs: [${metadata.code_refs.join(", ")}]`);
|
|
4073
3535
|
}
|
|
3536
|
+
if (metadata.exception) {
|
|
3537
|
+
lines.push(` exception: "${metadata.exception.replace(/"/g, '\\"')}"`);
|
|
3538
|
+
}
|
|
4074
3539
|
lines.push("---");
|
|
4075
3540
|
return lines.join("\n");
|
|
4076
3541
|
}
|
|
@@ -4092,22 +3557,18 @@ function updateFrontmatterContent(content, updates) {
|
|
|
4092
3557
|
...updates
|
|
4093
3558
|
};
|
|
4094
3559
|
if (updates.dependencies && existing.metadata.dependencies) {
|
|
4095
|
-
merged.dependencies = [
|
|
4096
|
-
...existing.metadata.dependencies,
|
|
4097
|
-
|
|
4098
|
-
])];
|
|
3560
|
+
merged.dependencies = [
|
|
3561
|
+
.../* @__PURE__ */ new Set([...existing.metadata.dependencies, ...updates.dependencies])
|
|
3562
|
+
];
|
|
4099
3563
|
}
|
|
4100
3564
|
if (updates.modules && existing.metadata.modules) {
|
|
4101
|
-
merged.modules = [.../* @__PURE__ */ new Set([
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
])];
|
|
3565
|
+
merged.modules = [.../* @__PURE__ */ new Set([...existing.metadata.modules, ...updates.modules])];
|
|
3566
|
+
}
|
|
3567
|
+
if (updates.related && existing.metadata.related) {
|
|
3568
|
+
merged.related = [.../* @__PURE__ */ new Set([...existing.metadata.related, ...updates.related])];
|
|
4105
3569
|
}
|
|
4106
3570
|
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
|
-
])];
|
|
3571
|
+
merged.code_refs = [.../* @__PURE__ */ new Set([...existing.metadata.code_refs, ...updates.code_refs])];
|
|
4111
3572
|
}
|
|
4112
3573
|
const newFrontmatter = serializeMetadata(merged);
|
|
4113
3574
|
return content.replace(FRONTMATTER_REGEX, newFrontmatter + "\n\n");
|
|
@@ -4118,7 +3579,7 @@ init_monitoring_patterns();
|
|
|
4118
3579
|
init_config();
|
|
4119
3580
|
import picomatch from "picomatch";
|
|
4120
3581
|
function computeFileHash(filePath) {
|
|
4121
|
-
const content =
|
|
3582
|
+
const content = readFileSync6(filePath);
|
|
4122
3583
|
return createHash("sha256").update(content).digest("hex");
|
|
4123
3584
|
}
|
|
4124
3585
|
var FileClassifier = class {
|
|
@@ -4156,7 +3617,7 @@ function collectFiles(dir, rootDir, classifier, trackUnknown, files = []) {
|
|
|
4156
3617
|
if (!existsSync6(dir)) {
|
|
4157
3618
|
return files;
|
|
4158
3619
|
}
|
|
4159
|
-
const entries =
|
|
3620
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
4160
3621
|
for (const entry of entries) {
|
|
4161
3622
|
const fullPath = join6(dir, entry.name);
|
|
4162
3623
|
const relativePath = relative2(rootDir, fullPath);
|
|
@@ -4178,7 +3639,7 @@ function collectFiles(dir, rootDir, classifier, trackUnknown, files = []) {
|
|
|
4178
3639
|
continue;
|
|
4179
3640
|
}
|
|
4180
3641
|
try {
|
|
4181
|
-
const stats =
|
|
3642
|
+
const stats = statSync2(fullPath);
|
|
4182
3643
|
const ext = extname(entry.name).toLowerCase().slice(1);
|
|
4183
3644
|
const isBinary = isBinaryExtension(ext);
|
|
4184
3645
|
const shouldComputeHash = category === "allow" || !isBinary && stats.size < 10 * 1024 * 1024;
|
|
@@ -4374,50 +3835,6 @@ function getDocuments(projectId, status) {
|
|
|
4374
3835
|
query += " ORDER BY path";
|
|
4375
3836
|
return queryAll(query, params);
|
|
4376
3837
|
}
|
|
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
3838
|
function getDocumentByPath(projectId, filePath) {
|
|
4422
3839
|
return queryOne(
|
|
4423
3840
|
`SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
|
|
@@ -4428,28 +3845,6 @@ function getDocumentByPath(projectId, filePath) {
|
|
|
4428
3845
|
[projectId, filePath]
|
|
4429
3846
|
);
|
|
4430
3847
|
}
|
|
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
|
-
);
|
|
4441
|
-
}
|
|
4442
|
-
function searchDocumentsByTldr(projectId, searchTerm) {
|
|
4443
|
-
return queryAll(
|
|
4444
|
-
`SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
|
|
4445
|
-
has_frontmatter, meta_status, meta_version, meta_tldr, meta_title,
|
|
4446
|
-
meta_modules, meta_dependencies, meta_code_refs, meta_authors
|
|
4447
|
-
FROM documents
|
|
4448
|
-
WHERE project_id = ? AND meta_tldr LIKE ? AND status != 'deleted'
|
|
4449
|
-
ORDER BY path`,
|
|
4450
|
-
[projectId, `%${searchTerm}%`]
|
|
4451
|
-
);
|
|
4452
|
-
}
|
|
4453
3848
|
function getUnanalyzedDocuments(projectId, limit, offset) {
|
|
4454
3849
|
let query = `
|
|
4455
3850
|
SELECT id, path, filename, extension, status, size_bytes, last_scanned_at,
|
|
@@ -4673,7 +4068,7 @@ function trackShadowFile(projectId, projectPath, filePath) {
|
|
|
4673
4068
|
return getDocumentWithAnalysis(projectId, filePath) ?? null;
|
|
4674
4069
|
}
|
|
4675
4070
|
try {
|
|
4676
|
-
const stats =
|
|
4071
|
+
const stats = statSync2(fullPath);
|
|
4677
4072
|
const hash = computeFileHash(fullPath);
|
|
4678
4073
|
const filename = filePath.split("/").pop() ?? filePath;
|
|
4679
4074
|
const ext = extname(filename).slice(1);
|
|
@@ -4694,11 +4089,11 @@ function trackShadowFile(projectId, projectPath, filePath) {
|
|
|
4694
4089
|
|
|
4695
4090
|
// src/services/comment-parser.ts
|
|
4696
4091
|
init_connection();
|
|
4697
|
-
import { readFileSync as
|
|
4092
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
4698
4093
|
var USER_COMMENT_PATTERN = /\[\[!\s*([\s\S]*?)\s*\]\]/g;
|
|
4699
4094
|
var AI_COMMENT_PATTERN = /\[\{!\s*([\s\S]*?)\s*\}]/g;
|
|
4700
4095
|
function parseComments(filePath) {
|
|
4701
|
-
const content =
|
|
4096
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
4702
4097
|
const lines = content.split("\n");
|
|
4703
4098
|
const comments = [];
|
|
4704
4099
|
const positionToLine = [];
|
|
@@ -5059,10 +4454,9 @@ function startSession(projectId, name) {
|
|
|
5059
4454
|
[projectId, "active"]
|
|
5060
4455
|
);
|
|
5061
4456
|
if (existing) {
|
|
5062
|
-
run(
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
);
|
|
4457
|
+
run(`UPDATE sessions SET status = 'abandoned', ended_at = datetime('now') WHERE id = ?`, [
|
|
4458
|
+
existing.id
|
|
4459
|
+
]);
|
|
5066
4460
|
}
|
|
5067
4461
|
const sessionId = generateId();
|
|
5068
4462
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5086,20 +4480,22 @@ function startSession(projectId, name) {
|
|
|
5086
4480
|
}
|
|
5087
4481
|
function endSession(projectId, summary) {
|
|
5088
4482
|
const session = queryOne(
|
|
5089
|
-
"SELECT id, started_at, entities_modified, files_modified FROM sessions WHERE project_id = ? AND status = ?",
|
|
4483
|
+
"SELECT id, name, started_at, entities_modified, files_modified FROM sessions WHERE project_id = ? AND status = ?",
|
|
5090
4484
|
[projectId, "active"]
|
|
5091
4485
|
);
|
|
5092
4486
|
if (!session) {
|
|
5093
4487
|
return null;
|
|
5094
4488
|
}
|
|
5095
4489
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5096
|
-
run(
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
4490
|
+
run(`UPDATE sessions SET status = 'completed', ended_at = ?, summary = ? WHERE id = ?`, [
|
|
4491
|
+
now,
|
|
4492
|
+
summary ?? null,
|
|
4493
|
+
session.id
|
|
4494
|
+
]);
|
|
5100
4495
|
return {
|
|
5101
4496
|
id: session.id,
|
|
5102
4497
|
projectId,
|
|
4498
|
+
name: session.name,
|
|
5103
4499
|
startedAt: session.started_at,
|
|
5104
4500
|
endedAt: now,
|
|
5105
4501
|
summary: summary ?? null,
|
|
@@ -5109,16 +4505,14 @@ function endSession(projectId, summary) {
|
|
|
5109
4505
|
};
|
|
5110
4506
|
}
|
|
5111
4507
|
function getActiveSession(projectId) {
|
|
5112
|
-
const session = queryOne(
|
|
5113
|
-
"SELECT * FROM sessions WHERE project_id = ? AND status = ?",
|
|
5114
|
-
[projectId, "active"]
|
|
5115
|
-
);
|
|
4508
|
+
const session = queryOne("SELECT * FROM sessions WHERE project_id = ? AND status = ?", [projectId, "active"]);
|
|
5116
4509
|
if (!session) {
|
|
5117
4510
|
return null;
|
|
5118
4511
|
}
|
|
5119
4512
|
return {
|
|
5120
4513
|
id: session.id,
|
|
5121
4514
|
projectId: session.project_id,
|
|
4515
|
+
name: session.name,
|
|
5122
4516
|
startedAt: session.started_at,
|
|
5123
4517
|
endedAt: session.ended_at,
|
|
5124
4518
|
summary: session.summary,
|
|
@@ -5128,10 +4522,7 @@ function getActiveSession(projectId) {
|
|
|
5128
4522
|
};
|
|
5129
4523
|
}
|
|
5130
4524
|
function getSessionByName(projectId, name) {
|
|
5131
|
-
const session = queryOne(
|
|
5132
|
-
"SELECT * FROM sessions WHERE project_id = ? AND name = ?",
|
|
5133
|
-
[projectId, name]
|
|
5134
|
-
);
|
|
4525
|
+
const session = queryOne("SELECT * FROM sessions WHERE project_id = ? AND name = ?", [projectId, name]);
|
|
5135
4526
|
if (!session) {
|
|
5136
4527
|
return null;
|
|
5137
4528
|
}
|
|
@@ -5148,10 +4539,7 @@ function getSessionByName(projectId, name) {
|
|
|
5148
4539
|
};
|
|
5149
4540
|
}
|
|
5150
4541
|
function resumeSession(projectId, sessionId) {
|
|
5151
|
-
const session = queryOne(
|
|
5152
|
-
"SELECT * FROM sessions WHERE id = ? AND project_id = ?",
|
|
5153
|
-
[sessionId, projectId]
|
|
5154
|
-
);
|
|
4542
|
+
const session = queryOne("SELECT * FROM sessions WHERE id = ? AND project_id = ?", [sessionId, projectId]);
|
|
5155
4543
|
if (!session) {
|
|
5156
4544
|
return null;
|
|
5157
4545
|
}
|
|
@@ -5163,15 +4551,11 @@ function resumeSession(projectId, sessionId) {
|
|
|
5163
4551
|
[projectId, "active", sessionId]
|
|
5164
4552
|
);
|
|
5165
4553
|
if (existing) {
|
|
5166
|
-
run(
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
);
|
|
4554
|
+
run(`UPDATE sessions SET status = 'abandoned', ended_at = datetime('now') WHERE id = ?`, [
|
|
4555
|
+
existing.id
|
|
4556
|
+
]);
|
|
5170
4557
|
}
|
|
5171
|
-
run(
|
|
5172
|
-
`UPDATE sessions SET status = 'active', ended_at = NULL WHERE id = ?`,
|
|
5173
|
-
[sessionId]
|
|
5174
|
-
);
|
|
4558
|
+
run(`UPDATE sessions SET status = 'active', ended_at = NULL WHERE id = ?`, [sessionId]);
|
|
5175
4559
|
return {
|
|
5176
4560
|
id: session.id,
|
|
5177
4561
|
projectId: session.project_id,
|
|
@@ -5249,6 +4633,7 @@ function getSession(sessionId) {
|
|
|
5249
4633
|
return {
|
|
5250
4634
|
id: session.id,
|
|
5251
4635
|
projectId: session.project_id,
|
|
4636
|
+
name: session.name,
|
|
5252
4637
|
startedAt: session.started_at,
|
|
5253
4638
|
endedAt: session.ended_at,
|
|
5254
4639
|
summary: session.summary,
|
|
@@ -8419,321 +7804,872 @@ uxJourneyCommand.command("delete").alias("rm").argument("<key>", "Journey key").
|
|
|
8419
7804
|
// src/commands/doc.ts
|
|
8420
7805
|
init_connection();
|
|
8421
7806
|
import { Command as Command19 } from "commander";
|
|
8422
|
-
import {
|
|
8423
|
-
import {
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
7807
|
+
import { existsSync as existsSync7 } from "fs";
|
|
7808
|
+
import { resolve as resolve4, relative as relative4 } from "path";
|
|
7809
|
+
|
|
7810
|
+
// src/services/dependency-graph.ts
|
|
7811
|
+
init_connection();
|
|
7812
|
+
import { resolve as resolve3, relative as relative3, dirname as dirname3 } from "path";
|
|
7813
|
+
function buildDependencyGraph(projectId) {
|
|
7814
|
+
const docs = queryAll(
|
|
7815
|
+
`SELECT id, path, meta_status, last_approved_at, last_modified_at, meta_dependencies
|
|
7816
|
+
FROM documents
|
|
7817
|
+
WHERE project_id = ?`,
|
|
7818
|
+
[projectId]
|
|
7819
|
+
);
|
|
7820
|
+
const graph = /* @__PURE__ */ new Map();
|
|
7821
|
+
for (const doc of docs) {
|
|
7822
|
+
const dependencies = doc.meta_dependencies ? JSON.parse(doc.meta_dependencies) : [];
|
|
7823
|
+
graph.set(doc.path, {
|
|
7824
|
+
id: doc.id,
|
|
7825
|
+
path: doc.path,
|
|
7826
|
+
status: doc.meta_status || "UNKNOWN",
|
|
7827
|
+
lastApprovedAt: doc.last_approved_at || void 0,
|
|
7828
|
+
lastModifiedAt: doc.last_modified_at || void 0,
|
|
7829
|
+
dependencies,
|
|
7830
|
+
dependents: []
|
|
7831
|
+
});
|
|
8431
7832
|
}
|
|
8432
|
-
const
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
7833
|
+
for (const [path, node] of graph.entries()) {
|
|
7834
|
+
for (const depPath of node.dependencies) {
|
|
7835
|
+
const absoluteDep = resolveDocPath(path, depPath);
|
|
7836
|
+
const depNode = graph.get(absoluteDep);
|
|
7837
|
+
if (depNode) {
|
|
7838
|
+
depNode.dependents.push(path);
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
8436
7841
|
}
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
7842
|
+
return graph;
|
|
7843
|
+
}
|
|
7844
|
+
function resolveDocPath(fromPath, relativePath) {
|
|
7845
|
+
if (relativePath.startsWith("./") || relativePath.startsWith("../")) {
|
|
7846
|
+
const fromDir = dirname3(fromPath);
|
|
7847
|
+
const resolved = resolve3(fromDir, relativePath);
|
|
7848
|
+
return relative3(".", resolved);
|
|
8441
7849
|
}
|
|
8442
|
-
return
|
|
7850
|
+
return relativePath;
|
|
8443
7851
|
}
|
|
8444
|
-
function
|
|
7852
|
+
function checkStaleness(docPath, graph) {
|
|
7853
|
+
const doc = graph.get(docPath);
|
|
7854
|
+
if (!doc) {
|
|
7855
|
+
return {
|
|
7856
|
+
isStale: false,
|
|
7857
|
+
staleDependencies: []
|
|
7858
|
+
};
|
|
7859
|
+
}
|
|
7860
|
+
if (!doc.lastApprovedAt) {
|
|
7861
|
+
return {
|
|
7862
|
+
isStale: false,
|
|
7863
|
+
staleDependencies: []
|
|
7864
|
+
};
|
|
7865
|
+
}
|
|
7866
|
+
const staleDeps = [];
|
|
7867
|
+
for (const depRelPath of doc.dependencies) {
|
|
7868
|
+
const depAbsPath = resolveDocPath(docPath, depRelPath);
|
|
7869
|
+
const depNode = graph.get(depAbsPath);
|
|
7870
|
+
if (!depNode || !depNode.lastModifiedAt) {
|
|
7871
|
+
continue;
|
|
7872
|
+
}
|
|
7873
|
+
if (depNode.lastModifiedAt > doc.lastApprovedAt) {
|
|
7874
|
+
staleDeps.push({
|
|
7875
|
+
path: depRelPath,
|
|
7876
|
+
lastModified: depNode.lastModifiedAt,
|
|
7877
|
+
parentApproved: doc.lastApprovedAt
|
|
7878
|
+
});
|
|
7879
|
+
}
|
|
7880
|
+
}
|
|
7881
|
+
const isStale = staleDeps.length > 0;
|
|
8445
7882
|
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(", ") : "-"
|
|
7883
|
+
isStale,
|
|
7884
|
+
reason: isStale ? `${staleDeps.length} dependencies modified since approval` : void 0,
|
|
7885
|
+
staleDependencies: staleDeps
|
|
8456
7886
|
};
|
|
8457
7887
|
}
|
|
8458
|
-
|
|
8459
|
-
const
|
|
8460
|
-
|
|
8461
|
-
|
|
7888
|
+
function checkCascadeApproval(docPath, graph) {
|
|
7889
|
+
const doc = graph.get(docPath);
|
|
7890
|
+
if (!doc) {
|
|
7891
|
+
return {
|
|
7892
|
+
canCascade: false,
|
|
7893
|
+
affectedDocs: [],
|
|
7894
|
+
blockedBy: []
|
|
7895
|
+
};
|
|
7896
|
+
}
|
|
7897
|
+
const canApprove = [];
|
|
7898
|
+
const blocked = [];
|
|
7899
|
+
for (const dependentPath of doc.dependents) {
|
|
7900
|
+
const dependent = graph.get(dependentPath);
|
|
7901
|
+
if (!dependent) {
|
|
7902
|
+
continue;
|
|
7903
|
+
}
|
|
7904
|
+
const allDepsApproved = dependent.dependencies.every((depRelPath) => {
|
|
7905
|
+
const depAbsPath = resolveDocPath(dependentPath, depRelPath);
|
|
7906
|
+
const depNode = graph.get(depAbsPath);
|
|
7907
|
+
if (depAbsPath === docPath) {
|
|
7908
|
+
return true;
|
|
7909
|
+
}
|
|
7910
|
+
return depNode && depNode.status === "APPROVED";
|
|
7911
|
+
});
|
|
7912
|
+
if (allDepsApproved) {
|
|
7913
|
+
canApprove.push(dependentPath);
|
|
7914
|
+
} else {
|
|
7915
|
+
blocked.push(dependentPath);
|
|
7916
|
+
}
|
|
7917
|
+
}
|
|
7918
|
+
return {
|
|
7919
|
+
canCascade: canApprove.length > 0,
|
|
7920
|
+
affectedDocs: canApprove,
|
|
7921
|
+
blockedBy: blocked
|
|
7922
|
+
};
|
|
7923
|
+
}
|
|
7924
|
+
function getTopologicalOrder(graph) {
|
|
7925
|
+
const visited = /* @__PURE__ */ new Set();
|
|
7926
|
+
const result = [];
|
|
7927
|
+
function visit(path) {
|
|
7928
|
+
if (visited.has(path)) {
|
|
7929
|
+
return;
|
|
7930
|
+
}
|
|
7931
|
+
visited.add(path);
|
|
7932
|
+
const node = graph.get(path);
|
|
7933
|
+
if (!node) {
|
|
7934
|
+
return;
|
|
7935
|
+
}
|
|
7936
|
+
for (const depRelPath of node.dependencies) {
|
|
7937
|
+
const depAbsPath = resolveDocPath(path, depRelPath);
|
|
7938
|
+
visit(depAbsPath);
|
|
7939
|
+
}
|
|
7940
|
+
result.push(path);
|
|
7941
|
+
}
|
|
7942
|
+
for (const path of graph.keys()) {
|
|
7943
|
+
visit(path);
|
|
7944
|
+
}
|
|
7945
|
+
return result;
|
|
7946
|
+
}
|
|
7947
|
+
function getFoundationalDocs(graph) {
|
|
7948
|
+
const foundational = [];
|
|
7949
|
+
for (const [path, node] of graph.entries()) {
|
|
7950
|
+
if (node.dependencies.length === 0) {
|
|
7951
|
+
foundational.push(path);
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
return foundational;
|
|
7955
|
+
}
|
|
7956
|
+
function getDirectDependents(docPath, graph) {
|
|
7957
|
+
const doc = graph.get(docPath);
|
|
7958
|
+
return doc ? [...doc.dependents] : [];
|
|
7959
|
+
}
|
|
7960
|
+
function getTransitiveDependents(docPath, graph) {
|
|
7961
|
+
const visited = /* @__PURE__ */ new Set();
|
|
7962
|
+
const result = [];
|
|
7963
|
+
function visit(path) {
|
|
7964
|
+
if (visited.has(path)) {
|
|
7965
|
+
return;
|
|
7966
|
+
}
|
|
7967
|
+
visited.add(path);
|
|
7968
|
+
const node = graph.get(path);
|
|
7969
|
+
if (!node) {
|
|
7970
|
+
return;
|
|
7971
|
+
}
|
|
7972
|
+
for (const dependentPath of node.dependents) {
|
|
7973
|
+
result.push(dependentPath);
|
|
7974
|
+
visit(dependentPath);
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
visit(docPath);
|
|
7978
|
+
return result;
|
|
7979
|
+
}
|
|
7980
|
+
|
|
7981
|
+
// src/commands/doc.ts
|
|
7982
|
+
init_config();
|
|
7983
|
+
init_config();
|
|
7984
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync8 } from "fs";
|
|
7985
|
+
async function ensureProjectContext() {
|
|
7986
|
+
const projectPath = findProjectRoot();
|
|
7987
|
+
if (!projectPath) {
|
|
7988
|
+
throw new Error('Not in an AIGILE project. Run "aigile init" first.');
|
|
7989
|
+
}
|
|
7990
|
+
const config = loadProjectConfig(projectPath);
|
|
7991
|
+
if (!config) {
|
|
7992
|
+
throw new Error("Could not load project config.");
|
|
7993
|
+
}
|
|
7994
|
+
const project = queryOne("SELECT id FROM projects WHERE key = ?", [
|
|
7995
|
+
config.project.key
|
|
7996
|
+
]);
|
|
7997
|
+
if (!project) {
|
|
7998
|
+
throw new Error(`Project "${config.project.key}" not found in database.`);
|
|
7999
|
+
}
|
|
8000
|
+
return { projectId: project.id, projectPath, projectKey: config.project.key };
|
|
8001
|
+
}
|
|
8002
|
+
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) => {
|
|
8003
|
+
const opts = getOutputOptions(integrityCommand);
|
|
8004
|
+
const cwd = process.cwd();
|
|
8005
|
+
try {
|
|
8006
|
+
await runIntegrityCheck(cwd, options, opts);
|
|
8007
|
+
} catch (err) {
|
|
8008
|
+
error(err instanceof Error ? err.message : String(err), opts);
|
|
8462
8009
|
process.exit(1);
|
|
8463
8010
|
}
|
|
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);
|
|
8011
|
+
});
|
|
8012
|
+
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) => {
|
|
8013
|
+
const opts = getOutputOptions(approveCommand);
|
|
8014
|
+
const cwd = process.cwd();
|
|
8015
|
+
try {
|
|
8016
|
+
await approveDocument(cwd, filePath, options, opts);
|
|
8017
|
+
} catch (err) {
|
|
8018
|
+
error(err instanceof Error ? err.message : String(err), opts);
|
|
8019
|
+
process.exit(1);
|
|
8479
8020
|
}
|
|
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
8021
|
});
|
|
8492
|
-
|
|
8493
|
-
const opts = getOutputOptions(
|
|
8494
|
-
const
|
|
8495
|
-
|
|
8022
|
+
var ackCommand = new Command19("ack").description("Acknowledge document changes (clears STALE)").argument("<file>", "File path to acknowledge").action(async (filePath) => {
|
|
8023
|
+
const opts = getOutputOptions(ackCommand);
|
|
8024
|
+
const cwd = process.cwd();
|
|
8025
|
+
try {
|
|
8026
|
+
await acknowledgeDocument(cwd, filePath, opts);
|
|
8027
|
+
} catch (err) {
|
|
8028
|
+
error(err instanceof Error ? err.message : String(err), opts);
|
|
8496
8029
|
process.exit(1);
|
|
8497
8030
|
}
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8031
|
+
});
|
|
8032
|
+
var readyCommand = new Command19("ready").description("Mark document as ready for review").argument("<file>", "File path to mark ready").action(async (filePath) => {
|
|
8033
|
+
const opts = getOutputOptions(readyCommand);
|
|
8034
|
+
const cwd = process.cwd();
|
|
8035
|
+
try {
|
|
8036
|
+
await markReady(cwd, filePath, opts);
|
|
8037
|
+
} catch (err) {
|
|
8038
|
+
error(err instanceof Error ? err.message : String(err), opts);
|
|
8501
8039
|
process.exit(1);
|
|
8502
8040
|
}
|
|
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
|
-
);
|
|
8041
|
+
});
|
|
8042
|
+
var reviewCommand = new Command19("review").description("Interactive document review (TUI)").action(async () => {
|
|
8043
|
+
const opts = getOutputOptions(reviewCommand);
|
|
8044
|
+
warning("Interactive review TUI not yet implemented", opts);
|
|
8045
|
+
info("Use 'aigile doc integrity' to see document status", opts);
|
|
8046
|
+
});
|
|
8047
|
+
var exceptionCommand = new Command19("exception").description("Mark document with structural exception").argument("<file>", "File path").argument("<reason>", "Reason for exception").action(async (filePath, reason) => {
|
|
8048
|
+
const opts = getOutputOptions(exceptionCommand);
|
|
8049
|
+
const cwd = process.cwd();
|
|
8050
|
+
try {
|
|
8051
|
+
await markException(cwd, filePath, reason, opts);
|
|
8052
|
+
} catch (err) {
|
|
8053
|
+
error(err instanceof Error ? err.message : String(err), opts);
|
|
8054
|
+
process.exit(1);
|
|
8557
8055
|
}
|
|
8558
8056
|
});
|
|
8559
|
-
|
|
8560
|
-
const opts = getOutputOptions(
|
|
8561
|
-
const
|
|
8562
|
-
|
|
8057
|
+
var refactorCommand = new Command19("refactor").description("Mark document for refactoring").argument("<file>", "File path").action(async (filePath) => {
|
|
8058
|
+
const opts = getOutputOptions(refactorCommand);
|
|
8059
|
+
const cwd = process.cwd();
|
|
8060
|
+
try {
|
|
8061
|
+
await markRefactoring(cwd, filePath, opts);
|
|
8062
|
+
} catch (err) {
|
|
8063
|
+
error(err instanceof Error ? err.message : String(err), opts);
|
|
8563
8064
|
process.exit(1);
|
|
8564
8065
|
}
|
|
8565
|
-
|
|
8566
|
-
|
|
8066
|
+
});
|
|
8067
|
+
var archiveCommand = new Command19("archive").description("Archive a document").argument("<file>", "File path to archive").action(async (filePath) => {
|
|
8068
|
+
const opts = getOutputOptions(archiveCommand);
|
|
8069
|
+
const cwd = process.cwd();
|
|
8567
8070
|
try {
|
|
8568
|
-
|
|
8569
|
-
} catch {
|
|
8570
|
-
error(
|
|
8071
|
+
await archiveDocument(cwd, filePath, opts);
|
|
8072
|
+
} catch (err) {
|
|
8073
|
+
error(err instanceof Error ? err.message : String(err), opts);
|
|
8571
8074
|
process.exit(1);
|
|
8572
8075
|
}
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8076
|
+
});
|
|
8077
|
+
async function runIntegrityCheck(cwd, options, opts) {
|
|
8078
|
+
const config = loadProjectConfig(cwd);
|
|
8079
|
+
if (!config?.project?.key) {
|
|
8080
|
+
throw new Error("AIGILE not initialized in this directory");
|
|
8576
8081
|
}
|
|
8577
|
-
|
|
8578
|
-
|
|
8082
|
+
const projectId = config.project.key;
|
|
8083
|
+
const graph = buildDependencyGraph(projectId);
|
|
8084
|
+
blank();
|
|
8085
|
+
header("Document Integrity Check", opts);
|
|
8086
|
+
blank();
|
|
8087
|
+
const orderedDocs = getTopologicalOrder(graph);
|
|
8088
|
+
let issueCount = 0;
|
|
8089
|
+
const stale = [];
|
|
8090
|
+
const needsReview = [];
|
|
8091
|
+
const templates = [];
|
|
8092
|
+
for (const docPath of orderedDocs) {
|
|
8093
|
+
const node = graph.get(docPath);
|
|
8094
|
+
if (!node) continue;
|
|
8095
|
+
const stalenessCheck = checkStaleness(docPath, graph);
|
|
8096
|
+
const hasIssues = stalenessCheck.isStale || node.status === "NEEDS_REVIEW" || node.status === "TEMPLATE";
|
|
8097
|
+
if (!hasIssues && !options.all) {
|
|
8098
|
+
continue;
|
|
8099
|
+
}
|
|
8100
|
+
console.log(`\u{1F4C4} ${docPath}`);
|
|
8101
|
+
console.log(` Status: ${node.status}`);
|
|
8102
|
+
if (stalenessCheck.isStale) {
|
|
8103
|
+
console.log(` \u26A0\uFE0F STALE: ${stalenessCheck.reason}`);
|
|
8104
|
+
stale.push(docPath);
|
|
8105
|
+
issueCount++;
|
|
8106
|
+
}
|
|
8107
|
+
if (node.status === "NEEDS_REVIEW") {
|
|
8108
|
+
console.log(` \u{1F4DD} Needs review`);
|
|
8109
|
+
needsReview.push(docPath);
|
|
8110
|
+
issueCount++;
|
|
8111
|
+
}
|
|
8112
|
+
if (node.status === "TEMPLATE") {
|
|
8113
|
+
console.log(` \u{1F4CB} Template (needs filling)`);
|
|
8114
|
+
templates.push(docPath);
|
|
8115
|
+
issueCount++;
|
|
8116
|
+
}
|
|
8117
|
+
if (node.dependencies.length > 0) {
|
|
8118
|
+
console.log(` Dependencies: ${node.dependencies.join(", ")}`);
|
|
8119
|
+
}
|
|
8120
|
+
if (node.dependents.length > 0) {
|
|
8121
|
+
console.log(` Dependents: ${node.dependents.length} files`);
|
|
8122
|
+
}
|
|
8123
|
+
blank();
|
|
8579
8124
|
}
|
|
8580
|
-
|
|
8581
|
-
|
|
8125
|
+
blank();
|
|
8126
|
+
header("Summary", opts);
|
|
8127
|
+
console.log(` Total documents: ${orderedDocs.length}`);
|
|
8128
|
+
console.log(` Issues found: ${issueCount}`);
|
|
8129
|
+
if (stale.length > 0) {
|
|
8130
|
+
console.log(` - Stale: ${stale.length}`);
|
|
8582
8131
|
}
|
|
8583
|
-
if (
|
|
8584
|
-
|
|
8132
|
+
if (needsReview.length > 0) {
|
|
8133
|
+
console.log(` - Needs review: ${needsReview.length}`);
|
|
8585
8134
|
}
|
|
8586
|
-
if (
|
|
8587
|
-
|
|
8135
|
+
if (templates.length > 0) {
|
|
8136
|
+
console.log(` - Templates: ${templates.length}`);
|
|
8588
8137
|
}
|
|
8589
|
-
|
|
8590
|
-
|
|
8138
|
+
blank();
|
|
8139
|
+
if (issueCount === 0) {
|
|
8140
|
+
success("\u2713 All documents are up to date", opts);
|
|
8141
|
+
} else {
|
|
8142
|
+
info("Run 'aigile doc approve <file>' to approve documents", opts);
|
|
8143
|
+
info("Run 'aigile doc ack <file>' to acknowledge changes", opts);
|
|
8144
|
+
}
|
|
8145
|
+
}
|
|
8146
|
+
async function approveDocument(cwd, filePath, options, opts) {
|
|
8147
|
+
const config = loadProjectConfig(cwd);
|
|
8148
|
+
if (!config?.project?.key) {
|
|
8149
|
+
throw new Error("AIGILE not initialized in this directory");
|
|
8591
8150
|
}
|
|
8592
|
-
|
|
8593
|
-
|
|
8151
|
+
const absolutePath = resolve4(cwd, filePath);
|
|
8152
|
+
if (!existsSync7(absolutePath)) {
|
|
8153
|
+
throw new Error(`File not found: ${filePath}`);
|
|
8594
8154
|
}
|
|
8595
|
-
|
|
8596
|
-
|
|
8155
|
+
const projectId = config.project.key;
|
|
8156
|
+
const relativePath = relative4(cwd, absolutePath);
|
|
8157
|
+
const doc = queryOne(
|
|
8158
|
+
`SELECT id, meta_status FROM documents WHERE project_id = ? AND path = ?`,
|
|
8159
|
+
[projectId, relativePath]
|
|
8160
|
+
);
|
|
8161
|
+
if (!doc) {
|
|
8162
|
+
throw new Error(`Document not found in database: ${filePath}`);
|
|
8163
|
+
}
|
|
8164
|
+
const content = readFileSync8(absolutePath, "utf-8");
|
|
8165
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8166
|
+
const updatedContent = updateFrontmatterContent(content, {
|
|
8167
|
+
status: "APPROVED",
|
|
8168
|
+
reviewed: now
|
|
8169
|
+
});
|
|
8170
|
+
writeFileSync5(absolutePath, updatedContent, "utf-8");
|
|
8171
|
+
run(`UPDATE documents SET meta_status = ?, last_approved_at = ?, reviewed_at = ? WHERE id = ?`, [
|
|
8172
|
+
"APPROVED",
|
|
8173
|
+
now,
|
|
8174
|
+
now,
|
|
8175
|
+
doc.id
|
|
8176
|
+
]);
|
|
8177
|
+
success(`\u2713 Approved: ${filePath}`, opts);
|
|
8178
|
+
if (options.cascade) {
|
|
8179
|
+
const graph = buildDependencyGraph(projectId);
|
|
8180
|
+
const cascadeResult = checkCascadeApproval(relativePath, graph);
|
|
8181
|
+
if (cascadeResult.canCascade && cascadeResult.affectedDocs.length > 0) {
|
|
8182
|
+
blank();
|
|
8183
|
+
info(`Cascade approving ${cascadeResult.affectedDocs.length} dependents...`, opts);
|
|
8184
|
+
for (const depPath of cascadeResult.affectedDocs) {
|
|
8185
|
+
const depAbsolute = resolve4(cwd, depPath);
|
|
8186
|
+
const depContent = readFileSync8(depAbsolute, "utf-8");
|
|
8187
|
+
const updatedDep = updateFrontmatterContent(depContent, {
|
|
8188
|
+
status: "APPROVED",
|
|
8189
|
+
reviewed: now
|
|
8190
|
+
});
|
|
8191
|
+
writeFileSync5(depAbsolute, updatedDep, "utf-8");
|
|
8192
|
+
const depDoc = queryOne(
|
|
8193
|
+
`SELECT id FROM documents WHERE project_id = ? AND path = ?`,
|
|
8194
|
+
[projectId, depPath]
|
|
8195
|
+
);
|
|
8196
|
+
if (depDoc) {
|
|
8197
|
+
run(
|
|
8198
|
+
`UPDATE documents SET meta_status = ?, last_approved_at = ?, reviewed_at = ? WHERE id = ?`,
|
|
8199
|
+
["APPROVED", now, now, depDoc.id]
|
|
8200
|
+
);
|
|
8201
|
+
}
|
|
8202
|
+
success(` \u2713 ${depPath}`, opts);
|
|
8203
|
+
}
|
|
8204
|
+
}
|
|
8597
8205
|
}
|
|
8598
|
-
|
|
8599
|
-
|
|
8206
|
+
}
|
|
8207
|
+
async function acknowledgeDocument(cwd, filePath, opts) {
|
|
8208
|
+
const config = loadProjectConfig(cwd);
|
|
8209
|
+
if (!config?.project?.key) {
|
|
8210
|
+
throw new Error("AIGILE not initialized in this directory");
|
|
8211
|
+
}
|
|
8212
|
+
const absolutePath = resolve4(cwd, filePath);
|
|
8213
|
+
if (!existsSync7(absolutePath)) {
|
|
8214
|
+
throw new Error(`File not found: ${filePath}`);
|
|
8215
|
+
}
|
|
8216
|
+
const projectId = config.project.key;
|
|
8217
|
+
const relativePath = relative4(cwd, absolutePath);
|
|
8218
|
+
const doc = queryOne(
|
|
8219
|
+
`SELECT id FROM documents WHERE project_id = ? AND path = ?`,
|
|
8220
|
+
[projectId, relativePath]
|
|
8221
|
+
);
|
|
8222
|
+
if (!doc) {
|
|
8223
|
+
throw new Error(`Document not found in database: ${filePath}`);
|
|
8224
|
+
}
|
|
8225
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8226
|
+
run(`UPDATE documents SET last_approved_at = ? WHERE id = ?`, [now, doc.id]);
|
|
8227
|
+
success(`\u2713 Acknowledged: ${filePath}`, opts);
|
|
8228
|
+
info("Document is no longer marked as stale", opts);
|
|
8229
|
+
}
|
|
8230
|
+
async function markReady(cwd, filePath, opts) {
|
|
8231
|
+
const config = loadProjectConfig(cwd);
|
|
8232
|
+
if (!config?.project?.key) {
|
|
8233
|
+
throw new Error("AIGILE not initialized in this directory");
|
|
8234
|
+
}
|
|
8235
|
+
const absolutePath = resolve4(cwd, filePath);
|
|
8236
|
+
if (!existsSync7(absolutePath)) {
|
|
8237
|
+
throw new Error(`File not found: ${filePath}`);
|
|
8238
|
+
}
|
|
8239
|
+
const projectId = config.project.key;
|
|
8240
|
+
const relativePath = relative4(cwd, absolutePath);
|
|
8241
|
+
const content = readFileSync8(absolutePath, "utf-8");
|
|
8242
|
+
const updatedContent = updateFrontmatterContent(content, {
|
|
8243
|
+
status: "NEEDS_REVIEW"
|
|
8244
|
+
});
|
|
8245
|
+
writeFileSync5(absolutePath, updatedContent, "utf-8");
|
|
8246
|
+
const doc = queryOne(
|
|
8247
|
+
`SELECT id FROM documents WHERE project_id = ? AND path = ?`,
|
|
8248
|
+
[projectId, relativePath]
|
|
8249
|
+
);
|
|
8250
|
+
if (doc) {
|
|
8251
|
+
run(`UPDATE documents SET meta_status = ?, needs_review = 1 WHERE id = ?`, [
|
|
8252
|
+
"NEEDS_REVIEW",
|
|
8253
|
+
doc.id
|
|
8254
|
+
]);
|
|
8255
|
+
}
|
|
8256
|
+
success(`\u2713 Marked ready for review: ${filePath}`, opts);
|
|
8257
|
+
}
|
|
8258
|
+
async function markException(cwd, filePath, reason, opts) {
|
|
8259
|
+
const config = loadProjectConfig(cwd);
|
|
8260
|
+
if (!config?.project?.key) {
|
|
8261
|
+
throw new Error("AIGILE not initialized in this directory");
|
|
8262
|
+
}
|
|
8263
|
+
const absolutePath = resolve4(cwd, filePath);
|
|
8264
|
+
if (!existsSync7(absolutePath)) {
|
|
8265
|
+
throw new Error(`File not found: ${filePath}`);
|
|
8266
|
+
}
|
|
8267
|
+
const projectId = config.project.key;
|
|
8268
|
+
const relativePath = relative4(cwd, absolutePath);
|
|
8269
|
+
const content = readFileSync8(absolutePath, "utf-8");
|
|
8270
|
+
const updatedContent = updateFrontmatterContent(content, {
|
|
8271
|
+
exception: reason
|
|
8272
|
+
});
|
|
8273
|
+
writeFileSync5(absolutePath, updatedContent, "utf-8");
|
|
8274
|
+
const doc = queryOne(
|
|
8275
|
+
`SELECT id FROM documents WHERE project_id = ? AND path = ?`,
|
|
8276
|
+
[projectId, relativePath]
|
|
8277
|
+
);
|
|
8278
|
+
if (doc) {
|
|
8279
|
+
run(`UPDATE documents SET meta_exception = ? WHERE id = ?`, [reason, doc.id]);
|
|
8280
|
+
}
|
|
8281
|
+
success(`\u2713 Marked exception: ${filePath}`, opts);
|
|
8282
|
+
info(`Reason: ${reason}`, opts);
|
|
8283
|
+
}
|
|
8284
|
+
async function markRefactoring(cwd, filePath, opts) {
|
|
8285
|
+
const config = loadProjectConfig(cwd);
|
|
8286
|
+
if (!config?.project?.key) {
|
|
8287
|
+
throw new Error("AIGILE not initialized in this directory");
|
|
8288
|
+
}
|
|
8289
|
+
const absolutePath = resolve4(cwd, filePath);
|
|
8290
|
+
if (!existsSync7(absolutePath)) {
|
|
8291
|
+
throw new Error(`File not found: ${filePath}`);
|
|
8292
|
+
}
|
|
8293
|
+
const projectId = config.project.key;
|
|
8294
|
+
const relativePath = relative4(cwd, absolutePath);
|
|
8295
|
+
const doc = queryOne(
|
|
8296
|
+
`SELECT id FROM documents WHERE project_id = ? AND path = ?`,
|
|
8297
|
+
[projectId, relativePath]
|
|
8298
|
+
);
|
|
8299
|
+
if (!doc) {
|
|
8300
|
+
throw new Error(`Document not found in database: ${filePath}`);
|
|
8301
|
+
}
|
|
8302
|
+
run(`UPDATE documents SET monitoring_category = ? WHERE id = ?`, [
|
|
8303
|
+
"REFACTORING_REQUIRED",
|
|
8304
|
+
doc.id
|
|
8305
|
+
]);
|
|
8306
|
+
success(`\u2713 Marked for refactoring: ${filePath}`, opts);
|
|
8307
|
+
info("This file needs to be moved or restructured", opts);
|
|
8308
|
+
}
|
|
8309
|
+
async function archiveDocument(cwd, filePath, opts) {
|
|
8310
|
+
const config = loadProjectConfig(cwd);
|
|
8311
|
+
if (!config?.project?.key) {
|
|
8312
|
+
throw new Error("AIGILE not initialized in this directory");
|
|
8313
|
+
}
|
|
8314
|
+
const absolutePath = resolve4(cwd, filePath);
|
|
8315
|
+
if (!existsSync7(absolutePath)) {
|
|
8316
|
+
throw new Error(`File not found: ${filePath}`);
|
|
8317
|
+
}
|
|
8318
|
+
const projectId = config.project.key;
|
|
8319
|
+
const relativePath = relative4(cwd, absolutePath);
|
|
8320
|
+
const content = readFileSync8(absolutePath, "utf-8");
|
|
8321
|
+
const updatedContent = updateFrontmatterContent(content, {
|
|
8322
|
+
status: "ARCHIVED"
|
|
8323
|
+
});
|
|
8324
|
+
writeFileSync5(absolutePath, updatedContent, "utf-8");
|
|
8325
|
+
const doc = queryOne(
|
|
8326
|
+
`SELECT id FROM documents WHERE project_id = ? AND path = ?`,
|
|
8327
|
+
[projectId, relativePath]
|
|
8328
|
+
);
|
|
8329
|
+
if (doc) {
|
|
8330
|
+
run(`UPDATE documents SET meta_status = ? WHERE id = ?`, ["ARCHIVED", doc.id]);
|
|
8331
|
+
}
|
|
8332
|
+
success(`\u2713 Archived: ${filePath}`, opts);
|
|
8333
|
+
info("Document moved to archived status", opts);
|
|
8334
|
+
}
|
|
8335
|
+
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) => {
|
|
8336
|
+
const { projectId, projectPath } = await ensureProjectContext();
|
|
8337
|
+
const filePath = file.startsWith(".aigile/") ? file : `.aigile/${file}`;
|
|
8338
|
+
const graph = buildDependencyGraph(projectId);
|
|
8339
|
+
const doc = graph.get(filePath);
|
|
8340
|
+
if (!doc) {
|
|
8341
|
+
error(`Document not found: ${filePath}`);
|
|
8600
8342
|
process.exit(1);
|
|
8601
8343
|
}
|
|
8602
|
-
const
|
|
8603
|
-
|
|
8344
|
+
const directDependents = getDirectDependents(filePath, graph);
|
|
8345
|
+
const allDependents = opts.depth === 1 ? directDependents : getTransitiveDependents(filePath, graph);
|
|
8346
|
+
if (opts.json) {
|
|
8347
|
+
console.log(
|
|
8348
|
+
JSON.stringify(
|
|
8349
|
+
{
|
|
8350
|
+
document: filePath,
|
|
8351
|
+
directDependents,
|
|
8352
|
+
allDependents,
|
|
8353
|
+
count: allDependents.length
|
|
8354
|
+
},
|
|
8355
|
+
null,
|
|
8356
|
+
2
|
|
8357
|
+
)
|
|
8358
|
+
);
|
|
8359
|
+
return;
|
|
8360
|
+
}
|
|
8361
|
+
success(`Dependents of: ${filePath}`, opts);
|
|
8362
|
+
console.log();
|
|
8363
|
+
if (allDependents.length === 0) {
|
|
8364
|
+
info("No dependents found (this is a leaf document)", opts);
|
|
8365
|
+
return;
|
|
8366
|
+
}
|
|
8367
|
+
console.log(`Total: ${allDependents.length} dependent(s)`);
|
|
8368
|
+
console.log();
|
|
8369
|
+
for (const depPath of allDependents) {
|
|
8370
|
+
const depDoc = graph.get(depPath);
|
|
8371
|
+
const tldr = depDoc?.status ? `[${depDoc.status}]` : "";
|
|
8372
|
+
console.log(` \u2022 ${depPath} ${tldr}`);
|
|
8373
|
+
}
|
|
8374
|
+
});
|
|
8375
|
+
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) => {
|
|
8376
|
+
const { projectId, projectPath } = await ensureProjectContext();
|
|
8377
|
+
const filePath = file.startsWith(".aigile/") ? file : `.aigile/${file}`;
|
|
8378
|
+
const graph = buildDependencyGraph(projectId);
|
|
8379
|
+
const doc = graph.get(filePath);
|
|
8380
|
+
if (!doc) {
|
|
8381
|
+
error(`Document not found: ${filePath}`);
|
|
8382
|
+
process.exit(1);
|
|
8383
|
+
}
|
|
8384
|
+
const dependencies = doc.dependencies || [];
|
|
8385
|
+
if (opts.json) {
|
|
8386
|
+
console.log(
|
|
8387
|
+
JSON.stringify(
|
|
8388
|
+
{
|
|
8389
|
+
document: filePath,
|
|
8390
|
+
dependencies,
|
|
8391
|
+
count: dependencies.length
|
|
8392
|
+
},
|
|
8393
|
+
null,
|
|
8394
|
+
2
|
|
8395
|
+
)
|
|
8396
|
+
);
|
|
8397
|
+
return;
|
|
8398
|
+
}
|
|
8399
|
+
success(`Dependencies of: ${filePath}`, opts);
|
|
8400
|
+
console.log();
|
|
8401
|
+
if (dependencies.length === 0) {
|
|
8402
|
+
info("No dependencies (this is a foundational document)", opts);
|
|
8403
|
+
return;
|
|
8404
|
+
}
|
|
8405
|
+
console.log(
|
|
8406
|
+
`Total: ${dependencies.length} dependenc${dependencies.length === 1 ? "y" : "ies"}`
|
|
8407
|
+
);
|
|
8408
|
+
console.log();
|
|
8409
|
+
for (const depPath of dependencies) {
|
|
8410
|
+
const depDoc = graph.get(depPath);
|
|
8411
|
+
const tldr = depDoc?.status ? `[${depDoc.status}]` : "";
|
|
8412
|
+
console.log(` \u2022 ${depPath} ${tldr}`);
|
|
8413
|
+
}
|
|
8414
|
+
});
|
|
8415
|
+
var nextTemplateCommand = new Command19("next-template").description("Find the next template document to fill (foundation-first)").option("--json", "Output as JSON").action(async (opts) => {
|
|
8416
|
+
const { projectId } = await ensureProjectContext();
|
|
8417
|
+
const templates = queryAll(
|
|
8418
|
+
`SELECT path, meta_tldr, meta_dependencies
|
|
8419
|
+
FROM documents
|
|
8420
|
+
WHERE project_id = ? AND meta_status = 'TEMPLATE'
|
|
8421
|
+
ORDER BY path ASC`,
|
|
8422
|
+
[projectId]
|
|
8423
|
+
);
|
|
8424
|
+
if (templates.length === 0) {
|
|
8604
8425
|
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));
|
|
8426
|
+
console.log(JSON.stringify({ nextTemplate: null, remaining: 0 }, null, 2));
|
|
8427
|
+
return;
|
|
8618
8428
|
}
|
|
8429
|
+
success("\u2713 No templates remaining - all documents filled!", opts);
|
|
8619
8430
|
return;
|
|
8620
8431
|
}
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8432
|
+
const graph = buildDependencyGraph(projectId);
|
|
8433
|
+
const foundational = getFoundationalDocs(graph).filter((path) => {
|
|
8434
|
+
const doc = graph.get(path);
|
|
8435
|
+
return doc?.status === "TEMPLATE";
|
|
8436
|
+
});
|
|
8437
|
+
const nextTemplate = foundational[0] || templates[0].path;
|
|
8438
|
+
const nextDoc = graph.get(nextTemplate);
|
|
8439
|
+
if (opts.json) {
|
|
8440
|
+
console.log(
|
|
8441
|
+
JSON.stringify(
|
|
8442
|
+
{
|
|
8443
|
+
nextTemplate,
|
|
8444
|
+
tldr: nextDoc?.status || null,
|
|
8445
|
+
remaining: templates.length,
|
|
8446
|
+
foundational: foundational.length
|
|
8447
|
+
},
|
|
8448
|
+
null,
|
|
8449
|
+
2
|
|
8450
|
+
)
|
|
8451
|
+
);
|
|
8452
|
+
return;
|
|
8453
|
+
}
|
|
8454
|
+
success(`Next template to fill: ${nextTemplate}`, opts);
|
|
8455
|
+
if (nextDoc?.status) {
|
|
8456
|
+
info(`TLDR: ${nextDoc.status}`, opts);
|
|
8457
|
+
}
|
|
8458
|
+
console.log();
|
|
8459
|
+
info(`Remaining templates: ${templates.length}`, opts);
|
|
8460
|
+
info(`Foundational templates: ${foundational.length}`, opts);
|
|
8461
|
+
});
|
|
8462
|
+
var statsCommand = new Command19("stats").description("Show document statistics and progress").option("--json", "Output as JSON").action(async (opts) => {
|
|
8463
|
+
const { projectId } = await ensureProjectContext();
|
|
8464
|
+
const statusCounts = queryAll(
|
|
8465
|
+
`SELECT meta_status, COUNT(*) as count
|
|
8466
|
+
FROM documents
|
|
8467
|
+
WHERE project_id = ? AND meta_status IS NOT NULL
|
|
8468
|
+
GROUP BY meta_status`,
|
|
8469
|
+
[projectId]
|
|
8470
|
+
);
|
|
8471
|
+
const stats = {};
|
|
8472
|
+
let total = 0;
|
|
8473
|
+
for (const row of statusCounts) {
|
|
8474
|
+
stats[row.meta_status || "UNKNOWN"] = row.count;
|
|
8475
|
+
total += row.count;
|
|
8476
|
+
}
|
|
8477
|
+
const graph = buildDependencyGraph(projectId);
|
|
8478
|
+
let staleCount = 0;
|
|
8479
|
+
for (const [path] of graph) {
|
|
8480
|
+
const staleness = checkStaleness(path, graph);
|
|
8481
|
+
if (staleness.isStale) {
|
|
8482
|
+
staleCount++;
|
|
8626
8483
|
}
|
|
8627
|
-
} catch (err) {
|
|
8628
|
-
error(`Could not write file: ${filePath}`, opts);
|
|
8629
|
-
process.exit(1);
|
|
8630
8484
|
}
|
|
8485
|
+
if (opts.json) {
|
|
8486
|
+
console.log(
|
|
8487
|
+
JSON.stringify(
|
|
8488
|
+
{
|
|
8489
|
+
total,
|
|
8490
|
+
byStatus: stats,
|
|
8491
|
+
stale: staleCount,
|
|
8492
|
+
completion: total > 0 ? Math.round((stats.APPROVED || 0) / total * 100) : 0
|
|
8493
|
+
},
|
|
8494
|
+
null,
|
|
8495
|
+
2
|
|
8496
|
+
)
|
|
8497
|
+
);
|
|
8498
|
+
return;
|
|
8499
|
+
}
|
|
8500
|
+
success("Document Statistics", opts);
|
|
8501
|
+
console.log();
|
|
8502
|
+
console.log(`Total Documents: ${total}`);
|
|
8503
|
+
console.log();
|
|
8504
|
+
if (Object.keys(stats).length > 0) {
|
|
8505
|
+
console.log("By Status:");
|
|
8506
|
+
for (const [status, count] of Object.entries(stats)) {
|
|
8507
|
+
const pct = total > 0 ? (count / total * 100).toFixed(1) : "0.0";
|
|
8508
|
+
console.log(` ${status.padEnd(15)} ${count.toString().padStart(4)} (${pct}%)`);
|
|
8509
|
+
}
|
|
8510
|
+
console.log();
|
|
8511
|
+
}
|
|
8512
|
+
if (staleCount > 0) {
|
|
8513
|
+
warning(`\u26A0 ${staleCount} document(s) are stale (dependencies changed)`, opts);
|
|
8514
|
+
}
|
|
8515
|
+
const approved = stats.APPROVED || 0;
|
|
8516
|
+
const completion = total > 0 ? (approved / total * 100).toFixed(1) : "0.0";
|
|
8517
|
+
info(`Completion: ${completion}% (${approved}/${total} approved)`, opts);
|
|
8631
8518
|
});
|
|
8632
|
-
docCommand
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8519
|
+
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);
|
|
8520
|
+
|
|
8521
|
+
// src/commands/daemon.ts
|
|
8522
|
+
init_connection();
|
|
8523
|
+
import { Command as Command20 } from "commander";
|
|
8524
|
+
import {
|
|
8525
|
+
existsSync as existsSync11,
|
|
8526
|
+
writeFileSync as writeFileSync7,
|
|
8527
|
+
unlinkSync,
|
|
8528
|
+
readFileSync as readFileSync11,
|
|
8529
|
+
mkdirSync as mkdirSync5,
|
|
8530
|
+
statSync as statSync5,
|
|
8531
|
+
renameSync,
|
|
8532
|
+
readdirSync as readdirSync3
|
|
8533
|
+
} from "fs";
|
|
8534
|
+
import { join as join12, dirname as dirname4 } from "path";
|
|
8535
|
+
import { homedir as homedir2, platform } from "os";
|
|
8536
|
+
import { spawn, spawnSync, execSync as execSync2 } from "child_process";
|
|
8537
|
+
init_config();
|
|
8538
|
+
|
|
8539
|
+
// src/services/daemon-manager.ts
|
|
8540
|
+
import { existsSync as existsSync10 } from "fs";
|
|
8541
|
+
import { join as join11 } from "path";
|
|
8542
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
8543
|
+
|
|
8544
|
+
// src/services/file-watcher.ts
|
|
8545
|
+
init_connection();
|
|
8546
|
+
import { watch } from "chokidar";
|
|
8547
|
+
import { relative as relative5, extname as extname2, basename as basename4 } from "path";
|
|
8548
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
|
|
8549
|
+
import { EventEmitter } from "events";
|
|
8550
|
+
init_monitoring_patterns();
|
|
8551
|
+
init_config();
|
|
8552
|
+
|
|
8553
|
+
// src/services/document-status-transitions.ts
|
|
8554
|
+
init_connection();
|
|
8555
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
|
|
8556
|
+
import { resolve as resolve5 } from "path";
|
|
8557
|
+
import { createHash as createHash2 } from "crypto";
|
|
8558
|
+
async function handleDocumentEdit(projectId, docPath, absolutePath) {
|
|
8559
|
+
if (!existsSync8(absolutePath)) {
|
|
8560
|
+
return false;
|
|
8637
8561
|
}
|
|
8638
|
-
const
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
process.exit(1);
|
|
8562
|
+
const doc = queryOne(
|
|
8563
|
+
`SELECT id, meta_status, content_hash FROM documents WHERE project_id = ? AND path = ?`,
|
|
8564
|
+
[projectId, docPath]
|
|
8565
|
+
);
|
|
8566
|
+
if (!doc) {
|
|
8567
|
+
return false;
|
|
8645
8568
|
}
|
|
8646
|
-
const
|
|
8647
|
-
if (
|
|
8648
|
-
|
|
8649
|
-
process.exit(1);
|
|
8569
|
+
const currentStatus = doc.meta_status;
|
|
8570
|
+
if (!currentStatus || currentStatus === "ARCHIVED") {
|
|
8571
|
+
return false;
|
|
8650
8572
|
}
|
|
8651
|
-
const metadata = {
|
|
8652
|
-
status: options.status ?? "DRAFT",
|
|
8653
|
-
version: options.version ?? "0.1",
|
|
8654
|
-
tldr: options.tldr ?? "",
|
|
8655
|
-
title: options.title
|
|
8656
|
-
};
|
|
8657
|
-
const newContent = updateFrontmatterContent(content, metadata);
|
|
8658
8573
|
try {
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8574
|
+
const parsed = parseFrontmatterFromFile(absolutePath);
|
|
8575
|
+
const fileStatus = parsed?.metadata?.status;
|
|
8576
|
+
const content = readFileSync9(absolutePath, "utf-8");
|
|
8577
|
+
const contentHash = createHash2("sha256").update(content).digest("hex");
|
|
8578
|
+
if (contentHash === doc.content_hash) {
|
|
8579
|
+
return false;
|
|
8580
|
+
}
|
|
8581
|
+
let newStatus = null;
|
|
8582
|
+
switch (currentStatus) {
|
|
8583
|
+
case "TEMPLATE":
|
|
8584
|
+
newStatus = "DRAFT";
|
|
8585
|
+
break;
|
|
8586
|
+
case "APPROVED":
|
|
8587
|
+
newStatus = "NEEDS_REVIEW";
|
|
8588
|
+
break;
|
|
8589
|
+
case "NEEDS_REVIEW":
|
|
8590
|
+
case "DRAFT":
|
|
8591
|
+
break;
|
|
8592
|
+
}
|
|
8593
|
+
if (!newStatus) {
|
|
8594
|
+
return false;
|
|
8595
|
+
}
|
|
8596
|
+
const updatedContent = updateFrontmatterContent(content, {
|
|
8597
|
+
status: newStatus
|
|
8598
|
+
});
|
|
8599
|
+
writeFileSync6(absolutePath, updatedContent, "utf-8");
|
|
8600
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8601
|
+
run(
|
|
8602
|
+
`UPDATE documents
|
|
8603
|
+
SET meta_status = ?, content_hash = ?, last_modified_at = ?, needs_review = ?
|
|
8604
|
+
WHERE id = ?`,
|
|
8605
|
+
[
|
|
8606
|
+
newStatus,
|
|
8607
|
+
contentHash,
|
|
8608
|
+
now,
|
|
8609
|
+
newStatus === "NEEDS_REVIEW" ? 1 : 0,
|
|
8610
|
+
doc.id
|
|
8611
|
+
]
|
|
8612
|
+
);
|
|
8613
|
+
console.log(
|
|
8614
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] Status transition: ${docPath} (${currentStatus} \u2192 ${newStatus})`
|
|
8615
|
+
);
|
|
8616
|
+
return true;
|
|
8617
|
+
} catch (err) {
|
|
8618
|
+
console.error(
|
|
8619
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] Error in status transition for ${docPath}: ${err}`
|
|
8620
|
+
);
|
|
8621
|
+
return false;
|
|
8667
8622
|
}
|
|
8668
|
-
}
|
|
8669
|
-
|
|
8670
|
-
const
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8623
|
+
}
|
|
8624
|
+
async function markDependentsStale(projectId, docPath) {
|
|
8625
|
+
const dependents = queryAll(
|
|
8626
|
+
`SELECT id, path, meta_dependencies FROM documents WHERE project_id = ?`,
|
|
8627
|
+
[projectId]
|
|
8628
|
+
);
|
|
8629
|
+
let markedCount = 0;
|
|
8630
|
+
for (const dep of dependents) {
|
|
8631
|
+
try {
|
|
8632
|
+
const dependencies = dep.meta_dependencies ? JSON.parse(dep.meta_dependencies) : [];
|
|
8633
|
+
if (dependencies.includes(docPath)) {
|
|
8634
|
+
run(`UPDATE documents SET needs_review = 1 WHERE id = ?`, [dep.id]);
|
|
8635
|
+
markedCount++;
|
|
8636
|
+
}
|
|
8637
|
+
} catch {
|
|
8638
|
+
}
|
|
8674
8639
|
}
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
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
|
-
if (opts.json) {
|
|
8691
|
-
console.log(JSON.stringify({ success: true, data: stats }));
|
|
8692
|
-
} else {
|
|
8693
|
-
details(
|
|
8694
|
-
stats,
|
|
8695
|
-
[
|
|
8696
|
-
{ label: "Total Documents", key: "total_documents" },
|
|
8697
|
-
{ label: "With Frontmatter", key: "with_frontmatter" },
|
|
8698
|
-
{ label: "Without Frontmatter", key: "without_frontmatter" },
|
|
8699
|
-
{ label: "TEMPLATE", key: "templates" },
|
|
8700
|
-
{ label: "DRAFT", key: "drafts" },
|
|
8701
|
-
{ label: "IN-REVIEW", key: "in_review" },
|
|
8702
|
-
{ label: "APPROVED", key: "approved" }
|
|
8703
|
-
],
|
|
8704
|
-
opts
|
|
8640
|
+
if (markedCount > 0) {
|
|
8641
|
+
console.log(
|
|
8642
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] Marked ${markedCount} dependents as stale for ${docPath}`
|
|
8705
8643
|
);
|
|
8706
8644
|
}
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8645
|
+
return markedCount;
|
|
8646
|
+
}
|
|
8647
|
+
function isAigileDoc(docPath) {
|
|
8648
|
+
return docPath.startsWith(".aigile/") && (docPath.endsWith(".md") || docPath.endsWith(".feature"));
|
|
8649
|
+
}
|
|
8650
|
+
async function onFileChanged(projectId, projectPath, relativeDocPath) {
|
|
8651
|
+
if (!isAigileDoc(relativeDocPath)) {
|
|
8652
|
+
return;
|
|
8653
|
+
}
|
|
8654
|
+
const absolutePath = resolve5(projectPath, relativeDocPath);
|
|
8655
|
+
const transitioned = await handleDocumentEdit(
|
|
8656
|
+
projectId,
|
|
8657
|
+
relativeDocPath,
|
|
8658
|
+
absolutePath
|
|
8659
|
+
);
|
|
8660
|
+
if (transitioned || existsSync8(absolutePath)) {
|
|
8661
|
+
await markDependentsStale(projectId, relativeDocPath);
|
|
8662
|
+
}
|
|
8663
|
+
}
|
|
8722
8664
|
|
|
8723
8665
|
// src/services/file-watcher.ts
|
|
8724
|
-
init_connection();
|
|
8725
|
-
import { watch } from "chokidar";
|
|
8726
|
-
import { relative as relative3, extname as extname2, basename as basename4 } from "path";
|
|
8727
|
-
import { readFileSync as readFileSync8, statSync as statSync2 } from "fs";
|
|
8728
|
-
import { EventEmitter } from "events";
|
|
8729
|
-
init_monitoring_patterns();
|
|
8730
|
-
init_config();
|
|
8731
8666
|
import picomatch2 from "picomatch";
|
|
8732
8667
|
var FileWatcher = class extends EventEmitter {
|
|
8733
8668
|
watcher = null;
|
|
8734
8669
|
config;
|
|
8735
8670
|
stats;
|
|
8736
8671
|
debounceTimers = /* @__PURE__ */ new Map();
|
|
8672
|
+
cleanupInterval = null;
|
|
8737
8673
|
// Tri-state pattern matchers
|
|
8738
8674
|
allowMatcher = null;
|
|
8739
8675
|
denyMatcher = null;
|
|
@@ -8817,6 +8753,12 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8817
8753
|
this.updateCategoryCounts();
|
|
8818
8754
|
this.emit("ready", this.stats);
|
|
8819
8755
|
});
|
|
8756
|
+
this.cleanupInterval = setInterval(
|
|
8757
|
+
() => {
|
|
8758
|
+
this.cleanupStaleTimers();
|
|
8759
|
+
},
|
|
8760
|
+
5 * 60 * 1e3
|
|
8761
|
+
);
|
|
8820
8762
|
}
|
|
8821
8763
|
/**
|
|
8822
8764
|
* Update category counts from database
|
|
@@ -8824,12 +8766,15 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8824
8766
|
*/
|
|
8825
8767
|
updateCategoryCounts() {
|
|
8826
8768
|
try {
|
|
8827
|
-
const counts = queryAll(
|
|
8769
|
+
const counts = queryAll(
|
|
8770
|
+
`
|
|
8828
8771
|
SELECT monitoring_category, COUNT(*) as count
|
|
8829
8772
|
FROM documents
|
|
8830
8773
|
WHERE project_id = ? AND status != 'deleted'
|
|
8831
8774
|
GROUP BY monitoring_category
|
|
8832
|
-
`,
|
|
8775
|
+
`,
|
|
8776
|
+
[this.config.projectId]
|
|
8777
|
+
);
|
|
8833
8778
|
this.stats.categoryCounts = { allow: 0, deny: 0, unknown: 0 };
|
|
8834
8779
|
for (const row of counts) {
|
|
8835
8780
|
const cat = row.monitoring_category;
|
|
@@ -8847,6 +8792,10 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8847
8792
|
if (!this.watcher) {
|
|
8848
8793
|
return;
|
|
8849
8794
|
}
|
|
8795
|
+
if (this.cleanupInterval) {
|
|
8796
|
+
clearInterval(this.cleanupInterval);
|
|
8797
|
+
this.cleanupInterval = null;
|
|
8798
|
+
}
|
|
8850
8799
|
for (const timer of this.debounceTimers.values()) {
|
|
8851
8800
|
clearTimeout(timer);
|
|
8852
8801
|
}
|
|
@@ -8856,6 +8805,28 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8856
8805
|
this.stats.isRunning = false;
|
|
8857
8806
|
this.emit("stopped");
|
|
8858
8807
|
}
|
|
8808
|
+
/**
|
|
8809
|
+
* Periodic cleanup of stale debounce timers
|
|
8810
|
+
* Removes timers for files that no longer exist
|
|
8811
|
+
*/
|
|
8812
|
+
cleanupStaleTimers() {
|
|
8813
|
+
const staleKeys = [];
|
|
8814
|
+
for (const [path] of this.debounceTimers) {
|
|
8815
|
+
if (!existsSync9(path)) {
|
|
8816
|
+
staleKeys.push(path);
|
|
8817
|
+
}
|
|
8818
|
+
}
|
|
8819
|
+
for (const key of staleKeys) {
|
|
8820
|
+
const timer = this.debounceTimers.get(key);
|
|
8821
|
+
if (timer) {
|
|
8822
|
+
clearTimeout(timer);
|
|
8823
|
+
this.debounceTimers.delete(key);
|
|
8824
|
+
}
|
|
8825
|
+
}
|
|
8826
|
+
if (staleKeys.length > 0) {
|
|
8827
|
+
console.log(`[FileWatcher] Cleaned up ${staleKeys.length} stale debounce timers`);
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8859
8830
|
/**
|
|
8860
8831
|
* Get current watcher statistics
|
|
8861
8832
|
*/
|
|
@@ -8881,6 +8852,11 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8881
8852
|
const existingTimer = this.debounceTimers.get(absolutePath);
|
|
8882
8853
|
if (existingTimer) {
|
|
8883
8854
|
clearTimeout(existingTimer);
|
|
8855
|
+
this.debounceTimers.delete(absolutePath);
|
|
8856
|
+
}
|
|
8857
|
+
if (type === "unlink") {
|
|
8858
|
+
this.processFileEvent(type, absolutePath);
|
|
8859
|
+
return;
|
|
8884
8860
|
}
|
|
8885
8861
|
const timer = setTimeout(() => {
|
|
8886
8862
|
this.debounceTimers.delete(absolutePath);
|
|
@@ -8892,7 +8868,7 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8892
8868
|
* Process a file event (after debounce)
|
|
8893
8869
|
*/
|
|
8894
8870
|
processFileEvent(type, absolutePath) {
|
|
8895
|
-
const relativePath =
|
|
8871
|
+
const relativePath = relative5(this.config.projectPath, absolutePath);
|
|
8896
8872
|
const category = this.classifyFile(relativePath);
|
|
8897
8873
|
if (category === "deny" && type !== "unlink") {
|
|
8898
8874
|
return;
|
|
@@ -8915,6 +8891,14 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8915
8891
|
break;
|
|
8916
8892
|
case "change":
|
|
8917
8893
|
this.syncFileChange(absolutePath, relativePath, category);
|
|
8894
|
+
onFileChanged(this.config.projectId, this.config.projectPath, relativePath).catch((err) => {
|
|
8895
|
+
this.emit("transitionError", {
|
|
8896
|
+
path: relativePath,
|
|
8897
|
+
error: err,
|
|
8898
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
8899
|
+
});
|
|
8900
|
+
console.error(`[FileWatcher] Error in onFileChanged for ${relativePath}:`, err);
|
|
8901
|
+
});
|
|
8918
8902
|
break;
|
|
8919
8903
|
case "unlink":
|
|
8920
8904
|
this.syncFileDelete(relativePath);
|
|
@@ -8933,7 +8917,7 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8933
8917
|
const filename = basename4(relativePath);
|
|
8934
8918
|
const isBinary = isBinaryExtension(ext);
|
|
8935
8919
|
try {
|
|
8936
|
-
const stats =
|
|
8920
|
+
const stats = statSync4(absolutePath);
|
|
8937
8921
|
const shouldComputeHash = category === "allow" || !isBinary && stats.size < 10 * 1024 * 1024;
|
|
8938
8922
|
const hash = shouldComputeHash ? computeFileHash(absolutePath) : null;
|
|
8939
8923
|
let hasFrontmatter = false;
|
|
@@ -8952,16 +8936,36 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8952
8936
|
[this.config.projectId, relativePath]
|
|
8953
8937
|
);
|
|
8954
8938
|
if (existing) {
|
|
8955
|
-
this.updateDocument(
|
|
8939
|
+
this.updateDocument(
|
|
8940
|
+
existing.id,
|
|
8941
|
+
hash,
|
|
8942
|
+
stats.size,
|
|
8943
|
+
hasFrontmatter,
|
|
8944
|
+
frontmatterRaw,
|
|
8945
|
+
metadata,
|
|
8946
|
+
category
|
|
8947
|
+
);
|
|
8956
8948
|
} else {
|
|
8957
|
-
this.insertDocument(
|
|
8949
|
+
this.insertDocument(
|
|
8950
|
+
relativePath,
|
|
8951
|
+
filename,
|
|
8952
|
+
ext,
|
|
8953
|
+
hash,
|
|
8954
|
+
stats.size,
|
|
8955
|
+
hasFrontmatter,
|
|
8956
|
+
frontmatterRaw,
|
|
8957
|
+
metadata,
|
|
8958
|
+
category
|
|
8959
|
+
);
|
|
8958
8960
|
}
|
|
8959
8961
|
} catch (err) {
|
|
8960
8962
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
8961
8963
|
if (errMsg.includes("Database") || errMsg.includes("database")) {
|
|
8962
8964
|
throw err;
|
|
8963
8965
|
}
|
|
8964
|
-
console.error(
|
|
8966
|
+
console.error(
|
|
8967
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] syncFileAdd error for ${relativePath}: ${errMsg}`
|
|
8968
|
+
);
|
|
8965
8969
|
}
|
|
8966
8970
|
}
|
|
8967
8971
|
/**
|
|
@@ -8979,10 +8983,9 @@ var FileWatcher = class extends EventEmitter {
|
|
|
8979
8983
|
[this.config.projectId, relativePath]
|
|
8980
8984
|
);
|
|
8981
8985
|
if (doc) {
|
|
8982
|
-
run(
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
);
|
|
8986
|
+
run(`UPDATE documents SET status = 'deleted', updated_at = datetime('now') WHERE id = ?`, [
|
|
8987
|
+
doc.id
|
|
8988
|
+
]);
|
|
8986
8989
|
}
|
|
8987
8990
|
}
|
|
8988
8991
|
/**
|
|
@@ -9076,6 +9079,7 @@ var INITIAL_RETRY_DELAY_MS = 5e3;
|
|
|
9076
9079
|
var DaemonManager = class extends EventEmitter2 {
|
|
9077
9080
|
watchers = /* @__PURE__ */ new Map();
|
|
9078
9081
|
watcherRetries = /* @__PURE__ */ new Map();
|
|
9082
|
+
retryTimeouts = /* @__PURE__ */ new Map();
|
|
9079
9083
|
running = false;
|
|
9080
9084
|
startedAt = null;
|
|
9081
9085
|
/**
|
|
@@ -9131,21 +9135,37 @@ var DaemonManager = class extends EventEmitter2 {
|
|
|
9131
9135
|
if (currentRetries < MAX_WATCHER_RETRIES) {
|
|
9132
9136
|
this.watcherRetries.set(project.key, currentRetries + 1);
|
|
9133
9137
|
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, currentRetries);
|
|
9134
|
-
console.log(
|
|
9135
|
-
|
|
9138
|
+
console.log(
|
|
9139
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Will retry in ${delay}ms (attempt ${currentRetries + 1}/${MAX_WATCHER_RETRIES})`
|
|
9140
|
+
);
|
|
9141
|
+
const existingTimeout2 = this.retryTimeouts.get(project.key);
|
|
9142
|
+
if (existingTimeout2) {
|
|
9143
|
+
clearTimeout(existingTimeout2);
|
|
9144
|
+
}
|
|
9145
|
+
const timeout = setTimeout(async () => {
|
|
9146
|
+
this.retryTimeouts.delete(project.key);
|
|
9136
9147
|
if (this.running) {
|
|
9137
9148
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Attempting restart...`);
|
|
9138
9149
|
await this.startWatcherWithRetry(project);
|
|
9139
9150
|
}
|
|
9140
9151
|
}, delay);
|
|
9152
|
+
this.retryTimeouts.set(project.key, timeout);
|
|
9141
9153
|
} else {
|
|
9142
|
-
console.error(
|
|
9154
|
+
console.error(
|
|
9155
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Max retries (${MAX_WATCHER_RETRIES}) exceeded - watcher disabled`
|
|
9156
|
+
);
|
|
9157
|
+
this.watcherRetries.delete(project.key);
|
|
9143
9158
|
this.emit("watcherDisabled", { project: project.key });
|
|
9144
9159
|
}
|
|
9145
9160
|
});
|
|
9146
9161
|
watcher.start();
|
|
9147
9162
|
this.watchers.set(project.key, watcher);
|
|
9148
|
-
this.watcherRetries.
|
|
9163
|
+
this.watcherRetries.delete(project.key);
|
|
9164
|
+
const existingTimeout = this.retryTimeouts.get(project.key);
|
|
9165
|
+
if (existingTimeout) {
|
|
9166
|
+
clearTimeout(existingTimeout);
|
|
9167
|
+
this.retryTimeouts.delete(project.key);
|
|
9168
|
+
}
|
|
9149
9169
|
console.log(` \u2713 ${project.key}: ${project.path}`);
|
|
9150
9170
|
} catch (error2) {
|
|
9151
9171
|
console.error(` \u2717 ${project.key}: Failed to start watcher - ${error2}`);
|
|
@@ -9153,12 +9173,22 @@ var DaemonManager = class extends EventEmitter2 {
|
|
|
9153
9173
|
if (retryCount < MAX_WATCHER_RETRIES) {
|
|
9154
9174
|
this.watcherRetries.set(project.key, retryCount + 1);
|
|
9155
9175
|
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
|
|
9156
|
-
console.log(
|
|
9157
|
-
|
|
9176
|
+
console.log(
|
|
9177
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project.key}] Will retry in ${delay}ms (attempt ${retryCount + 1}/${MAX_WATCHER_RETRIES})`
|
|
9178
|
+
);
|
|
9179
|
+
const existingTimeout = this.retryTimeouts.get(project.key);
|
|
9180
|
+
if (existingTimeout) {
|
|
9181
|
+
clearTimeout(existingTimeout);
|
|
9182
|
+
}
|
|
9183
|
+
const timeout = setTimeout(async () => {
|
|
9184
|
+
this.retryTimeouts.delete(project.key);
|
|
9158
9185
|
if (this.running) {
|
|
9159
9186
|
await this.startWatcherWithRetry(project);
|
|
9160
9187
|
}
|
|
9161
9188
|
}, delay);
|
|
9189
|
+
this.retryTimeouts.set(project.key, timeout);
|
|
9190
|
+
} else {
|
|
9191
|
+
this.watcherRetries.delete(project.key);
|
|
9162
9192
|
}
|
|
9163
9193
|
}
|
|
9164
9194
|
}
|
|
@@ -9170,6 +9200,11 @@ var DaemonManager = class extends EventEmitter2 {
|
|
|
9170
9200
|
return;
|
|
9171
9201
|
}
|
|
9172
9202
|
console.log("Stopping all watchers...");
|
|
9203
|
+
for (const [key, timeout] of this.retryTimeouts) {
|
|
9204
|
+
clearTimeout(timeout);
|
|
9205
|
+
console.log(` \u2713 Cleared retry timeout: ${key}`);
|
|
9206
|
+
}
|
|
9207
|
+
this.retryTimeouts.clear();
|
|
9173
9208
|
for (const [key, watcher] of this.watchers) {
|
|
9174
9209
|
try {
|
|
9175
9210
|
await watcher.stop();
|
|
@@ -9179,6 +9214,7 @@ var DaemonManager = class extends EventEmitter2 {
|
|
|
9179
9214
|
}
|
|
9180
9215
|
}
|
|
9181
9216
|
this.watchers.clear();
|
|
9217
|
+
this.watcherRetries.clear();
|
|
9182
9218
|
this.running = false;
|
|
9183
9219
|
this.startedAt = null;
|
|
9184
9220
|
this.emit("stopped");
|
|
@@ -9295,7 +9331,7 @@ var DaemonManager = class extends EventEmitter2 {
|
|
|
9295
9331
|
* Check if a project path is valid
|
|
9296
9332
|
*/
|
|
9297
9333
|
isValidProject(path) {
|
|
9298
|
-
return
|
|
9334
|
+
return existsSync10(path) && existsSync10(join11(path, ".aigile"));
|
|
9299
9335
|
}
|
|
9300
9336
|
};
|
|
9301
9337
|
var daemonManagerInstance = null;
|
|
@@ -9318,29 +9354,29 @@ var DAEMON_NAME = "com.aigile.watcher";
|
|
|
9318
9354
|
function getDaemonPaths() {
|
|
9319
9355
|
const aigileHome = getAigileHome();
|
|
9320
9356
|
const basePaths = {
|
|
9321
|
-
pidFile:
|
|
9322
|
-
logFile:
|
|
9357
|
+
pidFile: join12(aigileHome, "daemon.pid"),
|
|
9358
|
+
logFile: join12(aigileHome, "daemon.log")
|
|
9323
9359
|
};
|
|
9324
9360
|
if (PLATFORM === "darwin") {
|
|
9325
9361
|
return {
|
|
9326
9362
|
...basePaths,
|
|
9327
|
-
plist:
|
|
9363
|
+
plist: join12(homedir2(), "Library", "LaunchAgents", `${DAEMON_NAME}.plist`)
|
|
9328
9364
|
};
|
|
9329
9365
|
} else if (PLATFORM === "linux") {
|
|
9330
9366
|
return {
|
|
9331
9367
|
...basePaths,
|
|
9332
|
-
service:
|
|
9368
|
+
service: join12(homedir2(), ".config", "systemd", "user", `${DAEMON_NAME}.service`)
|
|
9333
9369
|
};
|
|
9334
9370
|
}
|
|
9335
9371
|
return basePaths;
|
|
9336
9372
|
}
|
|
9337
9373
|
function isDaemonRunning() {
|
|
9338
9374
|
const paths = getDaemonPaths();
|
|
9339
|
-
if (!
|
|
9375
|
+
if (!existsSync11(paths.pidFile)) {
|
|
9340
9376
|
return { running: false };
|
|
9341
9377
|
}
|
|
9342
9378
|
try {
|
|
9343
|
-
const pid = parseInt(
|
|
9379
|
+
const pid = parseInt(readFileSync11(paths.pidFile, "utf-8").trim(), 10);
|
|
9344
9380
|
try {
|
|
9345
9381
|
process.kill(pid, 0);
|
|
9346
9382
|
return { running: true, pid };
|
|
@@ -9354,12 +9390,12 @@ function isDaemonRunning() {
|
|
|
9354
9390
|
}
|
|
9355
9391
|
function writeCrashReport(error2) {
|
|
9356
9392
|
try {
|
|
9357
|
-
const crashDir =
|
|
9358
|
-
if (!
|
|
9393
|
+
const crashDir = join12(getAigileHome(), CRASH_DIR_NAME);
|
|
9394
|
+
if (!existsSync11(crashDir)) {
|
|
9359
9395
|
mkdirSync5(crashDir, { recursive: true });
|
|
9360
9396
|
}
|
|
9361
9397
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9362
|
-
const crashFile =
|
|
9398
|
+
const crashFile = join12(crashDir, `crash-${timestamp}.log`);
|
|
9363
9399
|
const report = [
|
|
9364
9400
|
`AIGILE Daemon Crash Report`,
|
|
9365
9401
|
`==========================`,
|
|
@@ -9372,7 +9408,7 @@ function writeCrashReport(error2) {
|
|
|
9372
9408
|
`Error:`,
|
|
9373
9409
|
error2 instanceof Error ? error2.stack || error2.message : String(error2)
|
|
9374
9410
|
].join("\n");
|
|
9375
|
-
|
|
9411
|
+
writeFileSync7(crashFile, report);
|
|
9376
9412
|
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Crash report saved: ${crashFile}`);
|
|
9377
9413
|
cleanupOldCrashReports(crashDir);
|
|
9378
9414
|
} catch (writeErr) {
|
|
@@ -9381,7 +9417,7 @@ function writeCrashReport(error2) {
|
|
|
9381
9417
|
}
|
|
9382
9418
|
function cleanupOldCrashReports(crashDir) {
|
|
9383
9419
|
try {
|
|
9384
|
-
const files =
|
|
9420
|
+
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
9421
|
for (let i = MAX_CRASH_REPORTS; i < files.length; i++) {
|
|
9386
9422
|
try {
|
|
9387
9423
|
unlinkSync(files[i].path);
|
|
@@ -9395,21 +9431,21 @@ function rotateLogIfNeeded() {
|
|
|
9395
9431
|
const paths = getDaemonPaths();
|
|
9396
9432
|
const logPath = paths.logFile;
|
|
9397
9433
|
try {
|
|
9398
|
-
if (!
|
|
9399
|
-
const stats =
|
|
9434
|
+
if (!existsSync11(logPath)) return;
|
|
9435
|
+
const stats = statSync5(logPath);
|
|
9400
9436
|
if (stats.size < MAX_LOG_SIZE) return;
|
|
9401
9437
|
const timestamp = Date.now();
|
|
9402
9438
|
const rotatedPath = `${logPath}.${timestamp}`;
|
|
9403
9439
|
renameSync(logPath, rotatedPath);
|
|
9404
9440
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Log rotated: ${rotatedPath}`);
|
|
9405
|
-
cleanupOldLogs(
|
|
9441
|
+
cleanupOldLogs(dirname4(logPath));
|
|
9406
9442
|
} catch (err) {
|
|
9407
9443
|
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Log rotation error: ${err}`);
|
|
9408
9444
|
}
|
|
9409
9445
|
}
|
|
9410
9446
|
function cleanupOldLogs(logDir) {
|
|
9411
9447
|
try {
|
|
9412
|
-
const files =
|
|
9448
|
+
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
9449
|
for (let i = MAX_LOG_FILES; i < files.length; i++) {
|
|
9414
9450
|
try {
|
|
9415
9451
|
unlinkSync(files[i].path);
|
|
@@ -9423,7 +9459,11 @@ function cleanupOldLogs(logDir) {
|
|
|
9423
9459
|
function generateLaunchAgentPlist() {
|
|
9424
9460
|
const paths = getDaemonPaths();
|
|
9425
9461
|
const nodePath = process.execPath;
|
|
9426
|
-
const aigilePath =
|
|
9462
|
+
const aigilePath = join12(
|
|
9463
|
+
dirname4(dirname4(import.meta.url.replace("file://", ""))),
|
|
9464
|
+
"bin",
|
|
9465
|
+
"aigile.js"
|
|
9466
|
+
);
|
|
9427
9467
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
9428
9468
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
9429
9469
|
<plist version="1.0">
|
|
@@ -9458,7 +9498,11 @@ function generateLaunchAgentPlist() {
|
|
|
9458
9498
|
function generateSystemdService() {
|
|
9459
9499
|
const paths = getDaemonPaths();
|
|
9460
9500
|
const nodePath = process.execPath;
|
|
9461
|
-
const aigilePath =
|
|
9501
|
+
const aigilePath = join12(
|
|
9502
|
+
dirname4(dirname4(import.meta.url.replace("file://", ""))),
|
|
9503
|
+
"bin",
|
|
9504
|
+
"aigile.js"
|
|
9505
|
+
);
|
|
9462
9506
|
return `[Unit]
|
|
9463
9507
|
Description=AIGILE File Watcher Daemon
|
|
9464
9508
|
After=network.target
|
|
@@ -9477,27 +9521,29 @@ StandardError=append:${paths.logFile}
|
|
|
9477
9521
|
[Install]
|
|
9478
9522
|
WantedBy=default.target`;
|
|
9479
9523
|
}
|
|
9480
|
-
daemonCommand.command("install").description(
|
|
9524
|
+
daemonCommand.command("install").description(
|
|
9525
|
+
"Install daemon to start automatically on system boot (watches ALL registered projects)"
|
|
9526
|
+
).action(() => {
|
|
9481
9527
|
const opts = getOutputOptions(daemonCommand);
|
|
9482
9528
|
const paths = getDaemonPaths();
|
|
9483
9529
|
if (PLATFORM === "darwin") {
|
|
9484
|
-
const plistDir =
|
|
9485
|
-
if (!
|
|
9530
|
+
const plistDir = dirname4(paths.plist);
|
|
9531
|
+
if (!existsSync11(plistDir)) {
|
|
9486
9532
|
mkdirSync5(plistDir, { recursive: true });
|
|
9487
9533
|
}
|
|
9488
9534
|
const plistContent = generateLaunchAgentPlist();
|
|
9489
|
-
|
|
9535
|
+
writeFileSync7(paths.plist, plistContent);
|
|
9490
9536
|
success("Installed macOS LaunchAgent", opts);
|
|
9491
9537
|
info(`Plist location: ${paths.plist}`, opts);
|
|
9492
9538
|
info("Daemon will watch ALL registered projects", opts);
|
|
9493
9539
|
info('Run "aigile daemon start" to start the watcher', opts);
|
|
9494
9540
|
} else if (PLATFORM === "linux") {
|
|
9495
|
-
const serviceDir =
|
|
9496
|
-
if (!
|
|
9541
|
+
const serviceDir = dirname4(paths.service);
|
|
9542
|
+
if (!existsSync11(serviceDir)) {
|
|
9497
9543
|
mkdirSync5(serviceDir, { recursive: true });
|
|
9498
9544
|
}
|
|
9499
9545
|
const serviceContent = generateSystemdService();
|
|
9500
|
-
|
|
9546
|
+
writeFileSync7(paths.service, serviceContent);
|
|
9501
9547
|
try {
|
|
9502
9548
|
execSync2("systemctl --user daemon-reload");
|
|
9503
9549
|
execSync2(`systemctl --user enable ${DAEMON_NAME}`);
|
|
@@ -9506,7 +9552,7 @@ daemonCommand.command("install").description("Install daemon to start automatica
|
|
|
9506
9552
|
info("Daemon will watch ALL registered projects", opts);
|
|
9507
9553
|
info('Run "aigile daemon start" to start the watcher', opts);
|
|
9508
9554
|
} catch (err) {
|
|
9509
|
-
|
|
9555
|
+
warning("Service file created but could not enable. You may need to run:", opts);
|
|
9510
9556
|
console.log(` systemctl --user daemon-reload`);
|
|
9511
9557
|
console.log(` systemctl --user enable ${DAEMON_NAME}`);
|
|
9512
9558
|
}
|
|
@@ -9527,14 +9573,14 @@ daemonCommand.command("uninstall").description("Remove daemon from auto-start").
|
|
|
9527
9573
|
} catch {
|
|
9528
9574
|
}
|
|
9529
9575
|
}
|
|
9530
|
-
if (PLATFORM === "darwin" && paths.plist &&
|
|
9576
|
+
if (PLATFORM === "darwin" && paths.plist && existsSync11(paths.plist)) {
|
|
9531
9577
|
try {
|
|
9532
9578
|
execSync2(`launchctl unload ${paths.plist}`);
|
|
9533
9579
|
} catch {
|
|
9534
9580
|
}
|
|
9535
9581
|
unlinkSync(paths.plist);
|
|
9536
9582
|
success("Removed macOS LaunchAgent", opts);
|
|
9537
|
-
} else if (PLATFORM === "linux" && paths.service &&
|
|
9583
|
+
} else if (PLATFORM === "linux" && paths.service && existsSync11(paths.service)) {
|
|
9538
9584
|
try {
|
|
9539
9585
|
execSync2(`systemctl --user stop ${DAEMON_NAME}`);
|
|
9540
9586
|
execSync2(`systemctl --user disable ${DAEMON_NAME}`);
|
|
@@ -9549,7 +9595,7 @@ daemonCommand.command("uninstall").description("Remove daemon from auto-start").
|
|
|
9549
9595
|
} else {
|
|
9550
9596
|
info("No daemon installation found", opts);
|
|
9551
9597
|
}
|
|
9552
|
-
if (
|
|
9598
|
+
if (existsSync11(paths.pidFile)) {
|
|
9553
9599
|
unlinkSync(paths.pidFile);
|
|
9554
9600
|
}
|
|
9555
9601
|
});
|
|
@@ -9561,7 +9607,7 @@ daemonCommand.command("start").description("Start the file watcher daemon (watch
|
|
|
9561
9607
|
info(`Daemon already running (PID: ${status.pid})`, opts);
|
|
9562
9608
|
return;
|
|
9563
9609
|
}
|
|
9564
|
-
if (PLATFORM === "darwin" && paths.plist &&
|
|
9610
|
+
if (PLATFORM === "darwin" && paths.plist && existsSync11(paths.plist)) {
|
|
9565
9611
|
try {
|
|
9566
9612
|
execSync2(`launchctl load ${paths.plist}`);
|
|
9567
9613
|
success("Started daemon via launchctl (watching all projects)", opts);
|
|
@@ -9569,7 +9615,7 @@ daemonCommand.command("start").description("Start the file watcher daemon (watch
|
|
|
9569
9615
|
error("Failed to start daemon via launchctl", opts);
|
|
9570
9616
|
process.exit(1);
|
|
9571
9617
|
}
|
|
9572
|
-
} else if (PLATFORM === "linux" && paths.service &&
|
|
9618
|
+
} else if (PLATFORM === "linux" && paths.service && existsSync11(paths.service)) {
|
|
9573
9619
|
try {
|
|
9574
9620
|
execSync2(`systemctl --user start ${DAEMON_NAME}`);
|
|
9575
9621
|
success("Started daemon via systemctl (watching all projects)", opts);
|
|
@@ -9586,7 +9632,7 @@ daemonCommand.command("start").description("Start the file watcher daemon (watch
|
|
|
9586
9632
|
if (child.pid) {
|
|
9587
9633
|
try {
|
|
9588
9634
|
process.kill(child.pid, 0);
|
|
9589
|
-
|
|
9635
|
+
writeFileSync7(paths.pidFile, String(child.pid));
|
|
9590
9636
|
success(`Started daemon (PID: ${child.pid}) - watching all projects`, opts);
|
|
9591
9637
|
} catch {
|
|
9592
9638
|
error("Failed to start daemon - process died immediately", opts);
|
|
@@ -9601,14 +9647,14 @@ daemonCommand.command("start").description("Start the file watcher daemon (watch
|
|
|
9601
9647
|
daemonCommand.command("stop").description("Stop the file watcher daemon").action(() => {
|
|
9602
9648
|
const opts = getOutputOptions(daemonCommand);
|
|
9603
9649
|
const paths = getDaemonPaths();
|
|
9604
|
-
if (PLATFORM === "darwin" && paths.plist &&
|
|
9650
|
+
if (PLATFORM === "darwin" && paths.plist && existsSync11(paths.plist)) {
|
|
9605
9651
|
try {
|
|
9606
9652
|
execSync2(`launchctl unload ${paths.plist}`);
|
|
9607
9653
|
success("Stopped daemon via launchctl", opts);
|
|
9608
9654
|
} catch {
|
|
9609
9655
|
info("Daemon was not running", opts);
|
|
9610
9656
|
}
|
|
9611
|
-
} else if (PLATFORM === "linux" && paths.service &&
|
|
9657
|
+
} else if (PLATFORM === "linux" && paths.service && existsSync11(paths.service)) {
|
|
9612
9658
|
try {
|
|
9613
9659
|
execSync2(`systemctl --user stop ${DAEMON_NAME}`);
|
|
9614
9660
|
success("Stopped daemon via systemctl", opts);
|
|
@@ -9620,7 +9666,7 @@ daemonCommand.command("stop").description("Stop the file watcher daemon").action
|
|
|
9620
9666
|
if (status.running && status.pid) {
|
|
9621
9667
|
try {
|
|
9622
9668
|
process.kill(status.pid, "SIGTERM");
|
|
9623
|
-
if (
|
|
9669
|
+
if (existsSync11(paths.pidFile)) {
|
|
9624
9670
|
unlinkSync(paths.pidFile);
|
|
9625
9671
|
}
|
|
9626
9672
|
success(`Stopped daemon (PID: ${status.pid})`, opts);
|
|
@@ -9641,13 +9687,16 @@ daemonCommand.command("status").description("Show daemon status for ALL register
|
|
|
9641
9687
|
const projectStats = [];
|
|
9642
9688
|
let totalFiles = { allow: 0, unknown: 0, total: 0 };
|
|
9643
9689
|
for (const project of projects) {
|
|
9644
|
-
const valid =
|
|
9645
|
-
const counts = queryAll(
|
|
9690
|
+
const valid = existsSync11(project.path) && existsSync11(join12(project.path, ".aigile"));
|
|
9691
|
+
const counts = queryAll(
|
|
9692
|
+
`
|
|
9646
9693
|
SELECT COALESCE(monitoring_category, 'unknown') as monitoring_category, COUNT(*) as count
|
|
9647
9694
|
FROM documents
|
|
9648
9695
|
WHERE project_id = ? AND status != 'deleted'
|
|
9649
9696
|
GROUP BY monitoring_category
|
|
9650
|
-
`,
|
|
9697
|
+
`,
|
|
9698
|
+
[project.id]
|
|
9699
|
+
);
|
|
9651
9700
|
let allow = 0;
|
|
9652
9701
|
let unknown = 0;
|
|
9653
9702
|
for (const row of counts) {
|
|
@@ -9671,30 +9720,34 @@ daemonCommand.command("status").description("Show daemon status for ALL register
|
|
|
9671
9720
|
}
|
|
9672
9721
|
const validCount = projectStats.filter((p) => p.valid).length;
|
|
9673
9722
|
if (opts.json) {
|
|
9674
|
-
console.log(
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9723
|
+
console.log(
|
|
9724
|
+
JSON.stringify({
|
|
9725
|
+
success: true,
|
|
9726
|
+
data: {
|
|
9727
|
+
running: status.running,
|
|
9728
|
+
pid: status.pid ?? null,
|
|
9729
|
+
platform: PLATFORM,
|
|
9730
|
+
installed: PLATFORM === "darwin" ? paths.plist && existsSync11(paths.plist) : PLATFORM === "linux" ? paths.service && existsSync11(paths.service) : false,
|
|
9731
|
+
projectCount: projects.length,
|
|
9732
|
+
validProjectCount: validCount,
|
|
9733
|
+
projects: projectStats,
|
|
9734
|
+
totalFiles,
|
|
9735
|
+
paths: {
|
|
9736
|
+
database: getDbPath(),
|
|
9737
|
+
pidFile: paths.pidFile,
|
|
9738
|
+
logFile: paths.logFile
|
|
9739
|
+
}
|
|
9689
9740
|
}
|
|
9690
|
-
}
|
|
9691
|
-
|
|
9741
|
+
})
|
|
9742
|
+
);
|
|
9692
9743
|
return;
|
|
9693
9744
|
}
|
|
9694
9745
|
console.log("\n\u{1F4CA} Daemon Status\n");
|
|
9695
|
-
console.log(
|
|
9746
|
+
console.log(
|
|
9747
|
+
`\u251C\u2500\u2500 Running: ${status.running ? "\u2705 Yes" : "\u274C No"}${status.pid ? ` (PID: ${status.pid})` : ""}`
|
|
9748
|
+
);
|
|
9696
9749
|
console.log(`\u251C\u2500\u2500 Platform: ${PLATFORM}`);
|
|
9697
|
-
const installed = PLATFORM === "darwin" ? paths.plist &&
|
|
9750
|
+
const installed = PLATFORM === "darwin" ? paths.plist && existsSync11(paths.plist) : PLATFORM === "linux" ? paths.service && existsSync11(paths.service) : false;
|
|
9698
9751
|
console.log(`\u251C\u2500\u2500 Installed: ${installed ? "\u2705 Yes" : "\u274C No"}`);
|
|
9699
9752
|
console.log(`\u251C\u2500\u2500 Projects: ${validCount}/${projects.length} valid`);
|
|
9700
9753
|
if (projectStats.length > 0) {
|
|
@@ -9715,7 +9768,9 @@ daemonCommand.command("status").description("Show daemon status for ALL register
|
|
|
9715
9768
|
console.log(` \u2514\u2500\u2500 Log File: ${paths.logFile}`);
|
|
9716
9769
|
console.log("");
|
|
9717
9770
|
if (projectStats.some((p) => !p.valid)) {
|
|
9718
|
-
console.log(
|
|
9771
|
+
console.log(
|
|
9772
|
+
'\u26A0\uFE0F Some projects have invalid paths. Run "aigile project cleanup" to remove them.\n'
|
|
9773
|
+
);
|
|
9719
9774
|
}
|
|
9720
9775
|
});
|
|
9721
9776
|
function getRelativeTime(isoDate) {
|
|
@@ -9742,7 +9797,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
|
|
|
9742
9797
|
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] FATAL: Uncaught exception:`);
|
|
9743
9798
|
console.error(err.stack || err.message);
|
|
9744
9799
|
writeCrashReport(err);
|
|
9745
|
-
if (
|
|
9800
|
+
if (existsSync11(paths.pidFile)) {
|
|
9746
9801
|
try {
|
|
9747
9802
|
unlinkSync(paths.pidFile);
|
|
9748
9803
|
} catch {
|
|
@@ -9754,7 +9809,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
|
|
|
9754
9809
|
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] FATAL: Unhandled promise rejection:`);
|
|
9755
9810
|
console.error(reason);
|
|
9756
9811
|
writeCrashReport(reason);
|
|
9757
|
-
if (
|
|
9812
|
+
if (existsSync11(paths.pidFile)) {
|
|
9758
9813
|
try {
|
|
9759
9814
|
unlinkSync(paths.pidFile);
|
|
9760
9815
|
} catch {
|
|
@@ -9763,26 +9818,36 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
|
|
|
9763
9818
|
process.exit(1);
|
|
9764
9819
|
});
|
|
9765
9820
|
rotateLogIfNeeded();
|
|
9766
|
-
|
|
9767
|
-
console.log(
|
|
9768
|
-
|
|
9821
|
+
writeFileSync7(paths.pidFile, String(process.pid));
|
|
9822
|
+
console.log(
|
|
9823
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] Starting AIGILE daemon for all registered projects...`
|
|
9824
|
+
);
|
|
9825
|
+
console.log(
|
|
9826
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] PID: ${process.pid}, Node: ${process.version}, Platform: ${platform()}`
|
|
9827
|
+
);
|
|
9769
9828
|
const manager = getDaemonManager();
|
|
9770
9829
|
if (!options.skipResync) {
|
|
9771
9830
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Performing initial resync for all projects...`);
|
|
9772
9831
|
try {
|
|
9773
9832
|
const results = await manager.resyncAll();
|
|
9774
9833
|
const projectCount = Object.keys(results).length;
|
|
9775
|
-
console.log(
|
|
9834
|
+
console.log(
|
|
9835
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] Resync complete: ${projectCount} projects synced`
|
|
9836
|
+
);
|
|
9776
9837
|
} catch (err) {
|
|
9777
9838
|
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Resync warning: ${err}`);
|
|
9778
9839
|
}
|
|
9779
9840
|
}
|
|
9780
9841
|
manager.on("sync", (event) => {
|
|
9781
9842
|
const categoryStr = event.category ? ` [${event.category}]` : "";
|
|
9782
|
-
console.log(
|
|
9843
|
+
console.log(
|
|
9844
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [${event.project}] Synced: ${event.type} ${event.path}${categoryStr}`
|
|
9845
|
+
);
|
|
9783
9846
|
});
|
|
9784
9847
|
manager.on("syncError", ({ project, event, error: err }) => {
|
|
9785
|
-
console.error(
|
|
9848
|
+
console.error(
|
|
9849
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project}] Sync error: ${event.type} ${event.path} - ${err}`
|
|
9850
|
+
);
|
|
9786
9851
|
});
|
|
9787
9852
|
manager.on("watcherError", ({ project, error: err }) => {
|
|
9788
9853
|
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] [${project}] Watcher error: ${err}`);
|
|
@@ -9793,8 +9858,10 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
|
|
|
9793
9858
|
isShuttingDown = true;
|
|
9794
9859
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Shutting down...`);
|
|
9795
9860
|
const forceExitTimeout = setTimeout(() => {
|
|
9796
|
-
console.error(
|
|
9797
|
-
|
|
9861
|
+
console.error(
|
|
9862
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] Shutdown timeout (${SHUTDOWN_TIMEOUT_MS}ms) - forcing exit`
|
|
9863
|
+
);
|
|
9864
|
+
if (existsSync11(paths.pidFile)) {
|
|
9798
9865
|
try {
|
|
9799
9866
|
unlinkSync(paths.pidFile);
|
|
9800
9867
|
} catch {
|
|
@@ -9808,7 +9875,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
|
|
|
9808
9875
|
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Error during shutdown: ${err}`);
|
|
9809
9876
|
}
|
|
9810
9877
|
clearTimeout(forceExitTimeout);
|
|
9811
|
-
if (
|
|
9878
|
+
if (existsSync11(paths.pidFile)) {
|
|
9812
9879
|
unlinkSync(paths.pidFile);
|
|
9813
9880
|
}
|
|
9814
9881
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Daemon stopped gracefully`);
|
|
@@ -9816,15 +9883,20 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
|
|
|
9816
9883
|
};
|
|
9817
9884
|
process.on("SIGTERM", shutdown);
|
|
9818
9885
|
process.on("SIGINT", shutdown);
|
|
9819
|
-
const logRotationInterval = setInterval(
|
|
9820
|
-
|
|
9821
|
-
|
|
9886
|
+
const logRotationInterval = setInterval(
|
|
9887
|
+
() => {
|
|
9888
|
+
rotateLogIfNeeded();
|
|
9889
|
+
},
|
|
9890
|
+
60 * 60 * 1e3
|
|
9891
|
+
);
|
|
9822
9892
|
process.on("exit", () => {
|
|
9823
9893
|
clearInterval(logRotationInterval);
|
|
9824
9894
|
});
|
|
9825
9895
|
try {
|
|
9826
9896
|
const status = await manager.start();
|
|
9827
|
-
console.log(
|
|
9897
|
+
console.log(
|
|
9898
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] Daemon started - watching ${status.watchingCount} projects`
|
|
9899
|
+
);
|
|
9828
9900
|
for (const p of status.projects) {
|
|
9829
9901
|
if (p.watching) {
|
|
9830
9902
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] \u2713 ${p.key}: ${p.path}`);
|
|
@@ -9832,7 +9904,7 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
|
|
|
9832
9904
|
}
|
|
9833
9905
|
} catch (err) {
|
|
9834
9906
|
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Failed to start daemon: ${err}`);
|
|
9835
|
-
if (
|
|
9907
|
+
if (existsSync11(paths.pidFile)) {
|
|
9836
9908
|
unlinkSync(paths.pidFile);
|
|
9837
9909
|
}
|
|
9838
9910
|
process.exit(1);
|
|
@@ -9841,10 +9913,15 @@ daemonCommand.command("run").option("--skip-resync", "Skip initial resync on sta
|
|
|
9841
9913
|
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
9914
|
const opts = getOutputOptions(daemonCommand);
|
|
9843
9915
|
const paths = getDaemonPaths();
|
|
9844
|
-
if (!
|
|
9916
|
+
if (!existsSync11(paths.logFile)) {
|
|
9845
9917
|
info("No log file found. Daemon may not have run yet.", opts);
|
|
9846
9918
|
return;
|
|
9847
9919
|
}
|
|
9920
|
+
const lines = parseInt(options.lines, 10);
|
|
9921
|
+
if (isNaN(lines) || lines < 1 || lines > 1e4) {
|
|
9922
|
+
error("Invalid lines parameter. Must be a number between 1 and 10000.", opts);
|
|
9923
|
+
return;
|
|
9924
|
+
}
|
|
9848
9925
|
if (options.follow) {
|
|
9849
9926
|
const tail = spawn("tail", ["-f", paths.logFile], {
|
|
9850
9927
|
stdio: "inherit"
|
|
@@ -9855,8 +9932,14 @@ daemonCommand.command("logs").option("-n, --lines <number>", "Number of lines to
|
|
|
9855
9932
|
});
|
|
9856
9933
|
} else {
|
|
9857
9934
|
try {
|
|
9858
|
-
const
|
|
9859
|
-
|
|
9935
|
+
const result = spawnSync("tail", ["-n", lines.toString(), paths.logFile], {
|
|
9936
|
+
encoding: "utf-8"
|
|
9937
|
+
});
|
|
9938
|
+
if (result.status === 0 && result.stdout) {
|
|
9939
|
+
console.log(result.stdout);
|
|
9940
|
+
} else {
|
|
9941
|
+
error("Failed to read log file", opts);
|
|
9942
|
+
}
|
|
9860
9943
|
} catch {
|
|
9861
9944
|
error("Failed to read log file", opts);
|
|
9862
9945
|
}
|
|
@@ -9874,13 +9957,15 @@ daemonCommand.command("resync").option("--project <key>", "Resync only a specifi
|
|
|
9874
9957
|
process.exit(1);
|
|
9875
9958
|
}
|
|
9876
9959
|
if (opts.json) {
|
|
9877
|
-
console.log(
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9960
|
+
console.log(
|
|
9961
|
+
JSON.stringify({
|
|
9962
|
+
success: true,
|
|
9963
|
+
data: {
|
|
9964
|
+
project: options.project,
|
|
9965
|
+
...result
|
|
9966
|
+
}
|
|
9967
|
+
})
|
|
9968
|
+
);
|
|
9884
9969
|
} else {
|
|
9885
9970
|
success(`Resync complete for ${options.project}:`, opts);
|
|
9886
9971
|
console.log(` Allow: ${result.allow}`);
|
|
@@ -9899,13 +9984,15 @@ daemonCommand.command("resync").option("--project <key>", "Resync only a specifi
|
|
|
9899
9984
|
const results = await manager.resyncAll();
|
|
9900
9985
|
const projectKeys = Object.keys(results);
|
|
9901
9986
|
if (opts.json) {
|
|
9902
|
-
console.log(
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9987
|
+
console.log(
|
|
9988
|
+
JSON.stringify({
|
|
9989
|
+
success: true,
|
|
9990
|
+
data: {
|
|
9991
|
+
projectCount: projectKeys.length,
|
|
9992
|
+
projects: results
|
|
9993
|
+
}
|
|
9994
|
+
})
|
|
9995
|
+
);
|
|
9909
9996
|
} else {
|
|
9910
9997
|
success(`Resync complete: ${projectKeys.length} projects`, opts);
|
|
9911
9998
|
for (const [key, result] of Object.entries(results)) {
|
|
@@ -9929,29 +10016,36 @@ daemonCommand.command("review").option("--list", "Just list unknown files withou
|
|
|
9929
10016
|
error("Could not load project config.", opts);
|
|
9930
10017
|
process.exit(1);
|
|
9931
10018
|
}
|
|
9932
|
-
const project = queryOne("SELECT id FROM projects WHERE key = ?", [
|
|
10019
|
+
const project = queryOne("SELECT id FROM projects WHERE key = ?", [
|
|
10020
|
+
config.project.key
|
|
10021
|
+
]);
|
|
9933
10022
|
if (!project) {
|
|
9934
10023
|
error(`Project "${config.project.key}" not found in database.`, opts);
|
|
9935
10024
|
process.exit(1);
|
|
9936
10025
|
}
|
|
9937
|
-
const unknownFiles = queryAll(
|
|
10026
|
+
const unknownFiles = queryAll(
|
|
10027
|
+
`
|
|
9938
10028
|
SELECT id, path, extension, size_bytes, updated_at
|
|
9939
10029
|
FROM documents
|
|
9940
10030
|
WHERE project_id = ? AND monitoring_category = 'unknown' AND status != 'deleted'
|
|
9941
10031
|
ORDER BY path
|
|
9942
|
-
`,
|
|
10032
|
+
`,
|
|
10033
|
+
[project.id]
|
|
10034
|
+
);
|
|
9943
10035
|
if (unknownFiles.length === 0) {
|
|
9944
10036
|
success("No unknown files to review! All files are classified.", opts);
|
|
9945
10037
|
return;
|
|
9946
10038
|
}
|
|
9947
10039
|
if (opts.json) {
|
|
9948
|
-
console.log(
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
10040
|
+
console.log(
|
|
10041
|
+
JSON.stringify({
|
|
10042
|
+
success: true,
|
|
10043
|
+
data: {
|
|
10044
|
+
count: unknownFiles.length,
|
|
10045
|
+
files: unknownFiles
|
|
10046
|
+
}
|
|
10047
|
+
})
|
|
10048
|
+
);
|
|
9955
10049
|
return;
|
|
9956
10050
|
}
|
|
9957
10051
|
if (options.list) {
|
|
@@ -9973,14 +10067,18 @@ daemonCommand.command("review").option("--list", "Just list unknown files withou
|
|
|
9973
10067
|
],
|
|
9974
10068
|
opts
|
|
9975
10069
|
);
|
|
9976
|
-
console.log(
|
|
10070
|
+
console.log(
|
|
10071
|
+
'\nUse "aigile daemon allow <pattern>" or "aigile daemon deny <pattern>" to classify files.'
|
|
10072
|
+
);
|
|
9977
10073
|
return;
|
|
9978
10074
|
}
|
|
9979
10075
|
console.log(`
|
|
9980
10076
|
\u{1F4CB} Unknown Files (${unknownFiles.length} total)
|
|
9981
10077
|
`);
|
|
9982
10078
|
console.log('Run "aigile daemon review --list" to see all files.');
|
|
9983
|
-
console.log(
|
|
10079
|
+
console.log(
|
|
10080
|
+
'Use "aigile daemon allow <pattern>" or "aigile daemon deny <pattern>" to classify.\n'
|
|
10081
|
+
);
|
|
9984
10082
|
const sample = unknownFiles.slice(0, 10);
|
|
9985
10083
|
for (const file of sample) {
|
|
9986
10084
|
console.log(` ${file.path} (.${file.extension || "no ext"})`);
|
|
@@ -10000,11 +10098,13 @@ function formatBytes(bytes) {
|
|
|
10000
10098
|
init_connection();
|
|
10001
10099
|
init_connection();
|
|
10002
10100
|
import { Command as Command21 } from "commander";
|
|
10003
|
-
import { readFileSync as
|
|
10004
|
-
import { join as
|
|
10101
|
+
import { readFileSync as readFileSync12, existsSync as existsSync12 } from "fs";
|
|
10102
|
+
import { join as join13, relative as relative6 } from "path";
|
|
10005
10103
|
import { glob } from "glob";
|
|
10006
10104
|
init_config();
|
|
10007
|
-
var fileCommand = new Command21("file").description(
|
|
10105
|
+
var fileCommand = new Command21("file").description(
|
|
10106
|
+
"Shadow mode file analysis and management for brownfield projects"
|
|
10107
|
+
);
|
|
10008
10108
|
function getProjectContext(opts) {
|
|
10009
10109
|
const projectRoot = findProjectRoot();
|
|
10010
10110
|
if (!projectRoot) {
|
|
@@ -10016,10 +10116,9 @@ function getProjectContext(opts) {
|
|
|
10016
10116
|
error("Could not load project config.", opts);
|
|
10017
10117
|
return null;
|
|
10018
10118
|
}
|
|
10019
|
-
const project = queryOne(
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
);
|
|
10119
|
+
const project = queryOne("SELECT id FROM projects WHERE key = ?", [
|
|
10120
|
+
config.project.key
|
|
10121
|
+
]);
|
|
10023
10122
|
if (!project) {
|
|
10024
10123
|
error(`Project "${config.project.key}" not found in database.`, opts);
|
|
10025
10124
|
return null;
|
|
@@ -10042,12 +10141,15 @@ fileCommand.command("analyze <path>").option("--tldr <tldr>", "One-line summary
|
|
|
10042
10141
|
if (!ctx) {
|
|
10043
10142
|
process.exit(1);
|
|
10044
10143
|
}
|
|
10045
|
-
const normalizedPath = filePath.startsWith("/") ?
|
|
10144
|
+
const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
|
|
10046
10145
|
const doc = getDocumentWithAnalysis(ctx.projectId, normalizedPath);
|
|
10047
10146
|
if (!doc) {
|
|
10048
10147
|
const tracked = trackShadowFile(ctx.projectId, ctx.projectRoot, normalizedPath);
|
|
10049
10148
|
if (!tracked) {
|
|
10050
|
-
error(
|
|
10149
|
+
error(
|
|
10150
|
+
`File not tracked. Run "aigile sync scan" or track with "aigile file track ${normalizedPath}"`,
|
|
10151
|
+
opts
|
|
10152
|
+
);
|
|
10051
10153
|
process.exit(1);
|
|
10052
10154
|
}
|
|
10053
10155
|
}
|
|
@@ -10072,24 +10174,26 @@ fileCommand.command("analyze <path>").option("--tldr <tldr>", "One-line summary
|
|
|
10072
10174
|
}
|
|
10073
10175
|
const updatedDoc = getDocumentWithAnalysis(ctx.projectId, normalizedPath);
|
|
10074
10176
|
if (opts.json) {
|
|
10075
|
-
console.log(
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10177
|
+
console.log(
|
|
10178
|
+
JSON.stringify({
|
|
10179
|
+
success: true,
|
|
10180
|
+
data: {
|
|
10181
|
+
path: normalizedPath,
|
|
10182
|
+
analyzed: true,
|
|
10183
|
+
analyzedAt: updatedDoc?.analyzed_at,
|
|
10184
|
+
metadata: {
|
|
10185
|
+
tldr: updatedDoc?.meta_tldr,
|
|
10186
|
+
module: updatedDoc?.inferred_module,
|
|
10187
|
+
component: updatedDoc?.inferred_component,
|
|
10188
|
+
type: updatedDoc?.file_type,
|
|
10189
|
+
dependencies: updatedDoc?.meta_dependencies ? JSON.parse(updatedDoc.meta_dependencies) : null,
|
|
10190
|
+
exports: updatedDoc?.exports ? JSON.parse(updatedDoc.exports) : null,
|
|
10191
|
+
complexity: updatedDoc?.complexity_score,
|
|
10192
|
+
confidence: updatedDoc?.analysis_confidence
|
|
10193
|
+
}
|
|
10090
10194
|
}
|
|
10091
|
-
}
|
|
10092
|
-
|
|
10195
|
+
})
|
|
10196
|
+
);
|
|
10093
10197
|
} else {
|
|
10094
10198
|
success(`Analysis added for: ${normalizedPath}`, opts);
|
|
10095
10199
|
if (updatedDoc?.meta_tldr) {
|
|
@@ -10138,24 +10242,26 @@ fileCommand.command("list").alias("ls").option("--unanalyzed", "Only files witho
|
|
|
10138
10242
|
documents = getUnanalyzedDocuments(ctx.projectId, limit, offset);
|
|
10139
10243
|
}
|
|
10140
10244
|
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
|
-
|
|
10245
|
+
console.log(
|
|
10246
|
+
JSON.stringify({
|
|
10247
|
+
success: true,
|
|
10248
|
+
data: documents.map((d) => ({
|
|
10249
|
+
path: d.path,
|
|
10250
|
+
filename: d.filename,
|
|
10251
|
+
extension: d.extension,
|
|
10252
|
+
module: d.inferred_module,
|
|
10253
|
+
component: d.inferred_component,
|
|
10254
|
+
fileType: d.file_type,
|
|
10255
|
+
analyzed: !!d.analyzed_at,
|
|
10256
|
+
analyzedAt: d.analyzed_at,
|
|
10257
|
+
confidence: d.analysis_confidence,
|
|
10258
|
+
tldr: d.meta_tldr,
|
|
10259
|
+
dependencies: d.meta_dependencies ? JSON.parse(d.meta_dependencies) : null,
|
|
10260
|
+
exports: d.exports ? JSON.parse(d.exports) : null,
|
|
10261
|
+
complexity: d.complexity_score
|
|
10262
|
+
}))
|
|
10263
|
+
})
|
|
10264
|
+
);
|
|
10159
10265
|
} else if (options.format === "paths") {
|
|
10160
10266
|
documents.forEach((d) => console.log(d.path));
|
|
10161
10267
|
} else {
|
|
@@ -10214,15 +10320,15 @@ fileCommand.command("read <path>").option("--with-metadata", "Include existing D
|
|
|
10214
10320
|
if (!ctx) {
|
|
10215
10321
|
process.exit(1);
|
|
10216
10322
|
}
|
|
10217
|
-
const normalizedPath = filePath.startsWith("/") ?
|
|
10218
|
-
const fullPath =
|
|
10219
|
-
if (!
|
|
10323
|
+
const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
|
|
10324
|
+
const fullPath = join13(ctx.projectRoot, normalizedPath);
|
|
10325
|
+
if (!existsSync12(fullPath)) {
|
|
10220
10326
|
error(`File not found: ${normalizedPath}`, opts);
|
|
10221
10327
|
process.exit(1);
|
|
10222
10328
|
}
|
|
10223
10329
|
let content;
|
|
10224
10330
|
try {
|
|
10225
|
-
content =
|
|
10331
|
+
content = readFileSync12(fullPath, "utf-8");
|
|
10226
10332
|
} catch {
|
|
10227
10333
|
error(`Could not read file: ${normalizedPath}`, opts);
|
|
10228
10334
|
process.exit(1);
|
|
@@ -10236,22 +10342,24 @@ fileCommand.command("read <path>").option("--with-metadata", "Include existing D
|
|
|
10236
10342
|
lines = lines.slice(0, options.limit);
|
|
10237
10343
|
}
|
|
10238
10344
|
if (opts.json) {
|
|
10239
|
-
console.log(
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10345
|
+
console.log(
|
|
10346
|
+
JSON.stringify({
|
|
10347
|
+
success: true,
|
|
10348
|
+
data: {
|
|
10349
|
+
path: normalizedPath,
|
|
10350
|
+
content: lines.join("\n"),
|
|
10351
|
+
lineCount: lines.length,
|
|
10352
|
+
metadata: metadata ? {
|
|
10353
|
+
tldr: metadata.meta_tldr,
|
|
10354
|
+
module: metadata.inferred_module,
|
|
10355
|
+
component: metadata.inferred_component,
|
|
10356
|
+
fileType: metadata.file_type,
|
|
10357
|
+
analyzed: !!metadata.analyzed_at,
|
|
10358
|
+
confidence: metadata.analysis_confidence
|
|
10359
|
+
} : null
|
|
10360
|
+
}
|
|
10361
|
+
})
|
|
10362
|
+
);
|
|
10255
10363
|
} else {
|
|
10256
10364
|
if (options.withMetadata && metadata) {
|
|
10257
10365
|
console.log(`# File: ${normalizedPath}`);
|
|
@@ -10275,7 +10383,7 @@ fileCommand.command("track <path>").option("--type <type>", "File type classific
|
|
|
10275
10383
|
if (!ctx) {
|
|
10276
10384
|
process.exit(1);
|
|
10277
10385
|
}
|
|
10278
|
-
const normalizedPath = filePath.startsWith("/") ?
|
|
10386
|
+
const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
|
|
10279
10387
|
const tracked = trackShadowFile(ctx.projectId, ctx.projectRoot, normalizedPath);
|
|
10280
10388
|
if (!tracked) {
|
|
10281
10389
|
error(`Could not track file: ${normalizedPath}`, opts);
|
|
@@ -10290,18 +10398,20 @@ fileCommand.command("track <path>").option("--type <type>", "File type classific
|
|
|
10290
10398
|
}
|
|
10291
10399
|
const doc = getDocumentWithAnalysis(ctx.projectId, normalizedPath);
|
|
10292
10400
|
if (opts.json) {
|
|
10293
|
-
console.log(
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10401
|
+
console.log(
|
|
10402
|
+
JSON.stringify({
|
|
10403
|
+
success: true,
|
|
10404
|
+
data: {
|
|
10405
|
+
path: normalizedPath,
|
|
10406
|
+
tracked: true,
|
|
10407
|
+
shadowMode: true,
|
|
10408
|
+
metadata: doc ? {
|
|
10409
|
+
fileType: doc.file_type,
|
|
10410
|
+
module: doc.inferred_module
|
|
10411
|
+
} : null
|
|
10412
|
+
}
|
|
10413
|
+
})
|
|
10414
|
+
);
|
|
10305
10415
|
} else {
|
|
10306
10416
|
success(`Tracked in shadow mode: ${normalizedPath}`, opts);
|
|
10307
10417
|
}
|
|
@@ -10312,39 +10422,41 @@ fileCommand.command("show <path>").description("Show detailed file analysis").ac
|
|
|
10312
10422
|
if (!ctx) {
|
|
10313
10423
|
process.exit(1);
|
|
10314
10424
|
}
|
|
10315
|
-
const normalizedPath = filePath.startsWith("/") ?
|
|
10425
|
+
const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
|
|
10316
10426
|
const doc = getDocumentWithAnalysis(ctx.projectId, normalizedPath);
|
|
10317
10427
|
if (!doc) {
|
|
10318
10428
|
error(`File not tracked: ${normalizedPath}`, opts);
|
|
10319
10429
|
process.exit(1);
|
|
10320
10430
|
}
|
|
10321
10431
|
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
|
-
|
|
10432
|
+
console.log(
|
|
10433
|
+
JSON.stringify({
|
|
10434
|
+
success: true,
|
|
10435
|
+
data: {
|
|
10436
|
+
path: doc.path,
|
|
10437
|
+
filename: doc.filename,
|
|
10438
|
+
extension: doc.extension,
|
|
10439
|
+
status: doc.status,
|
|
10440
|
+
sizeBytes: doc.size_bytes,
|
|
10441
|
+
lastScanned: doc.last_scanned_at,
|
|
10442
|
+
hasFrontmatter: !!doc.has_frontmatter,
|
|
10443
|
+
shadowMode: !!doc.shadow_mode,
|
|
10444
|
+
analysis: {
|
|
10445
|
+
analyzed: !!doc.analyzed_at,
|
|
10446
|
+
analyzedAt: doc.analyzed_at,
|
|
10447
|
+
confidence: doc.analysis_confidence,
|
|
10448
|
+
tldr: doc.meta_tldr,
|
|
10449
|
+
module: doc.inferred_module,
|
|
10450
|
+
component: doc.inferred_component,
|
|
10451
|
+
fileType: doc.file_type,
|
|
10452
|
+
complexity: doc.complexity_score,
|
|
10453
|
+
dependencies: doc.meta_dependencies ? JSON.parse(doc.meta_dependencies) : null,
|
|
10454
|
+
exports: doc.exports ? JSON.parse(doc.exports) : null,
|
|
10455
|
+
notes: doc.analysis_notes
|
|
10456
|
+
}
|
|
10345
10457
|
}
|
|
10346
|
-
}
|
|
10347
|
-
|
|
10458
|
+
})
|
|
10459
|
+
);
|
|
10348
10460
|
} else {
|
|
10349
10461
|
const displayData = {
|
|
10350
10462
|
path: doc.path,
|
|
@@ -10403,8 +10515,8 @@ fileCommand.command("tag").argument("<path>", "File path to tag").option("--chun
|
|
|
10403
10515
|
error('No active session. Start one with "aigile session start".', opts);
|
|
10404
10516
|
process.exit(1);
|
|
10405
10517
|
}
|
|
10406
|
-
const normalizedPath = filePath.startsWith("/") ?
|
|
10407
|
-
|
|
10518
|
+
const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
|
|
10519
|
+
let doc = queryOne(
|
|
10408
10520
|
"SELECT id FROM documents WHERE project_id = ? AND path = ?",
|
|
10409
10521
|
[ctx.projectId, normalizedPath]
|
|
10410
10522
|
);
|
|
@@ -10422,7 +10534,7 @@ fileCommand.command("tag").argument("<path>", "File path to tag").option("--chun
|
|
|
10422
10534
|
error(`Could not track file: ${normalizedPath}`, opts);
|
|
10423
10535
|
process.exit(1);
|
|
10424
10536
|
}
|
|
10425
|
-
doc
|
|
10537
|
+
doc = newDoc;
|
|
10426
10538
|
}
|
|
10427
10539
|
const validTypes = ["assigned", "explored", "skipped"];
|
|
10428
10540
|
if (!validTypes.includes(options.type)) {
|
|
@@ -10437,17 +10549,19 @@ fileCommand.command("tag").argument("<path>", "File path to tag").option("--chun
|
|
|
10437
10549
|
isFoundational: options.foundational ?? false
|
|
10438
10550
|
});
|
|
10439
10551
|
if (opts.json) {
|
|
10440
|
-
console.log(
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10552
|
+
console.log(
|
|
10553
|
+
JSON.stringify({
|
|
10554
|
+
success: true,
|
|
10555
|
+
data: {
|
|
10556
|
+
session_file_id: sessionFileId,
|
|
10557
|
+
path: normalizedPath,
|
|
10558
|
+
session_id: session.id,
|
|
10559
|
+
chunk_id: options.chunk ?? null,
|
|
10560
|
+
review_type: options.type,
|
|
10561
|
+
is_foundational: options.foundational ?? false
|
|
10562
|
+
}
|
|
10563
|
+
})
|
|
10564
|
+
);
|
|
10451
10565
|
} else {
|
|
10452
10566
|
success(`Tagged: ${normalizedPath}`, opts);
|
|
10453
10567
|
if (options.chunk) {
|
|
@@ -10470,7 +10584,7 @@ fileCommand.command("tag-batch").option("--chunk <id>", "Chunk ID for all files"
|
|
|
10470
10584
|
let filesToTag = [];
|
|
10471
10585
|
if (options.glob) {
|
|
10472
10586
|
const matches = await glob(options.glob, { cwd: ctx.projectRoot, nodir: true });
|
|
10473
|
-
filesToTag = matches.map((f) =>
|
|
10587
|
+
filesToTag = matches.map((f) => relative6(ctx.projectRoot, join13(ctx.projectRoot, f)));
|
|
10474
10588
|
} else {
|
|
10475
10589
|
error("Please provide --glob pattern. Stdin not supported yet.", opts);
|
|
10476
10590
|
process.exit(1);
|
|
@@ -10499,10 +10613,12 @@ fileCommand.command("tag-batch").option("--chunk <id>", "Chunk ID for all files"
|
|
|
10499
10613
|
tagged++;
|
|
10500
10614
|
}
|
|
10501
10615
|
if (opts.json) {
|
|
10502
|
-
console.log(
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10616
|
+
console.log(
|
|
10617
|
+
JSON.stringify({
|
|
10618
|
+
success: true,
|
|
10619
|
+
data: { tagged, skipped, total: filesToTag.length }
|
|
10620
|
+
})
|
|
10621
|
+
);
|
|
10506
10622
|
} else {
|
|
10507
10623
|
success(`Tagged ${tagged} files (${skipped} skipped - not tracked)`, opts);
|
|
10508
10624
|
}
|
|
@@ -10538,12 +10654,14 @@ fileCommand.command("untag").argument("<path>", "File path to untag").option("--
|
|
|
10538
10654
|
warning(`File "${filePath}" is not tagged in this session.`, opts);
|
|
10539
10655
|
return;
|
|
10540
10656
|
}
|
|
10541
|
-
|
|
10657
|
+
run("DELETE FROM session_files WHERE id = ?", [existing.id]);
|
|
10542
10658
|
if (opts.json) {
|
|
10543
|
-
console.log(
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10659
|
+
console.log(
|
|
10660
|
+
JSON.stringify({
|
|
10661
|
+
success: true,
|
|
10662
|
+
data: { path: filePath, untagged: true }
|
|
10663
|
+
})
|
|
10664
|
+
);
|
|
10547
10665
|
} else {
|
|
10548
10666
|
success(`Untagged: ${filePath}`, opts);
|
|
10549
10667
|
}
|
|
@@ -10586,12 +10704,14 @@ fileCommand.command("clear-tags").option("--session <id>", "Session ID (default:
|
|
|
10586
10704
|
deleteQuery += " AND chunk_id = ?";
|
|
10587
10705
|
deleteParams.push(options.chunk);
|
|
10588
10706
|
}
|
|
10589
|
-
|
|
10707
|
+
run(deleteQuery, deleteParams);
|
|
10590
10708
|
if (opts.json) {
|
|
10591
|
-
console.log(
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10709
|
+
console.log(
|
|
10710
|
+
JSON.stringify({
|
|
10711
|
+
success: true,
|
|
10712
|
+
data: { session_id: sessionId, cleared: count }
|
|
10713
|
+
})
|
|
10714
|
+
);
|
|
10595
10715
|
} else {
|
|
10596
10716
|
success(`Cleared ${count} tag(s)`, opts);
|
|
10597
10717
|
}
|
|
@@ -10616,15 +10736,17 @@ fileCommand.command("untagged").option("--session <id>", "Session ID (default: c
|
|
|
10616
10736
|
assignedOnly: options.assignedOnly
|
|
10617
10737
|
});
|
|
10618
10738
|
if (opts.json) {
|
|
10619
|
-
console.log(
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10739
|
+
console.log(
|
|
10740
|
+
JSON.stringify({
|
|
10741
|
+
success: true,
|
|
10742
|
+
data: {
|
|
10743
|
+
session_id: sessionId,
|
|
10744
|
+
chunk_id: options.chunk ?? null,
|
|
10745
|
+
count: untagged.length,
|
|
10746
|
+
files: untagged.map((f) => f.path)
|
|
10747
|
+
}
|
|
10748
|
+
})
|
|
10749
|
+
);
|
|
10628
10750
|
} else {
|
|
10629
10751
|
if (untagged.length === 0) {
|
|
10630
10752
|
success("All files have been tagged!", opts);
|
|
@@ -10692,22 +10814,24 @@ fileCommand.command("coverage").option("--session <id>", "Session ID (default: c
|
|
|
10692
10814
|
const total = totalTagged + untagged.length;
|
|
10693
10815
|
const pct = total > 0 ? Math.round(totalTagged / total * 100) : 100;
|
|
10694
10816
|
if (opts.json) {
|
|
10695
|
-
console.log(
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10817
|
+
console.log(
|
|
10818
|
+
JSON.stringify({
|
|
10819
|
+
success: true,
|
|
10820
|
+
data: {
|
|
10821
|
+
session_id: sessionId,
|
|
10822
|
+
total_files: total,
|
|
10823
|
+
tagged: totalTagged,
|
|
10824
|
+
untagged: untagged.length,
|
|
10825
|
+
coverage_percent: pct,
|
|
10826
|
+
by_type: {
|
|
10827
|
+
assigned: stats.assigned.reviewed,
|
|
10828
|
+
explored: stats.explored,
|
|
10829
|
+
foundational: stats.foundational,
|
|
10830
|
+
skipped: stats.skipped
|
|
10831
|
+
}
|
|
10708
10832
|
}
|
|
10709
|
-
}
|
|
10710
|
-
|
|
10833
|
+
})
|
|
10834
|
+
);
|
|
10711
10835
|
} else {
|
|
10712
10836
|
console.log(`
|
|
10713
10837
|
Coverage for session ${sessionId.slice(0, 8)}...`);
|
|
@@ -10734,7 +10858,7 @@ fileCommand.command("flag").argument("<path>", "File path to flag").option("--du
|
|
|
10734
10858
|
error('No active session. Start one with "aigile session start".', opts);
|
|
10735
10859
|
process.exit(1);
|
|
10736
10860
|
}
|
|
10737
|
-
const normalizedPath = filePath.startsWith("/") ?
|
|
10861
|
+
const normalizedPath = filePath.startsWith("/") ? relative6(ctx.projectRoot, filePath) : filePath;
|
|
10738
10862
|
const sessionFile = queryOne(
|
|
10739
10863
|
`SELECT sf.id FROM session_files sf
|
|
10740
10864
|
JOIN documents d ON sf.document_id = d.id
|
|
@@ -10742,7 +10866,10 @@ fileCommand.command("flag").argument("<path>", "File path to flag").option("--du
|
|
|
10742
10866
|
[session.id, normalizedPath]
|
|
10743
10867
|
);
|
|
10744
10868
|
if (!sessionFile) {
|
|
10745
|
-
error(
|
|
10869
|
+
error(
|
|
10870
|
+
`File not tagged in this session: ${normalizedPath}. Tag it first with "aigile file tag".`,
|
|
10871
|
+
opts
|
|
10872
|
+
);
|
|
10746
10873
|
process.exit(1);
|
|
10747
10874
|
}
|
|
10748
10875
|
const issues = [];
|
|
@@ -10761,13 +10888,15 @@ fileCommand.command("flag").argument("<path>", "File path to flag").option("--du
|
|
|
10761
10888
|
}
|
|
10762
10889
|
flagFileQualityIssue(sessionFile.id, issues);
|
|
10763
10890
|
if (opts.json) {
|
|
10764
|
-
console.log(
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10891
|
+
console.log(
|
|
10892
|
+
JSON.stringify({
|
|
10893
|
+
success: true,
|
|
10894
|
+
data: {
|
|
10895
|
+
path: normalizedPath,
|
|
10896
|
+
issues
|
|
10897
|
+
}
|
|
10898
|
+
})
|
|
10899
|
+
);
|
|
10771
10900
|
} else {
|
|
10772
10901
|
success(`Flagged: ${normalizedPath}`, opts);
|
|
10773
10902
|
for (const issue of issues) {
|
|
@@ -10795,10 +10924,9 @@ fileCommand.command("duplicates").option("--session <id>", "Session ID (default:
|
|
|
10795
10924
|
for (const sf of filesWithIssues) {
|
|
10796
10925
|
if (!sf.quality_issues) continue;
|
|
10797
10926
|
const issues = JSON.parse(sf.quality_issues);
|
|
10798
|
-
const doc = queryOne(
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
);
|
|
10927
|
+
const doc = queryOne("SELECT path FROM documents WHERE id = ?", [
|
|
10928
|
+
sf.document_id
|
|
10929
|
+
]);
|
|
10802
10930
|
if (!doc) continue;
|
|
10803
10931
|
for (const issue of issues) {
|
|
10804
10932
|
if (issue.startsWith("duplicate:")) {
|
|
@@ -10813,10 +10941,12 @@ fileCommand.command("duplicates").option("--session <id>", "Session ID (default:
|
|
|
10813
10941
|
}
|
|
10814
10942
|
}
|
|
10815
10943
|
if (opts.json) {
|
|
10816
|
-
console.log(
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10944
|
+
console.log(
|
|
10945
|
+
JSON.stringify({
|
|
10946
|
+
success: true,
|
|
10947
|
+
data: { duplicates }
|
|
10948
|
+
})
|
|
10949
|
+
);
|
|
10820
10950
|
} else {
|
|
10821
10951
|
if (duplicates.length === 0) {
|
|
10822
10952
|
info("No duplicates flagged.", opts);
|
|
@@ -10853,10 +10983,9 @@ fileCommand.command("issues").option("--session <id>", "Session ID (default: cur
|
|
|
10853
10983
|
}
|
|
10854
10984
|
const issueList = [];
|
|
10855
10985
|
for (const sf of filesWithIssues) {
|
|
10856
|
-
const doc = queryOne(
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
);
|
|
10986
|
+
const doc = queryOne("SELECT path FROM documents WHERE id = ?", [
|
|
10987
|
+
sf.document_id
|
|
10988
|
+
]);
|
|
10860
10989
|
if (!doc) continue;
|
|
10861
10990
|
issueList.push({
|
|
10862
10991
|
path: doc.path,
|
|
@@ -10864,10 +10993,12 @@ fileCommand.command("issues").option("--session <id>", "Session ID (default: cur
|
|
|
10864
10993
|
});
|
|
10865
10994
|
}
|
|
10866
10995
|
if (opts.json) {
|
|
10867
|
-
console.log(
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10996
|
+
console.log(
|
|
10997
|
+
JSON.stringify({
|
|
10998
|
+
success: true,
|
|
10999
|
+
data: { files_with_issues: issueList }
|
|
11000
|
+
})
|
|
11001
|
+
);
|
|
10871
11002
|
} else {
|
|
10872
11003
|
console.log(`Files with quality issues (${issueList.length}):`);
|
|
10873
11004
|
for (const file of issueList) {
|
|
@@ -10885,7 +11016,7 @@ init_connection();
|
|
|
10885
11016
|
init_connection();
|
|
10886
11017
|
import { Command as Command22 } from "commander";
|
|
10887
11018
|
import { glob as glob2 } from "glob";
|
|
10888
|
-
import { relative as
|
|
11019
|
+
import { relative as relative7, resolve as resolve6 } from "path";
|
|
10889
11020
|
init_config();
|
|
10890
11021
|
function safeParseArray(json) {
|
|
10891
11022
|
if (!json) return [];
|
|
@@ -10932,7 +11063,7 @@ chunkCommand.command("create").argument("<id>", "Chunk ID (e.g., chunk-001)").op
|
|
|
10932
11063
|
if (options.pattern) {
|
|
10933
11064
|
for (const pattern of options.pattern) {
|
|
10934
11065
|
const matches = await glob2(pattern, { cwd: projectRoot, nodir: true });
|
|
10935
|
-
assignedFiles.push(...matches.map((f) =>
|
|
11066
|
+
assignedFiles.push(...matches.map((f) => relative7(projectRoot, resolve6(projectRoot, f))));
|
|
10936
11067
|
}
|
|
10937
11068
|
}
|
|
10938
11069
|
if (options.assign) {
|
|
@@ -11046,12 +11177,12 @@ chunkCommand.command("list").alias("ls").description("List all chunks in current
|
|
|
11046
11177
|
}
|
|
11047
11178
|
const session = getActiveSession(project.id);
|
|
11048
11179
|
if (!session) {
|
|
11049
|
-
|
|
11180
|
+
warning("No active session.", opts);
|
|
11050
11181
|
return;
|
|
11051
11182
|
}
|
|
11052
11183
|
const chunks = getSessionChunks(session.id);
|
|
11053
11184
|
if (chunks.length === 0) {
|
|
11054
|
-
|
|
11185
|
+
warning("No chunks defined in current session.", opts);
|
|
11055
11186
|
return;
|
|
11056
11187
|
}
|
|
11057
11188
|
data(
|