@arcbridge/core 0.1.4 → 0.1.5

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
@@ -130,7 +130,8 @@ var TaskStatusSchema = z4.enum([
130
130
  "todo",
131
131
  "in-progress",
132
132
  "done",
133
- "blocked"
133
+ "blocked",
134
+ "cancelled"
134
135
  ]);
135
136
  var PhaseSchema = z4.object({
136
137
  id: z4.string().min(1),
@@ -300,7 +301,7 @@ function transaction(db, fn) {
300
301
  }
301
302
 
302
303
  // src/db/schema.ts
303
- var CURRENT_SCHEMA_VERSION = 2;
304
+ var CURRENT_SCHEMA_VERSION = 3;
304
305
  var SCHEMA_SQL = `
305
306
  -- Metadata
306
307
  CREATE TABLE IF NOT EXISTS arcbridge_meta (
@@ -447,7 +448,7 @@ CREATE TABLE IF NOT EXISTS tasks (
447
448
  phase_id TEXT NOT NULL REFERENCES phases(id),
448
449
  title TEXT NOT NULL,
449
450
  description TEXT,
450
- status TEXT NOT NULL DEFAULT 'todo' CHECK(status IN ('todo','in-progress','done','blocked')),
451
+ status TEXT NOT NULL DEFAULT 'todo' CHECK(status IN ('todo','in-progress','done','blocked','cancelled')),
451
452
  building_block TEXT REFERENCES building_blocks(id),
452
453
  quality_scenarios TEXT NOT NULL DEFAULT '[]',
453
454
  acceptance_criteria TEXT NOT NULL DEFAULT '[]',
@@ -545,6 +546,29 @@ var migrations = [
545
546
  CREATE INDEX IF NOT EXISTS idx_activity_phase ON agent_activity(phase_id);
546
547
  `);
547
548
  }
549
+ },
550
+ {
551
+ version: 3,
552
+ up: (db) => {
553
+ db.exec(`
554
+ CREATE TABLE tasks_new (
555
+ id TEXT PRIMARY KEY,
556
+ phase_id TEXT NOT NULL REFERENCES phases(id),
557
+ title TEXT NOT NULL,
558
+ description TEXT,
559
+ status TEXT NOT NULL DEFAULT 'todo' CHECK(status IN ('todo','in-progress','done','blocked','cancelled')),
560
+ building_block TEXT REFERENCES building_blocks(id),
561
+ quality_scenarios TEXT NOT NULL DEFAULT '[]',
562
+ acceptance_criteria TEXT NOT NULL DEFAULT '[]',
563
+ created_at TEXT NOT NULL,
564
+ completed_at TEXT
565
+ );
566
+ INSERT INTO tasks_new (id, phase_id, title, description, status, building_block, quality_scenarios, acceptance_criteria, created_at, completed_at)
567
+ SELECT id, phase_id, title, description, status, building_block, quality_scenarios, acceptance_criteria, created_at, completed_at FROM tasks;
568
+ DROP TABLE tasks;
569
+ ALTER TABLE tasks_new RENAME TO tasks;
570
+ `);
571
+ }
548
572
  }
549
573
  ];
550
574
  function migrate(db) {
@@ -738,7 +762,7 @@ function generateConfig(targetDir, input) {
738
762
 
739
763
  // src/generators/arc42-generator.ts
740
764
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
741
- import { join as join3 } from "path";
765
+ import { join as join4 } from "path";
742
766
  import matter from "gray-matter";
743
767
  import { stringify as stringify2 } from "yaml";
744
768
 
@@ -826,6 +850,10 @@ ${techStack(input.template)}
826
850
  };
827
851
  }
828
852
 
853
+ // src/templates/arc42/05-building-blocks.ts
854
+ import { existsSync as existsSync2, readdirSync } from "fs";
855
+ import { join as join3 } from "path";
856
+
829
857
  // src/templates/arc42/detect-layout.ts
830
858
  import { existsSync } from "fs";
831
859
  import { join as join2 } from "path";
@@ -959,13 +987,31 @@ function buildingBlocksTemplate(input) {
959
987
  }
960
988
  return blocks;
961
989
  }
962
- function buildDotnetBlocks(_inp) {
990
+ function buildDotnetBlocks(inp) {
991
+ const root = inp.projectRoot ?? ".";
992
+ let prefix = "";
993
+ try {
994
+ const primaryService = inp.dotnetServices?.find((s) => !s.path.includes("Test"));
995
+ if (primaryService && primaryService.path !== ".") {
996
+ prefix = primaryService.path.endsWith("/") ? primaryService.path : `${primaryService.path}/`;
997
+ } else {
998
+ const srcDir = join3(root, "src");
999
+ if (existsSync2(srcDir)) {
1000
+ const entries = readdirSync(srcDir).sort();
1001
+ const projDir = entries.find(
1002
+ (e) => existsSync2(join3(srcDir, e, `${e}.csproj`)) || existsSync2(join3(srcDir, e, "Program.cs"))
1003
+ );
1004
+ if (projDir) prefix = `src/${projDir}/`;
1005
+ }
1006
+ }
1007
+ } catch {
1008
+ }
963
1009
  const blocks = [
964
1010
  {
965
1011
  id: "api-host",
966
1012
  name: "API Host",
967
1013
  level: 1,
968
- code_paths: ["Program.cs", "Extensions/"],
1014
+ code_paths: [`${prefix}Program.cs`, `${prefix}Extensions/`],
969
1015
  interfaces: [],
970
1016
  quality_scenarios: [],
971
1017
  adrs: [],
@@ -976,7 +1022,7 @@ function buildingBlocksTemplate(input) {
976
1022
  id: "controllers",
977
1023
  name: "Controllers / Endpoints",
978
1024
  level: 1,
979
- code_paths: ["Controllers/", "Endpoints/"],
1025
+ code_paths: [`${prefix}Controllers/`, `${prefix}Endpoints/`],
980
1026
  interfaces: [],
981
1027
  quality_scenarios: ["SEC-03"],
982
1028
  adrs: [],
@@ -987,7 +1033,7 @@ function buildingBlocksTemplate(input) {
987
1033
  id: "domain",
988
1034
  name: "Domain",
989
1035
  level: 1,
990
- code_paths: ["Domain/", "Models/"],
1036
+ code_paths: [`${prefix}Domain/`, `${prefix}Models/`],
991
1037
  interfaces: [],
992
1038
  quality_scenarios: [],
993
1039
  adrs: [],
@@ -998,7 +1044,7 @@ function buildingBlocksTemplate(input) {
998
1044
  id: "services",
999
1045
  name: "Application Services",
1000
1046
  level: 1,
1001
- code_paths: ["Services/"],
1047
+ code_paths: [`${prefix}Services/`],
1002
1048
  interfaces: ["controllers"],
1003
1049
  quality_scenarios: [],
1004
1050
  adrs: [],
@@ -1009,7 +1055,7 @@ function buildingBlocksTemplate(input) {
1009
1055
  id: "middleware",
1010
1056
  name: "Middleware",
1011
1057
  level: 1,
1012
- code_paths: ["Middleware/"],
1058
+ code_paths: [`${prefix}Middleware/`],
1013
1059
  interfaces: [],
1014
1060
  quality_scenarios: ["REL-01"],
1015
1061
  adrs: [],
@@ -1021,7 +1067,7 @@ function buildingBlocksTemplate(input) {
1021
1067
  id: "auth-module",
1022
1068
  name: "Authentication & Authorization",
1023
1069
  level: 1,
1024
- code_paths: ["Auth/"],
1070
+ code_paths: [`${prefix}Auth/`],
1025
1071
  interfaces: [],
1026
1072
  quality_scenarios: ["SEC-01", "SEC-02"],
1027
1073
  adrs: [],
@@ -1032,7 +1078,7 @@ function buildingBlocksTemplate(input) {
1032
1078
  id: "data-access",
1033
1079
  name: "Data Access",
1034
1080
  level: 1,
1035
- code_paths: ["Data/", "Repositories/", "Migrations/"],
1081
+ code_paths: [`${prefix}Data/`, `${prefix}Repositories/`, `${prefix}Migrations/`],
1036
1082
  interfaces: ["domain"],
1037
1083
  quality_scenarios: [],
1038
1084
  adrs: [],
@@ -1847,8 +1893,8 @@ function writeMarkdownWithFrontmatter(filePath, frontmatter, body) {
1847
1893
  writeFileSync2(filePath, content, "utf-8");
1848
1894
  }
1849
1895
  function generateArc42(targetDir, input) {
1850
- const arc42Dir = join3(targetDir, ".arcbridge", "arc42");
1851
- const decisionsDir = join3(arc42Dir, "09-decisions");
1896
+ const arc42Dir = join4(targetDir, ".arcbridge", "arc42");
1897
+ const decisionsDir = join4(arc42Dir, "09-decisions");
1852
1898
  mkdirSync2(arc42Dir, { recursive: true });
1853
1899
  mkdirSync2(decisionsDir, { recursive: true });
1854
1900
  const sections = [
@@ -1863,17 +1909,17 @@ function generateArc42(targetDir, input) {
1863
1909
  const inputWithRoot = { ...input, projectRoot: targetDir };
1864
1910
  for (const { file, template } of sections) {
1865
1911
  const { frontmatter, body } = template(inputWithRoot);
1866
- writeMarkdownWithFrontmatter(join3(arc42Dir, file), frontmatter, body);
1912
+ writeMarkdownWithFrontmatter(join4(arc42Dir, file), frontmatter, body);
1867
1913
  }
1868
1914
  const adr = firstAdrTemplate(inputWithRoot);
1869
1915
  writeMarkdownWithFrontmatter(
1870
- join3(decisionsDir, adr.filename),
1916
+ join4(decisionsDir, adr.filename),
1871
1917
  adr.frontmatter,
1872
1918
  adr.body
1873
1919
  );
1874
1920
  const qualityScenarios = qualityScenariosTemplate(input);
1875
1921
  writeFileSync2(
1876
- join3(arc42Dir, "10-quality-scenarios.yaml"),
1922
+ join4(arc42Dir, "10-quality-scenarios.yaml"),
1877
1923
  stringify2(qualityScenarios),
1878
1924
  "utf-8"
1879
1925
  );
@@ -1881,7 +1927,7 @@ function generateArc42(targetDir, input) {
1881
1927
 
1882
1928
  // src/generators/plan-generator.ts
1883
1929
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
1884
- import { join as join4 } from "path";
1930
+ import { join as join5 } from "path";
1885
1931
  import { stringify as stringify3 } from "yaml";
1886
1932
 
1887
1933
  // src/templates/phases/nextjs-app-router.ts
@@ -2805,25 +2851,25 @@ var planTemplates = {
2805
2851
  "dotnet-webapi": { plan: phasePlanTemplate4, tasks: phaseTasksTemplate4 }
2806
2852
  };
2807
2853
  function generatePlan(targetDir, input) {
2808
- const planDir = join4(targetDir, ".arcbridge", "plan");
2809
- const tasksDir = join4(planDir, "tasks");
2854
+ const planDir = join5(targetDir, ".arcbridge", "plan");
2855
+ const tasksDir = join5(planDir, "tasks");
2810
2856
  mkdirSync3(planDir, { recursive: true });
2811
2857
  mkdirSync3(tasksDir, { recursive: true });
2812
2858
  const tmpl = planTemplates[input.template] ?? planTemplates["nextjs-app-router"];
2813
2859
  const phasePlan = tmpl.plan(input);
2814
- writeFileSync3(join4(planDir, "phases.yaml"), stringify3(phasePlan), "utf-8");
2860
+ writeFileSync3(join5(planDir, "phases.yaml"), stringify3(phasePlan), "utf-8");
2815
2861
  for (const phase of phasePlan.phases) {
2816
2862
  const taskFile = tmpl.tasks(input, phase.id);
2817
2863
  if (taskFile) {
2818
2864
  writeFileSync3(
2819
- join4(tasksDir, `${phase.id}.yaml`),
2865
+ join5(tasksDir, `${phase.id}.yaml`),
2820
2866
  stringify3(taskFile),
2821
2867
  "utf-8"
2822
2868
  );
2823
2869
  }
2824
2870
  }
2825
2871
  writeFileSync3(
2826
- join4(planDir, "sync-log.md"),
2872
+ join5(planDir, "sync-log.md"),
2827
2873
  `# Sync Log
2828
2874
 
2829
2875
  Architecture sync events are recorded here.
@@ -2834,7 +2880,7 @@ Architecture sync events are recorded here.
2834
2880
 
2835
2881
  // src/generators/agent-generator.ts
2836
2882
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
2837
- import { join as join5 } from "path";
2883
+ import { join as join6 } from "path";
2838
2884
  import matter2 from "gray-matter";
2839
2885
 
2840
2886
  // src/templates/agents/architect.ts
@@ -2930,7 +2976,9 @@ You are responsible for maintaining these sections in \`.arcbridge/arc42/\`. Upd
2930
2976
 
2931
2977
  ## Project Planning
2932
2978
 
2933
- At the start of a project (after init), plan tasks across ALL phases:
2979
+ At the start of a project (after init), plan the full roadmap:
2980
+ - **ArcBridge generates 4 phases as a starting template** (Setup, Foundation, Features, Polish). These are a baseline \u2014 adapt them to your project's actual scope and complexity.
2981
+ - **For larger projects, add more phases** by editing \`.arcbridge/plan/phases.yaml\` and running \`arcbridge_reindex\`. Split "Core Features" into multiple phases if needed (e.g., "Phase 2: Auth & Users", "Phase 3: Core Business Logic", "Phase 4: Integrations", "Phase 5: Polish").
2934
2982
  - **Phase 0-1 tasks are ready to use** \u2014 they cover setup and foundation for this template
2935
2983
  - **Phase 2+ tasks are examples** \u2014 replace them with real tasks derived from the project's requirements and specifications
2936
2984
  - Review the phase plan with \`arcbridge_get_phase_plan\` and replace example tasks with project-specific ones
@@ -2938,7 +2986,6 @@ At the start of a project (after init), plan tasks across ALL phases:
2938
2986
  - Keep each phase reasonably scoped \u2014 3-6 tasks per phase is ideal
2939
2987
  - Map tasks to building blocks so drift detection tracks coverage
2940
2988
  - Link tasks to quality scenarios so gate checks are meaningful
2941
- - Plan the full roadmap before diving into implementation \u2014 this prevents phases from becoming too large or unfocused
2942
2989
 
2943
2990
  ## Working Style
2944
2991
 
@@ -3089,11 +3136,13 @@ function qualityGuardianTemplate() {
3089
3136
  "arcbridge_search_symbols",
3090
3137
  "arcbridge_get_symbol",
3091
3138
  "arcbridge_get_component_graph",
3092
- "arcbridge_get_boundary_analysis"
3093
- // Phase 3+: "arcbridge_run_role_check"
3139
+ "arcbridge_get_boundary_analysis",
3140
+ "arcbridge_update_scenario_status",
3141
+ "arcbridge_verify_scenarios",
3142
+ "arcbridge_run_role_check"
3094
3143
  ],
3095
3144
  denied_tools: [],
3096
- read_only: true,
3145
+ read_only: false,
3097
3146
  quality_focus: [
3098
3147
  "performance",
3099
3148
  "accessibility",
@@ -3124,6 +3173,23 @@ function qualityGuardianTemplate() {
3124
3173
  - Every quality scenario with status "untested" needs attention
3125
3174
  - Performance budgets are hard limits, not guidelines
3126
3175
 
3176
+ ## Linking Tests to Quality Scenarios
3177
+
3178
+ Quality scenarios need \`linked_tests\` to be verifiable via \`arcbridge_verify_scenarios\`. To link tests:
3179
+
3180
+ 1. Edit \`.arcbridge/arc42/10-quality-scenarios.yaml\`
3181
+ 2. Add test file paths to the \`linked_tests\` array for each scenario:
3182
+ \`\`\`yaml
3183
+ - id: SEC-01
3184
+ linked_tests:
3185
+ - "src/__tests__/auth.test.ts"
3186
+ - "src/__tests__/middleware.test.ts"
3187
+ \`\`\`
3188
+ 3. Use glob patterns for broader matching: \`"src/**/*.security.test.ts"\`
3189
+ 4. After linking, run \`arcbridge_verify_scenarios\` to execute the tests and update scenario status
3190
+
3191
+ Without \`linked_tests\`, scenarios remain "untested" \u2014 so link tests early, ideally when creating the test file.
3192
+
3127
3193
  ## Review Checklist
3128
3194
 
3129
3195
  1. **Performance:** Bundle size, LCP, API latency against defined budgets
@@ -3145,7 +3211,11 @@ function phaseManagerTemplate() {
3145
3211
  "arcbridge_get_phase_plan",
3146
3212
  "arcbridge_get_current_tasks",
3147
3213
  "arcbridge_update_task",
3214
+ "arcbridge_delete_task",
3215
+ "arcbridge_create_task",
3148
3216
  "arcbridge_check_drift",
3217
+ "arcbridge_verify_scenarios",
3218
+ "arcbridge_update_scenario_status",
3149
3219
  "arcbridge_get_open_questions",
3150
3220
  "arcbridge_propose_arc42_update"
3151
3221
  ],
@@ -3180,9 +3250,11 @@ function phaseManagerTemplate() {
3180
3250
  ## Task Planning
3181
3251
 
3182
3252
  Before starting any phase, ensure proper task planning:
3253
+ - **ArcBridge generates 4 phases as a starting template.** For larger projects, add more phases by editing \`.arcbridge/plan/phases.yaml\` and running \`arcbridge_reindex\`.
3183
3254
  - **Phase 0-1 tasks are concrete** \u2014 they cover project setup and foundation. Follow them as-is.
3184
3255
  - **Phase 2+ tasks are examples only** \u2014 they show the *shape* of later phases but must be replaced with real tasks derived from the project's actual requirements and specs.
3185
- - **At project start, plan ALL phases** \u2014 review \`arcbridge_get_phase_plan\`, delete example tasks in Phase 2+, and create real tasks based on the product spec and building blocks.
3256
+ - **At project start, plan ALL phases** \u2014 review \`arcbridge_get_phase_plan\`, delete example tasks in Phase 2+ using \`arcbridge_delete_task\`, and create real tasks based on the product spec.
3257
+ - **Delete vs cancel** \u2014 use \`arcbridge_delete_task\` for example/template tasks that should be removed. Use \`arcbridge_update_task\` with status \`cancelled\` for planned tasks that turned out to be unnecessary (preserves the decision trail).
3186
3258
  - **Keep phases small and focused** \u2014 if a phase has more than 6-8 tasks, split it into sub-phases
3187
3259
  - **Tasks should be concrete and verifiable** \u2014 each task needs clear acceptance criteria
3188
3260
  - **Link tasks to building blocks** \u2014 this enables drift detection and progress tracking
@@ -3194,8 +3266,9 @@ Before starting any phase, ensure proper task planning:
3194
3266
  2. Review task coverage for the NEXT phase \u2014 create tasks if empty
3195
3267
  3. Run drift detection (\`arcbridge_check_drift\`)
3196
3268
  4. Propose arc42 updates if drift is detected
3197
- 5. Check quality gate requirements
3198
- 6. Mark phase complete or report blockers`
3269
+ 5. Ensure quality scenarios have \`linked_tests\` \u2014 edit \`10-quality-scenarios.yaml\` to add test file paths so \`arcbridge_verify_scenarios\` can run them
3270
+ 6. Check quality gate requirements
3271
+ 7. Mark phase complete or report blockers`
3199
3272
  };
3200
3273
  }
3201
3274
 
@@ -3422,10 +3495,10 @@ var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite"]);
3422
3495
  function writeAgentRole(dir, role) {
3423
3496
  const { system_prompt, ...frontmatter } = role;
3424
3497
  const content = matter2.stringify(system_prompt, frontmatter);
3425
- writeFileSync4(join5(dir, `${role.role_id}.md`), content, "utf-8");
3498
+ writeFileSync4(join6(dir, `${role.role_id}.md`), content, "utf-8");
3426
3499
  }
3427
3500
  function generateAgentRoles(targetDir, template) {
3428
- const agentsDir = join5(targetDir, ".arcbridge", "agents");
3501
+ const agentsDir = join6(targetDir, ".arcbridge", "agents");
3429
3502
  mkdirSync4(agentsDir, { recursive: true });
3430
3503
  const roles = [
3431
3504
  architectTemplate(),
@@ -3446,19 +3519,19 @@ function generateAgentRoles(targetDir, template) {
3446
3519
  }
3447
3520
 
3448
3521
  // src/generators/db-generator.ts
3449
- import { join as join6 } from "path";
3450
- import { existsSync as existsSync2, readFileSync, readdirSync, appendFileSync } from "fs";
3522
+ import { join as join7 } from "path";
3523
+ import { existsSync as existsSync3, readFileSync, readdirSync as readdirSync2, appendFileSync } from "fs";
3451
3524
  import { parse } from "yaml";
3452
3525
  import matter3 from "gray-matter";
3453
3526
  function populateBuildingBlocks(db, targetDir) {
3454
3527
  const warnings = [];
3455
- const filePath = join6(
3528
+ const filePath = join7(
3456
3529
  targetDir,
3457
3530
  ".arcbridge",
3458
3531
  "arc42",
3459
3532
  "05-building-blocks.md"
3460
3533
  );
3461
- if (!existsSync2(filePath)) {
3534
+ if (!existsSync3(filePath)) {
3462
3535
  warnings.push("Building blocks file not found, skipping");
3463
3536
  return warnings;
3464
3537
  }
@@ -3467,7 +3540,7 @@ function populateBuildingBlocks(db, targetDir) {
3467
3540
  const result = BuildingBlocksFrontmatterSchema.safeParse(data);
3468
3541
  if (!result.success) {
3469
3542
  warnings.push(
3470
- `Invalid building blocks frontmatter: ${result.error.issues.map((i) => i.message).join(", ")}`
3543
+ `Invalid building blocks frontmatter: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
3471
3544
  );
3472
3545
  return warnings;
3473
3546
  }
@@ -3494,13 +3567,13 @@ function populateBuildingBlocks(db, targetDir) {
3494
3567
  }
3495
3568
  function populateQualityScenarios(db, targetDir) {
3496
3569
  const warnings = [];
3497
- const filePath = join6(
3570
+ const filePath = join7(
3498
3571
  targetDir,
3499
3572
  ".arcbridge",
3500
3573
  "arc42",
3501
3574
  "10-quality-scenarios.yaml"
3502
3575
  );
3503
- if (!existsSync2(filePath)) {
3576
+ if (!existsSync3(filePath)) {
3504
3577
  warnings.push("Quality scenarios file not found, skipping");
3505
3578
  return warnings;
3506
3579
  }
@@ -3509,7 +3582,7 @@ function populateQualityScenarios(db, targetDir) {
3509
3582
  const result = QualityScenariosFileSchema.safeParse(parsed);
3510
3583
  if (!result.success) {
3511
3584
  warnings.push(
3512
- `Invalid quality scenarios: ${result.error.issues.map((i) => i.message).join(", ")}`
3585
+ `Invalid quality scenarios: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
3513
3586
  );
3514
3587
  return warnings;
3515
3588
  }
@@ -3536,8 +3609,8 @@ function populateQualityScenarios(db, targetDir) {
3536
3609
  }
3537
3610
  function populatePhases(db, targetDir) {
3538
3611
  const warnings = [];
3539
- const phasesPath = join6(targetDir, ".arcbridge", "plan", "phases.yaml");
3540
- if (!existsSync2(phasesPath)) {
3612
+ const phasesPath = join7(targetDir, ".arcbridge", "plan", "phases.yaml");
3613
+ if (!existsSync3(phasesPath)) {
3541
3614
  warnings.push("Phases file not found, skipping");
3542
3615
  return warnings;
3543
3616
  }
@@ -3546,7 +3619,7 @@ function populatePhases(db, targetDir) {
3546
3619
  const result = PhasesFileSchema.safeParse(parsed);
3547
3620
  if (!result.success) {
3548
3621
  warnings.push(
3549
- `Invalid phases file: ${result.error.issues.map((i) => i.message).join(", ")}`
3622
+ `Invalid phases file: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
3550
3623
  );
3551
3624
  return warnings;
3552
3625
  }
@@ -3570,20 +3643,20 @@ function populatePhases(db, targetDir) {
3570
3643
  phase.started_at ?? null,
3571
3644
  phase.completed_at ?? null
3572
3645
  );
3573
- const taskPath = join6(
3646
+ const taskPath = join7(
3574
3647
  targetDir,
3575
3648
  ".arcbridge",
3576
3649
  "plan",
3577
3650
  "tasks",
3578
3651
  `${phase.id}.yaml`
3579
3652
  );
3580
- if (!existsSync2(taskPath)) continue;
3653
+ if (!existsSync3(taskPath)) continue;
3581
3654
  const taskRaw = readFileSync(taskPath, "utf-8");
3582
3655
  const taskParsed = parse(taskRaw);
3583
3656
  const taskResult = TaskFileSchema.safeParse(taskParsed);
3584
3657
  if (!taskResult.success) {
3585
3658
  warnings.push(
3586
- `Invalid task file for ${phase.id}: ${taskResult.error.issues.map((i) => i.message).join(", ")}`
3659
+ `Invalid task file for ${phase.id}: ${taskResult.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
3587
3660
  );
3588
3661
  continue;
3589
3662
  }
@@ -3605,27 +3678,27 @@ function populatePhases(db, targetDir) {
3605
3678
  }
3606
3679
  function populateAdrs(db, targetDir) {
3607
3680
  const warnings = [];
3608
- const decisionsDir = join6(
3681
+ const decisionsDir = join7(
3609
3682
  targetDir,
3610
3683
  ".arcbridge",
3611
3684
  "arc42",
3612
3685
  "09-decisions"
3613
3686
  );
3614
- if (!existsSync2(decisionsDir)) {
3687
+ if (!existsSync3(decisionsDir)) {
3615
3688
  return warnings;
3616
3689
  }
3617
- const files = readdirSync(decisionsDir).filter((f) => f.endsWith(".md"));
3690
+ const files = readdirSync2(decisionsDir).filter((f) => f.endsWith(".md"));
3618
3691
  const insert = db.prepare(`
3619
3692
  INSERT OR IGNORE INTO adrs (id, title, status, date, context, decision, consequences, affected_blocks, affected_files, quality_scenarios)
3620
3693
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3621
3694
  `);
3622
3695
  for (const file of files) {
3623
- const raw = readFileSync(join6(decisionsDir, file), "utf-8");
3696
+ const raw = readFileSync(join7(decisionsDir, file), "utf-8");
3624
3697
  const { data, content } = matter3(raw);
3625
3698
  const result = AdrFrontmatterSchema.safeParse(data);
3626
3699
  if (!result.success) {
3627
3700
  warnings.push(
3628
- `Invalid ADR ${file}: ${result.error.issues.map((i) => i.message).join(", ")}`
3701
+ `Invalid ADR ${file}: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
3629
3702
  );
3630
3703
  continue;
3631
3704
  }
@@ -3715,7 +3788,7 @@ function refreshFromDocs(db, targetDir) {
3715
3788
  return warnings;
3716
3789
  }
3717
3790
  function generateDatabase(targetDir, input) {
3718
- const dbPath = join6(targetDir, ".arcbridge", "index.db");
3791
+ const dbPath = join7(targetDir, ".arcbridge", "index.db");
3719
3792
  const db = openDatabase(dbPath);
3720
3793
  initializeSchema(db);
3721
3794
  const allWarnings = [];
@@ -3735,9 +3808,9 @@ function generateDatabase(targetDir, input) {
3735
3808
  return { db, warnings: allWarnings };
3736
3809
  }
3737
3810
  function ensureGitignore(targetDir) {
3738
- const gitignorePath = join6(targetDir, ".gitignore");
3811
+ const gitignorePath = join7(targetDir, ".gitignore");
3739
3812
  const marker = ".arcbridge/index.db";
3740
- if (existsSync2(gitignorePath)) {
3813
+ if (existsSync3(gitignorePath)) {
3741
3814
  const content = readFileSync(gitignorePath, "utf-8");
3742
3815
  if (content.includes(marker)) return;
3743
3816
  }
@@ -3751,14 +3824,14 @@ function ensureGitignore(targetDir) {
3751
3824
  }
3752
3825
 
3753
3826
  // src/indexer/index.ts
3754
- import { relative as relative5, join as join13 } from "path";
3755
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
3827
+ import { relative as relative5, join as join14 } from "path";
3828
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
3756
3829
  import { execFileSync as execFileSync2 } from "child_process";
3757
3830
  import YAML from "yaml";
3758
3831
 
3759
3832
  // src/indexer/program.ts
3760
3833
  import ts from "typescript";
3761
- import { join as join7, dirname } from "path";
3834
+ import { join as join8, dirname } from "path";
3762
3835
  function createTsProgram(options) {
3763
3836
  const projectRoot = options.projectRoot;
3764
3837
  const configPath = options.tsconfigPath ?? ts.findConfigFile(projectRoot, ts.sys.fileExists, "tsconfig.json");
@@ -3782,8 +3855,8 @@ function createTsProgram(options) {
3782
3855
  for (const ref of config.references) {
3783
3856
  const refRelPath = typeof ref === "string" ? ref : ref.path;
3784
3857
  if (!refRelPath) continue;
3785
- const refFullPath = join7(dirname(configPath), refRelPath);
3786
- const refConfigPath = refFullPath.endsWith(".json") ? refFullPath : join7(refFullPath, "tsconfig.json");
3858
+ const refFullPath = join8(dirname(configPath), refRelPath);
3859
+ const refConfigPath = refFullPath.endsWith(".json") ? refFullPath : join8(refFullPath, "tsconfig.json");
3787
3860
  if (ts.sys.fileExists(refConfigPath)) {
3788
3861
  const refConfig = ts.readConfigFile(refConfigPath, ts.sys.readFile);
3789
3862
  const rc = refConfig.config;
@@ -3811,7 +3884,7 @@ function createTsProgram(options) {
3811
3884
  const parsed = ts.parseJsonConfigFileContent(
3812
3885
  configFile.config,
3813
3886
  ts.sys,
3814
- join7(projectRoot),
3887
+ join8(projectRoot),
3815
3888
  { noEmit: true },
3816
3889
  resolvedConfigPath
3817
3890
  );
@@ -3821,7 +3894,7 @@ function createTsProgram(options) {
3821
3894
  });
3822
3895
  const checker = program.getTypeChecker();
3823
3896
  const sourceFiles = program.getSourceFiles().filter(
3824
- (sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules")
3897
+ (sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules") && !sf.fileName.includes(".next/") && !sf.fileName.includes(".next\\")
3825
3898
  );
3826
3899
  return { program, checker, sourceFiles, projectRoot };
3827
3900
  }
@@ -4579,8 +4652,8 @@ function writeComponents(db, components) {
4579
4652
  }
4580
4653
 
4581
4654
  // src/indexer/route-analyzer.ts
4582
- import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "fs";
4583
- import { join as join8, relative as relative3 } from "path";
4655
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync } from "fs";
4656
+ import { join as join9, relative as relative3 } from "path";
4584
4657
  var FILE_KIND_MAP = {
4585
4658
  "page": "page",
4586
4659
  "layout": "layout",
@@ -4591,15 +4664,22 @@ var FILE_KIND_MAP = {
4591
4664
  };
4592
4665
  var TS_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"];
4593
4666
  function analyzeRoutes(projectRoot, db, service = "main") {
4594
- const appDir = join8(projectRoot, "app");
4595
- if (!existsSync3(appDir) || !statSync(appDir).isDirectory()) {
4667
+ let appDir = join9(projectRoot, "app");
4668
+ try {
4669
+ if (!existsSync4(appDir) || !statSync(appDir).isDirectory()) {
4670
+ appDir = join9(projectRoot, "src", "app");
4671
+ if (!existsSync4(appDir) || !statSync(appDir).isDirectory()) {
4672
+ return 0;
4673
+ }
4674
+ }
4675
+ } catch {
4596
4676
  return 0;
4597
4677
  }
4598
4678
  const routes = [];
4599
4679
  const layoutStack = [];
4600
4680
  for (const ext of TS_EXTENSIONS) {
4601
- const middlewarePath = join8(projectRoot, `middleware${ext}`);
4602
- if (existsSync3(middlewarePath)) {
4681
+ const middlewarePath = join9(projectRoot, `middleware${ext}`);
4682
+ if (existsSync4(middlewarePath)) {
4603
4683
  routes.push({
4604
4684
  id: `route::middleware`,
4605
4685
  routePath: "/",
@@ -4619,15 +4699,15 @@ function analyzeRoutes(projectRoot, db, service = "main") {
4619
4699
  function walkAppDir(dir, routePath, routes, layoutStack, service, projectRoot) {
4620
4700
  let entries;
4621
4701
  try {
4622
- entries = readdirSync2(dir);
4702
+ entries = readdirSync3(dir);
4623
4703
  } catch {
4624
4704
  return;
4625
4705
  }
4626
4706
  const currentLayout = layoutStack.length > 0 ? layoutStack[layoutStack.length - 1] : null;
4627
4707
  for (const [convention, kind] of Object.entries(FILE_KIND_MAP)) {
4628
4708
  for (const ext of TS_EXTENSIONS) {
4629
- const filePath = join8(dir, `${convention}${ext}`);
4630
- if (existsSync3(filePath)) {
4709
+ const filePath = join9(dir, `${convention}${ext}`);
4710
+ if (existsSync4(filePath)) {
4631
4711
  const relPath = relative3(projectRoot, dir);
4632
4712
  const routeId = `route::${relPath}/${convention}`;
4633
4713
  const route = {
@@ -4651,7 +4731,7 @@ function walkAppDir(dir, routePath, routes, layoutStack, service, projectRoot) {
4651
4731
  }
4652
4732
  }
4653
4733
  for (const entry of entries.sort()) {
4654
- const fullPath = join8(dir, entry);
4734
+ const fullPath = join9(dir, entry);
4655
4735
  try {
4656
4736
  if (!statSync(fullPath).isDirectory()) continue;
4657
4737
  } catch {
@@ -4817,16 +4897,16 @@ function writeDependencies(db, dependencies) {
4817
4897
 
4818
4898
  // src/indexer/dotnet-indexer.ts
4819
4899
  import { execFileSync } from "child_process";
4820
- import { resolve, join as join9, dirname as dirname2, relative as relative4, basename } from "path";
4821
- import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4, accessSync, constants } from "fs";
4900
+ import { resolve, join as join10, dirname as dirname2, relative as relative4, basename } from "path";
4901
+ import { readdirSync as readdirSync4, readFileSync as readFileSync3, existsSync as existsSync5, accessSync, constants } from "fs";
4822
4902
  import { fileURLToPath } from "url";
4823
4903
  function findDotnetProject(projectRoot) {
4824
4904
  try {
4825
- const entries = readdirSync3(projectRoot);
4905
+ const entries = readdirSync4(projectRoot);
4826
4906
  const sln = entries.find((e) => e.endsWith(".sln"));
4827
- if (sln) return join9(projectRoot, sln);
4907
+ if (sln) return join10(projectRoot, sln);
4828
4908
  const csproj = entries.find((e) => e.endsWith(".csproj"));
4829
- if (csproj) return join9(projectRoot, csproj);
4909
+ if (csproj) return join10(projectRoot, csproj);
4830
4910
  return null;
4831
4911
  } catch {
4832
4912
  return null;
@@ -4841,8 +4921,8 @@ function parseSolutionProjects(slnPath) {
4841
4921
  while ((match = projectPattern.exec(content)) !== null) {
4842
4922
  const name = match[1];
4843
4923
  const relativeCsprojPath = match[2].replace(/\\/g, "/");
4844
- const fullCsprojPath = resolve(join9(slnDir, relativeCsprojPath));
4845
- if (!existsSync4(fullCsprojPath)) continue;
4924
+ const fullCsprojPath = resolve(join10(slnDir, relativeCsprojPath));
4925
+ if (!existsSync5(fullCsprojPath)) continue;
4846
4926
  const projectDir = relative4(slnDir, dirname2(fullCsprojPath)).replace(/\\/g, "/") || ".";
4847
4927
  const isTestProject = /[.\x2d]tests?$/i.test(name) || /[.\x2d](unit|integration|functional|e2e)tests?$/i.test(name);
4848
4928
  projects.push({
@@ -4878,7 +4958,7 @@ function hasGlobalTool() {
4878
4958
  if (!dir) continue;
4879
4959
  for (const ext of extensions) {
4880
4960
  try {
4881
- accessSync(join9(dir, `${name}${ext}`), constants.X_OK);
4961
+ accessSync(join10(dir, `${name}${ext}`), constants.X_OK);
4882
4962
  return true;
4883
4963
  } catch {
4884
4964
  continue;
@@ -4895,7 +4975,7 @@ function resolveIndexerProject() {
4895
4975
  resolve(currentDir2, "../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj")
4896
4976
  ];
4897
4977
  for (const candidate of candidates) {
4898
- if (existsSync4(candidate)) return candidate;
4978
+ if (existsSync5(candidate)) return candidate;
4899
4979
  }
4900
4980
  return null;
4901
4981
  }
@@ -5037,7 +5117,7 @@ function indexDotnetProjectRoslyn(db, options) {
5037
5117
 
5038
5118
  // src/indexer/csharp/indexer.ts
5039
5119
  import { readFileSync as readFileSync4 } from "fs";
5040
- import { join as join10 } from "path";
5120
+ import { join as join11 } from "path";
5041
5121
  import { globbySync } from "globby";
5042
5122
 
5043
5123
  // src/indexer/csharp/parser.ts
@@ -5889,7 +5969,7 @@ async function indexCSharpTreeSitter(db, options) {
5889
5969
  for (const filePath of csFiles) {
5890
5970
  const relPath = filePath.replace(/\\/g, "/");
5891
5971
  currentPaths.add(relPath);
5892
- const fullPath = join10(projectRoot, relPath);
5972
+ const fullPath = join11(projectRoot, relPath);
5893
5973
  const content = readFileSync4(fullPath, "utf-8");
5894
5974
  const hash = hashContent(content);
5895
5975
  const tree = parseCSharp(content);
@@ -5965,12 +6045,12 @@ async function indexCSharpTreeSitter(db, options) {
5965
6045
  }
5966
6046
 
5967
6047
  // src/indexer/package-deps.ts
5968
- import { join as join11 } from "path";
5969
- import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "fs";
6048
+ import { join as join12 } from "path";
6049
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5 } from "fs";
5970
6050
  function indexPackageDependencies(db, projectRoot, service = "main") {
5971
6051
  const deps = [];
5972
- const pkgJsonPath = join11(projectRoot, "package.json");
5973
- if (existsSync5(pkgJsonPath)) {
6052
+ const pkgJsonPath = join12(projectRoot, "package.json");
6053
+ if (existsSync6(pkgJsonPath)) {
5974
6054
  deps.push(...parsePackageJson(pkgJsonPath));
5975
6055
  }
5976
6056
  const csprojFiles = findCsprojFiles(projectRoot);
@@ -6032,10 +6112,10 @@ function findCsprojFiles(dir, maxDepth = 4) {
6032
6112
  function walk(currentDir2, depth) {
6033
6113
  if (depth > maxDepth) return;
6034
6114
  try {
6035
- const entries = readdirSync4(currentDir2, { withFileTypes: true });
6115
+ const entries = readdirSync5(currentDir2, { withFileTypes: true });
6036
6116
  for (const entry of entries) {
6037
6117
  if (entry.name === "bin" || entry.name === "obj" || entry.name === "node_modules" || entry.name === ".git") continue;
6038
- const fullPath = join11(currentDir2, entry.name);
6118
+ const fullPath = join12(currentDir2, entry.name);
6039
6119
  if (entry.isFile() && entry.name.endsWith(".csproj")) {
6040
6120
  results.push(fullPath);
6041
6121
  } else if (entry.isDirectory()) {
@@ -6051,10 +6131,10 @@ function findCsprojFiles(dir, maxDepth = 4) {
6051
6131
 
6052
6132
  // src/config/loader.ts
6053
6133
  import { readFileSync as readFileSync6 } from "fs";
6054
- import { join as join12 } from "path";
6134
+ import { join as join13 } from "path";
6055
6135
  import yaml from "yaml";
6056
6136
  function loadConfig(projectRoot) {
6057
- const configPath = join12(projectRoot, ".arcbridge", "config.yaml");
6137
+ const configPath = join13(projectRoot, ".arcbridge", "config.yaml");
6058
6138
  try {
6059
6139
  const raw = readFileSync6(configPath, "utf-8");
6060
6140
  const parsed = ArcBridgeConfigSchema.safeParse(yaml.parse(raw));
@@ -6073,8 +6153,8 @@ function loadConfig(projectRoot) {
6073
6153
 
6074
6154
  // src/indexer/index.ts
6075
6155
  function detectProjectLanguage(projectRoot) {
6076
- if (existsSync6(join13(projectRoot, "tsconfig.json"))) return "typescript";
6077
- if (existsSync6(join13(projectRoot, "package.json"))) return "typescript";
6156
+ if (existsSync7(join14(projectRoot, "tsconfig.json"))) return "typescript";
6157
+ if (existsSync7(join14(projectRoot, "package.json"))) return "typescript";
6078
6158
  if (findDotnetProject(projectRoot)) return "csharp";
6079
6159
  return "typescript";
6080
6160
  }
@@ -6102,7 +6182,7 @@ function resolveCSharpBackend(projectRoot) {
6102
6182
  let setting = config?.indexing?.csharp_indexer;
6103
6183
  if (!setting && error) {
6104
6184
  try {
6105
- const raw = readFileSync7(join13(projectRoot, ".arcbridge", "config.yaml"), "utf-8");
6185
+ const raw = readFileSync7(join14(projectRoot, ".arcbridge", "config.yaml"), "utf-8");
6106
6186
  const parsed = YAML.parse(raw);
6107
6187
  const rawSetting = parsed?.indexing?.csharp_indexer;
6108
6188
  if (rawSetting === "roslyn" || rawSetting === "tree-sitter") {
@@ -6478,18 +6558,18 @@ function safeParseJson(value, fallback) {
6478
6558
  }
6479
6559
 
6480
6560
  // src/sync/yaml-writer.ts
6481
- import { join as join14 } from "path";
6482
- import { existsSync as existsSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
6561
+ import { join as join15 } from "path";
6562
+ import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
6483
6563
  import { parse as parse2, stringify as stringify4 } from "yaml";
6484
6564
  function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
6485
- const taskPath = join14(
6565
+ const taskPath = join15(
6486
6566
  projectRoot,
6487
6567
  ".arcbridge",
6488
6568
  "plan",
6489
6569
  "tasks",
6490
6570
  `${phaseId}.yaml`
6491
6571
  );
6492
- if (!existsSync7(taskPath)) return;
6572
+ if (!existsSync8(taskPath)) return;
6493
6573
  const raw = readFileSync8(taskPath, "utf-8");
6494
6574
  const parsed = parse2(raw);
6495
6575
  const result = TaskFileSchema.safeParse(parsed);
@@ -6506,11 +6586,11 @@ function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
6506
6586
  writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
6507
6587
  }
6508
6588
  function addTaskToYaml(projectRoot, phaseId, task) {
6509
- const tasksDir = join14(projectRoot, ".arcbridge", "plan", "tasks");
6510
- const taskPath = join14(tasksDir, `${phaseId}.yaml`);
6589
+ const tasksDir = join15(projectRoot, ".arcbridge", "plan", "tasks");
6590
+ const taskPath = join15(tasksDir, `${phaseId}.yaml`);
6511
6591
  mkdirSync5(tasksDir, { recursive: true });
6512
6592
  let taskFile;
6513
- if (existsSync7(taskPath)) {
6593
+ if (existsSync8(taskPath)) {
6514
6594
  const raw = readFileSync8(taskPath, "utf-8");
6515
6595
  const parsed = parse2(raw);
6516
6596
  const result = TaskFileSchema.safeParse(parsed);
@@ -6528,13 +6608,13 @@ function addTaskToYaml(projectRoot, phaseId, task) {
6528
6608
  writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
6529
6609
  }
6530
6610
  function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
6531
- const phasesPath = join14(
6611
+ const phasesPath = join15(
6532
6612
  projectRoot,
6533
6613
  ".arcbridge",
6534
6614
  "plan",
6535
6615
  "phases.yaml"
6536
6616
  );
6537
- if (!existsSync7(phasesPath)) return;
6617
+ if (!existsSync8(phasesPath)) return;
6538
6618
  const raw = readFileSync8(phasesPath, "utf-8");
6539
6619
  const parsed = parse2(raw);
6540
6620
  const result = PhasesFileSchema.safeParse(parsed);
@@ -6547,14 +6627,14 @@ function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
6547
6627
  if (completedAt) phase.completed_at = completedAt;
6548
6628
  writeFileSync5(phasesPath, stringify4(phasesFile), "utf-8");
6549
6629
  }
6550
- function syncScenarioToYaml(projectRoot, scenarioId, status) {
6551
- const scenarioPath = join14(
6630
+ function syncScenarioToYaml(projectRoot, scenarioId, status, linkedTests, verification) {
6631
+ const scenarioPath = join15(
6552
6632
  projectRoot,
6553
6633
  ".arcbridge",
6554
6634
  "arc42",
6555
6635
  "10-quality-scenarios.yaml"
6556
6636
  );
6557
- if (!existsSync7(scenarioPath)) return;
6637
+ if (!existsSync8(scenarioPath)) return;
6558
6638
  const raw = readFileSync8(scenarioPath, "utf-8");
6559
6639
  const parsed = parse2(raw);
6560
6640
  const result = QualityScenariosFileSchema.safeParse(parsed);
@@ -6563,14 +6643,55 @@ function syncScenarioToYaml(projectRoot, scenarioId, status) {
6563
6643
  const scenario = scenariosFile.scenarios.find((s) => s.id === scenarioId);
6564
6644
  if (!scenario) return;
6565
6645
  scenario.status = status;
6646
+ if (linkedTests) {
6647
+ scenario.linked_tests = linkedTests;
6648
+ }
6649
+ if (verification) {
6650
+ scenario.verification = verification;
6651
+ }
6566
6652
  writeFileSync5(scenarioPath, stringify4(scenariosFile), "utf-8");
6567
6653
  }
6654
+ function deleteTaskFromYaml(projectRoot, phaseId, taskId) {
6655
+ try {
6656
+ const taskPath = join15(
6657
+ projectRoot,
6658
+ ".arcbridge",
6659
+ "plan",
6660
+ "tasks",
6661
+ `${phaseId}.yaml`
6662
+ );
6663
+ if (!existsSync8(taskPath)) {
6664
+ return { success: true };
6665
+ }
6666
+ const raw = readFileSync8(taskPath, "utf-8");
6667
+ const result = TaskFileSchema.safeParse(parse2(raw));
6668
+ if (!result.success) {
6669
+ return {
6670
+ success: false,
6671
+ warning: `Could not parse ${phaseId}.yaml \u2014 task may reappear after reindex`
6672
+ };
6673
+ }
6674
+ const taskFile = result.data;
6675
+ const before = taskFile.tasks.length;
6676
+ taskFile.tasks = taskFile.tasks.filter((t) => t.id !== taskId);
6677
+ if (taskFile.tasks.length === before) {
6678
+ return { success: true };
6679
+ }
6680
+ writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
6681
+ return { success: true };
6682
+ } catch (err) {
6683
+ return {
6684
+ success: false,
6685
+ warning: `YAML update failed: ${err instanceof Error ? err.message : String(err)} \u2014 task may reappear after reindex`
6686
+ };
6687
+ }
6688
+ }
6568
6689
 
6569
6690
  // src/sync/task-inference.ts
6570
6691
  function inferTaskStatuses(db, phaseId) {
6571
6692
  const results = [];
6572
6693
  const tasks = db.prepare(
6573
- "SELECT id, title, status, building_block, quality_scenarios, acceptance_criteria FROM tasks WHERE phase_id = ? AND status != 'done'"
6694
+ "SELECT id, title, status, building_block, quality_scenarios, acceptance_criteria FROM tasks WHERE phase_id = ? AND status NOT IN ('done', 'cancelled')"
6574
6695
  ).all(phaseId);
6575
6696
  for (const task of tasks) {
6576
6697
  const inference = inferSingleTask(db, task);
@@ -6695,7 +6816,7 @@ function safeParseJson2(value, fallback) {
6695
6816
 
6696
6817
  // src/generators/sync-generator.ts
6697
6818
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
6698
- import { join as join15, dirname as dirname4 } from "path";
6819
+ import { join as join16, dirname as dirname4 } from "path";
6699
6820
 
6700
6821
  // src/templates/sync/claude-skill.ts
6701
6822
  function claudeSkillTemplate(config) {
@@ -6873,20 +6994,20 @@ To enable automatic sync:
6873
6994
  function generateSyncFiles(targetDir, config) {
6874
6995
  const generated = [];
6875
6996
  const action = githubActionTemplate(config);
6876
- const actionPath = join15(targetDir, action.relativePath);
6997
+ const actionPath = join16(targetDir, action.relativePath);
6877
6998
  mkdirSync6(dirname4(actionPath), { recursive: true });
6878
6999
  writeFileSync6(actionPath, action.content, "utf-8");
6879
7000
  generated.push(action.relativePath);
6880
7001
  if (config.platforms.includes("claude")) {
6881
7002
  const skill = claudeSkillTemplate(config);
6882
- const skillPath = join15(targetDir, skill.relativePath);
7003
+ const skillPath = join16(targetDir, skill.relativePath);
6883
7004
  mkdirSync6(dirname4(skillPath), { recursive: true });
6884
7005
  writeFileSync6(skillPath, skill.content, "utf-8");
6885
7006
  generated.push(skill.relativePath);
6886
7007
  }
6887
7008
  if (config.platforms.includes("copilot")) {
6888
7009
  const hook = copilotHookTemplate(config);
6889
- const hookPath = join15(targetDir, hook.relativePath);
7010
+ const hookPath = join16(targetDir, hook.relativePath);
6890
7011
  mkdirSync6(dirname4(hookPath), { recursive: true });
6891
7012
  writeFileSync6(hookPath, hook.content, "utf-8");
6892
7013
  generated.push(hook.relativePath);
@@ -6896,7 +7017,7 @@ function generateSyncFiles(targetDir, config) {
6896
7017
 
6897
7018
  // src/metrics/activity.ts
6898
7019
  import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
6899
- import { join as join16 } from "path";
7020
+ import { join as join17 } from "path";
6900
7021
  function insertActivity(db, params) {
6901
7022
  const totalTokens = params.totalTokens ?? (params.inputTokens != null && params.outputTokens != null ? params.inputTokens + params.outputTokens : null);
6902
7023
  const stmt = db.prepare(`
@@ -7054,7 +7175,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
7054
7175
  });
7055
7176
  const rows = result.rows;
7056
7177
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
7057
- const dir = join16(projectRoot, ".arcbridge", "metrics");
7178
+ const dir = join17(projectRoot, ".arcbridge", "metrics");
7058
7179
  mkdirSync7(dir, { recursive: true });
7059
7180
  let content;
7060
7181
  let filename;
@@ -7151,7 +7272,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
7151
7272
  break;
7152
7273
  }
7153
7274
  }
7154
- const filePath = join16(dir, filename);
7275
+ const filePath = join17(dir, filename);
7155
7276
  writeFileSync7(filePath, content, "utf-8");
7156
7277
  return filePath;
7157
7278
  }
@@ -7219,6 +7340,8 @@ function esc(val) {
7219
7340
 
7220
7341
  // src/git/helpers.ts
7221
7342
  import { execFileSync as execFileSync3 } from "child_process";
7343
+ import { realpathSync } from "fs";
7344
+ import { relative as relative6 } from "path";
7222
7345
  function resolveRef(projectRoot, since, db) {
7223
7346
  switch (since) {
7224
7347
  case "last-commit":
@@ -7310,6 +7433,33 @@ function setSyncCommit(db, key, sha) {
7310
7433
  "INSERT OR REPLACE INTO arcbridge_meta (key, value) VALUES (?, ?)"
7311
7434
  ).run(key, sha);
7312
7435
  }
7436
+ function getRepoRoot(projectRoot) {
7437
+ try {
7438
+ return execFileSync3(
7439
+ "git",
7440
+ ["rev-parse", "--show-toplevel"],
7441
+ { cwd: projectRoot, encoding: "utf-8", timeout: 5e3 }
7442
+ ).trim();
7443
+ } catch {
7444
+ return null;
7445
+ }
7446
+ }
7447
+ function scopeToProject(changedFiles, projectRoot) {
7448
+ const repoRoot = getRepoRoot(projectRoot);
7449
+ if (!repoRoot) return changedFiles;
7450
+ let projectRel;
7451
+ try {
7452
+ projectRel = relative6(realpathSync(repoRoot), realpathSync(projectRoot));
7453
+ } catch {
7454
+ return changedFiles;
7455
+ }
7456
+ if (!projectRel || projectRel === "." || projectRel.startsWith("..")) return changedFiles;
7457
+ const prefix = projectRel.replace(/\\/g, "/");
7458
+ return changedFiles.filter((f) => f.path.startsWith(prefix + "/") || f.path === prefix).map((f) => ({
7459
+ ...f,
7460
+ path: f.path.startsWith(prefix + "/") ? f.path.slice(prefix.length + 1) : f.path
7461
+ }));
7462
+ }
7313
7463
  function parseStatusCode(code) {
7314
7464
  switch (code.charAt(0)) {
7315
7465
  case "A":
@@ -7325,7 +7475,7 @@ function parseStatusCode(code) {
7325
7475
 
7326
7476
  // src/testing/runner.ts
7327
7477
  import { execFileSync as execFileSync4 } from "child_process";
7328
- import { existsSync as existsSync8 } from "fs";
7478
+ import { existsSync as existsSync9 } from "fs";
7329
7479
  import { resolve as resolve3 } from "path";
7330
7480
  function verifyScenarios(db, projectRoot, options) {
7331
7481
  const results = [];
@@ -7363,7 +7513,7 @@ function verifyScenarios(db, projectRoot, options) {
7363
7513
  }
7364
7514
  if (testPaths.length === 0) continue;
7365
7515
  const missingPaths = testPaths.filter(
7366
- (tp) => !existsSync8(resolve3(projectRoot, tp))
7516
+ (tp) => !existsSync9(resolve3(projectRoot, tp))
7367
7517
  );
7368
7518
  if (missingPaths.length === testPaths.length) {
7369
7519
  results.push({
@@ -7378,7 +7528,7 @@ function verifyScenarios(db, projectRoot, options) {
7378
7528
  continue;
7379
7529
  }
7380
7530
  const existingPaths = testPaths.filter(
7381
- (tp) => existsSync8(resolve3(projectRoot, tp))
7531
+ (tp) => existsSync9(resolve3(projectRoot, tp))
7382
7532
  );
7383
7533
  const start = Date.now();
7384
7534
  let passed;
@@ -7432,21 +7582,21 @@ ${output}`;
7432
7582
  }
7433
7583
 
7434
7584
  // src/roles/loader.ts
7435
- import { readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
7436
- import { join as join17 } from "path";
7585
+ import { readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
7586
+ import { join as join18 } from "path";
7437
7587
  import matter4 from "gray-matter";
7438
7588
  function loadRoles(projectRoot) {
7439
- const agentsDir = join17(projectRoot, ".arcbridge", "agents");
7589
+ const agentsDir = join18(projectRoot, ".arcbridge", "agents");
7440
7590
  const roles = [];
7441
7591
  const errors = [];
7442
7592
  let files;
7443
7593
  try {
7444
- files = readdirSync5(agentsDir).filter((f) => f.endsWith(".md")).sort();
7594
+ files = readdirSync6(agentsDir).filter((f) => f.endsWith(".md")).sort();
7445
7595
  } catch {
7446
7596
  return { roles: [], errors: [`Agent directory not found: ${agentsDir}`] };
7447
7597
  }
7448
7598
  for (const file of files) {
7449
- const filePath = join17(agentsDir, file);
7599
+ const filePath = join18(agentsDir, file);
7450
7600
  try {
7451
7601
  const raw = readFileSync9(filePath, "utf-8");
7452
7602
  const parsed = matter4(raw);
@@ -7473,7 +7623,7 @@ function loadRole(projectRoot, roleId) {
7473
7623
  if (!/^[a-z0-9-]+$/.test(roleId)) {
7474
7624
  return { role: null, error: `Invalid role ID: "${roleId}" (must be kebab-case)` };
7475
7625
  }
7476
- const filePath = join17(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
7626
+ const filePath = join18(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
7477
7627
  try {
7478
7628
  const raw = readFileSync9(filePath, "utf-8");
7479
7629
  const parsed = matter4(raw);
@@ -7512,6 +7662,7 @@ export {
7512
7662
  TaskSchema,
7513
7663
  addTaskToYaml,
7514
7664
  applyInferences,
7665
+ deleteTaskFromYaml,
7515
7666
  detectDrift,
7516
7667
  detectProjectLanguage,
7517
7668
  discoverDotnetServices,
@@ -7540,6 +7691,7 @@ export {
7540
7691
  queryMetrics,
7541
7692
  refreshFromDocs,
7542
7693
  resolveRef,
7694
+ scopeToProject,
7543
7695
  setSyncCommit,
7544
7696
  suppressSqliteWarning,
7545
7697
  syncPhaseToYaml,