@arcbridge/core 0.1.2 → 0.1.4

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
@@ -210,15 +210,93 @@ var AgentRoleSchema = z6.object({
210
210
  });
211
211
 
212
212
  // src/db/connection.ts
213
- import Database from "better-sqlite3";
213
+ import { DatabaseSync } from "node:sqlite";
214
+ var suppressed = false;
215
+ function suppressSqliteWarning() {
216
+ if (suppressed) return;
217
+ suppressed = true;
218
+ const origEmit = process.emit;
219
+ process.emit = function(event, ...args) {
220
+ if (event === "warning" && args[0]?.name === "ExperimentalWarning" && typeof args[0]?.message === "string" && args[0].message.includes("SQLite")) {
221
+ return true;
222
+ }
223
+ return origEmit.apply(process, [event, ...args]);
224
+ };
225
+ }
226
+ function sanitizeParams(params) {
227
+ return params.map((p) => p === void 0 ? null : p);
228
+ }
214
229
  function openDatabase(dbPath) {
215
- const db = new Database(dbPath);
216
- db.pragma("journal_mode = WAL");
217
- db.pragma("foreign_keys = ON");
230
+ const db = new DatabaseSync(dbPath);
231
+ db.exec("PRAGMA journal_mode = WAL");
232
+ db.exec("PRAGMA foreign_keys = ON");
233
+ patchPrepare(db);
218
234
  return db;
219
235
  }
220
236
  function openMemoryDatabase() {
221
- return openDatabase(":memory:");
237
+ const db = new DatabaseSync(":memory:");
238
+ db.exec("PRAGMA foreign_keys = ON");
239
+ patchPrepare(db);
240
+ return db;
241
+ }
242
+ function patchPrepare(db) {
243
+ const originalPrepare = db.prepare.bind(db);
244
+ db.prepare = (sql) => {
245
+ const stmt = originalPrepare(sql);
246
+ const origRun = stmt.run.bind(stmt);
247
+ const origGet = stmt.get.bind(stmt);
248
+ const origAll = stmt.all.bind(stmt);
249
+ stmt.run = (...params) => origRun(...sanitizeParams(params));
250
+ stmt.get = (...params) => origGet(...sanitizeParams(params));
251
+ stmt.all = (...params) => origAll(...sanitizeParams(params));
252
+ return stmt;
253
+ };
254
+ }
255
+ function rejectAsync(fn) {
256
+ if (fn.constructor.name === "AsyncFunction") {
257
+ throw new Error(
258
+ "transaction() received an async function. Use a synchronous function \u2014 node:sqlite is synchronous and the transaction would commit before the async work completes."
259
+ );
260
+ }
261
+ }
262
+ var txDepth = /* @__PURE__ */ new WeakMap();
263
+ function transaction(db, fn) {
264
+ rejectAsync(fn);
265
+ const depth = txDepth.get(db) ?? 0;
266
+ if (depth === 0) {
267
+ db.exec("BEGIN");
268
+ txDepth.set(db, 1);
269
+ try {
270
+ const result = fn();
271
+ db.exec("COMMIT");
272
+ return result;
273
+ } catch (err) {
274
+ try {
275
+ db.exec("ROLLBACK");
276
+ } catch {
277
+ }
278
+ throw err;
279
+ } finally {
280
+ txDepth.set(db, 0);
281
+ }
282
+ }
283
+ const name = `sp_${depth}`;
284
+ db.exec(`SAVEPOINT ${name}`);
285
+ txDepth.set(db, depth + 1);
286
+ try {
287
+ const result = fn();
288
+ db.exec(`RELEASE ${name}`);
289
+ return result;
290
+ } catch (err) {
291
+ try {
292
+ db.exec(`ROLLBACK TO ${name}`);
293
+ db.exec(`RELEASE ${name}`);
294
+ } catch {
295
+ }
296
+ throw err;
297
+ } finally {
298
+ txDepth.set(db, depth);
299
+ }
222
300
  }
223
301
 
224
302
  // src/db/schema.ts
@@ -477,12 +555,12 @@ function migrate(db) {
477
555
  }
478
556
  const pending = migrations.filter((m) => m.version > currentVersion).sort((a, b) => a.version - b.version);
479
557
  for (const migration of pending) {
480
- db.transaction(() => {
558
+ transaction(db, () => {
481
559
  migration.up(db);
482
560
  db.prepare(
483
561
  "UPDATE arcbridge_meta SET value = ? WHERE key = 'schema_version'"
484
562
  ).run(String(migration.version));
485
- })();
563
+ });
486
564
  }
487
565
  }
488
566
 
@@ -1062,10 +1140,16 @@ ${input.template === "dotnet-webapi" ? `| Platform | Description | Notes |
1062
1140
  // src/templates/arc42/09-decisions.ts
1063
1141
  function firstAdrTemplate(input) {
1064
1142
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1065
- if (input.template === "dotnet-webapi") {
1066
- return dotnetAdr(input, now);
1143
+ switch (input.template) {
1144
+ case "dotnet-webapi":
1145
+ return dotnetAdr(input, now);
1146
+ case "react-vite":
1147
+ return reactViteAdr(input, now);
1148
+ case "api-service":
1149
+ return apiServiceAdr(input, now);
1150
+ default:
1151
+ return nextjsAdr(input, now);
1067
1152
  }
1068
- return nextjsAdr(input, now);
1069
1153
  }
1070
1154
  function nextjsAdr(input, date) {
1071
1155
  const { appPrefix } = detectProjectLayout(input.projectRoot, input.template);
@@ -1100,6 +1184,73 @@ Use Next.js with the App Router (introduced in Next.js 13+) as the application f
1100
1184
  `
1101
1185
  };
1102
1186
  }
1187
+ function reactViteAdr(input, date) {
1188
+ const { srcPrefix } = detectProjectLayout(input.projectRoot, input.template);
1189
+ return {
1190
+ filename: "001-react-vite.md",
1191
+ frontmatter: {
1192
+ id: "001-react-vite",
1193
+ title: "Use React with Vite",
1194
+ status: "accepted",
1195
+ date,
1196
+ affected_blocks: ["app-shell"],
1197
+ affected_files: [srcPrefix || "./"],
1198
+ quality_scenarios: []
1199
+ },
1200
+ body: `# ADR-001: Use React with Vite
1201
+
1202
+ ## Context
1203
+
1204
+ ${input.name} needs a fast, modern frontend framework for building a single-page application (SPA) with TypeScript.
1205
+
1206
+ ## Decision
1207
+
1208
+ Use React with Vite as the build tool and development server.
1209
+
1210
+ ## Consequences
1211
+
1212
+ - **Positive:** Extremely fast dev server with hot module replacement (HMR)
1213
+ - **Positive:** Optimized production builds with tree shaking and code splitting
1214
+ - **Positive:** Simple configuration, no SSR complexity
1215
+ - **Positive:** Large React ecosystem of libraries and components
1216
+ - **Negative:** Client-side only \u2014 no built-in server-side rendering (add later if needed)
1217
+ - **Negative:** Requires separate backend service for API endpoints
1218
+ `
1219
+ };
1220
+ }
1221
+ function apiServiceAdr(input, date) {
1222
+ const { srcPrefix } = detectProjectLayout(input.projectRoot, input.template);
1223
+ return {
1224
+ filename: "001-api-service.md",
1225
+ frontmatter: {
1226
+ id: "001-api-service",
1227
+ title: "Use Node.js API Service",
1228
+ status: "accepted",
1229
+ date,
1230
+ affected_blocks: ["app-shell"],
1231
+ affected_files: [srcPrefix || "./"],
1232
+ quality_scenarios: []
1233
+ },
1234
+ body: `# ADR-001: Use Node.js API Service
1235
+
1236
+ ## Context
1237
+
1238
+ ${input.name} needs a backend API service with TypeScript support, good performance, and a simple architecture.
1239
+
1240
+ ## Decision
1241
+
1242
+ Use a Node.js HTTP framework (Express, Fastify, or Hono) as the API service foundation.
1243
+
1244
+ ## Consequences
1245
+
1246
+ - **Positive:** TypeScript-first with full type safety
1247
+ - **Positive:** Rich middleware ecosystem for auth, validation, logging
1248
+ - **Positive:** Easy to deploy to containers, serverless, or traditional hosts
1249
+ - **Negative:** Single-threaded \u2014 CPU-intensive operations need worker threads or external services
1250
+ - **Negative:** No built-in ORM \u2014 need to choose data access strategy
1251
+ `
1252
+ };
1253
+ }
1103
1254
  function dotnetAdr(input, date) {
1104
1255
  return {
1105
1256
  filename: "001-aspnet-core-webapi.md",
@@ -1883,6 +2034,116 @@ function phaseTasksTemplate(_input, phaseId) {
1883
2034
  ]
1884
2035
  }
1885
2036
  ]
2037
+ },
2038
+ "phase-2-features": {
2039
+ schema_version: 1,
2040
+ phase_id: "phase-2-features",
2041
+ tasks: [
2042
+ {
2043
+ id: "task-2.1-core-pages",
2044
+ title: "Implement core application pages",
2045
+ status: "todo",
2046
+ quality_scenarios: ["PERF-01"],
2047
+ acceptance_criteria: [
2048
+ "Core pages created with proper data fetching",
2049
+ "Server/client components properly separated",
2050
+ "Loading and error states handled"
2051
+ ]
2052
+ },
2053
+ {
2054
+ id: "task-2.2-api-routes",
2055
+ title: "Implement API routes",
2056
+ status: "todo",
2057
+ quality_scenarios: ["SEC-01", "PERF-02"],
2058
+ acceptance_criteria: [
2059
+ "API routes created for core operations",
2060
+ "Input validation on all endpoints",
2061
+ "Error responses standardized"
2062
+ ]
2063
+ },
2064
+ {
2065
+ id: "task-2.3-data-layer",
2066
+ title: "Set up data access layer",
2067
+ status: "todo",
2068
+ quality_scenarios: ["MAINT-01"],
2069
+ acceptance_criteria: [
2070
+ "Data fetching patterns established",
2071
+ "Caching strategy implemented",
2072
+ "Type-safe data access"
2073
+ ]
2074
+ },
2075
+ {
2076
+ id: "task-2.4-integration-tests",
2077
+ title: "Write integration tests for core flows",
2078
+ status: "todo",
2079
+ quality_scenarios: ["MAINT-02"],
2080
+ acceptance_criteria: [
2081
+ "Happy path tested for each core feature",
2082
+ "API route tests written",
2083
+ "Test coverage meets threshold"
2084
+ ]
2085
+ },
2086
+ {
2087
+ id: "task-2.5-document-decisions",
2088
+ title: "Document Phase 2 architectural decisions",
2089
+ status: "todo",
2090
+ quality_scenarios: [],
2091
+ acceptance_criteria: [
2092
+ "ADRs for data fetching and API design choices",
2093
+ "Building blocks updated with new code paths"
2094
+ ]
2095
+ }
2096
+ ]
2097
+ },
2098
+ "phase-3-polish": {
2099
+ schema_version: 1,
2100
+ phase_id: "phase-3-polish",
2101
+ tasks: [
2102
+ {
2103
+ id: "task-3.1-error-handling",
2104
+ title: "Implement comprehensive error handling",
2105
+ status: "todo",
2106
+ quality_scenarios: ["SEC-01"],
2107
+ acceptance_criteria: [
2108
+ "Error boundaries at route level",
2109
+ "Custom error.tsx and not-found.tsx pages",
2110
+ "API error responses standardized"
2111
+ ]
2112
+ },
2113
+ {
2114
+ id: "task-3.2-accessibility",
2115
+ title: "Accessibility audit and fixes",
2116
+ status: "todo",
2117
+ quality_scenarios: ["A11Y-01", "A11Y-02"],
2118
+ acceptance_criteria: [
2119
+ "Keyboard navigation works for all interactive elements",
2120
+ "Screen reader compatible (ARIA labels, roles)",
2121
+ "WCAG 2.1 AA compliance verified"
2122
+ ]
2123
+ },
2124
+ {
2125
+ id: "task-3.3-performance",
2126
+ title: "Performance optimization",
2127
+ status: "todo",
2128
+ quality_scenarios: ["PERF-01"],
2129
+ acceptance_criteria: [
2130
+ "Bundle size optimized (dynamic imports, tree shaking)",
2131
+ "Core Web Vitals meet thresholds (LCP < 2.5s)",
2132
+ "Server-side rendering verified for SEO pages"
2133
+ ]
2134
+ },
2135
+ {
2136
+ id: "task-3.4-deployment",
2137
+ title: "Configure production deployment",
2138
+ status: "todo",
2139
+ quality_scenarios: [],
2140
+ acceptance_criteria: [
2141
+ "Production build configuration verified",
2142
+ "Environment variables documented",
2143
+ "Deployment to Vercel/hosting configured"
2144
+ ]
2145
+ }
2146
+ ]
1886
2147
  }
1887
2148
  };
1888
2149
  return tasksByPhase[phaseId] ?? null;
@@ -2050,6 +2311,105 @@ function phaseTasksTemplate2(_input, phaseId) {
2050
2311
  ]
2051
2312
  }
2052
2313
  ]
2314
+ },
2315
+ "phase-2-features": {
2316
+ schema_version: 1,
2317
+ phase_id: "phase-2-features",
2318
+ tasks: [
2319
+ {
2320
+ id: "task-2.1-core-feature",
2321
+ title: "Implement primary feature",
2322
+ status: "todo",
2323
+ quality_scenarios: ["PERF-01"],
2324
+ acceptance_criteria: [
2325
+ "Core user flow works end-to-end",
2326
+ "Unit and component tests written",
2327
+ "Meets performance budgets"
2328
+ ]
2329
+ },
2330
+ {
2331
+ id: "task-2.2-state-management",
2332
+ title: "Set up state management for complex data",
2333
+ status: "todo",
2334
+ quality_scenarios: ["MAINT-01"],
2335
+ acceptance_criteria: [
2336
+ "State management pattern established",
2337
+ "Data fetching/caching strategy implemented",
2338
+ "Loading and error states handled"
2339
+ ]
2340
+ },
2341
+ {
2342
+ id: "task-2.3-integration-tests",
2343
+ title: "Write integration tests for core flows",
2344
+ status: "todo",
2345
+ quality_scenarios: ["MAINT-02"],
2346
+ acceptance_criteria: [
2347
+ "Happy path tested for each core feature",
2348
+ "Error scenarios tested",
2349
+ "Test coverage meets threshold"
2350
+ ]
2351
+ },
2352
+ {
2353
+ id: "task-2.4-document-decisions",
2354
+ title: "Document Phase 2 architectural decisions",
2355
+ status: "todo",
2356
+ quality_scenarios: [],
2357
+ acceptance_criteria: [
2358
+ "ADRs for state management and data fetching choices",
2359
+ "Building blocks updated with new code paths"
2360
+ ]
2361
+ }
2362
+ ]
2363
+ },
2364
+ "phase-3-polish": {
2365
+ schema_version: 1,
2366
+ phase_id: "phase-3-polish",
2367
+ tasks: [
2368
+ {
2369
+ id: "task-3.1-error-handling",
2370
+ title: "Implement comprehensive error handling",
2371
+ status: "todo",
2372
+ quality_scenarios: ["SEC-01"],
2373
+ acceptance_criteria: [
2374
+ "Error boundaries at route level",
2375
+ "User-friendly error messages",
2376
+ "Error logging configured"
2377
+ ]
2378
+ },
2379
+ {
2380
+ id: "task-3.2-accessibility",
2381
+ title: "Accessibility audit and fixes",
2382
+ status: "todo",
2383
+ quality_scenarios: ["A11Y-01", "A11Y-02"],
2384
+ acceptance_criteria: [
2385
+ "Keyboard navigation works for all interactive elements",
2386
+ "Screen reader compatible (ARIA labels, roles)",
2387
+ "WCAG 2.1 AA compliance verified"
2388
+ ]
2389
+ },
2390
+ {
2391
+ id: "task-3.3-performance",
2392
+ title: "Performance optimization",
2393
+ status: "todo",
2394
+ quality_scenarios: ["PERF-01"],
2395
+ acceptance_criteria: [
2396
+ "Bundle size optimized (code splitting, tree shaking)",
2397
+ "Lighthouse performance score >= 90",
2398
+ "No unnecessary re-renders in hot paths"
2399
+ ]
2400
+ },
2401
+ {
2402
+ id: "task-3.4-deployment",
2403
+ title: "Configure production build and deployment",
2404
+ status: "todo",
2405
+ quality_scenarios: [],
2406
+ acceptance_criteria: [
2407
+ "Production build configuration verified",
2408
+ "Deployment pipeline configured",
2409
+ "Environment variables documented"
2410
+ ]
2411
+ }
2412
+ ]
2053
2413
  }
2054
2414
  };
2055
2415
  return tasksByPhase[phaseId] ?? null;
@@ -2568,6 +2928,18 @@ You are responsible for maintaining these sections in \`.arcbridge/arc42/\`. Upd
2568
2928
  - All ADRs and their status
2569
2929
  - Building block \u2192 code mapping
2570
2930
 
2931
+ ## Project Planning
2932
+
2933
+ At the start of a project (after init), plan tasks across ALL phases:
2934
+ - **Phase 0-1 tasks are ready to use** \u2014 they cover setup and foundation for this template
2935
+ - **Phase 2+ tasks are examples** \u2014 replace them with real tasks derived from the project's requirements and specifications
2936
+ - Review the phase plan with \`arcbridge_get_phase_plan\` and replace example tasks with project-specific ones
2937
+ - Create tasks using \`arcbridge_create_task\` with the phase ID shown in the plan
2938
+ - Keep each phase reasonably scoped \u2014 3-6 tasks per phase is ideal
2939
+ - Map tasks to building blocks so drift detection tracks coverage
2940
+ - 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
+
2571
2943
  ## Working Style
2572
2944
 
2573
2945
  Think at the system level. Before making changes, consider:
@@ -2805,13 +3177,25 @@ function phaseManagerTemplate() {
2805
3177
  - Tasks must be "done" before a phase can complete
2806
3178
  - Quality scenarios linked to phase tasks must be verified
2807
3179
 
3180
+ ## Task Planning
3181
+
3182
+ Before starting any phase, ensure proper task planning:
3183
+ - **Phase 0-1 tasks are concrete** \u2014 they cover project setup and foundation. Follow them as-is.
3184
+ - **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.
3186
+ - **Keep phases small and focused** \u2014 if a phase has more than 6-8 tasks, split it into sub-phases
3187
+ - **Tasks should be concrete and verifiable** \u2014 each task needs clear acceptance criteria
3188
+ - **Link tasks to building blocks** \u2014 this enables drift detection and progress tracking
3189
+ - **Use \`arcbridge_create_task\` with the phase ID** shown in \`arcbridge_get_phase_plan\` output (e.g., \`phase-2-features\`)
3190
+
2808
3191
  ## Phase Transition Process
2809
3192
 
2810
3193
  1. Verify all tasks in current phase are "done"
2811
- 2. Run drift detection (check_drift)
2812
- 3. Propose arc42 updates if drift is detected
2813
- 4. Check quality gate requirements
2814
- 5. Mark phase complete or report blockers`
3194
+ 2. Review task coverage for the NEXT phase \u2014 create tasks if empty
3195
+ 3. Run drift detection (\`arcbridge_check_drift\`)
3196
+ 4. Propose arc42 updates if drift is detected
3197
+ 5. Check quality gate requirements
3198
+ 6. Mark phase complete or report blockers`
2815
3199
  };
2816
3200
  }
2817
3201
 
@@ -3275,7 +3659,8 @@ function refreshFromDocs(db, targetDir) {
3275
3659
  const scenarioStatusMap = new Map(
3276
3660
  existingScenarios.map((s) => [s.id, s.status])
3277
3661
  );
3278
- const refresh = db.transaction(() => {
3662
+ db.exec("PRAGMA foreign_keys = OFF");
3663
+ const refresh = () => transaction(db, () => {
3279
3664
  db.prepare("DELETE FROM tasks").run();
3280
3665
  db.prepare("DELETE FROM phases").run();
3281
3666
  db.prepare("UPDATE contracts SET building_block = NULL").run();
@@ -3287,6 +3672,16 @@ function refreshFromDocs(db, targetDir) {
3287
3672
  warnings.push(...populateQualityScenarios(db, targetDir));
3288
3673
  warnings.push(...populatePhases(db, targetDir));
3289
3674
  warnings.push(...populateAdrs(db, targetDir));
3675
+ const orphaned = db.prepare(`
3676
+ UPDATE tasks SET building_block = NULL
3677
+ WHERE building_block IS NOT NULL
3678
+ AND building_block NOT IN (SELECT id FROM building_blocks)
3679
+ `).run();
3680
+ if (orphaned.changes > 0) {
3681
+ warnings.push(
3682
+ `${orphaned.changes} task(s) referenced removed building blocks (references cleared)`
3683
+ );
3684
+ }
3290
3685
  const restoreTask = db.prepare(
3291
3686
  "UPDATE tasks SET status = ?, completed_at = ? WHERE id = ?"
3292
3687
  );
@@ -3312,7 +3707,11 @@ function refreshFromDocs(db, targetDir) {
3312
3707
  }
3313
3708
  }
3314
3709
  });
3315
- refresh();
3710
+ try {
3711
+ refresh();
3712
+ } finally {
3713
+ db.exec("PRAGMA foreign_keys = ON");
3714
+ }
3316
3715
  return warnings;
3317
3716
  }
3318
3717
  function generateDatabase(targetDir, input) {
@@ -3326,12 +3725,12 @@ function generateDatabase(targetDir, input) {
3326
3725
  upsert.run("project_name", input.name);
3327
3726
  upsert.run("project_type", input.template);
3328
3727
  upsert.run("last_full_index", (/* @__PURE__ */ new Date()).toISOString());
3329
- db.transaction(() => {
3728
+ transaction(db, () => {
3330
3729
  allWarnings.push(...populateBuildingBlocks(db, targetDir));
3331
3730
  allWarnings.push(...populateQualityScenarios(db, targetDir));
3332
3731
  allWarnings.push(...populatePhases(db, targetDir));
3333
3732
  allWarnings.push(...populateAdrs(db, targetDir));
3334
- })();
3733
+ });
3335
3734
  ensureGitignore(targetDir);
3336
3735
  return { db, warnings: allWarnings };
3337
3736
  }
@@ -3359,7 +3758,7 @@ import YAML from "yaml";
3359
3758
 
3360
3759
  // src/indexer/program.ts
3361
3760
  import ts from "typescript";
3362
- import { join as join7 } from "path";
3761
+ import { join as join7, dirname } from "path";
3363
3762
  function createTsProgram(options) {
3364
3763
  const projectRoot = options.projectRoot;
3365
3764
  const configPath = options.tsconfigPath ?? ts.findConfigFile(projectRoot, ts.sys.fileExists, "tsconfig.json");
@@ -3368,7 +3767,7 @@ function createTsProgram(options) {
3368
3767
  `No tsconfig.json found in ${projectRoot}. TypeScript indexing requires a tsconfig.json.`
3369
3768
  );
3370
3769
  }
3371
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
3770
+ let configFile = ts.readConfigFile(configPath, ts.sys.readFile);
3372
3771
  if (configFile.error) {
3373
3772
  const message = ts.flattenDiagnosticMessageText(
3374
3773
  configFile.error.messageText,
@@ -3376,12 +3775,45 @@ function createTsProgram(options) {
3376
3775
  );
3377
3776
  throw new Error(`Failed to read tsconfig.json: ${message}`);
3378
3777
  }
3778
+ let resolvedConfigPath = configPath;
3779
+ const config = configFile.config;
3780
+ const hasOwnFiles = config.include && config.include.length > 0 || config.files && config.files.length > 0;
3781
+ if (config.references && !hasOwnFiles) {
3782
+ for (const ref of config.references) {
3783
+ const refRelPath = typeof ref === "string" ? ref : ref.path;
3784
+ if (!refRelPath) continue;
3785
+ const refFullPath = join7(dirname(configPath), refRelPath);
3786
+ const refConfigPath = refFullPath.endsWith(".json") ? refFullPath : join7(refFullPath, "tsconfig.json");
3787
+ if (ts.sys.fileExists(refConfigPath)) {
3788
+ const refConfig = ts.readConfigFile(refConfigPath, ts.sys.readFile);
3789
+ const rc = refConfig.config;
3790
+ if (!refConfig.error && (rc.include?.length > 0 || rc.files?.length > 0)) {
3791
+ configFile = refConfig;
3792
+ resolvedConfigPath = refConfigPath;
3793
+ break;
3794
+ }
3795
+ }
3796
+ }
3797
+ if (resolvedConfigPath === configPath) {
3798
+ for (const candidate of ["tsconfig.app.json", "tsconfig.src.json"]) {
3799
+ const refPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, candidate);
3800
+ if (refPath) {
3801
+ const refConfig = ts.readConfigFile(refPath, ts.sys.readFile);
3802
+ if (!refConfig.error) {
3803
+ configFile = refConfig;
3804
+ resolvedConfigPath = refPath;
3805
+ break;
3806
+ }
3807
+ }
3808
+ }
3809
+ }
3810
+ }
3379
3811
  const parsed = ts.parseJsonConfigFileContent(
3380
3812
  configFile.config,
3381
3813
  ts.sys,
3382
3814
  join7(projectRoot),
3383
3815
  { noEmit: true },
3384
- configPath
3816
+ resolvedConfigPath
3385
3817
  );
3386
3818
  const program = ts.createProgram({
3387
3819
  rootNames: parsed.fileNames,
@@ -4130,7 +4562,7 @@ function writeComponents(db, components) {
4130
4562
  const existingIds = new Set(
4131
4563
  db.prepare("SELECT id FROM symbols").all().map((r) => r.id)
4132
4564
  );
4133
- const run = db.transaction(() => {
4565
+ transaction(db, () => {
4134
4566
  for (const c of components) {
4135
4567
  if (!existingIds.has(c.symbolId)) continue;
4136
4568
  insert.run(
@@ -4144,7 +4576,6 @@ function writeComponents(db, components) {
4144
4576
  );
4145
4577
  }
4146
4578
  });
4147
- run();
4148
4579
  }
4149
4580
 
4150
4581
  // src/indexer/route-analyzer.ts
@@ -4273,7 +4704,7 @@ function writeRoutes(db, routes) {
4273
4704
  id, route_path, kind, http_methods, has_auth, parent_layout, service
4274
4705
  ) VALUES (?, ?, ?, ?, ?, ?, ?)
4275
4706
  `);
4276
- const run = db.transaction(() => {
4707
+ transaction(db, () => {
4277
4708
  for (const r of routes) {
4278
4709
  insert.run(
4279
4710
  r.id,
@@ -4286,7 +4717,6 @@ function writeRoutes(db, routes) {
4286
4717
  );
4287
4718
  }
4288
4719
  });
4289
- run();
4290
4720
  }
4291
4721
 
4292
4722
  // src/indexer/content-hash.ts
@@ -4320,7 +4750,7 @@ function removeSymbolsForFiles(db, filePaths) {
4320
4750
  const deleteComponents = db.prepare(
4321
4751
  "DELETE FROM components WHERE symbol_id IN (SELECT id FROM symbols WHERE file_path = ?)"
4322
4752
  );
4323
- const run = db.transaction(() => {
4753
+ transaction(db, () => {
4324
4754
  for (const fp of filePaths) {
4325
4755
  deleteDepsSource.run(fp);
4326
4756
  deleteDepsTarget.run(fp);
@@ -4328,7 +4758,6 @@ function removeSymbolsForFiles(db, filePaths) {
4328
4758
  deleteSymbols.run(fp);
4329
4759
  }
4330
4760
  });
4331
- run();
4332
4761
  }
4333
4762
  function writeSymbols(db, symbols, service, language = "typescript") {
4334
4763
  if (symbols.length === 0) return;
@@ -4348,7 +4777,7 @@ function writeSymbols(db, symbols, service, language = "typescript") {
4348
4777
  )
4349
4778
  `);
4350
4779
  const now = (/* @__PURE__ */ new Date()).toISOString();
4351
- const run = db.transaction(() => {
4780
+ transaction(db, () => {
4352
4781
  for (const s of symbols) {
4353
4782
  insert.run(
4354
4783
  s.id,
@@ -4372,7 +4801,6 @@ function writeSymbols(db, symbols, service, language = "typescript") {
4372
4801
  );
4373
4802
  }
4374
4803
  });
4375
- run();
4376
4804
  }
4377
4805
  function writeDependencies(db, dependencies) {
4378
4806
  if (dependencies.length === 0) return;
@@ -4380,17 +4808,16 @@ function writeDependencies(db, dependencies) {
4380
4808
  INSERT OR IGNORE INTO dependencies (source_symbol, target_symbol, kind)
4381
4809
  VALUES (?, ?, ?)
4382
4810
  `);
4383
- const run = db.transaction(() => {
4811
+ transaction(db, () => {
4384
4812
  for (const dep of dependencies) {
4385
4813
  insert.run(dep.sourceSymbolId, dep.targetSymbolId, dep.kind);
4386
4814
  }
4387
4815
  });
4388
- run();
4389
4816
  }
4390
4817
 
4391
4818
  // src/indexer/dotnet-indexer.ts
4392
4819
  import { execFileSync } from "child_process";
4393
- import { resolve, join as join9, dirname, relative as relative4, basename } from "path";
4820
+ import { resolve, join as join9, dirname as dirname2, relative as relative4, basename } from "path";
4394
4821
  import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4, accessSync, constants } from "fs";
4395
4822
  import { fileURLToPath } from "url";
4396
4823
  function findDotnetProject(projectRoot) {
@@ -4407,7 +4834,7 @@ function findDotnetProject(projectRoot) {
4407
4834
  }
4408
4835
  function parseSolutionProjects(slnPath) {
4409
4836
  const content = readFileSync3(slnPath, "utf-8");
4410
- const slnDir = dirname(slnPath);
4837
+ const slnDir = dirname2(slnPath);
4411
4838
  const projects = [];
4412
4839
  const projectPattern = /Project\("\{[^}]+\}"\)\s*=\s*"([^"]+)",\s*"([^"]+\.csproj)"/g;
4413
4840
  let match;
@@ -4416,7 +4843,7 @@ function parseSolutionProjects(slnPath) {
4416
4843
  const relativeCsprojPath = match[2].replace(/\\/g, "/");
4417
4844
  const fullCsprojPath = resolve(join9(slnDir, relativeCsprojPath));
4418
4845
  if (!existsSync4(fullCsprojPath)) continue;
4419
- const projectDir = relative4(slnDir, dirname(fullCsprojPath)).replace(/\\/g, "/") || ".";
4846
+ const projectDir = relative4(slnDir, dirname2(fullCsprojPath)).replace(/\\/g, "/") || ".";
4420
4847
  const isTestProject = /[.\x2d]tests?$/i.test(name) || /[.\x2d](unit|integration|functional|e2e)tests?$/i.test(name);
4421
4848
  projects.push({
4422
4849
  name,
@@ -4461,7 +4888,7 @@ function hasGlobalTool() {
4461
4888
  return false;
4462
4889
  }
4463
4890
  function resolveIndexerProject() {
4464
- const currentDir2 = dirname(fileURLToPath(import.meta.url));
4891
+ const currentDir2 = dirname2(fileURLToPath(import.meta.url));
4465
4892
  const candidates = [
4466
4893
  resolve(currentDir2, "../../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
4467
4894
  resolve(currentDir2, "../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
@@ -4582,7 +5009,7 @@ function indexDotnetProjectRoslyn(db, options) {
4582
5009
  INSERT OR REPLACE INTO routes (id, route_path, kind, http_methods, has_auth, service)
4583
5010
  VALUES (?, ?, ?, ?, ?, ?)
4584
5011
  `);
4585
- const runRoutes = db.transaction(() => {
5012
+ transaction(db, () => {
4586
5013
  for (const route of output.routes) {
4587
5014
  insertRoute.run(
4588
5015
  route.id,
@@ -4594,7 +5021,6 @@ function indexDotnetProjectRoslyn(db, options) {
4594
5021
  );
4595
5022
  }
4596
5023
  });
4597
- runRoutes();
4598
5024
  }
4599
5025
  return {
4600
5026
  symbolsIndexed: output.symbols.length,
@@ -4616,10 +5042,10 @@ import { globbySync } from "globby";
4616
5042
 
4617
5043
  // src/indexer/csharp/parser.ts
4618
5044
  import { accessSync as accessSync2, constants as constants2 } from "fs";
4619
- import { dirname as dirname2, resolve as resolve2 } from "path";
5045
+ import { dirname as dirname3, resolve as resolve2 } from "path";
4620
5046
  import { fileURLToPath as fileURLToPath2 } from "url";
4621
5047
  import "web-tree-sitter";
4622
- var currentDir = dirname2(fileURLToPath2(import.meta.url));
5048
+ var currentDir = dirname3(fileURLToPath2(import.meta.url));
4623
5049
  var cachedParser = null;
4624
5050
  var initPromise = null;
4625
5051
  function resolveGrammarPath() {
@@ -5513,7 +5939,7 @@ async function indexCSharpTreeSitter(db, options) {
5513
5939
  INSERT OR REPLACE INTO routes (id, route_path, kind, http_methods, has_auth, service)
5514
5940
  VALUES (?, ?, ?, ?, ?, ?)
5515
5941
  `);
5516
- const runRoutes = db.transaction(() => {
5942
+ transaction(db, () => {
5517
5943
  for (const route of allRoutes) {
5518
5944
  insertRoute.run(
5519
5945
  route.id,
@@ -5525,7 +5951,6 @@ async function indexCSharpTreeSitter(db, options) {
5525
5951
  );
5526
5952
  }
5527
5953
  });
5528
- runRoutes();
5529
5954
  }
5530
5955
  return {
5531
5956
  symbolsIndexed: allNewSymbols.length,
@@ -5557,12 +5982,11 @@ function indexPackageDependencies(db, projectRoot, service = "main") {
5557
5982
  const insert = db.prepare(
5558
5983
  "INSERT OR IGNORE INTO package_dependencies (name, version, source, service) VALUES (?, ?, ?, ?)"
5559
5984
  );
5560
- const run = db.transaction(() => {
5985
+ transaction(db, () => {
5561
5986
  for (const dep of deps) {
5562
5987
  insert.run(dep.name, dep.version, dep.source, service);
5563
5988
  }
5564
5989
  });
5565
- run();
5566
5990
  return deps.length;
5567
5991
  }
5568
5992
  function parsePackageJson(filePath) {
@@ -5815,12 +6239,11 @@ function writeDriftLog(db, entries) {
5815
6239
  VALUES (?, ?, ?, ?, ?, ?)
5816
6240
  `);
5817
6241
  const now = (/* @__PURE__ */ new Date()).toISOString();
5818
- const run = db.transaction(() => {
6242
+ transaction(db, () => {
5819
6243
  for (const e of entries) {
5820
6244
  insert.run(now, e.kind, e.severity, e.description, e.affectedBlock, e.affectedFile);
5821
6245
  }
5822
6246
  });
5823
- run();
5824
6247
  }
5825
6248
  function detectUndocumentedModules(db, entries, ignorePaths = []) {
5826
6249
  const blocks = db.prepare("SELECT id, name, code_paths FROM building_blocks").all();
@@ -5978,7 +6401,17 @@ function detectNewDependencies(db, entries) {
5978
6401
  const adrs = db.prepare("SELECT id, title, context, decision FROM adrs WHERE status != 'superseded'").all();
5979
6402
  const adrText = adrs.map((a) => `${a.title} ${a.context ?? ""} ${a.decision ?? ""}`.toLowerCase()).join(" ");
5980
6403
  const trivialPackages = /* @__PURE__ */ new Set([
5981
- // npm
6404
+ // npm — frameworks (core deps that don't need ADRs)
6405
+ "react",
6406
+ "react-dom",
6407
+ "next",
6408
+ "vite",
6409
+ "@vitejs/plugin-react",
6410
+ "express",
6411
+ "fastify",
6412
+ "hono",
6413
+ "koa",
6414
+ // npm — dev tooling
5982
6415
  "typescript",
5983
6416
  "eslint",
5984
6417
  "prettier",
@@ -5986,15 +6419,28 @@ function detectNewDependencies(db, entries) {
5986
6419
  "jest",
5987
6420
  "@types/node",
5988
6421
  "@types/react",
6422
+ "@types/react-dom",
5989
6423
  "tsup",
5990
6424
  "tsx",
5991
- // nuget
6425
+ "ts-node",
6426
+ "nodemon",
6427
+ "@eslint/js",
6428
+ "typescript-eslint",
6429
+ // npm — build/bundler
6430
+ "esbuild",
6431
+ "rollup",
6432
+ "webpack",
6433
+ "postcss",
6434
+ "tailwindcss",
6435
+ "autoprefixer",
6436
+ // nuget — test
5992
6437
  "microsoft.net.test.sdk",
5993
6438
  "xunit",
5994
6439
  "xunit.runner.visualstudio",
5995
6440
  "nunit",
5996
6441
  "nunit3testadapter",
5997
6442
  "coverlet.collector",
6443
+ // nuget — framework
5998
6444
  "microsoft.aspnetcore.openapi",
5999
6445
  "swashbuckle.aspnetcore"
6000
6446
  ]);
@@ -6139,12 +6585,11 @@ function applyInferences(db, inferences, projectRoot) {
6139
6585
  "UPDATE tasks SET status = ?, completed_at = CASE WHEN ? = 'done' THEN ? ELSE completed_at END WHERE id = ?"
6140
6586
  );
6141
6587
  const now = (/* @__PURE__ */ new Date()).toISOString();
6142
- const run = db.transaction(() => {
6588
+ transaction(db, () => {
6143
6589
  for (const inf of inferences) {
6144
6590
  update.run(inf.inferredStatus, inf.inferredStatus, now, inf.taskId);
6145
6591
  }
6146
6592
  });
6147
- run();
6148
6593
  for (const inf of inferences) {
6149
6594
  const task = db.prepare("SELECT phase_id FROM tasks WHERE id = ?").get(inf.taskId);
6150
6595
  if (task) {
@@ -6250,7 +6695,7 @@ function safeParseJson2(value, fallback) {
6250
6695
 
6251
6696
  // src/generators/sync-generator.ts
6252
6697
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
6253
- import { join as join15, dirname as dirname3 } from "path";
6698
+ import { join as join15, dirname as dirname4 } from "path";
6254
6699
 
6255
6700
  // src/templates/sync/claude-skill.ts
6256
6701
  function claudeSkillTemplate(config) {
@@ -6429,20 +6874,20 @@ function generateSyncFiles(targetDir, config) {
6429
6874
  const generated = [];
6430
6875
  const action = githubActionTemplate(config);
6431
6876
  const actionPath = join15(targetDir, action.relativePath);
6432
- mkdirSync6(dirname3(actionPath), { recursive: true });
6877
+ mkdirSync6(dirname4(actionPath), { recursive: true });
6433
6878
  writeFileSync6(actionPath, action.content, "utf-8");
6434
6879
  generated.push(action.relativePath);
6435
6880
  if (config.platforms.includes("claude")) {
6436
6881
  const skill = claudeSkillTemplate(config);
6437
6882
  const skillPath = join15(targetDir, skill.relativePath);
6438
- mkdirSync6(dirname3(skillPath), { recursive: true });
6883
+ mkdirSync6(dirname4(skillPath), { recursive: true });
6439
6884
  writeFileSync6(skillPath, skill.content, "utf-8");
6440
6885
  generated.push(skill.relativePath);
6441
6886
  }
6442
6887
  if (config.platforms.includes("copilot")) {
6443
6888
  const hook = copilotHookTemplate(config);
6444
6889
  const hookPath = join15(targetDir, hook.relativePath);
6445
- mkdirSync6(dirname3(hookPath), { recursive: true });
6890
+ mkdirSync6(dirname4(hookPath), { recursive: true });
6446
6891
  writeFileSync6(hookPath, hook.content, "utf-8");
6447
6892
  generated.push(hook.relativePath);
6448
6893
  }
@@ -6798,6 +7243,7 @@ function resolveRef(projectRoot, since, db) {
6798
7243
  }
6799
7244
  }
6800
7245
  function getChangedFiles(projectRoot, ref) {
7246
+ const byPath = /* @__PURE__ */ new Map();
6801
7247
  try {
6802
7248
  execFileSync3("git", ["rev-parse", "--verify", ref], {
6803
7249
  cwd: projectRoot,
@@ -6810,16 +7256,24 @@ function getChangedFiles(projectRoot, ref) {
6810
7256
  ["diff", "--name-status", "--no-renames", ref, "HEAD"],
6811
7257
  { cwd: projectRoot, encoding: "utf-8", timeout: 1e4 }
6812
7258
  ).trim();
6813
- if (!output) return [];
6814
- return output.split("\n").map((line) => {
6815
- const [statusCode, ...pathParts] = line.split(" ");
6816
- const path = pathParts.join(" ");
6817
- const status = parseStatusCode(statusCode ?? "M");
6818
- return { status, path };
6819
- });
7259
+ if (output) {
7260
+ for (const line of output.split("\n")) {
7261
+ const [statusCode, ...pathParts] = line.split(" ");
7262
+ const path = pathParts.join(" ");
7263
+ byPath.set(path, { status: parseStatusCode(statusCode ?? "M"), path });
7264
+ }
7265
+ }
6820
7266
  } catch {
6821
- return getUncommittedChanges(projectRoot);
6822
7267
  }
7268
+ for (const change of getUncommittedChanges(projectRoot)) {
7269
+ const existing = byPath.get(change.path);
7270
+ if (!existing) {
7271
+ byPath.set(change.path, change);
7272
+ } else if (change.status === "deleted") {
7273
+ byPath.set(change.path, change);
7274
+ }
7275
+ }
7276
+ return Array.from(byPath.values());
6823
7277
  }
6824
7278
  function getUncommittedChanges(projectRoot) {
6825
7279
  try {
@@ -6827,11 +7281,12 @@ function getUncommittedChanges(projectRoot) {
6827
7281
  "git",
6828
7282
  ["status", "--porcelain", "-uno"],
6829
7283
  { cwd: projectRoot, encoding: "utf-8", timeout: 5e3 }
6830
- ).trim();
6831
- if (!output) return [];
6832
- return output.split("\n").map((line) => {
7284
+ );
7285
+ const lines = output.split("\n").filter((l) => l.length >= 3);
7286
+ if (lines.length === 0) return [];
7287
+ return lines.map((line) => {
6833
7288
  const statusCode = line.slice(0, 2).trim();
6834
- const path = line.slice(3);
7289
+ const path = line.slice(3).replace(/\r$/, "");
6835
7290
  const status = statusCode === "D" ? "deleted" : statusCode === "A" ? "added" : "modified";
6836
7291
  return { status, path };
6837
7292
  });
@@ -7086,9 +7541,11 @@ export {
7086
7541
  refreshFromDocs,
7087
7542
  resolveRef,
7088
7543
  setSyncCommit,
7544
+ suppressSqliteWarning,
7089
7545
  syncPhaseToYaml,
7090
7546
  syncScenarioToYaml,
7091
7547
  syncTaskToYaml,
7548
+ transaction,
7092
7549
  verifyScenarios,
7093
7550
  writeDriftLog
7094
7551
  };