@arcbridge/core 0.6.0 → 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.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, writeFileSync as writeFileSync2 } from "fs";
875
- import { join as join4 } from "path";
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 join3 } from "path";
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 join2 } from "path";
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(join2(projectRoot, "src"))) return "src/";
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(join2(projectRoot, "src", "app"))) return "src/app";
1068
- if (existsSync(join2(projectRoot, "app"))) return "app";
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 = join3(root, "src");
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(join3(srcDir, e, `${e}.csproj`)) || existsSync2(join3(srcDir, e, "Program.cs"))
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
- writeFileSync2(filePath, content, "utf-8");
2960
+ atomicWriteFileSync(filePath, content);
2901
2961
  }
2902
2962
  function generateArc42(targetDir, input) {
2903
- const arc42Dir = join4(targetDir, ".arcbridge", "arc42");
2904
- const decisionsDir = join4(arc42Dir, "09-decisions");
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(join4(arc42Dir, file), frontmatter, body);
2981
+ writeMarkdownWithFrontmatter(join5(arc42Dir, file), frontmatter, body);
2922
2982
  }
2923
2983
  const adr = firstAdrTemplate(inputWithRoot);
2924
2984
  writeMarkdownWithFrontmatter(
2925
- join4(decisionsDir, adr.filename),
2985
+ join5(decisionsDir, adr.filename),
2926
2986
  adr.frontmatter,
2927
2987
  adr.body
2928
2988
  );
2929
2989
  const qualityScenarios = qualityScenariosTemplate(input);
2930
- writeFileSync2(
2931
- join4(arc42Dir, "10-quality-scenarios.yaml"),
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 join5 } from "path";
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 = join5(targetDir, ".arcbridge", "plan");
4576
- const tasksDir = join5(planDir, "tasks");
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(join5(planDir, "phases.yaml"), stringify3(phasePlan), "utf-8");
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
- join5(tasksDir, `${phase.id}.yaml`),
4645
+ join6(tasksDir, `${phase.id}.yaml`),
4587
4646
  stringify3(taskFile),
4588
4647
  "utf-8"
4589
4648
  );
4590
4649
  }
4591
4650
  }
4592
4651
  writeFileSync3(
4593
- join5(planDir, "sync-log.md"),
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 join6 } from "path";
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(join6(dir, `${role.role_id}.md`), content, "utf-8");
5309
+ writeFileSync4(join7(dir, `${role.role_id}.md`), content, "utf-8");
5251
5310
  }
5252
5311
  function generateAgentRoles(targetDir, template) {
5253
- const agentsDir = join6(targetDir, ".arcbridge", "agents");
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 join7 } from "path";
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 = join7(
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
- const { data } = matter3(raw);
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
- warnings.push(
5295
- `Invalid building blocks frontmatter: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
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 = join7(
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
- const parsed = parse(raw);
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
- warnings.push(
5337
- `Invalid quality scenarios: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
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 = join7(targetDir, ".arcbridge", "plan", "phases.yaml");
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
- const parsed = parse(raw);
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
- warnings.push(
5374
- `Invalid phases file: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
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 = join7(
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
- const taskParsed = parse(taskRaw);
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 = join7(
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(join7(decisionsDir, file), "utf-8");
5449
- const { data, content } = matter3(raw);
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 = join7(targetDir, ".arcbridge", "index.db");
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 = join7(targetDir, ".gitignore");
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 join16 } from "path";
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 join8, dirname } from "path";
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 = join8(dirname(configPath), refRelPath);
5613
- const refConfigPath = refFullPath.endsWith(".json") ? refFullPath : join8(refFullPath, "tsconfig.json");
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
- join8(projectRoot),
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 join9, relative as relative3 } from "path";
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 = join9(projectRoot, "app");
6602
+ let appDir = join10(projectRoot, "app");
6491
6603
  try {
6492
- if (!existsSync4(appDir) || !statSync(appDir).isDirectory()) {
6493
- appDir = join9(projectRoot, "src", "app");
6494
- if (!existsSync4(appDir) || !statSync(appDir).isDirectory()) {
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 = join9(projectRoot, `middleware${ext}`);
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 = join9(dir, `${convention}${ext}`);
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 = join9(dir, entry);
6669
+ const fullPath = join10(dir, entry);
6558
6670
  try {
6559
- if (!statSync(fullPath).isDirectory()) continue;
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 join10, dirname as dirname2, relative as relative4, basename } from "path";
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 join10(projectRoot, sln);
6843
+ if (sln) return join11(projectRoot, sln);
6732
6844
  const csproj = entries.find((e) => e.endsWith(".csproj"));
6733
- if (csproj) return join10(projectRoot, csproj);
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 = dirname2(slnPath);
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(join10(slnDir, relativeCsprojPath));
6861
+ const fullCsprojPath = resolve(join11(slnDir, relativeCsprojPath));
6749
6862
  if (!existsSync5(fullCsprojPath)) continue;
6750
- const projectDir = relative4(slnDir, dirname2(fullCsprojPath)).replace(/\\/g, "/") || ".";
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 = basename(csproj, ".csproj");
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(join10(dir, `${name}${ext}`), constants.X_OK);
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 = dirname2(fileURLToPath(import.meta.url));
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 join11 } from "path";
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 dirname3, resolve as resolve2 } from "path";
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 = dirname3(fileURLToPath2(import.meta.url));
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 = join11(projectRoot, relPath);
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 join12 } from "path";
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 dirname4, resolve as resolve3 } from "path";
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 = dirname4(fileURLToPath3(import.meta.url));
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 = join12(projectRoot, relPath);
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 join13 } from "path";
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 dirname5, resolve as resolve4 } from "path";
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 = dirname5(fileURLToPath4(import.meta.url));
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 = join13(projectRoot, relPath);
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 join14 } from "path";
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 = join14(projectRoot, "package.json");
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 = join14(currentDir4, entry.name);
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 join15 } from "path";
9613
+ import { join as join16 } from "path";
9499
9614
  import yaml from "yaml";
9500
9615
  function loadConfig(projectRoot) {
9501
- const configPath = join15(projectRoot, ".arcbridge", "config.yaml");
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(join16(projectRoot, "ProjectSettings")) && existsSync7(join16(projectRoot, "Assets"))) {
9635
+ if (existsSync7(join17(projectRoot, "ProjectSettings")) && existsSync7(join17(projectRoot, "Assets"))) {
9521
9636
  return "csharp";
9522
9637
  }
9523
- if (existsSync7(join16(projectRoot, "tsconfig.json"))) return "typescript";
9638
+ if (existsSync7(join17(projectRoot, "tsconfig.json"))) return "typescript";
9524
9639
  if (findDotnetProject(projectRoot)) return "csharp";
9525
- if (existsSync7(join16(projectRoot, "go.mod"))) return "go";
9526
- if (existsSync7(join16(projectRoot, "pyproject.toml")) || existsSync7(join16(projectRoot, "requirements.txt")) || existsSync7(join16(projectRoot, "setup.py"))) {
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(join16(projectRoot, "package.json"))) return "typescript";
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(join16(projectRoot, ".arcbridge", "config.yaml"), "utf-8");
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 join17 } from "path";
9984
- import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync } from "fs";
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 = join17(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
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 = join17(projectRoot, ".arcbridge", "plan", "phases.yaml");
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
- writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
10129
+ atomicWriteFileSync(taskPath, stringify4(taskFile));
10015
10130
  }
10016
10131
  function addTaskToYaml(projectRoot, phaseId, task) {
10017
- const tasksDir = join17(projectRoot, ".arcbridge", "plan", "tasks");
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 = join17(tasksDir, `${phaseId}.yaml`);
10025
- writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
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
- writeFileSync5(phasesPath, stringify4(phasesFile), "utf-8");
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 = join17(projectRoot, ".arcbridge", "plan", "tasks");
10172
+ const tasksDir = join18(projectRoot, ".arcbridge", "plan", "tasks");
10058
10173
  mkdirSync5(tasksDir, { recursive: true });
10059
- const taskFilePath = join17(tasksDir, `${phase.id}.yaml`);
10174
+ const taskFilePath = join18(tasksDir, `${phase.id}.yaml`);
10060
10175
  if (!existsSync8(taskFilePath)) {
10061
- writeFileSync5(
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
- writeFileSync5(phasesPath, stringify4(phasesFile), "utf-8");
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 = join17(
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
- writeFileSync5(scenarioPath, stringify4(scenariosFile), "utf-8");
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
- writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
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
- writeFileSync5(phasesPath, stringify4(phasesFile), "utf-8");
10155
- const taskFilePath = join17(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
10268
+ atomicWriteFileSync(phasesPath, stringify4(phasesFile));
10269
+ const taskFilePath = join18(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
10156
10270
  try {
10157
- unlinkSync(taskFilePath);
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 writeFileSync6 } from "fs";
10299
- import { join as join18, dirname as dirname6 } from "path";
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 = join18(targetDir, action.relativePath);
10478
- mkdirSync6(dirname6(actionPath), { recursive: true });
10479
- writeFileSync6(actionPath, action.content, "utf-8");
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 = join18(targetDir, skill.relativePath);
10484
- mkdirSync6(dirname6(skillPath), { recursive: true });
10485
- writeFileSync6(skillPath, skill.content, "utf-8");
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 = join18(targetDir, hook.relativePath);
10491
- mkdirSync6(dirname6(hookPath), { recursive: true });
10492
- writeFileSync6(hookPath, hook.content, "utf-8");
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 writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
10500
- import { join as join19 } from "path";
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 = join19(projectRoot, ".arcbridge", "metrics");
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 = join19(dir, filename);
10756
- writeFileSync7(filePath, content, "utf-8");
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(realpathSync(repoRoot), realpathSync(projectRoot));
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 join20 } from "path";
11180
+ import { join as join21 } from "path";
11067
11181
  import matter4 from "gray-matter";
11068
11182
  function loadRoles(projectRoot) {
11069
- const agentsDir = join20(projectRoot, ".arcbridge", "agents");
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 = join20(agentsDir, file);
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 = join20(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
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,