@arcbridge/core 0.6.1 → 0.6.2
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/index.d.ts +37 -1
- package/dist/index.js +276 -159
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -205,6 +205,15 @@ var AgentRoleSchema = z6.object({
|
|
|
205
205
|
|
|
206
206
|
// src/db/connection.ts
|
|
207
207
|
import { DatabaseSync } from "node:sqlite";
|
|
208
|
+
|
|
209
|
+
// src/utils/log.ts
|
|
210
|
+
function logWarn(context, err) {
|
|
211
|
+
const detail = err === void 0 ? "" : `: ${err instanceof Error ? err.message : String(err)}`;
|
|
212
|
+
process.stderr.write(`[arcbridge] ${context}${detail}
|
|
213
|
+
`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/db/connection.ts
|
|
208
217
|
var suppressed = false;
|
|
209
218
|
function suppressSqliteWarning() {
|
|
210
219
|
if (suppressed) return;
|
|
@@ -267,7 +276,8 @@ function transaction(db, fn) {
|
|
|
267
276
|
} catch (err) {
|
|
268
277
|
try {
|
|
269
278
|
db.exec("ROLLBACK");
|
|
270
|
-
} catch {
|
|
279
|
+
} catch (rollbackErr) {
|
|
280
|
+
logWarn("Transaction rollback failed", rollbackErr);
|
|
271
281
|
}
|
|
272
282
|
throw err;
|
|
273
283
|
} finally {
|
|
@@ -285,7 +295,8 @@ function transaction(db, fn) {
|
|
|
285
295
|
try {
|
|
286
296
|
db.exec(`ROLLBACK TO ${name}`);
|
|
287
297
|
db.exec(`RELEASE ${name}`);
|
|
288
|
-
} catch {
|
|
298
|
+
} catch (rollbackErr) {
|
|
299
|
+
logWarn(`Savepoint ${name} rollback failed`, rollbackErr);
|
|
289
300
|
}
|
|
290
301
|
throw err;
|
|
291
302
|
} finally {
|
|
@@ -871,8 +882,57 @@ function generateConfig(targetDir, input) {
|
|
|
871
882
|
}
|
|
872
883
|
|
|
873
884
|
// src/generators/arc42-generator.ts
|
|
874
|
-
import { mkdirSync as mkdirSync2
|
|
875
|
-
|
|
885
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
886
|
+
|
|
887
|
+
// src/utils/fs.ts
|
|
888
|
+
import {
|
|
889
|
+
writeFileSync as writeFileSync2,
|
|
890
|
+
renameSync,
|
|
891
|
+
unlinkSync,
|
|
892
|
+
statSync,
|
|
893
|
+
chmodSync,
|
|
894
|
+
lstatSync,
|
|
895
|
+
realpathSync
|
|
896
|
+
} from "fs";
|
|
897
|
+
import { dirname, basename, join as join2 } from "path";
|
|
898
|
+
var tmpCounter = 0;
|
|
899
|
+
function atomicWriteFileSync(filePath, content) {
|
|
900
|
+
let target;
|
|
901
|
+
let mode;
|
|
902
|
+
try {
|
|
903
|
+
target = realpathSync(filePath);
|
|
904
|
+
mode = statSync(target).mode & 511;
|
|
905
|
+
} catch {
|
|
906
|
+
let isDanglingLink = false;
|
|
907
|
+
try {
|
|
908
|
+
isDanglingLink = lstatSync(filePath).isSymbolicLink();
|
|
909
|
+
} catch {
|
|
910
|
+
}
|
|
911
|
+
if (isDanglingLink) {
|
|
912
|
+
writeFileSync2(filePath, content, "utf-8");
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
target = filePath;
|
|
916
|
+
}
|
|
917
|
+
const tmpPath = join2(
|
|
918
|
+
dirname(target),
|
|
919
|
+
`.${basename(target)}.${process.pid}.${tmpCounter++}.tmp`
|
|
920
|
+
);
|
|
921
|
+
try {
|
|
922
|
+
writeFileSync2(tmpPath, content, "utf-8");
|
|
923
|
+
if (mode !== void 0) chmodSync(tmpPath, mode);
|
|
924
|
+
renameSync(tmpPath, target);
|
|
925
|
+
} catch (err) {
|
|
926
|
+
try {
|
|
927
|
+
unlinkSync(tmpPath);
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
throw err;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/generators/arc42-generator.ts
|
|
935
|
+
import { join as join5 } from "path";
|
|
876
936
|
import matter from "gray-matter";
|
|
877
937
|
import { stringify as stringify2 } from "yaml";
|
|
878
938
|
|
|
@@ -1046,11 +1106,11 @@ ${input.quality_priorities.map((q) => `| ${q} | *How will you achieve ${q}?* |`)
|
|
|
1046
1106
|
|
|
1047
1107
|
// src/templates/arc42/05-building-blocks.ts
|
|
1048
1108
|
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
1049
|
-
import { join as
|
|
1109
|
+
import { join as join4 } from "path";
|
|
1050
1110
|
|
|
1051
1111
|
// src/templates/arc42/detect-layout.ts
|
|
1052
1112
|
import { existsSync } from "fs";
|
|
1053
|
-
import { join as
|
|
1113
|
+
import { join as join3 } from "path";
|
|
1054
1114
|
function detectProjectLayout(projectRoot, template) {
|
|
1055
1115
|
const srcPrefix = detectSrcPrefix(projectRoot);
|
|
1056
1116
|
const appPrefix = detectAppPrefix(projectRoot, srcPrefix);
|
|
@@ -1059,13 +1119,13 @@ function detectProjectLayout(projectRoot, template) {
|
|
|
1059
1119
|
}
|
|
1060
1120
|
function detectSrcPrefix(projectRoot) {
|
|
1061
1121
|
if (!projectRoot) return "src/";
|
|
1062
|
-
if (existsSync(
|
|
1122
|
+
if (existsSync(join3(projectRoot, "src"))) return "src/";
|
|
1063
1123
|
return "";
|
|
1064
1124
|
}
|
|
1065
1125
|
function detectAppPrefix(projectRoot, srcPrefix) {
|
|
1066
1126
|
if (!projectRoot) return `${srcPrefix}app`;
|
|
1067
|
-
if (existsSync(
|
|
1068
|
-
if (existsSync(
|
|
1127
|
+
if (existsSync(join3(projectRoot, "src", "app"))) return "src/app";
|
|
1128
|
+
if (existsSync(join3(projectRoot, "app"))) return "app";
|
|
1069
1129
|
return `${srcPrefix}app`;
|
|
1070
1130
|
}
|
|
1071
1131
|
function getEntrypoints(template, srcPrefix, appPrefix) {
|
|
@@ -1252,11 +1312,11 @@ function buildingBlocksTemplate(input) {
|
|
|
1252
1312
|
if (primaryService && primaryService.path !== ".") {
|
|
1253
1313
|
prefix = primaryService.path.endsWith("/") ? primaryService.path : `${primaryService.path}/`;
|
|
1254
1314
|
} else {
|
|
1255
|
-
const srcDir =
|
|
1315
|
+
const srcDir = join4(root, "src");
|
|
1256
1316
|
if (existsSync2(srcDir)) {
|
|
1257
1317
|
const entries = readdirSync(srcDir).sort();
|
|
1258
1318
|
const projDir = entries.find(
|
|
1259
|
-
(e) => existsSync2(
|
|
1319
|
+
(e) => existsSync2(join4(srcDir, e, `${e}.csproj`)) || existsSync2(join4(srcDir, e, "Program.cs"))
|
|
1260
1320
|
);
|
|
1261
1321
|
if (projDir) prefix = `src/${projDir}/`;
|
|
1262
1322
|
}
|
|
@@ -2897,11 +2957,11 @@ Track technical debt items for ${input.name} here. Each item should include:
|
|
|
2897
2957
|
// src/generators/arc42-generator.ts
|
|
2898
2958
|
function writeMarkdownWithFrontmatter(filePath, frontmatter, body) {
|
|
2899
2959
|
const content = matter.stringify(body, frontmatter);
|
|
2900
|
-
|
|
2960
|
+
atomicWriteFileSync(filePath, content);
|
|
2901
2961
|
}
|
|
2902
2962
|
function generateArc42(targetDir, input) {
|
|
2903
|
-
const arc42Dir =
|
|
2904
|
-
const decisionsDir =
|
|
2963
|
+
const arc42Dir = join5(targetDir, ".arcbridge", "arc42");
|
|
2964
|
+
const decisionsDir = join5(arc42Dir, "09-decisions");
|
|
2905
2965
|
mkdirSync2(arc42Dir, { recursive: true });
|
|
2906
2966
|
mkdirSync2(decisionsDir, { recursive: true });
|
|
2907
2967
|
const sections = [
|
|
@@ -2918,25 +2978,24 @@ function generateArc42(targetDir, input) {
|
|
|
2918
2978
|
const inputWithRoot = { ...input, projectRoot: targetDir };
|
|
2919
2979
|
for (const { file, template } of sections) {
|
|
2920
2980
|
const { frontmatter, body } = template(inputWithRoot);
|
|
2921
|
-
writeMarkdownWithFrontmatter(
|
|
2981
|
+
writeMarkdownWithFrontmatter(join5(arc42Dir, file), frontmatter, body);
|
|
2922
2982
|
}
|
|
2923
2983
|
const adr = firstAdrTemplate(inputWithRoot);
|
|
2924
2984
|
writeMarkdownWithFrontmatter(
|
|
2925
|
-
|
|
2985
|
+
join5(decisionsDir, adr.filename),
|
|
2926
2986
|
adr.frontmatter,
|
|
2927
2987
|
adr.body
|
|
2928
2988
|
);
|
|
2929
2989
|
const qualityScenarios = qualityScenariosTemplate(input);
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
stringify2(qualityScenarios)
|
|
2933
|
-
"utf-8"
|
|
2990
|
+
atomicWriteFileSync(
|
|
2991
|
+
join5(arc42Dir, "10-quality-scenarios.yaml"),
|
|
2992
|
+
stringify2(qualityScenarios)
|
|
2934
2993
|
);
|
|
2935
2994
|
}
|
|
2936
2995
|
|
|
2937
2996
|
// src/generators/plan-generator.ts
|
|
2938
2997
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2939
|
-
import { join as
|
|
2998
|
+
import { join as join6 } from "path";
|
|
2940
2999
|
import { stringify as stringify3 } from "yaml";
|
|
2941
3000
|
|
|
2942
3001
|
// src/templates/phases/nextjs-app-router.ts
|
|
@@ -4572,25 +4631,25 @@ var planTemplates = {
|
|
|
4572
4631
|
"fullstack-nextjs-dotnet": { plan: phasePlanTemplate7, tasks: phaseTasksTemplate7 }
|
|
4573
4632
|
};
|
|
4574
4633
|
function generatePlan(targetDir, input) {
|
|
4575
|
-
const planDir =
|
|
4576
|
-
const tasksDir =
|
|
4634
|
+
const planDir = join6(targetDir, ".arcbridge", "plan");
|
|
4635
|
+
const tasksDir = join6(planDir, "tasks");
|
|
4577
4636
|
mkdirSync3(planDir, { recursive: true });
|
|
4578
4637
|
mkdirSync3(tasksDir, { recursive: true });
|
|
4579
4638
|
const tmpl = planTemplates[input.template] ?? planTemplates["nextjs-app-router"];
|
|
4580
4639
|
const phasePlan = tmpl.plan(input);
|
|
4581
|
-
writeFileSync3(
|
|
4640
|
+
writeFileSync3(join6(planDir, "phases.yaml"), stringify3(phasePlan), "utf-8");
|
|
4582
4641
|
for (const phase of phasePlan.phases) {
|
|
4583
4642
|
const taskFile = tmpl.tasks(input, phase.id);
|
|
4584
4643
|
if (taskFile) {
|
|
4585
4644
|
writeFileSync3(
|
|
4586
|
-
|
|
4645
|
+
join6(tasksDir, `${phase.id}.yaml`),
|
|
4587
4646
|
stringify3(taskFile),
|
|
4588
4647
|
"utf-8"
|
|
4589
4648
|
);
|
|
4590
4649
|
}
|
|
4591
4650
|
}
|
|
4592
4651
|
writeFileSync3(
|
|
4593
|
-
|
|
4652
|
+
join6(planDir, "sync-log.md"),
|
|
4594
4653
|
`# Sync Log
|
|
4595
4654
|
|
|
4596
4655
|
Architecture sync events are recorded here.
|
|
@@ -4601,7 +4660,7 @@ Architecture sync events are recorded here.
|
|
|
4601
4660
|
|
|
4602
4661
|
// src/generators/agent-generator.ts
|
|
4603
4662
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
4604
|
-
import { join as
|
|
4663
|
+
import { join as join7 } from "path";
|
|
4605
4664
|
import matter2 from "gray-matter";
|
|
4606
4665
|
|
|
4607
4666
|
// src/templates/agents/architect.ts
|
|
@@ -5247,10 +5306,10 @@ var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "
|
|
|
5247
5306
|
function writeAgentRole(dir, role) {
|
|
5248
5307
|
const { system_prompt, ...frontmatter } = role;
|
|
5249
5308
|
const content = matter2.stringify(system_prompt, frontmatter);
|
|
5250
|
-
writeFileSync4(
|
|
5309
|
+
writeFileSync4(join7(dir, `${role.role_id}.md`), content, "utf-8");
|
|
5251
5310
|
}
|
|
5252
5311
|
function generateAgentRoles(targetDir, template) {
|
|
5253
|
-
const agentsDir =
|
|
5312
|
+
const agentsDir = join7(targetDir, ".arcbridge", "agents");
|
|
5254
5313
|
mkdirSync4(agentsDir, { recursive: true });
|
|
5255
5314
|
const roles = [
|
|
5256
5315
|
architectTemplate(),
|
|
@@ -5271,13 +5330,23 @@ function generateAgentRoles(targetDir, template) {
|
|
|
5271
5330
|
}
|
|
5272
5331
|
|
|
5273
5332
|
// src/generators/db-generator.ts
|
|
5274
|
-
import { join as
|
|
5333
|
+
import { join as join8 } from "path";
|
|
5275
5334
|
import { existsSync as existsSync3, readFileSync, readdirSync as readdirSync2, appendFileSync } from "fs";
|
|
5276
5335
|
import { parse } from "yaml";
|
|
5277
5336
|
import matter3 from "gray-matter";
|
|
5337
|
+
var RefreshValidationError = class extends Error {
|
|
5338
|
+
constructor(file, detail) {
|
|
5339
|
+
super(`${file} failed validation: ${detail} \u2014 refresh aborted, fix the file and re-run.`);
|
|
5340
|
+
this.file = file;
|
|
5341
|
+
this.name = "RefreshValidationError";
|
|
5342
|
+
}
|
|
5343
|
+
};
|
|
5344
|
+
function formatZodIssues(error) {
|
|
5345
|
+
return error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ");
|
|
5346
|
+
}
|
|
5278
5347
|
function populateBuildingBlocks(db, targetDir) {
|
|
5279
5348
|
const warnings = [];
|
|
5280
|
-
const filePath =
|
|
5349
|
+
const filePath = join8(
|
|
5281
5350
|
targetDir,
|
|
5282
5351
|
".arcbridge",
|
|
5283
5352
|
"arc42",
|
|
@@ -5288,13 +5357,21 @@ function populateBuildingBlocks(db, targetDir) {
|
|
|
5288
5357
|
return warnings;
|
|
5289
5358
|
}
|
|
5290
5359
|
const raw = readFileSync(filePath, "utf-8");
|
|
5291
|
-
|
|
5360
|
+
let data;
|
|
5361
|
+
try {
|
|
5362
|
+
({ data } = matter3(raw));
|
|
5363
|
+
} catch (err) {
|
|
5364
|
+
throw new RefreshValidationError(
|
|
5365
|
+
".arcbridge/arc42/05-building-blocks.md",
|
|
5366
|
+
err instanceof Error ? err.message : String(err)
|
|
5367
|
+
);
|
|
5368
|
+
}
|
|
5292
5369
|
const result = BuildingBlocksFrontmatterSchema.safeParse(data);
|
|
5293
5370
|
if (!result.success) {
|
|
5294
|
-
|
|
5295
|
-
|
|
5371
|
+
throw new RefreshValidationError(
|
|
5372
|
+
".arcbridge/arc42/05-building-blocks.md",
|
|
5373
|
+
formatZodIssues(result.error)
|
|
5296
5374
|
);
|
|
5297
|
-
return warnings;
|
|
5298
5375
|
}
|
|
5299
5376
|
const fm = result.data;
|
|
5300
5377
|
const insert = db.prepare(`
|
|
@@ -5319,7 +5396,7 @@ function populateBuildingBlocks(db, targetDir) {
|
|
|
5319
5396
|
}
|
|
5320
5397
|
function populateQualityScenarios(db, targetDir) {
|
|
5321
5398
|
const warnings = [];
|
|
5322
|
-
const filePath =
|
|
5399
|
+
const filePath = join8(
|
|
5323
5400
|
targetDir,
|
|
5324
5401
|
".arcbridge",
|
|
5325
5402
|
"arc42",
|
|
@@ -5330,13 +5407,21 @@ function populateQualityScenarios(db, targetDir) {
|
|
|
5330
5407
|
return warnings;
|
|
5331
5408
|
}
|
|
5332
5409
|
const raw = readFileSync(filePath, "utf-8");
|
|
5333
|
-
|
|
5410
|
+
let parsed;
|
|
5411
|
+
try {
|
|
5412
|
+
parsed = parse(raw);
|
|
5413
|
+
} catch (err) {
|
|
5414
|
+
throw new RefreshValidationError(
|
|
5415
|
+
".arcbridge/arc42/10-quality-scenarios.yaml",
|
|
5416
|
+
err instanceof Error ? err.message : String(err)
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
5334
5419
|
const result = QualityScenariosFileSchema.safeParse(parsed);
|
|
5335
5420
|
if (!result.success) {
|
|
5336
|
-
|
|
5337
|
-
|
|
5421
|
+
throw new RefreshValidationError(
|
|
5422
|
+
".arcbridge/arc42/10-quality-scenarios.yaml",
|
|
5423
|
+
formatZodIssues(result.error)
|
|
5338
5424
|
);
|
|
5339
|
-
return warnings;
|
|
5340
5425
|
}
|
|
5341
5426
|
const insert = db.prepare(`
|
|
5342
5427
|
INSERT OR IGNORE INTO quality_scenarios (id, name, category, scenario, expected, priority, linked_code, linked_tests, linked_blocks, verification, status)
|
|
@@ -5361,19 +5446,27 @@ function populateQualityScenarios(db, targetDir) {
|
|
|
5361
5446
|
}
|
|
5362
5447
|
function populatePhases(db, targetDir) {
|
|
5363
5448
|
const warnings = [];
|
|
5364
|
-
const phasesPath =
|
|
5449
|
+
const phasesPath = join8(targetDir, ".arcbridge", "plan", "phases.yaml");
|
|
5365
5450
|
if (!existsSync3(phasesPath)) {
|
|
5366
5451
|
warnings.push("Phases file not found, skipping");
|
|
5367
5452
|
return warnings;
|
|
5368
5453
|
}
|
|
5369
5454
|
const raw = readFileSync(phasesPath, "utf-8");
|
|
5370
|
-
|
|
5455
|
+
let parsed;
|
|
5456
|
+
try {
|
|
5457
|
+
parsed = parse(raw);
|
|
5458
|
+
} catch (err) {
|
|
5459
|
+
throw new RefreshValidationError(
|
|
5460
|
+
".arcbridge/plan/phases.yaml",
|
|
5461
|
+
err instanceof Error ? err.message : String(err)
|
|
5462
|
+
);
|
|
5463
|
+
}
|
|
5371
5464
|
const result = PhasesFileSchema.safeParse(parsed);
|
|
5372
5465
|
if (!result.success) {
|
|
5373
|
-
|
|
5374
|
-
|
|
5466
|
+
throw new RefreshValidationError(
|
|
5467
|
+
".arcbridge/plan/phases.yaml",
|
|
5468
|
+
formatZodIssues(result.error)
|
|
5375
5469
|
);
|
|
5376
|
-
return warnings;
|
|
5377
5470
|
}
|
|
5378
5471
|
const insertPhase = db.prepare(`
|
|
5379
5472
|
INSERT OR IGNORE INTO phases (id, name, phase_number, status, description, gate_status, started_at, completed_at)
|
|
@@ -5395,7 +5488,7 @@ function populatePhases(db, targetDir) {
|
|
|
5395
5488
|
phase.started_at ?? null,
|
|
5396
5489
|
phase.completed_at ?? null
|
|
5397
5490
|
);
|
|
5398
|
-
const taskPath =
|
|
5491
|
+
const taskPath = join8(
|
|
5399
5492
|
targetDir,
|
|
5400
5493
|
".arcbridge",
|
|
5401
5494
|
"plan",
|
|
@@ -5404,7 +5497,15 @@ function populatePhases(db, targetDir) {
|
|
|
5404
5497
|
);
|
|
5405
5498
|
if (!existsSync3(taskPath)) continue;
|
|
5406
5499
|
const taskRaw = readFileSync(taskPath, "utf-8");
|
|
5407
|
-
|
|
5500
|
+
let taskParsed;
|
|
5501
|
+
try {
|
|
5502
|
+
taskParsed = parse(taskRaw);
|
|
5503
|
+
} catch (err) {
|
|
5504
|
+
warnings.push(
|
|
5505
|
+
`Invalid task file for ${phase.id}: ${err instanceof Error ? err.message : String(err)}`
|
|
5506
|
+
);
|
|
5507
|
+
continue;
|
|
5508
|
+
}
|
|
5408
5509
|
const taskResult = TaskFileSchema.safeParse(taskParsed);
|
|
5409
5510
|
if (!taskResult.success) {
|
|
5410
5511
|
warnings.push(
|
|
@@ -5430,7 +5531,7 @@ function populatePhases(db, targetDir) {
|
|
|
5430
5531
|
}
|
|
5431
5532
|
function populateAdrs(db, targetDir) {
|
|
5432
5533
|
const warnings = [];
|
|
5433
|
-
const decisionsDir =
|
|
5534
|
+
const decisionsDir = join8(
|
|
5434
5535
|
targetDir,
|
|
5435
5536
|
".arcbridge",
|
|
5436
5537
|
"arc42",
|
|
@@ -5445,8 +5546,17 @@ function populateAdrs(db, targetDir) {
|
|
|
5445
5546
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5446
5547
|
`);
|
|
5447
5548
|
for (const file of files) {
|
|
5448
|
-
const raw = readFileSync(
|
|
5449
|
-
|
|
5549
|
+
const raw = readFileSync(join8(decisionsDir, file), "utf-8");
|
|
5550
|
+
let data;
|
|
5551
|
+
let content;
|
|
5552
|
+
try {
|
|
5553
|
+
({ data, content } = matter3(raw));
|
|
5554
|
+
} catch (err) {
|
|
5555
|
+
warnings.push(
|
|
5556
|
+
`Invalid ADR ${file}: ${err instanceof Error ? err.message : String(err)}`
|
|
5557
|
+
);
|
|
5558
|
+
continue;
|
|
5559
|
+
}
|
|
5450
5560
|
const result = AdrFrontmatterSchema.safeParse(data);
|
|
5451
5561
|
if (!result.success) {
|
|
5452
5562
|
warnings.push(
|
|
@@ -5540,7 +5650,7 @@ function refreshFromDocs(db, targetDir) {
|
|
|
5540
5650
|
return warnings;
|
|
5541
5651
|
}
|
|
5542
5652
|
function generateDatabase(targetDir, input) {
|
|
5543
|
-
const dbPath =
|
|
5653
|
+
const dbPath = join8(targetDir, ".arcbridge", "index.db");
|
|
5544
5654
|
const db = openDatabase(dbPath);
|
|
5545
5655
|
initializeSchema(db);
|
|
5546
5656
|
const allWarnings = [];
|
|
@@ -5561,7 +5671,7 @@ function generateDatabase(targetDir, input) {
|
|
|
5561
5671
|
return { db, warnings: allWarnings };
|
|
5562
5672
|
}
|
|
5563
5673
|
function ensureGitignore(targetDir) {
|
|
5564
|
-
const gitignorePath =
|
|
5674
|
+
const gitignorePath = join8(targetDir, ".gitignore");
|
|
5565
5675
|
const marker = ".arcbridge/index.db";
|
|
5566
5676
|
if (existsSync3(gitignorePath)) {
|
|
5567
5677
|
const content = readFileSync(gitignorePath, "utf-8");
|
|
@@ -5578,14 +5688,14 @@ function ensureGitignore(targetDir) {
|
|
|
5578
5688
|
|
|
5579
5689
|
// src/indexer/index.ts
|
|
5580
5690
|
import ts6 from "typescript";
|
|
5581
|
-
import { relative as relative5, join as
|
|
5691
|
+
import { relative as relative5, join as join17 } from "path";
|
|
5582
5692
|
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
5583
5693
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5584
5694
|
import YAML from "yaml";
|
|
5585
5695
|
|
|
5586
5696
|
// src/indexer/program.ts
|
|
5587
5697
|
import ts from "typescript";
|
|
5588
|
-
import { join as
|
|
5698
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
5589
5699
|
function createTsProgram(options) {
|
|
5590
5700
|
const projectRoot = options.projectRoot;
|
|
5591
5701
|
const configPath = options.tsconfigPath ?? ts.findConfigFile(projectRoot, ts.sys.fileExists, "tsconfig.json");
|
|
@@ -5609,8 +5719,8 @@ function createTsProgram(options) {
|
|
|
5609
5719
|
for (const ref of config.references) {
|
|
5610
5720
|
const refRelPath = typeof ref === "string" ? ref : ref.path;
|
|
5611
5721
|
if (!refRelPath) continue;
|
|
5612
|
-
const refFullPath =
|
|
5613
|
-
const refConfigPath = refFullPath.endsWith(".json") ? refFullPath :
|
|
5722
|
+
const refFullPath = join9(dirname2(configPath), refRelPath);
|
|
5723
|
+
const refConfigPath = refFullPath.endsWith(".json") ? refFullPath : join9(refFullPath, "tsconfig.json");
|
|
5614
5724
|
if (ts.sys.fileExists(refConfigPath)) {
|
|
5615
5725
|
const refConfig = ts.readConfigFile(refConfigPath, ts.sys.readFile);
|
|
5616
5726
|
const rc = refConfig.config;
|
|
@@ -5638,7 +5748,7 @@ function createTsProgram(options) {
|
|
|
5638
5748
|
const parsed = ts.parseJsonConfigFileContent(
|
|
5639
5749
|
configFile.config,
|
|
5640
5750
|
ts.sys,
|
|
5641
|
-
|
|
5751
|
+
join9(projectRoot),
|
|
5642
5752
|
{ noEmit: true },
|
|
5643
5753
|
resolvedConfigPath
|
|
5644
5754
|
);
|
|
@@ -6316,7 +6426,7 @@ function getPropsType(node, checker) {
|
|
|
6316
6426
|
if (typeStr === "{}" || typeStr === "any") return null;
|
|
6317
6427
|
return typeStr;
|
|
6318
6428
|
}
|
|
6319
|
-
function analyzeComponents(sourceFiles, checker, projectRoot, db, allClient = false) {
|
|
6429
|
+
function analyzeComponents(sourceFiles, checker, projectRoot, db, allClient = false, service = "main") {
|
|
6320
6430
|
const components = [];
|
|
6321
6431
|
for (const sf of sourceFiles) {
|
|
6322
6432
|
const relPath = relative2(projectRoot, sf.fileName);
|
|
@@ -6443,22 +6553,24 @@ function analyzeComponents(sourceFiles, checker, projectRoot, db, allClient = fa
|
|
|
6443
6553
|
}
|
|
6444
6554
|
});
|
|
6445
6555
|
}
|
|
6446
|
-
writeComponents(db, components);
|
|
6556
|
+
writeComponents(db, components, service);
|
|
6447
6557
|
return components.length;
|
|
6448
6558
|
}
|
|
6449
|
-
function writeComponents(db, components) {
|
|
6450
|
-
if (components.length === 0) return;
|
|
6451
|
-
db.prepare("DELETE FROM components").run();
|
|
6452
|
-
const insert = db.prepare(`
|
|
6453
|
-
INSERT OR IGNORE INTO components (
|
|
6454
|
-
symbol_id, is_client, is_server_action, has_state,
|
|
6455
|
-
context_providers, context_consumers, props_type
|
|
6456
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6457
|
-
`);
|
|
6458
|
-
const existingIds = new Set(
|
|
6459
|
-
db.prepare("SELECT id FROM symbols").all().map((r) => r.id)
|
|
6460
|
-
);
|
|
6559
|
+
function writeComponents(db, components, service) {
|
|
6461
6560
|
transaction(db, () => {
|
|
6561
|
+
db.prepare(
|
|
6562
|
+
"DELETE FROM components WHERE symbol_id IN (SELECT id FROM symbols WHERE service = ?)"
|
|
6563
|
+
).run(service);
|
|
6564
|
+
if (components.length === 0) return;
|
|
6565
|
+
const insert = db.prepare(`
|
|
6566
|
+
INSERT OR IGNORE INTO components (
|
|
6567
|
+
symbol_id, is_client, is_server_action, has_state,
|
|
6568
|
+
context_providers, context_consumers, props_type
|
|
6569
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6570
|
+
`);
|
|
6571
|
+
const existingIds = new Set(
|
|
6572
|
+
db.prepare("SELECT id FROM symbols WHERE service = ?").all(service).map((r) => r.id)
|
|
6573
|
+
);
|
|
6462
6574
|
for (const c of components) {
|
|
6463
6575
|
if (!existingIds.has(c.symbolId)) continue;
|
|
6464
6576
|
insert.run(
|
|
@@ -6475,8 +6587,8 @@ function writeComponents(db, components) {
|
|
|
6475
6587
|
}
|
|
6476
6588
|
|
|
6477
6589
|
// src/indexer/route-analyzer.ts
|
|
6478
|
-
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync } from "fs";
|
|
6479
|
-
import { join as
|
|
6590
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
6591
|
+
import { join as join10, relative as relative3 } from "path";
|
|
6480
6592
|
var FILE_KIND_MAP = {
|
|
6481
6593
|
"page": "page",
|
|
6482
6594
|
"layout": "layout",
|
|
@@ -6487,11 +6599,11 @@ var FILE_KIND_MAP = {
|
|
|
6487
6599
|
};
|
|
6488
6600
|
var TS_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"];
|
|
6489
6601
|
function analyzeRoutes(projectRoot, db, service = "main") {
|
|
6490
|
-
let appDir =
|
|
6602
|
+
let appDir = join10(projectRoot, "app");
|
|
6491
6603
|
try {
|
|
6492
|
-
if (!existsSync4(appDir) || !
|
|
6493
|
-
appDir =
|
|
6494
|
-
if (!existsSync4(appDir) || !
|
|
6604
|
+
if (!existsSync4(appDir) || !statSync2(appDir).isDirectory()) {
|
|
6605
|
+
appDir = join10(projectRoot, "src", "app");
|
|
6606
|
+
if (!existsSync4(appDir) || !statSync2(appDir).isDirectory()) {
|
|
6495
6607
|
return 0;
|
|
6496
6608
|
}
|
|
6497
6609
|
}
|
|
@@ -6501,7 +6613,7 @@ function analyzeRoutes(projectRoot, db, service = "main") {
|
|
|
6501
6613
|
const routes = [];
|
|
6502
6614
|
const layoutStack = [];
|
|
6503
6615
|
for (const ext of TS_EXTENSIONS) {
|
|
6504
|
-
const middlewarePath =
|
|
6616
|
+
const middlewarePath = join10(projectRoot, `middleware${ext}`);
|
|
6505
6617
|
if (existsSync4(middlewarePath)) {
|
|
6506
6618
|
routes.push({
|
|
6507
6619
|
id: `route::middleware`,
|
|
@@ -6529,7 +6641,7 @@ function walkAppDir(dir, routePath, routes, layoutStack, service, projectRoot) {
|
|
|
6529
6641
|
const currentLayout = layoutStack.length > 0 ? layoutStack[layoutStack.length - 1] : null;
|
|
6530
6642
|
for (const [convention, kind] of Object.entries(FILE_KIND_MAP)) {
|
|
6531
6643
|
for (const ext of TS_EXTENSIONS) {
|
|
6532
|
-
const filePath =
|
|
6644
|
+
const filePath = join10(dir, `${convention}${ext}`);
|
|
6533
6645
|
if (existsSync4(filePath)) {
|
|
6534
6646
|
const relPath = relative3(projectRoot, dir);
|
|
6535
6647
|
const routeId = `route::${relPath}/${convention}`;
|
|
@@ -6554,9 +6666,9 @@ function walkAppDir(dir, routePath, routes, layoutStack, service, projectRoot) {
|
|
|
6554
6666
|
}
|
|
6555
6667
|
}
|
|
6556
6668
|
for (const entry of entries.sort()) {
|
|
6557
|
-
const fullPath =
|
|
6669
|
+
const fullPath = join10(dir, entry);
|
|
6558
6670
|
try {
|
|
6559
|
-
if (!
|
|
6671
|
+
if (!statSync2(fullPath).isDirectory()) continue;
|
|
6560
6672
|
} catch {
|
|
6561
6673
|
continue;
|
|
6562
6674
|
}
|
|
@@ -6721,33 +6833,34 @@ function writeDependencies(db, dependencies) {
|
|
|
6721
6833
|
|
|
6722
6834
|
// src/indexer/dotnet-indexer.ts
|
|
6723
6835
|
import { execFileSync } from "child_process";
|
|
6724
|
-
import { resolve, join as
|
|
6836
|
+
import { resolve, join as join11, dirname as dirname3, relative as relative4, basename as basename2 } from "path";
|
|
6725
6837
|
import { readdirSync as readdirSync4, readFileSync as readFileSync3, existsSync as existsSync5, accessSync, constants } from "fs";
|
|
6726
6838
|
import { fileURLToPath } from "url";
|
|
6727
6839
|
function findDotnetProject(projectRoot) {
|
|
6728
6840
|
try {
|
|
6729
6841
|
const entries = readdirSync4(projectRoot);
|
|
6730
6842
|
const sln = entries.find((e) => e.endsWith(".sln"));
|
|
6731
|
-
if (sln) return
|
|
6843
|
+
if (sln) return join11(projectRoot, sln);
|
|
6732
6844
|
const csproj = entries.find((e) => e.endsWith(".csproj"));
|
|
6733
|
-
if (csproj) return
|
|
6845
|
+
if (csproj) return join11(projectRoot, csproj);
|
|
6734
6846
|
return null;
|
|
6735
|
-
} catch {
|
|
6847
|
+
} catch (err) {
|
|
6848
|
+
logWarn(`Could not scan ${projectRoot} for .sln/.csproj`, err);
|
|
6736
6849
|
return null;
|
|
6737
6850
|
}
|
|
6738
6851
|
}
|
|
6739
6852
|
function parseSolutionProjects(slnPath) {
|
|
6740
6853
|
const content = readFileSync3(slnPath, "utf-8");
|
|
6741
|
-
const slnDir =
|
|
6854
|
+
const slnDir = dirname3(slnPath);
|
|
6742
6855
|
const projects = [];
|
|
6743
6856
|
const projectPattern = /Project\("\{[^}]+\}"\)\s*=\s*"([^"]+)",\s*"([^"]+\.csproj)"/g;
|
|
6744
6857
|
let match;
|
|
6745
6858
|
while ((match = projectPattern.exec(content)) !== null) {
|
|
6746
6859
|
const name = match[1];
|
|
6747
6860
|
const relativeCsprojPath = match[2].replace(/\\/g, "/");
|
|
6748
|
-
const fullCsprojPath = resolve(
|
|
6861
|
+
const fullCsprojPath = resolve(join11(slnDir, relativeCsprojPath));
|
|
6749
6862
|
if (!existsSync5(fullCsprojPath)) continue;
|
|
6750
|
-
const projectDir = relative4(slnDir,
|
|
6863
|
+
const projectDir = relative4(slnDir, dirname3(fullCsprojPath)).replace(/\\/g, "/") || ".";
|
|
6751
6864
|
const isTestProject = /[.\x2d]tests?$/i.test(name) || /[.\x2d](unit|integration|functional|e2e)tests?$/i.test(name);
|
|
6752
6865
|
projects.push({
|
|
6753
6866
|
name,
|
|
@@ -6763,7 +6876,7 @@ function discoverDotnetServices(projectRoot) {
|
|
|
6763
6876
|
if (!slnPath || !slnPath.endsWith(".sln")) {
|
|
6764
6877
|
const csproj = slnPath;
|
|
6765
6878
|
if (!csproj) return [];
|
|
6766
|
-
const name =
|
|
6879
|
+
const name = basename2(csproj, ".csproj");
|
|
6767
6880
|
return [{
|
|
6768
6881
|
name,
|
|
6769
6882
|
path: ".",
|
|
@@ -6782,7 +6895,7 @@ function hasGlobalTool() {
|
|
|
6782
6895
|
if (!dir) continue;
|
|
6783
6896
|
for (const ext of extensions) {
|
|
6784
6897
|
try {
|
|
6785
|
-
accessSync(
|
|
6898
|
+
accessSync(join11(dir, `${name}${ext}`), constants.X_OK);
|
|
6786
6899
|
return true;
|
|
6787
6900
|
} catch {
|
|
6788
6901
|
continue;
|
|
@@ -6792,7 +6905,7 @@ function hasGlobalTool() {
|
|
|
6792
6905
|
return false;
|
|
6793
6906
|
}
|
|
6794
6907
|
function resolveIndexerProject() {
|
|
6795
|
-
const currentDir4 =
|
|
6908
|
+
const currentDir4 = dirname3(fileURLToPath(import.meta.url));
|
|
6796
6909
|
const candidates = [
|
|
6797
6910
|
resolve(currentDir4, "../../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
|
|
6798
6911
|
resolve(currentDir4, "../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
|
|
@@ -6822,6 +6935,7 @@ function runDotnetIndexer(dotnetProject, hashesJson, cwd) {
|
|
|
6822
6935
|
return execFileSync("arcbridge-dotnet-indexer", args, { ...EXEC_OPTIONS, cwd });
|
|
6823
6936
|
} catch (err) {
|
|
6824
6937
|
globalToolError = err;
|
|
6938
|
+
logWarn("Global tool arcbridge-dotnet-indexer failed, trying monorepo source fallback", err);
|
|
6825
6939
|
}
|
|
6826
6940
|
}
|
|
6827
6941
|
const indexerProject = resolveIndexerProject();
|
|
@@ -6839,7 +6953,8 @@ function runDotnetIndexer(dotnetProject, hashesJson, cwd) {
|
|
|
6839
6953
|
["run", "--project", indexerProject, "--no-build", "--", ...args],
|
|
6840
6954
|
{ ...EXEC_OPTIONS, cwd }
|
|
6841
6955
|
);
|
|
6842
|
-
} catch {
|
|
6956
|
+
} catch (err) {
|
|
6957
|
+
logWarn(".NET indexer --no-build run failed, retrying with build (expected on first run)", err);
|
|
6843
6958
|
try {
|
|
6844
6959
|
return execFileSync(
|
|
6845
6960
|
"dotnet",
|
|
@@ -6941,15 +7056,15 @@ function indexDotnetProjectRoslyn(db, options) {
|
|
|
6941
7056
|
|
|
6942
7057
|
// src/indexer/csharp/indexer.ts
|
|
6943
7058
|
import { readFileSync as readFileSync4 } from "fs";
|
|
6944
|
-
import { join as
|
|
7059
|
+
import { join as join12 } from "path";
|
|
6945
7060
|
import { globbySync } from "globby";
|
|
6946
7061
|
|
|
6947
7062
|
// src/indexer/csharp/parser.ts
|
|
6948
7063
|
import { accessSync as accessSync2, constants as constants2 } from "fs";
|
|
6949
|
-
import { dirname as
|
|
7064
|
+
import { dirname as dirname4, resolve as resolve2 } from "path";
|
|
6950
7065
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6951
7066
|
import "web-tree-sitter";
|
|
6952
|
-
var currentDir =
|
|
7067
|
+
var currentDir = dirname4(fileURLToPath2(import.meta.url));
|
|
6953
7068
|
var cachedParser = null;
|
|
6954
7069
|
var initPromise = null;
|
|
6955
7070
|
function resolveGrammarPath() {
|
|
@@ -7807,7 +7922,7 @@ async function indexCSharpTreeSitter(db, options) {
|
|
|
7807
7922
|
for (const filePath of csFiles) {
|
|
7808
7923
|
const relPath = filePath.replace(/\\/g, "/");
|
|
7809
7924
|
currentPaths.add(relPath);
|
|
7810
|
-
const fullPath =
|
|
7925
|
+
const fullPath = join12(projectRoot, relPath);
|
|
7811
7926
|
const content = readFileSync4(fullPath, "utf-8");
|
|
7812
7927
|
const hash = hashContent(content);
|
|
7813
7928
|
const tree = parseCSharp(content);
|
|
@@ -7884,15 +7999,15 @@ async function indexCSharpTreeSitter(db, options) {
|
|
|
7884
7999
|
|
|
7885
8000
|
// src/indexer/python/indexer.ts
|
|
7886
8001
|
import { readFileSync as readFileSync5 } from "fs";
|
|
7887
|
-
import { join as
|
|
8002
|
+
import { join as join13 } from "path";
|
|
7888
8003
|
import { globbySync as globbySync2 } from "globby";
|
|
7889
8004
|
|
|
7890
8005
|
// src/indexer/python/parser.ts
|
|
7891
8006
|
import { accessSync as accessSync3, constants as constants3 } from "fs";
|
|
7892
|
-
import { dirname as
|
|
8007
|
+
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
7893
8008
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7894
8009
|
import "web-tree-sitter";
|
|
7895
|
-
var currentDir2 =
|
|
8010
|
+
var currentDir2 = dirname5(fileURLToPath3(import.meta.url));
|
|
7896
8011
|
var cachedParser2 = null;
|
|
7897
8012
|
var initPromise2 = null;
|
|
7898
8013
|
function resolveGrammarPath2() {
|
|
@@ -8508,7 +8623,7 @@ async function indexPythonTreeSitter(db, options) {
|
|
|
8508
8623
|
for (const filePath of pyFiles) {
|
|
8509
8624
|
const relPath = filePath.replace(/\\/g, "/");
|
|
8510
8625
|
currentPaths.add(relPath);
|
|
8511
|
-
const fullPath =
|
|
8626
|
+
const fullPath = join13(projectRoot, relPath);
|
|
8512
8627
|
const content = readFileSync5(fullPath, "utf-8");
|
|
8513
8628
|
const hash = hashContent(content);
|
|
8514
8629
|
const tree = parsePython(content);
|
|
@@ -8585,15 +8700,15 @@ async function indexPythonTreeSitter(db, options) {
|
|
|
8585
8700
|
|
|
8586
8701
|
// src/indexer/go/indexer.ts
|
|
8587
8702
|
import { readFileSync as readFileSync6 } from "fs";
|
|
8588
|
-
import { join as
|
|
8703
|
+
import { join as join14 } from "path";
|
|
8589
8704
|
import { globbySync as globbySync3 } from "globby";
|
|
8590
8705
|
|
|
8591
8706
|
// src/indexer/go/parser.ts
|
|
8592
8707
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
8593
|
-
import { dirname as
|
|
8708
|
+
import { dirname as dirname6, resolve as resolve4 } from "path";
|
|
8594
8709
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8595
8710
|
import "web-tree-sitter";
|
|
8596
|
-
var currentDir3 =
|
|
8711
|
+
var currentDir3 = dirname6(fileURLToPath4(import.meta.url));
|
|
8597
8712
|
var cachedParser3 = null;
|
|
8598
8713
|
var initPromise3 = null;
|
|
8599
8714
|
function resolveGrammarPath3() {
|
|
@@ -9333,7 +9448,7 @@ async function indexGoTreeSitter(db, options) {
|
|
|
9333
9448
|
for (const filePath of goFiles) {
|
|
9334
9449
|
const relPath = filePath.replace(/\\/g, "/");
|
|
9335
9450
|
currentPaths.add(relPath);
|
|
9336
|
-
const fullPath =
|
|
9451
|
+
const fullPath = join14(projectRoot, relPath);
|
|
9337
9452
|
const content = readFileSync6(fullPath, "utf-8");
|
|
9338
9453
|
const hash = hashContent(content);
|
|
9339
9454
|
const tree = parseGo(content);
|
|
@@ -9409,11 +9524,11 @@ async function indexGoTreeSitter(db, options) {
|
|
|
9409
9524
|
}
|
|
9410
9525
|
|
|
9411
9526
|
// src/indexer/package-deps.ts
|
|
9412
|
-
import { join as
|
|
9527
|
+
import { join as join15 } from "path";
|
|
9413
9528
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync5 } from "fs";
|
|
9414
9529
|
function indexPackageDependencies(db, projectRoot, service = "main") {
|
|
9415
9530
|
const deps = [];
|
|
9416
|
-
const pkgJsonPath =
|
|
9531
|
+
const pkgJsonPath = join15(projectRoot, "package.json");
|
|
9417
9532
|
if (existsSync6(pkgJsonPath)) {
|
|
9418
9533
|
deps.push(...parsePackageJson(pkgJsonPath));
|
|
9419
9534
|
}
|
|
@@ -9479,7 +9594,7 @@ function findCsprojFiles(dir, maxDepth = 4) {
|
|
|
9479
9594
|
const entries = readdirSync5(currentDir4, { withFileTypes: true });
|
|
9480
9595
|
for (const entry of entries) {
|
|
9481
9596
|
if (entry.name === "bin" || entry.name === "obj" || entry.name === "node_modules" || entry.name === ".git") continue;
|
|
9482
|
-
const fullPath =
|
|
9597
|
+
const fullPath = join15(currentDir4, entry.name);
|
|
9483
9598
|
if (entry.isFile() && entry.name.endsWith(".csproj")) {
|
|
9484
9599
|
results.push(fullPath);
|
|
9485
9600
|
} else if (entry.isDirectory()) {
|
|
@@ -9495,10 +9610,10 @@ function findCsprojFiles(dir, maxDepth = 4) {
|
|
|
9495
9610
|
|
|
9496
9611
|
// src/config/loader.ts
|
|
9497
9612
|
import { readFileSync as readFileSync8 } from "fs";
|
|
9498
|
-
import { join as
|
|
9613
|
+
import { join as join16 } from "path";
|
|
9499
9614
|
import yaml from "yaml";
|
|
9500
9615
|
function loadConfig(projectRoot) {
|
|
9501
|
-
const configPath =
|
|
9616
|
+
const configPath = join16(projectRoot, ".arcbridge", "config.yaml");
|
|
9502
9617
|
try {
|
|
9503
9618
|
const raw = readFileSync8(configPath, "utf-8");
|
|
9504
9619
|
const parsed = ArcBridgeConfigSchema.safeParse(yaml.parse(raw));
|
|
@@ -9517,16 +9632,16 @@ function loadConfig(projectRoot) {
|
|
|
9517
9632
|
|
|
9518
9633
|
// src/indexer/index.ts
|
|
9519
9634
|
function detectProjectLanguage(projectRoot) {
|
|
9520
|
-
if (existsSync7(
|
|
9635
|
+
if (existsSync7(join17(projectRoot, "ProjectSettings")) && existsSync7(join17(projectRoot, "Assets"))) {
|
|
9521
9636
|
return "csharp";
|
|
9522
9637
|
}
|
|
9523
|
-
if (existsSync7(
|
|
9638
|
+
if (existsSync7(join17(projectRoot, "tsconfig.json"))) return "typescript";
|
|
9524
9639
|
if (findDotnetProject(projectRoot)) return "csharp";
|
|
9525
|
-
if (existsSync7(
|
|
9526
|
-
if (existsSync7(
|
|
9640
|
+
if (existsSync7(join17(projectRoot, "go.mod"))) return "go";
|
|
9641
|
+
if (existsSync7(join17(projectRoot, "pyproject.toml")) || existsSync7(join17(projectRoot, "requirements.txt")) || existsSync7(join17(projectRoot, "setup.py"))) {
|
|
9527
9642
|
return "python";
|
|
9528
9643
|
}
|
|
9529
|
-
if (existsSync7(
|
|
9644
|
+
if (existsSync7(join17(projectRoot, "package.json"))) return "typescript";
|
|
9530
9645
|
return "typescript";
|
|
9531
9646
|
}
|
|
9532
9647
|
async function indexProject(db, options) {
|
|
@@ -9582,7 +9697,7 @@ function resolveCSharpBackend(projectRoot) {
|
|
|
9582
9697
|
let setting = config?.indexing?.csharp_indexer;
|
|
9583
9698
|
if (!setting && error) {
|
|
9584
9699
|
try {
|
|
9585
|
-
const raw = readFileSync9(
|
|
9700
|
+
const raw = readFileSync9(join17(projectRoot, ".arcbridge", "config.yaml"), "utf-8");
|
|
9586
9701
|
const parsed = YAML.parse(raw);
|
|
9587
9702
|
const rawSetting = parsed?.indexing?.csharp_indexer;
|
|
9588
9703
|
if (rawSetting === "roslyn" || rawSetting === "tree-sitter") {
|
|
@@ -9654,7 +9769,7 @@ function indexTypeScriptProject(db, options) {
|
|
|
9654
9769
|
const CLIENT_ONLY_TEMPLATES = /* @__PURE__ */ new Set(["react-vite", "angular-app"]);
|
|
9655
9770
|
const projectType = db.prepare("SELECT value FROM arcbridge_meta WHERE key = 'project_type'").get()?.value;
|
|
9656
9771
|
const allClient = projectType ? CLIENT_ONLY_TEMPLATES.has(projectType) : false;
|
|
9657
|
-
const componentsAnalyzed = analyzeComponents(sourceFiles, checker, projectRoot, db, allClient);
|
|
9772
|
+
const componentsAnalyzed = analyzeComponents(sourceFiles, checker, projectRoot, db, allClient, service);
|
|
9658
9773
|
const routesAnalyzed = analyzeRoutes(projectRoot, db, service);
|
|
9659
9774
|
return {
|
|
9660
9775
|
symbolsIndexed: allSymbols.length,
|
|
@@ -9980,11 +10095,11 @@ function safeParseJson(value, fallback) {
|
|
|
9980
10095
|
}
|
|
9981
10096
|
|
|
9982
10097
|
// src/sync/yaml-writer.ts
|
|
9983
|
-
import { join as
|
|
9984
|
-
import { existsSync as existsSync8, readFileSync as readFileSync10,
|
|
10098
|
+
import { join as join18 } from "path";
|
|
10099
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10, mkdirSync as mkdirSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
9985
10100
|
import { parse as parse2, stringify as stringify4 } from "yaml";
|
|
9986
10101
|
function readTaskFile(projectRoot, phaseId) {
|
|
9987
|
-
const path =
|
|
10102
|
+
const path = join18(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
|
|
9988
10103
|
if (!existsSync8(path)) return { error: "not-found" };
|
|
9989
10104
|
const raw = readFileSync10(path, "utf-8");
|
|
9990
10105
|
const result = TaskFileSchema.safeParse(parse2(raw));
|
|
@@ -9992,7 +10107,7 @@ function readTaskFile(projectRoot, phaseId) {
|
|
|
9992
10107
|
return { data: result.data, path };
|
|
9993
10108
|
}
|
|
9994
10109
|
function readPhasesFile(projectRoot) {
|
|
9995
|
-
const path =
|
|
10110
|
+
const path = join18(projectRoot, ".arcbridge", "plan", "phases.yaml");
|
|
9996
10111
|
if (!existsSync8(path)) return { error: "not-found" };
|
|
9997
10112
|
const raw = readFileSync10(path, "utf-8");
|
|
9998
10113
|
const result = PhasesFileSchema.safeParse(parse2(raw));
|
|
@@ -10011,18 +10126,18 @@ function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
|
|
|
10011
10126
|
} else if (status !== "done") {
|
|
10012
10127
|
delete task.completed_at;
|
|
10013
10128
|
}
|
|
10014
|
-
|
|
10129
|
+
atomicWriteFileSync(taskPath, stringify4(taskFile));
|
|
10015
10130
|
}
|
|
10016
10131
|
function addTaskToYaml(projectRoot, phaseId, task) {
|
|
10017
|
-
const tasksDir =
|
|
10132
|
+
const tasksDir = join18(projectRoot, ".arcbridge", "plan", "tasks");
|
|
10018
10133
|
mkdirSync5(tasksDir, { recursive: true });
|
|
10019
10134
|
const readResult = readTaskFile(projectRoot, phaseId);
|
|
10020
10135
|
const taskFile = "error" in readResult ? { schema_version: 1, phase_id: phaseId, tasks: [] } : readResult.data;
|
|
10021
10136
|
if (!taskFile.tasks.some((t) => t.id === task.id)) {
|
|
10022
10137
|
taskFile.tasks.push(task);
|
|
10023
10138
|
}
|
|
10024
|
-
const taskPath =
|
|
10025
|
-
|
|
10139
|
+
const taskPath = join18(tasksDir, `${phaseId}.yaml`);
|
|
10140
|
+
atomicWriteFileSync(taskPath, stringify4(taskFile));
|
|
10026
10141
|
}
|
|
10027
10142
|
function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
|
|
10028
10143
|
const readResult = readPhasesFile(projectRoot);
|
|
@@ -10033,7 +10148,7 @@ function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
|
|
|
10033
10148
|
phase.status = status;
|
|
10034
10149
|
if (startedAt) phase.started_at = startedAt;
|
|
10035
10150
|
if (completedAt) phase.completed_at = completedAt;
|
|
10036
|
-
|
|
10151
|
+
atomicWriteFileSync(phasesPath, stringify4(phasesFile));
|
|
10037
10152
|
}
|
|
10038
10153
|
function addPhaseToYaml(projectRoot, phase) {
|
|
10039
10154
|
try {
|
|
@@ -10054,14 +10169,13 @@ function addPhaseToYaml(projectRoot, phase) {
|
|
|
10054
10169
|
warning: `Phase number ${phase.phase_number} already used by '${conflicting?.id}'`
|
|
10055
10170
|
};
|
|
10056
10171
|
}
|
|
10057
|
-
const tasksDir =
|
|
10172
|
+
const tasksDir = join18(projectRoot, ".arcbridge", "plan", "tasks");
|
|
10058
10173
|
mkdirSync5(tasksDir, { recursive: true });
|
|
10059
|
-
const taskFilePath =
|
|
10174
|
+
const taskFilePath = join18(tasksDir, `${phase.id}.yaml`);
|
|
10060
10175
|
if (!existsSync8(taskFilePath)) {
|
|
10061
|
-
|
|
10176
|
+
atomicWriteFileSync(
|
|
10062
10177
|
taskFilePath,
|
|
10063
|
-
stringify4({ schema_version: 1, phase_id: phase.id, tasks: [] })
|
|
10064
|
-
"utf-8"
|
|
10178
|
+
stringify4({ schema_version: 1, phase_id: phase.id, tasks: [] })
|
|
10065
10179
|
);
|
|
10066
10180
|
}
|
|
10067
10181
|
if (existingById) {
|
|
@@ -10076,7 +10190,7 @@ function addPhaseToYaml(projectRoot, phase) {
|
|
|
10076
10190
|
gate_requirements: phase.gate_requirements ?? []
|
|
10077
10191
|
});
|
|
10078
10192
|
phasesFile.phases.sort((a, b) => a.phase_number - b.phase_number);
|
|
10079
|
-
|
|
10193
|
+
atomicWriteFileSync(phasesPath, stringify4(phasesFile));
|
|
10080
10194
|
return { success: true };
|
|
10081
10195
|
} catch (err) {
|
|
10082
10196
|
return {
|
|
@@ -10086,7 +10200,7 @@ function addPhaseToYaml(projectRoot, phase) {
|
|
|
10086
10200
|
}
|
|
10087
10201
|
}
|
|
10088
10202
|
function syncScenarioToYaml(projectRoot, scenarioId, status, linkedTests, verification) {
|
|
10089
|
-
const scenarioPath =
|
|
10203
|
+
const scenarioPath = join18(
|
|
10090
10204
|
projectRoot,
|
|
10091
10205
|
".arcbridge",
|
|
10092
10206
|
"arc42",
|
|
@@ -10107,7 +10221,7 @@ function syncScenarioToYaml(projectRoot, scenarioId, status, linkedTests, verifi
|
|
|
10107
10221
|
if (verification) {
|
|
10108
10222
|
scenario.verification = verification;
|
|
10109
10223
|
}
|
|
10110
|
-
|
|
10224
|
+
atomicWriteFileSync(scenarioPath, stringify4(scenariosFile));
|
|
10111
10225
|
}
|
|
10112
10226
|
function deleteTaskFromYaml(projectRoot, phaseId, taskId) {
|
|
10113
10227
|
try {
|
|
@@ -10127,7 +10241,7 @@ function deleteTaskFromYaml(projectRoot, phaseId, taskId) {
|
|
|
10127
10241
|
if (taskFile.tasks.length === before) {
|
|
10128
10242
|
return { success: true };
|
|
10129
10243
|
}
|
|
10130
|
-
|
|
10244
|
+
atomicWriteFileSync(taskPath, stringify4(taskFile));
|
|
10131
10245
|
return { success: true };
|
|
10132
10246
|
} catch (err) {
|
|
10133
10247
|
return {
|
|
@@ -10151,10 +10265,10 @@ function deletePhaseFromYaml(projectRoot, phaseId) {
|
|
|
10151
10265
|
if (phasesFile.phases.length === before) {
|
|
10152
10266
|
return { success: false, warning: `Phase '${phaseId}' not found in phases.yaml` };
|
|
10153
10267
|
}
|
|
10154
|
-
|
|
10155
|
-
const taskFilePath =
|
|
10268
|
+
atomicWriteFileSync(phasesPath, stringify4(phasesFile));
|
|
10269
|
+
const taskFilePath = join18(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
|
|
10156
10270
|
try {
|
|
10157
|
-
|
|
10271
|
+
unlinkSync2(taskFilePath);
|
|
10158
10272
|
} catch (e) {
|
|
10159
10273
|
if (e.code !== "ENOENT") throw e;
|
|
10160
10274
|
}
|
|
@@ -10295,8 +10409,8 @@ function safeParseJson2(value, fallback) {
|
|
|
10295
10409
|
}
|
|
10296
10410
|
|
|
10297
10411
|
// src/generators/sync-generator.ts
|
|
10298
|
-
import { mkdirSync as mkdirSync6, writeFileSync as
|
|
10299
|
-
import { join as
|
|
10412
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
10413
|
+
import { join as join19, dirname as dirname7 } from "path";
|
|
10300
10414
|
|
|
10301
10415
|
// src/templates/sync/claude-skill.ts
|
|
10302
10416
|
function claudeSkillTemplate(config) {
|
|
@@ -10474,30 +10588,30 @@ To enable automatic sync:
|
|
|
10474
10588
|
function generateSyncFiles(targetDir, config) {
|
|
10475
10589
|
const generated = [];
|
|
10476
10590
|
const action = githubActionTemplate(config);
|
|
10477
|
-
const actionPath =
|
|
10478
|
-
mkdirSync6(
|
|
10479
|
-
|
|
10591
|
+
const actionPath = join19(targetDir, action.relativePath);
|
|
10592
|
+
mkdirSync6(dirname7(actionPath), { recursive: true });
|
|
10593
|
+
writeFileSync5(actionPath, action.content, "utf-8");
|
|
10480
10594
|
generated.push(action.relativePath);
|
|
10481
10595
|
if (config.platforms.includes("claude")) {
|
|
10482
10596
|
const skill = claudeSkillTemplate(config);
|
|
10483
|
-
const skillPath =
|
|
10484
|
-
mkdirSync6(
|
|
10485
|
-
|
|
10597
|
+
const skillPath = join19(targetDir, skill.relativePath);
|
|
10598
|
+
mkdirSync6(dirname7(skillPath), { recursive: true });
|
|
10599
|
+
writeFileSync5(skillPath, skill.content, "utf-8");
|
|
10486
10600
|
generated.push(skill.relativePath);
|
|
10487
10601
|
}
|
|
10488
10602
|
if (config.platforms.includes("copilot")) {
|
|
10489
10603
|
const hook = copilotHookTemplate(config);
|
|
10490
|
-
const hookPath =
|
|
10491
|
-
mkdirSync6(
|
|
10492
|
-
|
|
10604
|
+
const hookPath = join19(targetDir, hook.relativePath);
|
|
10605
|
+
mkdirSync6(dirname7(hookPath), { recursive: true });
|
|
10606
|
+
writeFileSync5(hookPath, hook.content, "utf-8");
|
|
10493
10607
|
generated.push(hook.relativePath);
|
|
10494
10608
|
}
|
|
10495
10609
|
return generated;
|
|
10496
10610
|
}
|
|
10497
10611
|
|
|
10498
10612
|
// src/metrics/activity.ts
|
|
10499
|
-
import { writeFileSync as
|
|
10500
|
-
import { join as
|
|
10613
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
10614
|
+
import { join as join20 } from "path";
|
|
10501
10615
|
function insertActivity(db, params) {
|
|
10502
10616
|
const totalTokens = params.totalTokens ?? (params.inputTokens != null && params.outputTokens != null ? params.inputTokens + params.outputTokens : null);
|
|
10503
10617
|
const stmt = db.prepare(`
|
|
@@ -10655,7 +10769,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
|
|
|
10655
10769
|
});
|
|
10656
10770
|
const rows = result.rows;
|
|
10657
10771
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
10658
|
-
const dir =
|
|
10772
|
+
const dir = join20(projectRoot, ".arcbridge", "metrics");
|
|
10659
10773
|
mkdirSync7(dir, { recursive: true });
|
|
10660
10774
|
let content;
|
|
10661
10775
|
let filename;
|
|
@@ -10752,8 +10866,8 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
|
|
|
10752
10866
|
break;
|
|
10753
10867
|
}
|
|
10754
10868
|
}
|
|
10755
|
-
const filePath =
|
|
10756
|
-
|
|
10869
|
+
const filePath = join20(dir, filename);
|
|
10870
|
+
writeFileSync6(filePath, content, "utf-8");
|
|
10757
10871
|
return filePath;
|
|
10758
10872
|
}
|
|
10759
10873
|
function getGroupColumn(groupBy) {
|
|
@@ -10820,7 +10934,7 @@ function esc(val) {
|
|
|
10820
10934
|
|
|
10821
10935
|
// src/git/helpers.ts
|
|
10822
10936
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
10823
|
-
import { realpathSync } from "fs";
|
|
10937
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
10824
10938
|
import { relative as relative6 } from "path";
|
|
10825
10939
|
function resolveRef(projectRoot, since, db) {
|
|
10826
10940
|
switch (since) {
|
|
@@ -10929,7 +11043,7 @@ function scopeToProject(changedFiles, projectRoot) {
|
|
|
10929
11043
|
if (!repoRoot) return changedFiles;
|
|
10930
11044
|
let projectRel;
|
|
10931
11045
|
try {
|
|
10932
|
-
projectRel = relative6(
|
|
11046
|
+
projectRel = relative6(realpathSync2(repoRoot), realpathSync2(projectRoot));
|
|
10933
11047
|
} catch {
|
|
10934
11048
|
return changedFiles;
|
|
10935
11049
|
}
|
|
@@ -11063,10 +11177,10 @@ ${output}`;
|
|
|
11063
11177
|
|
|
11064
11178
|
// src/roles/loader.ts
|
|
11065
11179
|
import { readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
|
|
11066
|
-
import { join as
|
|
11180
|
+
import { join as join21 } from "path";
|
|
11067
11181
|
import matter4 from "gray-matter";
|
|
11068
11182
|
function loadRoles(projectRoot) {
|
|
11069
|
-
const agentsDir =
|
|
11183
|
+
const agentsDir = join21(projectRoot, ".arcbridge", "agents");
|
|
11070
11184
|
const roles = [];
|
|
11071
11185
|
const errors = [];
|
|
11072
11186
|
let files;
|
|
@@ -11076,7 +11190,7 @@ function loadRoles(projectRoot) {
|
|
|
11076
11190
|
return { roles: [], errors: [`Agent directory not found: ${agentsDir}`] };
|
|
11077
11191
|
}
|
|
11078
11192
|
for (const file of files) {
|
|
11079
|
-
const filePath =
|
|
11193
|
+
const filePath = join21(agentsDir, file);
|
|
11080
11194
|
try {
|
|
11081
11195
|
const raw = readFileSync11(filePath, "utf-8");
|
|
11082
11196
|
const parsed = matter4(raw);
|
|
@@ -11103,7 +11217,7 @@ function loadRole(projectRoot, roleId) {
|
|
|
11103
11217
|
if (!/^[a-z0-9-]+$/.test(roleId)) {
|
|
11104
11218
|
return { role: null, error: `Invalid role ID: "${roleId}" (must be kebab-case)` };
|
|
11105
11219
|
}
|
|
11106
|
-
const filePath =
|
|
11220
|
+
const filePath = join21(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
|
|
11107
11221
|
try {
|
|
11108
11222
|
const raw = readFileSync11(filePath, "utf-8");
|
|
11109
11223
|
const parsed = matter4(raw);
|
|
@@ -11139,11 +11253,13 @@ export {
|
|
|
11139
11253
|
QualityScenarioSchema,
|
|
11140
11254
|
QualityScenarioStatusSchema,
|
|
11141
11255
|
QualityScenariosFileSchema,
|
|
11256
|
+
RefreshValidationError,
|
|
11142
11257
|
TaskFileSchema,
|
|
11143
11258
|
TaskSchema,
|
|
11144
11259
|
addPhaseToYaml,
|
|
11145
11260
|
addTaskToYaml,
|
|
11146
11261
|
applyInferences,
|
|
11262
|
+
atomicWriteFileSync,
|
|
11147
11263
|
deletePhaseFromYaml,
|
|
11148
11264
|
deleteTaskFromYaml,
|
|
11149
11265
|
detectDrift,
|
|
@@ -11168,6 +11284,7 @@ export {
|
|
|
11168
11284
|
loadConfig,
|
|
11169
11285
|
loadRole,
|
|
11170
11286
|
loadRoles,
|
|
11287
|
+
logWarn,
|
|
11171
11288
|
migrate,
|
|
11172
11289
|
openDatabase,
|
|
11173
11290
|
openMemoryDatabase,
|