@arcbridge/mcp-server 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
@@ -22,7 +22,7 @@ function createContext() {
22
22
  // src/tools/init-project.ts
23
23
  import { z } from "zod";
24
24
  import { join } from "path";
25
- import { existsSync } from "fs";
25
+ import { existsSync, writeFileSync } from "fs";
26
26
  import {
27
27
  generateConfig,
28
28
  generateArc42,
@@ -44,9 +44,12 @@ function registerInitProject(server, ctx) {
44
44
  "Project template: nextjs-app-router (Next.js with App Router, SSR/SSG), react-vite (React SPA with Vite, client-only), api-service (Node.js API with Express/Fastify/Hono), dotnet-webapi (ASP.NET Core Web API, C#)"
45
45
  ),
46
46
  features: z.array(z.enum(["auth", "database", "api"])).default([]).describe("Features to scaffold"),
47
- quality_priorities: z.array(z.string()).default(["security", "performance", "accessibility"]).describe("Quality priorities in order"),
47
+ quality_priorities: z.array(z.string()).default(["security", "performance", "accessibility", "maintainability"]).describe("Quality priorities in order"),
48
48
  platforms: z.array(z.string()).default(["claude"]).describe("Target platforms for agent config generation"),
49
- target_dir: z.string().describe("Absolute path to the target project directory")
49
+ target_dir: z.string().describe("Absolute path to the target project directory"),
50
+ spec: z.string().optional().describe(
51
+ "Project specification or requirements text. Saved to .arcbridge/spec.md and referenced by agents for context. Can be a description, user stories, or any text that defines what the project should do."
52
+ )
50
53
  },
51
54
  async (params) => {
52
55
  const targetDir = params.target_dir;
@@ -83,6 +86,9 @@ function registerInitProject(server, ctx) {
83
86
  const { db: recoveredDb } = generateDatabase(targetDir, recoverInput);
84
87
  ctx.db = recoveredDb;
85
88
  ctx.projectRoot = targetDir;
89
+ if (params.spec) {
90
+ writeFileSync(join(targetDir, ".arcbridge", "spec.md"), params.spec, "utf-8");
91
+ }
86
92
  return {
87
93
  content: [
88
94
  {
@@ -121,6 +127,13 @@ Use \`arcbridge_get_project_status\` to see the current state.`
121
127
  platformWarnings.push(`Platform '${platform}': ${msg}`);
122
128
  }
123
129
  }
130
+ if (params.spec) {
131
+ writeFileSync(
132
+ join(targetDir, ".arcbridge", "spec.md"),
133
+ params.spec,
134
+ "utf-8"
135
+ );
136
+ }
124
137
  let indexResult = null;
125
138
  try {
126
139
  const result = await indexProject(db, { projectRoot: targetDir });
@@ -143,6 +156,7 @@ Use \`arcbridge_get_project_status\` to see the current state.`
143
156
  `**Template:** ${input.template}`,
144
157
  `**Features:** ${input.features.length > 0 ? input.features.join(", ") : "none"}`,
145
158
  `**Platforms:** ${params.platforms.join(", ")}`,
159
+ ...params.spec ? [`**Spec:** saved to .arcbridge/spec.md`] : [],
146
160
  "",
147
161
  "## Created",
148
162
  "",
@@ -175,12 +189,15 @@ Use \`arcbridge_get_project_status\` to see the current state.`
175
189
  ...allWarnings.map((w) => `- ${w}`)
176
190
  ] : [],
177
191
  "",
178
- "## Next Steps",
192
+ "## Next Steps \u2014 PLAN FIRST, BUILD SECOND",
193
+ "",
194
+ "**Do not start implementing yet.** First plan the full project roadmap:",
179
195
  "",
180
- "1. **Review the phase plan** \u2014 run `arcbridge_get_phase_plan` to see all phases and their tasks",
181
- "2. **Replace example tasks in Phase 2+** \u2014 Phase 0-1 tasks are ready to use, but Phase 2+ tasks are examples only. Replace them with real tasks from the project's requirements.",
182
- "3. **Activate the architect role** \u2014 run `arcbridge_activate_role` with role `architect` to get full architectural context",
183
- "4. **Start working** \u2014 use `arcbridge_get_current_tasks` to see what to do next",
196
+ "1. **Activate the architect role** \u2014 run `arcbridge_activate_role` with role `architect`",
197
+ "2. **Review the spec** \u2014 read `.arcbridge/spec.md` (if provided) and understand the full scope",
198
+ "3. **Review and adapt the phase plan** \u2014 run `arcbridge_get_phase_plan`. The 4 generated phases are a starting template. For larger projects, add more phases by editing `.arcbridge/plan/phases.yaml` and running `arcbridge_reindex`.",
199
+ "4. **Plan real tasks for ALL phases** \u2014 Phase 0-1 tasks are ready to use. Phase 2+ tasks are examples only \u2014 replace them with real tasks from the project's requirements using `arcbridge_create_task`.",
200
+ "5. **Only then start building** \u2014 use `arcbridge_get_current_tasks` to see what to do next",
184
201
  "",
185
202
  "Use `arcbridge_get_project_status` to see the full project status."
186
203
  ];
@@ -803,6 +820,23 @@ function registerGetCurrentTasks(server, ctx) {
803
820
  lines.push("");
804
821
  }
805
822
  }
823
+ const emptyFuturePhases = db.prepare(`
824
+ SELECT p.id, p.name, p.phase_number FROM phases p
825
+ WHERE p.status IN ('planned', 'in-progress')
826
+ AND p.id != ?
827
+ AND NOT EXISTS (SELECT 1 FROM tasks t WHERE t.phase_id = p.id)
828
+ ORDER BY p.phase_number
829
+ `).all(currentPhase.id);
830
+ if (emptyFuturePhases.length > 0) {
831
+ lines.push(
832
+ "---",
833
+ "",
834
+ "**Warning:** The following phases have no tasks yet:",
835
+ ...emptyFuturePhases.map((p) => `- Phase ${p.phase_number}: ${p.name} (\`${p.id}\`)`),
836
+ "",
837
+ "Use `arcbridge_create_task` to plan tasks before reaching these phases."
838
+ );
839
+ }
806
840
  return {
807
841
  content: [{ type: "text", text: lines.join("\n") }]
808
842
  };
@@ -856,7 +890,7 @@ function registerUpdateTask(server, ctx) {
856
890
  {
857
891
  target_dir: z8.string().describe("Absolute path to the project directory"),
858
892
  task_id: z8.string().describe("Task ID (e.g., 'task-0.1-init-nextjs')"),
859
- status: z8.enum(["in-progress", "done", "blocked"]).describe("New status"),
893
+ status: z8.enum(["in-progress", "done", "blocked", "cancelled"]).describe("New status. Use 'cancelled' for tasks that are no longer relevant."),
860
894
  notes: z8.string().optional().describe("Optional notes about the status change")
861
895
  },
862
896
  async (params) => {
@@ -881,10 +915,9 @@ function registerUpdateTask(server, ctx) {
881
915
  "UPDATE tasks SET status = ?, completed_at = ? WHERE id = ?"
882
916
  ).run(params.status, now, params.task_id);
883
917
  } else {
884
- db.prepare("UPDATE tasks SET status = ? WHERE id = ?").run(
885
- params.status,
886
- params.task_id
887
- );
918
+ db.prepare(
919
+ "UPDATE tasks SET status = ?, completed_at = NULL WHERE id = ?"
920
+ ).run(params.status, params.task_id);
888
921
  }
889
922
  syncTaskToYaml(
890
923
  params.target_dir,
@@ -901,15 +934,15 @@ function registerUpdateTask(server, ctx) {
901
934
  if (params.notes) {
902
935
  lines.push("", `**Notes:** ${params.notes}`);
903
936
  }
904
- if (params.status === "done") {
937
+ if (params.status === "done" || params.status === "cancelled") {
905
938
  const phaseStats = db.prepare(
906
- "SELECT COUNT(*) as total, SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done FROM tasks WHERE phase_id = ?"
939
+ "SELECT SUM(CASE WHEN status != 'cancelled' THEN 1 ELSE 0 END) as total, SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done FROM tasks WHERE phase_id = ?"
907
940
  ).get(task.phase_id);
908
941
  lines.push(
909
942
  "",
910
943
  `**Phase progress:** ${phaseStats.done}/${phaseStats.total} tasks complete`
911
944
  );
912
- if (phaseStats.done === phaseStats.total) {
945
+ if (phaseStats.total > 0 && phaseStats.done === phaseStats.total) {
913
946
  lines.push(
914
947
  "",
915
948
  "All tasks in this phase are complete! The phase is ready to advance."
@@ -941,7 +974,9 @@ function registerCreateTask(server, ctx) {
941
974
  target_dir: z9.string().describe("Absolute path to the project directory"),
942
975
  phase_id: z9.string().describe("Phase ID to add the task to"),
943
976
  title: z9.string().min(1).describe("Task title"),
944
- building_block: z9.string().optional().describe("Building block this task belongs to"),
977
+ building_block: z9.string().min(1).optional().describe(
978
+ "Building block this task belongs to. Use `arcbridge_get_building_blocks` to see available blocks. If no suitable block exists, create one in `.arcbridge/arc42/05-building-blocks.md` and run `arcbridge_reindex` first."
979
+ ),
945
980
  quality_scenarios: z9.array(z9.string()).default([]).describe("Quality scenario IDs this task addresses"),
946
981
  acceptance_criteria: z9.array(z9.string()).default([]).describe("Acceptance criteria for this task")
947
982
  },
@@ -966,6 +1001,27 @@ function registerCreateTask(server, ctx) {
966
1001
  const slug = params.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
967
1002
  const taskId = `task-${phase.phase_number}.${taskNum}-${slug}`;
968
1003
  const now = (/* @__PURE__ */ new Date()).toISOString();
1004
+ const blockId = params.building_block ?? null;
1005
+ if (blockId) {
1006
+ const block = db.prepare("SELECT id FROM building_blocks WHERE id = ?").get(blockId);
1007
+ if (!block) {
1008
+ const available = db.prepare("SELECT id, name FROM building_blocks ORDER BY id").all();
1009
+ const blockList = available.length > 0 ? available.map((b) => ` - \`${b.id}\` (${b.name})`).join("\n") : " (none \u2014 run `arcbridge_reindex` to populate from arc42 docs)";
1010
+ return {
1011
+ content: [
1012
+ {
1013
+ type: "text",
1014
+ text: `Building block \`${blockId}\` not found.
1015
+
1016
+ **Available blocks:**
1017
+ ${blockList}
1018
+
1019
+ If you need a new block, add it to \`.arcbridge/arc42/05-building-blocks.md\` and run \`arcbridge_reindex\`, then retry.`
1020
+ }
1021
+ ]
1022
+ };
1023
+ }
1024
+ }
969
1025
  db.prepare(
970
1026
  "INSERT INTO tasks (id, phase_id, title, description, status, building_block, quality_scenarios, acceptance_criteria, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
971
1027
  ).run(
@@ -974,7 +1030,7 @@ function registerCreateTask(server, ctx) {
974
1030
  params.title,
975
1031
  null,
976
1032
  "todo",
977
- params.building_block ?? null,
1033
+ blockId,
978
1034
  JSON.stringify(params.quality_scenarios),
979
1035
  JSON.stringify(params.acceptance_criteria),
980
1036
  now
@@ -1015,16 +1071,49 @@ function registerCreateTask(server, ctx) {
1015
1071
  );
1016
1072
  }
1017
1073
 
1018
- // src/tools/get-relevant-adrs.ts
1074
+ // src/tools/delete-task.ts
1019
1075
  import { z as z10 } from "zod";
1076
+ import { deleteTaskFromYaml } from "@arcbridge/core";
1077
+ function registerDeleteTask(server, ctx) {
1078
+ server.tool(
1079
+ "arcbridge_delete_task",
1080
+ "Delete a task permanently. Use this to remove example/template tasks or duplicates. For tasks that were planned but are no longer relevant, prefer `arcbridge_update_task` with status 'cancelled' instead \u2014 this preserves the decision trail.",
1081
+ {
1082
+ target_dir: z10.string().describe("Absolute path to the project directory"),
1083
+ task_id: z10.string().describe("Task ID to delete")
1084
+ },
1085
+ async (params) => {
1086
+ const db = ensureDb(ctx, params.target_dir);
1087
+ if (!db) return notInitialized();
1088
+ const task = db.prepare("SELECT id, title, phase_id FROM tasks WHERE id = ?").get(params.task_id);
1089
+ if (!task) {
1090
+ return textResult(
1091
+ `Task '${params.task_id}' not found. Use \`arcbridge_get_current_tasks\` to see available tasks.`
1092
+ );
1093
+ }
1094
+ const yamlResult = deleteTaskFromYaml(params.target_dir, task.phase_id, params.task_id);
1095
+ db.prepare("DELETE FROM tasks WHERE id = ?").run(params.task_id);
1096
+ const msg = `Task **${task.id}** deleted: "${task.title}"`;
1097
+ if (yamlResult.warning) {
1098
+ return textResult(`${msg}
1099
+
1100
+ **Warning:** ${yamlResult.warning}`);
1101
+ }
1102
+ return textResult(msg);
1103
+ }
1104
+ );
1105
+ }
1106
+
1107
+ // src/tools/get-relevant-adrs.ts
1108
+ import { z as z11 } from "zod";
1020
1109
  function registerGetRelevantAdrs(server, ctx) {
1021
1110
  server.tool(
1022
1111
  "arcbridge_get_relevant_adrs",
1023
1112
  "Get architectural decision records (ADRs) relevant to a specific file path or building block.",
1024
1113
  {
1025
- target_dir: z10.string().describe("Absolute path to the project directory"),
1026
- file_path: z10.string().optional().describe("File path to find relevant ADRs for"),
1027
- building_block: z10.string().optional().describe("Building block ID to find relevant ADRs for")
1114
+ target_dir: z11.string().describe("Absolute path to the project directory"),
1115
+ file_path: z11.string().optional().describe("File path to find relevant ADRs for"),
1116
+ building_block: z11.string().optional().describe("Building block ID to find relevant ADRs for")
1028
1117
  },
1029
1118
  async (params) => {
1030
1119
  const db = ensureDb(ctx, params.target_dir);
@@ -1108,17 +1197,17 @@ function formatAdrs(adrs, title) {
1108
1197
  }
1109
1198
 
1110
1199
  // src/tools/reindex.ts
1111
- import { z as z11 } from "zod";
1200
+ import { z as z12 } from "zod";
1112
1201
  import { indexProject as indexProject2, refreshFromDocs as refreshFromDocs4 } from "@arcbridge/core";
1113
1202
  function registerReindex(server, ctx) {
1114
1203
  server.tool(
1115
1204
  "arcbridge_reindex",
1116
1205
  "Re-index the project: refreshes architecture docs from arc42/YAML files, then reindexes code symbols (TypeScript & C#/.NET). This is the first step of the sync pipeline \u2014 use it to pick up manual doc edits and code changes.",
1117
1206
  {
1118
- target_dir: z11.string().describe("Absolute path to the project directory"),
1119
- tsconfig_path: z11.string().optional().describe("Override tsconfig.json path (default: auto-detect). Only used for TypeScript projects."),
1120
- service: z11.string().optional().describe("Service name for monorepo projects (default: 'main')"),
1121
- language: z11.enum(["typescript", "csharp", "auto"]).optional().describe("Project language. 'auto' detects from project files (default: 'auto')")
1207
+ target_dir: z12.string().describe("Absolute path to the project directory"),
1208
+ tsconfig_path: z12.string().optional().describe("Override tsconfig.json path (default: auto-detect). Only used for TypeScript projects."),
1209
+ service: z12.string().optional().describe("Service name for monorepo projects (default: 'main')"),
1210
+ language: z12.enum(["typescript", "csharp", "auto"]).optional().describe("Project language. 'auto' detects from project files (default: 'auto')")
1122
1211
  },
1123
1212
  async (params) => {
1124
1213
  const start = Date.now();
@@ -1133,9 +1222,13 @@ function registerReindex(server, ctx) {
1133
1222
  language: params.language
1134
1223
  });
1135
1224
  const lines = [
1136
- "# Indexing Complete",
1225
+ "# Reindex Complete",
1226
+ "",
1227
+ "## Architecture Docs",
1228
+ `- **Refreshed from docs:** building blocks, phases, tasks, quality scenarios, ADRs`,
1229
+ ...docWarnings.length > 0 ? [`- **Warnings:** ${docWarnings.join("; ")}`] : [],
1137
1230
  "",
1138
- `- **Docs refreshed:** ${docWarnings.length === 0 ? "OK" : docWarnings.join(", ")}`,
1231
+ "## Code Symbols",
1139
1232
  `- **Files processed:** ${result.filesProcessed}`,
1140
1233
  `- **Files skipped (unchanged):** ${result.filesSkipped}`,
1141
1234
  `- **Files removed:** ${result.filesRemoved}`,
@@ -1160,16 +1253,16 @@ function registerReindex(server, ctx) {
1160
1253
  }
1161
1254
 
1162
1255
  // src/tools/search-symbols.ts
1163
- import { z as z12 } from "zod";
1256
+ import { z as z13 } from "zod";
1164
1257
  function registerSearchSymbols(server, ctx) {
1165
1258
  server.tool(
1166
1259
  "arcbridge_search_symbols",
1167
1260
  "Search code symbols by name, kind, file path, or building block. Supports TypeScript and C#. Returns matching symbols with type signatures.",
1168
1261
  {
1169
- target_dir: z12.string().describe("Absolute path to the project directory"),
1170
- query: z12.string().optional().describe("Search term to match against symbol names"),
1171
- service: z12.string().optional().describe("Filter by service name (for multi-project solutions). Omit to search all services."),
1172
- kind: z12.enum([
1262
+ target_dir: z13.string().describe("Absolute path to the project directory"),
1263
+ query: z13.string().optional().describe("Search term to match against symbol names"),
1264
+ service: z13.string().optional().describe("Filter by service name (for multi-project solutions). Omit to search all services."),
1265
+ kind: z13.enum([
1173
1266
  "function",
1174
1267
  "class",
1175
1268
  "type",
@@ -1181,10 +1274,10 @@ function registerSearchSymbols(server, ctx) {
1181
1274
  "hook",
1182
1275
  "context"
1183
1276
  ]).optional().describe("Filter by symbol kind"),
1184
- file_path: z12.string().optional().describe("Filter by file path (prefix match)"),
1185
- is_exported: z12.boolean().optional().describe("Filter by export status"),
1186
- building_block: z12.string().optional().describe("Filter by building block ID (matches against code_paths)"),
1187
- limit: z12.number().int().min(1).max(200).default(50).describe("Maximum results to return (default: 50)")
1277
+ file_path: z13.string().optional().describe("Filter by file path (prefix match)"),
1278
+ is_exported: z13.boolean().optional().describe("Filter by export status"),
1279
+ building_block: z13.string().optional().describe("Filter by building block ID (matches against code_paths)"),
1280
+ limit: z13.number().int().min(1).max(200).default(50).describe("Maximum results to return (default: 50)")
1188
1281
  },
1189
1282
  async (params) => {
1190
1283
  const db = ensureDb(ctx, params.target_dir);
@@ -1271,7 +1364,7 @@ function registerSearchSymbols(server, ctx) {
1271
1364
  }
1272
1365
 
1273
1366
  // src/tools/get-symbol.ts
1274
- import { z as z13 } from "zod";
1367
+ import { z as z14 } from "zod";
1275
1368
  import { readFileSync, existsSync as existsSync3 } from "fs";
1276
1369
  import { join as join3 } from "path";
1277
1370
  function registerGetSymbol(server, ctx) {
@@ -1279,11 +1372,11 @@ function registerGetSymbol(server, ctx) {
1279
1372
  "arcbridge_get_symbol",
1280
1373
  "Get detailed information about a specific TypeScript symbol including its source code, type signature, and relationships.",
1281
1374
  {
1282
- target_dir: z13.string().describe("Absolute path to the project directory"),
1283
- symbol_id: z13.string().describe(
1375
+ target_dir: z14.string().describe("Absolute path to the project directory"),
1376
+ symbol_id: z14.string().describe(
1284
1377
  "Symbol ID (e.g. 'src/utils.ts::formatName#function')"
1285
1378
  ),
1286
- include_source: z13.boolean().default(true).describe("Include source code snippet (default: true)")
1379
+ include_source: z14.boolean().default(true).describe("Include source code snippet (default: true)")
1287
1380
  },
1288
1381
  async (params) => {
1289
1382
  const db = ensureDb(ctx, params.target_dir);
@@ -1381,21 +1474,21 @@ Use \`arcbridge_search_symbols\` to find symbols by name.`
1381
1474
  }
1382
1475
 
1383
1476
  // src/tools/get-dependency-graph.ts
1384
- import { z as z14 } from "zod";
1477
+ import { z as z15 } from "zod";
1385
1478
  function registerGetDependencyGraph(server, ctx) {
1386
1479
  server.tool(
1387
1480
  "arcbridge_get_dependency_graph",
1388
1481
  "Get the dependency graph for a module or file. Shows imports, calls, type usage, and inheritance relationships between symbols.",
1389
1482
  {
1390
- target_dir: z14.string().describe("Absolute path to the project directory"),
1391
- module: z14.string().describe(
1483
+ target_dir: z15.string().describe("Absolute path to the project directory"),
1484
+ module: z15.string().describe(
1392
1485
  "Module path relative to project root (e.g. 'src/lib/auth')"
1393
1486
  ),
1394
- direction: z14.enum(["dependencies", "dependents", "both"]).default("both").describe(
1487
+ direction: z15.enum(["dependencies", "dependents", "both"]).default("both").describe(
1395
1488
  "Graph direction: 'dependencies' (what this module uses), 'dependents' (what uses this module), or 'both'"
1396
1489
  ),
1397
- depth: z14.number().int().min(1).max(5).default(1).describe("How many levels to traverse (default: 1, max: 5)"),
1398
- service: z14.string().optional().describe("Filter by service name (for multi-project solutions). Omit to search all services.")
1490
+ depth: z15.number().int().min(1).max(5).default(1).describe("How many levels to traverse (default: 1, max: 5)"),
1491
+ service: z15.string().optional().describe("Filter by service name (for multi-project solutions). Omit to search all services.")
1399
1492
  },
1400
1493
  async (params) => {
1401
1494
  const maybeDb = ensureDb(ctx, params.target_dir);
@@ -1542,16 +1635,16 @@ function formatEdges(edges, modulePath, direction) {
1542
1635
  }
1543
1636
 
1544
1637
  // src/tools/get-component-graph.ts
1545
- import { z as z15 } from "zod";
1638
+ import { z as z16 } from "zod";
1546
1639
  function registerGetComponentGraph(server, ctx) {
1547
1640
  server.tool(
1548
1641
  "arcbridge_get_component_graph",
1549
1642
  "Get the React component graph: component hierarchy, props, state, context usage, and server/client boundaries.",
1550
1643
  {
1551
- target_dir: z15.string().describe("Absolute path to the project directory"),
1552
- file_path: z15.string().optional().describe("Filter to components in a specific file or directory prefix"),
1553
- client_only: z15.boolean().optional().describe("Only show client components ('use client')"),
1554
- with_state: z15.boolean().optional().describe("Only show components that use state (useState/useReducer)")
1644
+ target_dir: z16.string().describe("Absolute path to the project directory"),
1645
+ file_path: z16.string().optional().describe("Filter to components in a specific file or directory prefix"),
1646
+ client_only: z16.boolean().optional().describe("Only show client components ('use client')"),
1647
+ with_state: z16.boolean().optional().describe("Only show components that use state (useState/useReducer)")
1555
1648
  },
1556
1649
  async (params) => {
1557
1650
  const db = ensureDb(ctx, params.target_dir);
@@ -1655,16 +1748,16 @@ function registerGetComponentGraph(server, ctx) {
1655
1748
  }
1656
1749
 
1657
1750
  // src/tools/get-route-map.ts
1658
- import { z as z16 } from "zod";
1751
+ import { z as z17 } from "zod";
1659
1752
  function registerGetRouteMap(server, ctx) {
1660
1753
  server.tool(
1661
1754
  "arcbridge_get_route_map",
1662
1755
  "Get the route map: pages, layouts, API routes, and their hierarchy. Works with Next.js, ASP.NET controllers, and minimal APIs.",
1663
1756
  {
1664
- target_dir: z16.string().describe("Absolute path to the project directory"),
1665
- kind: z16.enum(["page", "layout", "loading", "error", "not-found", "api-route", "middleware"]).optional().describe("Filter by route kind"),
1666
- route_prefix: z16.string().optional().describe("Filter by route path prefix (e.g. '/dashboard' or '/api/orders')"),
1667
- service: z16.string().optional().describe("Filter by service name (for multi-project solutions). Omit to show all services.")
1757
+ target_dir: z17.string().describe("Absolute path to the project directory"),
1758
+ kind: z17.enum(["page", "layout", "loading", "error", "not-found", "api-route", "middleware"]).optional().describe("Filter by route kind"),
1759
+ route_prefix: z17.string().optional().describe("Filter by route path prefix (e.g. '/dashboard' or '/api/orders')"),
1760
+ service: z17.string().optional().describe("Filter by service name (for multi-project solutions). Omit to show all services.")
1668
1761
  },
1669
1762
  async (params) => {
1670
1763
  const db = ensureDb(ctx, params.target_dir);
@@ -1740,13 +1833,13 @@ function registerGetRouteMap(server, ctx) {
1740
1833
  }
1741
1834
 
1742
1835
  // src/tools/get-boundary-analysis.ts
1743
- import { z as z17 } from "zod";
1836
+ import { z as z18 } from "zod";
1744
1837
  function registerGetBoundaryAnalysis(server, ctx) {
1745
1838
  server.tool(
1746
1839
  "arcbridge_get_boundary_analysis",
1747
1840
  "Analyze server/client boundaries in a Next.js project. Identifies client components, server components, server actions, and potential boundary violations.",
1748
1841
  {
1749
- target_dir: z17.string().describe("Absolute path to the project directory")
1842
+ target_dir: z18.string().describe("Absolute path to the project directory")
1750
1843
  },
1751
1844
  async (params) => {
1752
1845
  const db = ensureDb(ctx, params.target_dir);
@@ -1850,15 +1943,15 @@ function registerGetBoundaryAnalysis(server, ctx) {
1850
1943
  }
1851
1944
 
1852
1945
  // src/tools/check-drift.ts
1853
- import { z as z18 } from "zod";
1946
+ import { z as z19 } from "zod";
1854
1947
  import { detectDrift, writeDriftLog } from "@arcbridge/core";
1855
1948
  function registerCheckDrift(server, ctx) {
1856
1949
  server.tool(
1857
1950
  "arcbridge_check_drift",
1858
1951
  "Detect architecture drift: undocumented modules, missing code paths, cross-block dependency violations, and stale ADR references.",
1859
1952
  {
1860
- target_dir: z18.string().describe("Absolute path to the project directory"),
1861
- persist: z18.boolean().default(true).describe("Write findings to drift_log table (default: true)")
1953
+ target_dir: z19.string().describe("Absolute path to the project directory"),
1954
+ persist: z19.boolean().default(true).describe("Write findings to drift_log table (default: true)")
1862
1955
  },
1863
1956
  async (params) => {
1864
1957
  const start = Date.now();
@@ -1930,15 +2023,16 @@ function registerCheckDrift(server, ctx) {
1930
2023
  }
1931
2024
 
1932
2025
  // src/tools/get-guidance.ts
1933
- import { z as z19 } from "zod";
2026
+ import { z as z20 } from "zod";
2027
+ import { loadConfig as loadConfig3 } from "@arcbridge/core";
1934
2028
  function registerGetGuidance(server, ctx) {
1935
2029
  server.tool(
1936
2030
  "arcbridge_get_guidance",
1937
2031
  "Get context-aware architectural guidance for a code change. Surfaces relevant quality scenarios, patterns, constraints, and questions to consider.",
1938
2032
  {
1939
- target_dir: z19.string().describe("Absolute path to the project directory"),
1940
- file_path: z19.string().optional().describe("File path you're working on (to determine building block and context)"),
1941
- action: z19.enum([
2033
+ target_dir: z20.string().describe("Absolute path to the project directory"),
2034
+ file_path: z20.string().optional().describe("File path you're working on (to determine building block and context)"),
2035
+ action: z20.enum([
1942
2036
  "adding-component",
1943
2037
  "adding-api-route",
1944
2038
  "adding-hook",
@@ -2068,7 +2162,10 @@ function registerGetGuidance(server, ctx) {
2068
2162
  }
2069
2163
  lines.push("");
2070
2164
  }
2071
- const actionGuidance = getActionGuidance(params.action);
2165
+ const projectRoot = ctx.projectRoot ?? params.target_dir;
2166
+ const { config: projConfig, error: configError } = loadConfig3(projectRoot);
2167
+ const projectType = configError ? "unknown" : projConfig?.project_type ?? "nextjs-app-router";
2168
+ const actionGuidance = getActionGuidance(params.action, projectType);
2072
2169
  if (actionGuidance) {
2073
2170
  lines.push("## Guidance", "", actionGuidance, "");
2074
2171
  }
@@ -2092,28 +2189,55 @@ function filterRelevantScenarios(scenarios, action, _blockId) {
2092
2189
  }
2093
2190
  return scenarios.filter((s) => relevantCategories.includes(s.category));
2094
2191
  }
2095
- function getActionGuidance(action) {
2096
- const guidance = {
2097
- "adding-component": "- Follow existing component patterns in this directory\n- Add props interface alongside the component\n- Consider server vs. client: does this need interactivity (`'use client'`)?\n- Check accessibility: keyboard navigation, ARIA labels, screen reader support\n- **Arc42:** If this introduces a new UI pattern, document it in `08-crosscutting.md`",
2098
- "adding-api-route": "- Ensure authentication middleware covers this route\n- Validate all input with zod or equivalent\n- Follow existing error response patterns\n- Consider rate limiting for public endpoints\n- If this introduces a new API pattern or convention, document it in an ADR\n- **Arc42:** Update `03-context.md` if this route exposes a new external integration; update `06-runtime-views.md` if it's a key workflow",
2099
- "adding-hook": "- Follow the `use` prefix convention\n- Keep hooks focused \u2014 one responsibility per hook\n- Consider memoization for expensive computations\n- Document the hook's return type",
2100
- "modifying-auth": "- Check all API routes still have auth coverage after changes\n- Verify no secrets leak to client components\n- Test edge cases: expired tokens, revoked sessions, role changes\n- Update security quality scenarios if behavior changes\n- Document the auth strategy and any changes in an ADR \u2014 auth decisions are critical to trace\n- **Arc42:** Update `08-crosscutting.md` with the auth pattern; update `06-runtime-views.md` with the auth flow",
2101
- "new-dependency": "- Document the dependency rationale in an ADR\n- Check bundle size impact (client-side deps)\n- Verify the dependency doesn't introduce known CVEs\n- Ensure the dependency's license is compatible\n- **Arc42:** If this dependency introduces a new external system, update `03-context.md`",
2102
- "refactoring": "- Ensure no cross-block boundary violations are introduced\n- Maintain existing public API contracts\n- Run tests before and after to verify behavior preservation\n- Check that no quality scenarios regress\n- If the refactoring changes architectural patterns, update or create an ADR to explain why\n- **Arc42:** Update `05-building-blocks.md` if module structure changed; update `08-crosscutting.md` if patterns changed",
2103
- "general": "- Check `arcbridge_get_relevant_adrs` for existing decisions that may constrain this change\n- If you're choosing between approaches, document the decision in an ADR\n- **Arc42:** Consider which documentation sections may need updating (check `.arcbridge/arc42/` \u2014 especially `05-building-blocks.md` and `08-crosscutting.md`)"
2104
- };
2105
- return guidance[action] ?? null;
2192
+ var FRONTEND_GUIDANCE = {
2193
+ "adding-component": "- Follow existing component patterns in this directory\n- Add props interface alongside the component\n- Consider server vs. client: does this need interactivity (`'use client'`)?\n- Check accessibility: keyboard navigation, ARIA labels, screen reader support\n- **Arc42:** If this introduces a new UI pattern, document it in `08-crosscutting.md`",
2194
+ "adding-api-route": "- Ensure authentication middleware covers this route\n- Validate all input with zod or equivalent\n- Follow existing error response patterns\n- Consider rate limiting for public endpoints\n- **Arc42:** Update `03-context.md` if this exposes a new external integration",
2195
+ "adding-hook": "- Follow the `use` prefix convention\n- Keep hooks focused \u2014 one responsibility per hook\n- Consider memoization for expensive computations\n- Document the hook's return type",
2196
+ "modifying-auth": "- Check all API routes still have auth coverage after changes\n- Verify no secrets leak to client components\n- Test edge cases: expired tokens, revoked sessions, role changes\n- **Arc42:** Update `08-crosscutting.md` with the auth pattern",
2197
+ "new-dependency": "- Document the dependency rationale in an ADR\n- Check bundle size impact (client-side deps)\n- Verify no known CVEs\n- **Arc42:** If this introduces a new external system, update `03-context.md`"
2198
+ };
2199
+ var DOTNET_GUIDANCE = {
2200
+ "adding-component": "- Follow the existing service/repository pattern\n- Register the new class in DI (Program.cs or extension method)\n- Add an interface if the component needs to be mockable\n- **Arc42:** Update `05-building-blocks.md` if this is a new architectural layer",
2201
+ "adding-api-route": "- Use `[HttpGet]`, `[HttpPost]`, etc. attributes for controller routes, or `MapGet`/`MapPost` for minimal APIs\n- Apply `[Authorize]` for protected endpoints\n- Validate input with data annotations or FluentValidation\n- Follow existing error response patterns (ProblemDetails)\n- **Arc42:** Update `03-context.md` if this exposes new external integrations; update `06-runtime-views.md` for key workflows",
2202
+ "adding-hook": "- .NET equivalent: create a middleware, filter, or hosted service\n- Register in the DI container\n- Follow the single responsibility principle",
2203
+ "modifying-auth": "- Check `[Authorize]` coverage on all controllers/endpoints\n- Verify JWT validation, claims, and policy configuration\n- Test edge cases: expired tokens, revoked sessions, role changes\n- **Arc42:** Update `08-crosscutting.md` with the auth pattern",
2204
+ "new-dependency": "- Document the NuGet package rationale in an ADR\n- Check for known vulnerabilities with `dotnet list package --vulnerable`\n- Verify license compatibility\n- **Arc42:** If this introduces a new external system, update `03-context.md`"
2205
+ };
2206
+ var API_GUIDANCE = {
2207
+ "adding-component": "- Follow existing module/service patterns\n- Add TypeScript interfaces for public APIs\n- Register in the dependency injection or module system\n- **Arc42:** Update `05-building-blocks.md` if this is a new architectural layer",
2208
+ "adding-api-route": "- Ensure authentication middleware covers this route\n- Validate all input with zod or equivalent\n- Follow existing error response patterns\n- Consider rate limiting for public endpoints\n- **Arc42:** Update `03-context.md` if this exposes a new external integration",
2209
+ "adding-hook": "- Use middleware for cross-cutting concerns\n- Keep middleware focused \u2014 one responsibility per middleware\n- Document the middleware's purpose and order",
2210
+ "modifying-auth": "- Check all routes still have auth coverage\n- Verify token validation and session handling\n- Test edge cases: expired tokens, revoked sessions\n- **Arc42:** Update `08-crosscutting.md` with the auth pattern",
2211
+ "new-dependency": "- Document the dependency rationale in an ADR\n- Verify no known CVEs\n- Ensure license compatibility\n- **Arc42:** If this introduces a new external system, update `03-context.md`"
2212
+ };
2213
+ var SHARED_GUIDANCE = {
2214
+ "refactoring": "- Ensure no cross-block boundary violations are introduced\n- Maintain existing public API contracts\n- Run tests before and after to verify behavior preservation\n- If the refactoring changes architectural patterns, update or create an ADR\n- **Arc42:** Update `05-building-blocks.md` if module structure changed; update `08-crosscutting.md` if patterns changed",
2215
+ "general": "- Check `arcbridge_get_relevant_adrs` for existing decisions that may constrain this change\n- If you're choosing between approaches, document the decision in an ADR\n- **Arc42:** Consider which documentation sections may need updating (check `.arcbridge/arc42/`)"
2216
+ };
2217
+ function getActionGuidance(action, projectType) {
2218
+ if (SHARED_GUIDANCE[action]) return SHARED_GUIDANCE[action];
2219
+ switch (projectType) {
2220
+ case "dotnet-webapi":
2221
+ return DOTNET_GUIDANCE[action] ?? null;
2222
+ case "api-service":
2223
+ return API_GUIDANCE[action] ?? null;
2224
+ case "react-vite":
2225
+ case "nextjs-app-router":
2226
+ return FRONTEND_GUIDANCE[action] ?? null;
2227
+ default:
2228
+ return null;
2229
+ }
2106
2230
  }
2107
2231
 
2108
2232
  // src/tools/get-open-questions.ts
2109
- import { z as z20 } from "zod";
2233
+ import { z as z21 } from "zod";
2110
2234
  function registerGetOpenQuestions(server, ctx) {
2111
2235
  server.tool(
2112
2236
  "arcbridge_get_open_questions",
2113
2237
  "Surface architectural gaps: untested quality scenarios, building blocks without boundaries, unresolved drift, and tasks missing acceptance criteria.",
2114
2238
  {
2115
- target_dir: z20.string().describe("Absolute path to the project directory"),
2116
- scope: z20.string().optional().describe("Focus scope: 'current-phase', 'building-block:<id>', or omit for project-wide")
2239
+ target_dir: z21.string().describe("Absolute path to the project directory"),
2240
+ scope: z21.string().optional().describe("Focus scope: 'current-phase', 'building-block:<id>', or omit for project-wide")
2117
2241
  },
2118
2242
  async (params) => {
2119
2243
  const db = ensureDb(ctx, params.target_dir);
@@ -2223,7 +2347,7 @@ function registerGetOpenQuestions(server, ctx) {
2223
2347
  }
2224
2348
 
2225
2349
  // src/tools/propose-arc42-update.ts
2226
- import { z as z21 } from "zod";
2350
+ import { z as z22 } from "zod";
2227
2351
  import {
2228
2352
  resolveRef,
2229
2353
  getChangedFiles,
@@ -2235,9 +2359,9 @@ function registerProposeArc42Update(server, ctx) {
2235
2359
  "arcbridge_propose_arc42_update",
2236
2360
  "Analyze code changes since a reference point and generate specific, actionable proposals for updating arc42 documentation.",
2237
2361
  {
2238
- target_dir: z21.string().describe("Absolute path to the project directory"),
2239
- changes_since: z21.string().default("last-sync").describe("Reference point: 'last-commit', 'last-sync', 'last-phase', or a git ref"),
2240
- update_sync_point: z21.boolean().default(false).describe("Update the stored sync commit to HEAD after generating proposals")
2362
+ target_dir: z22.string().describe("Absolute path to the project directory"),
2363
+ changes_since: z22.string().default("last-sync").describe("Reference point: 'last-commit', 'last-sync', 'last-phase', or a git ref"),
2364
+ update_sync_point: z22.boolean().default(false).describe("Update the stored sync commit to HEAD after generating proposals")
2241
2365
  },
2242
2366
  async (params) => {
2243
2367
  const db = ensureDb(ctx, params.target_dir);
@@ -2425,10 +2549,11 @@ function findCrossBlockConsumers(db, symbolId, sourceBlockId, blocks) {
2425
2549
  }
2426
2550
 
2427
2551
  // src/tools/get-practice-review.ts
2428
- import { z as z22 } from "zod";
2552
+ import { z as z23 } from "zod";
2429
2553
  import {
2430
2554
  resolveRef as resolveRef2,
2431
2555
  getChangedFiles as getChangedFiles2,
2556
+ scopeToProject,
2432
2557
  detectDrift as detectDrift2
2433
2558
  } from "@arcbridge/core";
2434
2559
  function registerGetPracticeReview(server, ctx) {
@@ -2436,15 +2561,16 @@ function registerGetPracticeReview(server, ctx) {
2436
2561
  "arcbridge_get_practice_review",
2437
2562
  "Structured, practice-aware review of recent code changes across 5 dimensions: Architecture, Security, Testing, Documentation, and Complexity.",
2438
2563
  {
2439
- target_dir: z22.string().describe("Absolute path to the project directory"),
2440
- since: z22.string().default("last-commit").describe("Reference point: 'last-commit', 'last-session', or 'last-phase'")
2564
+ target_dir: z23.string().describe("Absolute path to the project directory"),
2565
+ since: z23.string().default("last-commit").describe("Reference point: 'last-commit', 'last-session', or 'last-phase'")
2441
2566
  },
2442
2567
  async (params) => {
2443
2568
  const db = ensureDb(ctx, params.target_dir);
2444
2569
  if (!db) return notInitialized();
2445
2570
  const projectRoot = ctx.projectRoot ?? params.target_dir;
2446
2571
  const ref = resolveRef2(projectRoot, params.since, db);
2447
- const changedFiles = getChangedFiles2(projectRoot, ref.sha);
2572
+ const allChangedFiles = getChangedFiles2(projectRoot, ref.sha);
2573
+ const changedFiles = scopeToProject(allChangedFiles, projectRoot);
2448
2574
  if (changedFiles.length === 0) {
2449
2575
  return textResult(
2450
2576
  `# Practice Review
@@ -2752,7 +2878,7 @@ function reviewComplexity(db, changedFiles, findings) {
2752
2878
  }
2753
2879
 
2754
2880
  // src/tools/complete-phase.ts
2755
- import { z as z23 } from "zod";
2881
+ import { z as z24 } from "zod";
2756
2882
  import {
2757
2883
  detectDrift as detectDrift3,
2758
2884
  writeDriftLog as writeDriftLog2,
@@ -2761,7 +2887,7 @@ import {
2761
2887
  inferTaskStatuses,
2762
2888
  applyInferences,
2763
2889
  verifyScenarios,
2764
- loadConfig as loadConfig3,
2890
+ loadConfig as loadConfig4,
2765
2891
  refreshFromDocs as refreshFromDocs5,
2766
2892
  syncPhaseToYaml,
2767
2893
  transaction
@@ -2771,11 +2897,11 @@ function registerCompletePhase(server, ctx) {
2771
2897
  "arcbridge_complete_phase",
2772
2898
  "Attempt to complete a phase by validating all gates: tasks done, no critical drift, quality scenarios passing. Transitions the phase to 'complete' if all gates pass.",
2773
2899
  {
2774
- target_dir: z23.string().describe("Absolute path to the project directory"),
2775
- phase_id: z23.string().optional().describe("Phase ID to complete (defaults to current in-progress phase)"),
2776
- notes: z23.string().optional().describe("Optional notes about this phase completion"),
2777
- auto_infer: z23.boolean().default(true).describe("Automatically infer task statuses from code state before checking gates"),
2778
- run_tests: z23.boolean().default(false).describe("Run linked tests for quality scenarios before checking the quality gate")
2900
+ target_dir: z24.string().describe("Absolute path to the project directory"),
2901
+ phase_id: z24.string().optional().describe("Phase ID to complete (defaults to current in-progress phase)"),
2902
+ notes: z24.string().optional().describe("Optional notes about this phase completion"),
2903
+ auto_infer: z24.boolean().default(true).describe("Automatically infer task statuses from code state before checking gates"),
2904
+ run_tests: z24.boolean().default(false).describe("Run linked tests for quality scenarios before checking the quality gate")
2779
2905
  },
2780
2906
  async (params) => {
2781
2907
  const start = Date.now();
@@ -2816,7 +2942,7 @@ function registerCompletePhase(server, ctx) {
2816
2942
  }
2817
2943
  }
2818
2944
  const tasks = db.prepare("SELECT id, title, status FROM tasks WHERE phase_id = ?").all(phase.id);
2819
- const incompleteTasks = tasks.filter((t) => t.status !== "done");
2945
+ const incompleteTasks = tasks.filter((t) => t.status !== "done" && t.status !== "cancelled");
2820
2946
  const tasksPass = incompleteTasks.length === 0;
2821
2947
  const driftEntries = detectDrift3(db);
2822
2948
  writeDriftLog2(db, driftEntries);
@@ -2826,7 +2952,7 @@ function registerCompletePhase(server, ctx) {
2826
2952
  const projectRoot = ctx.projectRoot ?? params.target_dir;
2827
2953
  let testCommand = "npx vitest run";
2828
2954
  let timeoutMs = 6e4;
2829
- const configResult = loadConfig3(params.target_dir);
2955
+ const configResult = loadConfig4(params.target_dir);
2830
2956
  if (configResult.config) {
2831
2957
  testCommand = configResult.config.testing.test_command;
2832
2958
  timeoutMs = configResult.config.testing.timeout_ms;
@@ -2990,18 +3116,18 @@ function registerCompletePhase(server, ctx) {
2990
3116
  }
2991
3117
 
2992
3118
  // src/tools/activate-role.ts
2993
- import { z as z24 } from "zod";
3119
+ import { z as z25 } from "zod";
2994
3120
  import { loadRole, loadRoles } from "@arcbridge/core";
2995
3121
  function registerActivateRole(server, ctx) {
2996
3122
  server.tool(
2997
3123
  "arcbridge_activate_role",
2998
3124
  "Activate an agent role: loads the role's system prompt, required tools, quality focus, and pre-loaded architectural context.",
2999
3125
  {
3000
- target_dir: z24.string().describe("Absolute path to the project directory"),
3001
- role: z24.string().describe(
3126
+ target_dir: z25.string().describe("Absolute path to the project directory"),
3127
+ role: z25.string().describe(
3002
3128
  "Role ID to activate (e.g., 'architect', 'implementer', 'security-reviewer')"
3003
3129
  ),
3004
- building_block: z24.string().optional().describe("Focus on a specific building block (for implementer/code-reviewer roles)")
3130
+ building_block: z25.string().optional().describe("Focus on a specific building block (for implementer/code-reviewer roles)")
3005
3131
  },
3006
3132
  async (params) => {
3007
3133
  const db = ensureDb(ctx, params.target_dir);
@@ -3303,18 +3429,18 @@ function getRoleDefinition(roleId) {
3303
3429
  }
3304
3430
 
3305
3431
  // src/tools/verify-scenarios.ts
3306
- import { z as z25 } from "zod";
3307
- import { verifyScenarios as verifyScenarios2, loadConfig as loadConfig4 } from "@arcbridge/core";
3432
+ import { z as z26 } from "zod";
3433
+ import { verifyScenarios as verifyScenarios2, loadConfig as loadConfig5 } from "@arcbridge/core";
3308
3434
  function registerVerifyScenarios(server, ctx) {
3309
3435
  server.tool(
3310
3436
  "arcbridge_verify_scenarios",
3311
3437
  "Run linked tests for quality scenarios and update their pass/fail status. Only runs scenarios with verification='automatic' or 'semi-automatic' and non-empty linked_tests.",
3312
3438
  {
3313
- target_dir: z25.string().describe("Absolute path to the project directory"),
3314
- scenario_ids: z25.array(z25.string()).optional().describe(
3439
+ target_dir: z26.string().describe("Absolute path to the project directory"),
3440
+ scenario_ids: z26.array(z26.string()).optional().describe(
3315
3441
  "Specific scenario IDs to verify (e.g., ['SEC-01', 'PERF-01']). If omitted, verifies all automatic scenarios."
3316
3442
  ),
3317
- test_command: z25.string().optional().describe(
3443
+ test_command: z26.string().optional().describe(
3318
3444
  "Override the test command from config (e.g., 'npx jest'). File paths are appended as arguments."
3319
3445
  )
3320
3446
  },
@@ -3323,7 +3449,7 @@ function registerVerifyScenarios(server, ctx) {
3323
3449
  if (!db) return notInitialized();
3324
3450
  let testCommand = "npx vitest run";
3325
3451
  let timeoutMs = 6e4;
3326
- const configResult = loadConfig4(params.target_dir);
3452
+ const configResult = loadConfig5(params.target_dir);
3327
3453
  if (configResult.config) {
3328
3454
  testCommand = configResult.config.testing.test_command;
3329
3455
  timeoutMs = configResult.config.testing.timeout_ms;
@@ -3381,13 +3507,14 @@ function registerVerifyScenarios(server, ctx) {
3381
3507
  }
3382
3508
 
3383
3509
  // src/tools/run-role-check.ts
3384
- import { z as z26 } from "zod";
3510
+ import { z as z27 } from "zod";
3385
3511
  import {
3386
3512
  loadRole as loadRole2,
3387
3513
  loadRoles as loadRoles2,
3388
3514
  detectDrift as detectDrift4,
3389
3515
  resolveRef as resolveRef3,
3390
- getChangedFiles as getChangedFiles3
3516
+ getChangedFiles as getChangedFiles3,
3517
+ scopeToProject as scopeToProject2
3391
3518
  } from "@arcbridge/core";
3392
3519
  var SCOPE_VALUES = ["last-commit", "current-phase", "full-project"];
3393
3520
  function registerRunRoleCheck(server, ctx) {
@@ -3395,11 +3522,11 @@ function registerRunRoleCheck(server, ctx) {
3395
3522
  "arcbridge_run_role_check",
3396
3523
  "Run a role-specific architectural analysis: resolves the role and executes relevant checks (drift, quality scenarios, boundaries, changed files) based on the role's focus areas.",
3397
3524
  {
3398
- target_dir: z26.string().describe("Absolute path to the project directory"),
3399
- role: z26.string().describe(
3525
+ target_dir: z27.string().describe("Absolute path to the project directory"),
3526
+ role: z27.string().describe(
3400
3527
  "Role ID to run checks for (e.g., 'security-reviewer', 'quality-guardian', 'architect', 'phase-manager', 'code-reviewer')"
3401
3528
  ),
3402
- scope: z26.enum(SCOPE_VALUES).default("current-phase").describe(
3529
+ scope: z27.enum(SCOPE_VALUES).default("current-phase").describe(
3403
3530
  "Scope of analysis: 'last-commit' (recent changes), 'current-phase' (since phase start), 'full-project' (everything)"
3404
3531
  )
3405
3532
  },
@@ -3466,7 +3593,8 @@ function getChangedFilesForScope(db, projectRoot, scope) {
3466
3593
  const since = scope === "last-commit" ? "last-commit" : "last-phase";
3467
3594
  const ref = resolveRef3(projectRoot, since, db);
3468
3595
  try {
3469
- return getChangedFiles3(projectRoot, ref.sha);
3596
+ const files = getChangedFiles3(projectRoot, ref.sha);
3597
+ return scopeToProject2(files, projectRoot);
3470
3598
  } catch {
3471
3599
  return null;
3472
3600
  }
@@ -3755,7 +3883,7 @@ function runPhaseManagerCheck(db, projectRoot, lines) {
3755
3883
  }
3756
3884
  const ref = resolveRef3(projectRoot, "last-sync", db);
3757
3885
  try {
3758
- const changedFiles = getChangedFiles3(projectRoot, ref.sha);
3886
+ const changedFiles = scopeToProject2(getChangedFiles3(projectRoot, ref.sha), projectRoot);
3759
3887
  if (changedFiles.length > 0) {
3760
3888
  lines.push(`## Changes Since Last Sync (${ref.label})`, "");
3761
3889
  appendChangedFilesList(lines, changedFiles);
@@ -3818,34 +3946,125 @@ function runCustomRoleCheck(db, lines, roleDef) {
3818
3946
  appendDriftSection(db, lines);
3819
3947
  }
3820
3948
 
3949
+ // src/tools/update-scenario-status.ts
3950
+ import { z as z28 } from "zod";
3951
+ import { syncScenarioToYaml, transaction as transaction2 } from "@arcbridge/core";
3952
+ function registerUpdateScenarioStatus(server, ctx) {
3953
+ server.tool(
3954
+ "arcbridge_update_scenario_status",
3955
+ "Update a quality scenario's status and optionally link test files. Use this to mark scenarios as passing/failing after manual verification, or to link test files so `arcbridge_verify_scenarios` can run them automatically.",
3956
+ {
3957
+ target_dir: z28.string().describe("Absolute path to the project directory"),
3958
+ scenario_id: z28.string().describe("Quality scenario ID (e.g., 'SEC-01', 'PERF-01')"),
3959
+ status: z28.enum(["passing", "failing", "untested", "partial"]).describe("New status for the scenario"),
3960
+ linked_tests: z28.array(z28.string()).optional().describe(
3961
+ "Test file paths to link to this scenario (e.g., ['src/__tests__/auth.test.ts']). Once linked, `arcbridge_verify_scenarios` can run them automatically. Also sets verification to 'semi-automatic' if currently 'manual'."
3962
+ )
3963
+ },
3964
+ async (params) => {
3965
+ const db = ensureDb(ctx, params.target_dir);
3966
+ if (!db) return notInitialized();
3967
+ const projectRoot = ctx.projectRoot ?? params.target_dir;
3968
+ const scenario = db.prepare("SELECT id, name, status, linked_tests, verification FROM quality_scenarios WHERE id = ?").get(params.scenario_id);
3969
+ if (!scenario) {
3970
+ const available = db.prepare("SELECT id, name, status FROM quality_scenarios ORDER BY id").all();
3971
+ const list = available.length > 0 ? available.map((s) => ` - \`${s.id}\` ${s.name} (${s.status})`).join("\n") : " (none)";
3972
+ return textResult(
3973
+ `Scenario '${params.scenario_id}' not found.
3974
+
3975
+ **Available scenarios:**
3976
+ ${list}`
3977
+ );
3978
+ }
3979
+ const oldStatus = scenario.status;
3980
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3981
+ if (params.linked_tests) {
3982
+ const { isAbsolute, normalize } = await import("path");
3983
+ const invalid = params.linked_tests.filter((t) => {
3984
+ const norm = normalize(t);
3985
+ return isAbsolute(norm) || norm.startsWith("..");
3986
+ });
3987
+ if (invalid.length > 0) {
3988
+ return textResult(
3989
+ `Invalid test paths (must be relative, no '..' segments):
3990
+ ${invalid.map((p) => ` - ${p}`).join("\n")}`
3991
+ );
3992
+ }
3993
+ }
3994
+ transaction2(db, () => {
3995
+ db.prepare("UPDATE quality_scenarios SET status = ?, last_checked = ? WHERE id = ?").run(
3996
+ params.status,
3997
+ now,
3998
+ params.scenario_id
3999
+ );
4000
+ if (params.linked_tests) {
4001
+ db.prepare("UPDATE quality_scenarios SET linked_tests = ? WHERE id = ?").run(
4002
+ JSON.stringify(params.linked_tests),
4003
+ params.scenario_id
4004
+ );
4005
+ if (scenario.verification === "manual") {
4006
+ db.prepare("UPDATE quality_scenarios SET verification = 'semi-automatic' WHERE id = ?").run(
4007
+ params.scenario_id
4008
+ );
4009
+ }
4010
+ }
4011
+ });
4012
+ let yamlWarning;
4013
+ try {
4014
+ const newVerification = params.linked_tests && scenario.verification === "manual" ? "semi-automatic" : void 0;
4015
+ syncScenarioToYaml(projectRoot, params.scenario_id, params.status, params.linked_tests, newVerification);
4016
+ } catch (err) {
4017
+ yamlWarning = `YAML sync failed: ${err instanceof Error ? err.message : String(err)}. DB updated but YAML may be out of sync.`;
4018
+ }
4019
+ const lines = [
4020
+ `Scenario **${scenario.id}** (${scenario.name}) updated: ${oldStatus} \u2192 **${params.status}**`
4021
+ ];
4022
+ if (params.linked_tests) {
4023
+ lines.push(
4024
+ "",
4025
+ `**Linked tests:** ${params.linked_tests.length} file(s)`,
4026
+ ...params.linked_tests.map((t) => ` - ${t}`)
4027
+ );
4028
+ if (scenario.verification === "manual") {
4029
+ lines.push("", "*Verification upgraded from manual to semi-automatic*");
4030
+ }
4031
+ }
4032
+ if (yamlWarning) {
4033
+ lines.push("", `**Warning:** ${yamlWarning}`);
4034
+ }
4035
+ return textResult(lines.join("\n"));
4036
+ }
4037
+ );
4038
+ }
4039
+
3821
4040
  // src/tools/record-activity.ts
3822
- import { z as z27 } from "zod";
4041
+ import { z as z29 } from "zod";
3823
4042
  import { insertActivity as insertActivity2, getSessionTotals } from "@arcbridge/core";
3824
4043
  function registerRecordActivity(server, ctx) {
3825
4044
  server.tool(
3826
4045
  "arcbridge_record_activity",
3827
4046
  "Record agent activity \u2014 model, tokens, cost, duration, and optional quality snapshot. Use this to track what work was done and measure agent performance.",
3828
4047
  {
3829
- target_dir: z27.string().describe("Absolute path to the project directory"),
3830
- tool_name: z27.string().describe("Name of the tool or action performed (e.g., 'arcbridge_update_task', 'code_edit')"),
3831
- action: z27.string().optional().describe("Human-readable label (e.g., 'implement login form')"),
3832
- model: z27.string().optional().describe("Model identifier (e.g., 'claude-sonnet-4-20250514')"),
3833
- agent_role: z27.string().optional().describe("Active ArcBridge role (e.g., 'implementer')"),
3834
- task_id: z27.string().optional().describe("Associated task ID"),
3835
- phase_id: z27.string().optional().describe("Associated phase ID"),
3836
- input_tokens: z27.number().int().nonnegative().optional().describe("Input/prompt tokens"),
3837
- output_tokens: z27.number().int().nonnegative().optional().describe("Output/completion tokens"),
3838
- total_tokens: z27.number().int().nonnegative().optional().describe("Total tokens (auto-computed if input+output given)"),
3839
- cost_usd: z27.number().nonnegative().optional().describe("Estimated cost in USD"),
3840
- duration_ms: z27.number().int().nonnegative().optional().describe("Wall-clock duration in ms"),
3841
- drift_count: z27.number().int().nonnegative().optional().describe("Current drift count"),
3842
- drift_errors: z27.number().int().nonnegative().optional().describe("Current drift errors"),
3843
- test_pass_count: z27.number().int().nonnegative().optional().describe("Passing tests"),
3844
- test_fail_count: z27.number().int().nonnegative().optional().describe("Failing tests"),
3845
- lint_clean: z27.boolean().optional().describe("Whether lint passes cleanly"),
3846
- typecheck_clean: z27.boolean().optional().describe("Whether typecheck passes cleanly"),
3847
- notes: z27.string().optional().describe("Free-form notes"),
3848
- metadata: z27.record(z27.unknown()).optional().describe("Additional key-value metadata")
4048
+ target_dir: z29.string().describe("Absolute path to the project directory"),
4049
+ tool_name: z29.string().describe("Name of the tool or action performed (e.g., 'arcbridge_update_task', 'code_edit')"),
4050
+ action: z29.string().optional().describe("Human-readable label (e.g., 'implement login form')"),
4051
+ model: z29.string().optional().describe("Model identifier (e.g., 'claude-sonnet-4-20250514')"),
4052
+ agent_role: z29.string().optional().describe("Active ArcBridge role (e.g., 'implementer')"),
4053
+ task_id: z29.string().optional().describe("Associated task ID"),
4054
+ phase_id: z29.string().optional().describe("Associated phase ID"),
4055
+ input_tokens: z29.number().int().nonnegative().optional().describe("Input/prompt tokens"),
4056
+ output_tokens: z29.number().int().nonnegative().optional().describe("Output/completion tokens"),
4057
+ total_tokens: z29.number().int().nonnegative().optional().describe("Total tokens (auto-computed if input+output given)"),
4058
+ cost_usd: z29.number().nonnegative().optional().describe("Estimated cost in USD"),
4059
+ duration_ms: z29.number().int().nonnegative().optional().describe("Wall-clock duration in ms"),
4060
+ drift_count: z29.number().int().nonnegative().optional().describe("Current drift count"),
4061
+ drift_errors: z29.number().int().nonnegative().optional().describe("Current drift errors"),
4062
+ test_pass_count: z29.number().int().nonnegative().optional().describe("Passing tests"),
4063
+ test_fail_count: z29.number().int().nonnegative().optional().describe("Failing tests"),
4064
+ lint_clean: z29.boolean().optional().describe("Whether lint passes cleanly"),
4065
+ typecheck_clean: z29.boolean().optional().describe("Whether typecheck passes cleanly"),
4066
+ notes: z29.string().optional().describe("Free-form notes"),
4067
+ metadata: z29.record(z29.unknown()).optional().describe("Additional key-value metadata")
3849
4068
  },
3850
4069
  async (params) => {
3851
4070
  const db = ensureDb(ctx, params.target_dir);
@@ -3900,23 +4119,23 @@ function registerRecordActivity(server, ctx) {
3900
4119
  }
3901
4120
 
3902
4121
  // src/tools/get-metrics.ts
3903
- import { z as z28 } from "zod";
4122
+ import { z as z30 } from "zod";
3904
4123
  import { queryMetrics } from "@arcbridge/core";
3905
4124
  function registerGetMetrics(server, ctx) {
3906
4125
  server.tool(
3907
4126
  "arcbridge_get_metrics",
3908
4127
  "Query agent activity metrics \u2014 filter by model, task, phase, or time range. Group by model/task/phase/tool/day for aggregated views.",
3909
4128
  {
3910
- target_dir: z28.string().describe("Absolute path to the project directory"),
3911
- task_id: z28.string().optional().describe("Filter by task ID"),
3912
- phase_id: z28.string().optional().describe("Filter by phase ID"),
3913
- model: z28.string().optional().describe("Filter by model name"),
3914
- agent_role: z28.string().optional().describe("Filter by agent role"),
3915
- tool_name: z28.string().optional().describe("Filter by tool name"),
3916
- since: z28.string().optional().describe("ISO 8601 timestamp \u2014 activity after this time"),
3917
- until: z28.string().optional().describe("ISO 8601 timestamp \u2014 activity before this time"),
3918
- group_by: z28.enum(["model", "task", "phase", "tool", "day", "none"]).default("none").describe("Group results for aggregation"),
3919
- limit: z28.number().int().min(1).max(500).default(50).describe("Max rows in detail view (group_by=none)")
4129
+ target_dir: z30.string().describe("Absolute path to the project directory"),
4130
+ task_id: z30.string().optional().describe("Filter by task ID"),
4131
+ phase_id: z30.string().optional().describe("Filter by phase ID"),
4132
+ model: z30.string().optional().describe("Filter by model name"),
4133
+ agent_role: z30.string().optional().describe("Filter by agent role"),
4134
+ tool_name: z30.string().optional().describe("Filter by tool name"),
4135
+ since: z30.string().optional().describe("ISO 8601 timestamp \u2014 activity after this time"),
4136
+ until: z30.string().optional().describe("ISO 8601 timestamp \u2014 activity before this time"),
4137
+ group_by: z30.enum(["model", "task", "phase", "tool", "day", "none"]).default("none").describe("Group results for aggregation"),
4138
+ limit: z30.number().int().min(1).max(500).default(50).describe("Max rows in detail view (group_by=none)")
3920
4139
  },
3921
4140
  async (params) => {
3922
4141
  const db = ensureDb(ctx, params.target_dir);
@@ -3999,23 +4218,23 @@ function mdCell(val) {
3999
4218
  }
4000
4219
 
4001
4220
  // src/tools/export-metrics.ts
4002
- import { z as z29 } from "zod";
4221
+ import { z as z31 } from "zod";
4003
4222
  import { exportMetrics } from "@arcbridge/core";
4004
4223
  function registerExportMetrics(server, ctx) {
4005
4224
  server.tool(
4006
4225
  "arcbridge_export_metrics",
4007
4226
  "Export agent activity metrics to a file (JSON, CSV, or Markdown) in .arcbridge/metrics/ for git commits or reporting.",
4008
4227
  {
4009
- target_dir: z29.string().describe("Absolute path to the project directory"),
4010
- format: z29.enum(["json", "csv", "markdown"]).default("json").describe("Export format"),
4011
- task_id: z29.string().optional().describe("Filter by task ID"),
4012
- phase_id: z29.string().optional().describe("Filter by phase ID"),
4013
- model: z29.string().optional().describe("Filter by model name"),
4014
- agent_role: z29.string().optional().describe("Filter by agent role"),
4015
- tool_name: z29.string().optional().describe("Filter by tool name"),
4016
- since: z29.string().optional().describe("ISO 8601 \u2014 activity after this time"),
4017
- until: z29.string().optional().describe("ISO 8601 \u2014 activity before this time"),
4018
- max_rows: z29.number().int().min(1).default(1e5).describe("Maximum rows to export (default: 100,000)")
4228
+ target_dir: z31.string().describe("Absolute path to the project directory"),
4229
+ format: z31.enum(["json", "csv", "markdown"]).default("json").describe("Export format"),
4230
+ task_id: z31.string().optional().describe("Filter by task ID"),
4231
+ phase_id: z31.string().optional().describe("Filter by phase ID"),
4232
+ model: z31.string().optional().describe("Filter by model name"),
4233
+ agent_role: z31.string().optional().describe("Filter by agent role"),
4234
+ tool_name: z31.string().optional().describe("Filter by tool name"),
4235
+ since: z31.string().optional().describe("ISO 8601 \u2014 activity after this time"),
4236
+ until: z31.string().optional().describe("ISO 8601 \u2014 activity before this time"),
4237
+ max_rows: z31.number().int().min(1).default(1e5).describe("Maximum rows to export (default: 100,000)")
4019
4238
  },
4020
4239
  async (params) => {
4021
4240
  const db = ensureDb(ctx, params.target_dir);
@@ -4063,6 +4282,7 @@ function createArcBridgeServer() {
4063
4282
  registerGetCurrentTasks(server, ctx);
4064
4283
  registerUpdateTask(server, ctx);
4065
4284
  registerCreateTask(server, ctx);
4285
+ registerDeleteTask(server, ctx);
4066
4286
  registerReindex(server, ctx);
4067
4287
  registerSearchSymbols(server, ctx);
4068
4288
  registerGetSymbol(server, ctx);
@@ -4078,6 +4298,7 @@ function createArcBridgeServer() {
4078
4298
  registerCompletePhase(server, ctx);
4079
4299
  registerActivateRole(server, ctx);
4080
4300
  registerVerifyScenarios(server, ctx);
4301
+ registerUpdateScenarioStatus(server, ctx);
4081
4302
  registerRunRoleCheck(server, ctx);
4082
4303
  registerRecordActivity(server, ctx);
4083
4304
  registerGetMetrics(server, ctx);