@arcbridge/core 0.5.0 → 0.6.0

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
@@ -54,7 +54,8 @@ var ArcBridgeConfigSchema = z2.object({
54
54
  "api-service",
55
55
  "dotnet-webapi",
56
56
  "unity-game",
57
- "angular-app"
57
+ "angular-app",
58
+ "fullstack-nextjs-dotnet"
58
59
  ]).default("nextjs-app-router"),
59
60
  services: z2.array(ServiceSchema).default([]),
60
61
  platforms: z2.array(z2.enum(["claude", "copilot", "gemini", "codex", "opencode"])).default(["claude"]),
@@ -809,6 +810,46 @@ function configTemplate6(input) {
809
810
  };
810
811
  }
811
812
 
813
+ // src/templates/config/fullstack-nextjs-dotnet.ts
814
+ function configTemplate7(input) {
815
+ return {
816
+ schema_version: 1,
817
+ project_name: input.name,
818
+ project_type: "fullstack-nextjs-dotnet",
819
+ services: [
820
+ { name: "frontend", path: "frontend", type: "nextjs", tsconfig: "frontend/tsconfig.json" },
821
+ { name: "api", path: "api", type: "dotnet" }
822
+ ],
823
+ platforms: input.platforms,
824
+ quality_priorities: input.quality_priorities,
825
+ indexing: {
826
+ include: [
827
+ "frontend/src/**/*",
828
+ "frontend/app/**/*",
829
+ "api/**/*.cs",
830
+ "shared/**/*",
831
+ "contracts/**/*"
832
+ ],
833
+ exclude: ["node_modules", "dist", ".next", "bin", "obj", "coverage"],
834
+ default_mode: "fast",
835
+ csharp_indexer: "auto"
836
+ },
837
+ testing: {
838
+ test_command: "npm test",
839
+ timeout_ms: 6e4
840
+ },
841
+ drift: {
842
+ ignore_paths: []
843
+ },
844
+ metrics: { auto_record: false },
845
+ sync: {
846
+ auto_detect_drift: true,
847
+ drift_severity_threshold: "warning",
848
+ propose_updates_on: "phase-complete"
849
+ }
850
+ };
851
+ }
852
+
812
853
  // src/generators/config-generator.ts
813
854
  var configTemplates = {
814
855
  "nextjs-app-router": configTemplate,
@@ -816,7 +857,8 @@ var configTemplates = {
816
857
  "api-service": configTemplate3,
817
858
  "dotnet-webapi": configTemplate4,
818
859
  "unity-game": configTemplate5,
819
- "angular-app": configTemplate6
860
+ "angular-app": configTemplate6,
861
+ "fullstack-nextjs-dotnet": configTemplate7
820
862
  };
821
863
  function generateConfig(targetDir, input) {
822
864
  const templateFn = configTemplates[input.template] ?? configTemplate;
@@ -845,7 +887,7 @@ function introductionTemplate(input) {
845
887
 
846
888
  ## Requirements Overview
847
889
 
848
- ${input.name} is a ${input.template === "nextjs-app-router" ? "Next.js application using the App Router" : input.template === "angular-app" ? "Angular application with standalone components" : input.template === "dotnet-webapi" ? "ASP.NET Core Web API" : input.template === "unity-game" ? "Unity game built with a code-heavy C# architecture" : "web application"}.
890
+ ${input.name} is a ${input.template === "nextjs-app-router" ? "Next.js application using the App Router" : input.template === "angular-app" ? "Angular application with standalone components" : input.template === "dotnet-webapi" ? "ASP.NET Core Web API" : input.template === "unity-game" ? "Unity game built with a code-heavy C# architecture" : input.template === "fullstack-nextjs-dotnet" ? "fullstack application with a Next.js frontend and .NET API backend" : "web application"}.
849
891
 
850
892
  ### Key Features
851
893
 
@@ -1047,7 +1089,66 @@ function getEntrypoints(template, srcPrefix, appPrefix) {
1047
1089
  function buildingBlocksTemplate(input) {
1048
1090
  const now = (/* @__PURE__ */ new Date()).toISOString();
1049
1091
  const layout = detectProjectLayout(input.projectRoot, input.template);
1050
- const defaultBlocks = input.template === "dotnet-webapi" ? buildDotnetBlocks(input) : input.template === "unity-game" ? buildUnityBlocks() : input.template === "angular-app" ? buildAngularBlocks(layout) : buildJsBlocks(input, layout);
1092
+ const defaultBlocks = input.template === "fullstack-nextjs-dotnet" ? buildFullstackBlocks() : input.template === "dotnet-webapi" ? buildDotnetBlocks(input) : input.template === "unity-game" ? buildUnityBlocks() : input.template === "angular-app" ? buildAngularBlocks(layout) : buildJsBlocks(input, layout);
1093
+ function buildFullstackBlocks() {
1094
+ return [
1095
+ {
1096
+ id: "frontend-shell",
1097
+ name: "Frontend Shell",
1098
+ level: 1,
1099
+ code_paths: ["frontend/app/", "frontend/src/app/"],
1100
+ interfaces: [],
1101
+ quality_scenarios: ["PERF-01"],
1102
+ adrs: [],
1103
+ responsibility: "Next.js App Router layouts, pages, and top-level page structure",
1104
+ service: "frontend"
1105
+ },
1106
+ {
1107
+ id: "frontend-components",
1108
+ name: "Frontend Components",
1109
+ level: 1,
1110
+ code_paths: ["frontend/src/components/", "frontend/components/"],
1111
+ interfaces: [],
1112
+ quality_scenarios: ["A11Y-01"],
1113
+ adrs: [],
1114
+ responsibility: "Shared, reusable React UI components",
1115
+ service: "frontend"
1116
+ },
1117
+ {
1118
+ id: "api-controllers",
1119
+ name: "API Controllers",
1120
+ level: 1,
1121
+ code_paths: ["api/Controllers/"],
1122
+ interfaces: [],
1123
+ quality_scenarios: ["SEC-03"],
1124
+ adrs: [],
1125
+ responsibility: "ASP.NET Core API endpoint definitions and request handling",
1126
+ service: "api"
1127
+ },
1128
+ {
1129
+ id: "api-services",
1130
+ name: "API Services",
1131
+ level: 1,
1132
+ code_paths: ["api/Services/"],
1133
+ interfaces: ["api-controllers"],
1134
+ quality_scenarios: [],
1135
+ adrs: [],
1136
+ responsibility: "Business logic, use cases, and orchestration between domain and infrastructure",
1137
+ service: "api"
1138
+ },
1139
+ {
1140
+ id: "shared-contracts",
1141
+ name: "Shared Contracts",
1142
+ level: 1,
1143
+ code_paths: ["shared/", "contracts/"],
1144
+ interfaces: ["frontend-shell", "api-controllers"],
1145
+ quality_scenarios: ["MAINT-01"],
1146
+ adrs: [],
1147
+ responsibility: "API types, schemas, and contracts shared between the Next.js frontend and .NET backend",
1148
+ service: "api"
1149
+ }
1150
+ ];
1151
+ }
1051
1152
  function buildJsBlocks(inp, lt) {
1052
1153
  const src = lt.srcPrefix;
1053
1154
  const entries = lt.entrypoints;
@@ -2275,13 +2376,169 @@ var ANGULAR_SCENARIOS = {
2275
2376
  }
2276
2377
  ]
2277
2378
  };
2379
+ var FULLSTACK_NEXTJS_DOTNET_SCENARIOS = {
2380
+ security: [
2381
+ {
2382
+ id: "SEC-02",
2383
+ name: "No secrets in client bundles or source",
2384
+ category: "security",
2385
+ priority: "must",
2386
+ scenario: "Client-side JavaScript bundle and .NET source/config files are analyzed",
2387
+ expected: "No API keys, tokens, or secrets are found in client-side code or hardcoded in appsettings",
2388
+ linked_code: [],
2389
+ linked_tests: [],
2390
+ linked_blocks: ["frontend-shell", "api-controllers"],
2391
+ verification: "automatic",
2392
+ status: "untested"
2393
+ },
2394
+ {
2395
+ id: "SEC-04",
2396
+ name: "CORS policy restricts origins",
2397
+ category: "security",
2398
+ priority: "should",
2399
+ scenario: "A cross-origin request is made from an untrusted domain",
2400
+ expected: "Request is rejected by CORS middleware; only the frontend origin is allowed",
2401
+ linked_code: [],
2402
+ linked_tests: [],
2403
+ linked_blocks: ["api-controllers"],
2404
+ verification: "automatic",
2405
+ status: "untested"
2406
+ },
2407
+ {
2408
+ id: "SEC-05",
2409
+ name: "Cross-service auth token handling",
2410
+ category: "security",
2411
+ priority: "must",
2412
+ scenario: "Frontend sends an auth token to the .NET API on every protected request",
2413
+ expected: "Token is validated server-side; expired or invalid tokens return 401; tokens are never stored in localStorage",
2414
+ linked_code: [],
2415
+ linked_tests: [],
2416
+ linked_blocks: ["frontend-shell", "api-services"],
2417
+ verification: "automatic",
2418
+ status: "untested"
2419
+ }
2420
+ ],
2421
+ performance: [
2422
+ {
2423
+ id: "PERF-01",
2424
+ name: "Initial page load under 3s",
2425
+ category: "performance",
2426
+ priority: "should",
2427
+ scenario: "User loads the landing page on a 3G connection",
2428
+ expected: "Largest Contentful Paint (LCP) is under 3 seconds",
2429
+ linked_code: [],
2430
+ linked_tests: [],
2431
+ linked_blocks: ["frontend-shell"],
2432
+ verification: "semi-automatic",
2433
+ status: "untested"
2434
+ },
2435
+ {
2436
+ id: "PERF-03",
2437
+ name: "Frontend bundle under 200KB gzipped",
2438
+ category: "performance",
2439
+ priority: "should",
2440
+ scenario: "Production build output is analyzed",
2441
+ expected: "Main JavaScript bundle is under 200KB gzipped",
2442
+ linked_code: [],
2443
+ linked_tests: [],
2444
+ linked_blocks: ["frontend-shell", "frontend-components"],
2445
+ verification: "semi-automatic",
2446
+ status: "untested"
2447
+ },
2448
+ {
2449
+ id: "PERF-04",
2450
+ name: "Async all the way in .NET API",
2451
+ category: "performance",
2452
+ priority: "must",
2453
+ scenario: "I/O-bound operations in the API are reviewed",
2454
+ expected: "All I/O operations use async/await; no sync-over-async or blocking calls",
2455
+ linked_code: [],
2456
+ linked_tests: [],
2457
+ linked_blocks: ["api-controllers", "api-services"],
2458
+ verification: "semi-automatic",
2459
+ status: "untested"
2460
+ }
2461
+ ],
2462
+ reliability: [
2463
+ {
2464
+ id: "REL-02",
2465
+ name: "Health check covers dependencies",
2466
+ category: "reliability",
2467
+ priority: "should",
2468
+ scenario: "Orchestrator calls the /health endpoint",
2469
+ expected: "Returns degraded or unhealthy if database or critical external service is unreachable",
2470
+ linked_code: [],
2471
+ linked_tests: [],
2472
+ linked_blocks: ["api-controllers"],
2473
+ verification: "automatic",
2474
+ status: "untested"
2475
+ },
2476
+ {
2477
+ id: "REL-03",
2478
+ name: "Structured logging with correlation",
2479
+ category: "reliability",
2480
+ priority: "should",
2481
+ scenario: "A request flows through frontend to API and back",
2482
+ expected: "All log entries share a correlation ID across services; logs are structured JSON",
2483
+ linked_code: [],
2484
+ linked_tests: [],
2485
+ linked_blocks: ["api-services"],
2486
+ verification: "semi-automatic",
2487
+ status: "untested"
2488
+ }
2489
+ ],
2490
+ accessibility: [
2491
+ {
2492
+ id: "A11Y-01",
2493
+ name: "WCAG 2.1 AA compliance",
2494
+ category: "accessibility",
2495
+ priority: "should",
2496
+ scenario: "All pages are audited with axe-core",
2497
+ expected: "No critical or serious accessibility violations",
2498
+ linked_code: [],
2499
+ linked_tests: [],
2500
+ linked_blocks: ["frontend-components"],
2501
+ verification: "semi-automatic",
2502
+ status: "untested"
2503
+ },
2504
+ {
2505
+ id: "A11Y-02",
2506
+ name: "Keyboard navigation",
2507
+ category: "accessibility",
2508
+ priority: "should",
2509
+ scenario: "User navigates the entire application using only keyboard",
2510
+ expected: "All interactive elements are reachable and operable via keyboard",
2511
+ linked_code: [],
2512
+ linked_tests: [],
2513
+ linked_blocks: ["frontend-components"],
2514
+ verification: "manual",
2515
+ status: "untested"
2516
+ }
2517
+ ],
2518
+ maintainability: [
2519
+ {
2520
+ id: "MAINT-03",
2521
+ name: "API contract consistency",
2522
+ category: "maintainability",
2523
+ priority: "must",
2524
+ scenario: "Frontend type definitions are compared against API OpenAPI spec or shared contracts",
2525
+ expected: "All frontend API types match the backend contract; CI check prevents drift",
2526
+ linked_code: [],
2527
+ linked_tests: [],
2528
+ linked_blocks: ["shared-contracts"],
2529
+ verification: "automatic",
2530
+ status: "untested"
2531
+ }
2532
+ ]
2533
+ };
2278
2534
  var TEMPLATE_SCENARIOS = {
2279
2535
  "nextjs-app-router": FRONTEND_SCENARIOS,
2280
2536
  "react-vite": FRONTEND_SCENARIOS,
2281
2537
  "angular-app": ANGULAR_SCENARIOS,
2282
2538
  "api-service": API_SCENARIOS,
2283
2539
  "dotnet-webapi": DOTNET_SCENARIOS,
2284
- "unity-game": UNITY_GAME_SCENARIOS
2540
+ "unity-game": UNITY_GAME_SCENARIOS,
2541
+ "fullstack-nextjs-dotnet": FULLSTACK_NEXTJS_DOTNET_SCENARIOS
2285
2542
  };
2286
2543
  function mergeScenarios(template, category) {
2287
2544
  const shared = SHARED_SCENARIOS[category] ?? [];
@@ -3857,90 +4114,445 @@ function phaseTasksTemplate6(_input, phaseId) {
3857
4114
  ]
3858
4115
  },
3859
4116
  {
3860
- id: "task-0.2-routing",
3861
- title: "Set up routing with lazy loading",
4117
+ id: "task-0.2-routing",
4118
+ title: "Set up routing with lazy loading",
4119
+ status: "todo",
4120
+ building_block: "app-shell",
4121
+ quality_scenarios: ["PERF-05"],
4122
+ acceptance_criteria: [
4123
+ "App routes defined in app.routes.ts",
4124
+ "At least one feature route uses loadComponent or loadChildren",
4125
+ "Route guards and resolvers use functional approach"
4126
+ ]
4127
+ },
4128
+ {
4129
+ id: "task-0.3-core-services",
4130
+ title: "Set up core services and HTTP interceptor",
4131
+ status: "todo",
4132
+ building_block: "core-services",
4133
+ quality_scenarios: [],
4134
+ acceptance_criteria: [
4135
+ "HTTP interceptor for API base URL and error handling",
4136
+ "Core services provided in root",
4137
+ "Environment configuration set up"
4138
+ ]
4139
+ },
4140
+ {
4141
+ id: "task-0.4-testing",
4142
+ title: "Set up testing infrastructure",
4143
+ status: "todo",
4144
+ quality_scenarios: ["MAINT-02"],
4145
+ acceptance_criteria: [
4146
+ "Unit test framework configured (Vitest or Karma/Jasmine)",
4147
+ "First component test passes",
4148
+ "Test coverage reporting enabled"
4149
+ ]
4150
+ }
4151
+ ]
4152
+ },
4153
+ "phase-1-foundation": {
4154
+ schema_version: 1,
4155
+ phase_id: "phase-1-foundation",
4156
+ tasks: [
4157
+ {
4158
+ id: "task-1.1-shared-components",
4159
+ title: "Build shared component library",
4160
+ status: "todo",
4161
+ building_block: "shared-components",
4162
+ quality_scenarios: ["A11Y-01"],
4163
+ acceptance_criteria: [
4164
+ "Reusable UI components in shared/ directory",
4165
+ "Components use OnPush change detection or signals",
4166
+ "Components follow accessibility guidelines"
4167
+ ]
4168
+ },
4169
+ {
4170
+ id: "task-1.2-auth",
4171
+ title: "Implement authentication and route guards",
4172
+ status: "todo",
4173
+ building_block: "core-services",
4174
+ quality_scenarios: ["SEC-01"],
4175
+ acceptance_criteria: [
4176
+ "Auth service with login/logout/token management",
4177
+ "Functional route guard protecting private routes",
4178
+ "Auth interceptor attaching tokens to API requests"
4179
+ ]
4180
+ },
4181
+ {
4182
+ id: "task-1.3-api-client",
4183
+ title: "Set up API client layer",
4184
+ status: "todo",
4185
+ building_block: "api-client",
4186
+ quality_scenarios: [],
4187
+ acceptance_criteria: [
4188
+ "Typed API service methods for backend endpoints",
4189
+ "Error handling and retry logic",
4190
+ "Loading/error state management"
4191
+ ]
4192
+ },
4193
+ {
4194
+ id: "task-1.4-document-decisions",
4195
+ title: "Document architectural decisions as ADRs",
4196
+ status: "todo",
4197
+ quality_scenarios: [],
4198
+ acceptance_criteria: [
4199
+ "ADR for each significant architecture/pattern choice",
4200
+ "ADRs linked to affected building blocks and code paths"
4201
+ ]
4202
+ }
4203
+ ]
4204
+ }
4205
+ };
4206
+ return tasksByPhase[phaseId] ?? null;
4207
+ }
4208
+
4209
+ // src/templates/phases/fullstack-nextjs-dotnet.ts
4210
+ function phasePlanTemplate7(_input) {
4211
+ const phases = [
4212
+ {
4213
+ id: "phase-0-setup",
4214
+ name: "Monorepo Setup",
4215
+ phase_number: 0,
4216
+ status: "planned",
4217
+ description: "Initialize monorepo structure, scaffold Next.js frontend and .NET API backend, configure shared tooling",
4218
+ gate_requirements: [
4219
+ "Monorepo structure with frontend/ and api/ directories",
4220
+ "Next.js app builds and runs at localhost:3000",
4221
+ ".NET API builds and runs with health check at /health",
4222
+ "Shared dev scripts (start, build, test) work from root"
4223
+ ]
4224
+ },
4225
+ {
4226
+ id: "phase-1-api-foundation",
4227
+ name: "API Foundation",
4228
+ phase_number: 1,
4229
+ status: "planned",
4230
+ description: "Build .NET API controllers, services, authentication middleware, and database access layer",
4231
+ gate_requirements: [
4232
+ "Auth middleware protects API endpoints",
4233
+ "Global exception handler returns ProblemDetails",
4234
+ "Database access layer configured with migrations",
4235
+ "OpenAPI/Swagger documentation generated",
4236
+ "Quality scenarios SEC-01, SEC-02 verified"
4237
+ ]
4238
+ },
4239
+ {
4240
+ id: "phase-2-frontend-foundation",
4241
+ name: "Frontend Foundation",
4242
+ phase_number: 2,
4243
+ status: "planned",
4244
+ description: "Build Next.js layouts, reusable components, API client layer, and authentication UI",
4245
+ gate_requirements: [
4246
+ "App shell with layout and navigation working",
4247
+ "API client layer communicates with .NET backend",
4248
+ "Auth UI (login, register, protected routes) functional",
4249
+ "Component library with consistent styling",
4250
+ "Quality scenarios A11Y-01, PERF-01 verified"
4251
+ ]
4252
+ },
4253
+ {
4254
+ id: "phase-3-feature-integration",
4255
+ name: "Feature Integration",
4256
+ phase_number: 3,
4257
+ status: "planned",
4258
+ description: "Implement end-to-end features across frontend and backend, API contract synchronization, and shared types",
4259
+ gate_requirements: [
4260
+ "At least one full CRUD feature works end-to-end",
4261
+ "API contracts validated between frontend and backend",
4262
+ "Shared type definitions keep frontend and API in sync",
4263
+ "Integration tests cover cross-service workflows"
4264
+ ]
4265
+ },
4266
+ {
4267
+ id: "phase-4-production",
4268
+ name: "Production Readiness",
4269
+ phase_number: 4,
4270
+ status: "planned",
4271
+ description: "CI/CD pipeline, deployment configuration, monitoring, and performance optimization",
4272
+ gate_requirements: [
4273
+ "CI pipeline builds and tests both services",
4274
+ "Docker Compose or equivalent runs the full stack",
4275
+ "Rate limiting and CORS configured for production",
4276
+ "All quality scenarios passing",
4277
+ "Production deployment successful"
4278
+ ]
4279
+ }
4280
+ ];
4281
+ return { schema_version: 1, phases };
4282
+ }
4283
+ function phaseTasksTemplate7(_input, phaseId) {
4284
+ const tasksByPhase = {
4285
+ "phase-0-setup": {
4286
+ schema_version: 1,
4287
+ phase_id: "phase-0-setup",
4288
+ tasks: [
4289
+ {
4290
+ id: "task-0.1-monorepo-structure",
4291
+ title: "Set up monorepo structure with frontend and api directories",
4292
+ status: "todo",
4293
+ building_block: "frontend-shell",
4294
+ quality_scenarios: [],
4295
+ acceptance_criteria: [
4296
+ "Root package.json with workspace scripts",
4297
+ "frontend/ directory with Next.js app (App Router)",
4298
+ "api/ directory with ASP.NET Core Web API project",
4299
+ "Shared .editorconfig and linting configuration"
4300
+ ]
4301
+ },
4302
+ {
4303
+ id: "task-0.2-frontend-scaffold",
4304
+ title: "Scaffold Next.js frontend with App Router",
4305
+ status: "todo",
4306
+ building_block: "frontend-shell",
4307
+ quality_scenarios: ["MAINT-01"],
4308
+ acceptance_criteria: [
4309
+ "Next.js app created with TypeScript and App Router",
4310
+ "Root layout with metadata and font configuration",
4311
+ "Landing page renders at localhost:3000",
4312
+ "ESLint and Prettier configured"
4313
+ ]
4314
+ },
4315
+ {
4316
+ id: "task-0.3-api-scaffold",
4317
+ title: "Scaffold .NET API with health check",
4318
+ status: "todo",
4319
+ building_block: "api-controllers",
4320
+ quality_scenarios: [],
4321
+ acceptance_criteria: [
4322
+ "ASP.NET Core Web API project created",
4323
+ "Health check endpoint responds at /health",
4324
+ "DI container configured with core services",
4325
+ "Logging and configuration in place"
4326
+ ]
4327
+ },
4328
+ {
4329
+ id: "task-0.4-shared-tooling",
4330
+ title: "Configure shared development tooling and scripts",
4331
+ status: "todo",
4332
+ building_block: "shared-contracts",
4333
+ quality_scenarios: ["MAINT-02"],
4334
+ acceptance_criteria: [
4335
+ "Root-level scripts to start, build, and test both services",
4336
+ "Shared TypeScript types or OpenAPI contract directory",
4337
+ "Testing infrastructure for both frontend and backend",
4338
+ "Git hooks for linting and formatting"
4339
+ ]
4340
+ }
4341
+ ]
4342
+ },
4343
+ "phase-1-api-foundation": {
4344
+ schema_version: 1,
4345
+ phase_id: "phase-1-api-foundation",
4346
+ tasks: [
4347
+ {
4348
+ id: "task-1.1-api-auth",
4349
+ title: "Implement API authentication and authorization",
4350
+ status: "todo",
4351
+ building_block: "api-services",
4352
+ quality_scenarios: ["SEC-01", "SEC-02"],
4353
+ acceptance_criteria: [
4354
+ "JWT bearer auth configured in .NET API",
4355
+ "Authorization policies defined for role-based access",
4356
+ "Auth middleware applied to protected endpoints",
4357
+ "Token validation and refresh logic implemented"
4358
+ ]
4359
+ },
4360
+ {
4361
+ id: "task-1.2-api-controllers",
4362
+ title: "Build core API controllers and endpoints",
4363
+ status: "todo",
4364
+ building_block: "api-controllers",
4365
+ quality_scenarios: ["SEC-03"],
4366
+ acceptance_criteria: [
4367
+ "RESTful controllers for core resources",
4368
+ "Input validation via FluentValidation or data annotations",
4369
+ "Consistent response format with ProblemDetails for errors",
4370
+ "OpenAPI/Swagger documentation generated and accessible"
4371
+ ]
4372
+ },
4373
+ {
4374
+ id: "task-1.3-api-services",
4375
+ title: "Implement business logic and service layer",
4376
+ status: "todo",
4377
+ building_block: "api-services",
4378
+ quality_scenarios: ["MAINT-01"],
4379
+ acceptance_criteria: [
4380
+ "Service classes encapsulate business logic",
4381
+ "Repository pattern for data access",
4382
+ "EF Core or Dapper configured with migrations",
4383
+ "Unit tests cover service layer logic"
4384
+ ]
4385
+ },
4386
+ {
4387
+ id: "task-1.4-api-middleware",
4388
+ title: "Configure middleware pipeline and error handling",
4389
+ status: "todo",
4390
+ building_block: "api-controllers",
4391
+ quality_scenarios: ["REL-01"],
4392
+ acceptance_criteria: [
4393
+ "Global exception handler returns RFC 7807 ProblemDetails",
4394
+ "Request/response logging with correlation IDs",
4395
+ "CORS policy configured for frontend origin",
4396
+ "No stack traces leaked in production"
4397
+ ]
4398
+ }
4399
+ ]
4400
+ },
4401
+ "phase-2-frontend-foundation": {
4402
+ schema_version: 1,
4403
+ phase_id: "phase-2-frontend-foundation",
4404
+ tasks: [
4405
+ {
4406
+ id: "task-2.1-frontend-layout",
4407
+ title: "Build app shell with layouts and navigation",
4408
+ status: "todo",
4409
+ building_block: "frontend-shell",
4410
+ quality_scenarios: ["A11Y-01"],
4411
+ acceptance_criteria: [
4412
+ "Root layout with header, sidebar, and main content area",
4413
+ "Navigation component with active state indicators",
4414
+ "Responsive layout that works on mobile and desktop",
4415
+ "Loading and error boundary components"
4416
+ ]
4417
+ },
4418
+ {
4419
+ id: "task-2.2-frontend-components",
4420
+ title: "Create reusable UI component library",
4421
+ status: "todo",
4422
+ building_block: "frontend-components",
4423
+ quality_scenarios: ["A11Y-02"],
4424
+ acceptance_criteria: [
4425
+ "Button, Input, Card, Modal, and Table components",
4426
+ "Consistent styling with design tokens or Tailwind",
4427
+ "All components meet WCAG 2.1 AA accessibility",
4428
+ "Component documentation or Storybook setup"
4429
+ ]
4430
+ },
4431
+ {
4432
+ id: "task-2.3-api-client",
4433
+ title: "Build API client layer for backend communication",
4434
+ status: "todo",
4435
+ building_block: "shared-contracts",
4436
+ quality_scenarios: ["REL-01"],
4437
+ acceptance_criteria: [
4438
+ "Typed API client generated from OpenAPI spec or manually defined",
4439
+ "Error handling with user-friendly messages",
4440
+ "Auth token injection in request headers",
4441
+ "Request/response interceptors for logging and retry"
4442
+ ]
4443
+ },
4444
+ {
4445
+ id: "task-2.4-frontend-auth",
4446
+ title: "Implement frontend authentication UI and flow",
3862
4447
  status: "todo",
3863
- building_block: "app-shell",
3864
- quality_scenarios: ["PERF-05"],
4448
+ building_block: "frontend-shell",
4449
+ quality_scenarios: ["SEC-01"],
3865
4450
  acceptance_criteria: [
3866
- "App routes defined in app.routes.ts",
3867
- "At least one feature route uses loadComponent or loadChildren",
3868
- "Route guards and resolvers use functional approach"
4451
+ "Login and registration pages",
4452
+ "Protected route middleware or layout",
4453
+ "Token storage and refresh handling",
4454
+ "Auth state management (context or store)"
4455
+ ]
4456
+ }
4457
+ ]
4458
+ },
4459
+ "phase-3-feature-integration": {
4460
+ schema_version: 1,
4461
+ phase_id: "phase-3-feature-integration",
4462
+ tasks: [
4463
+ {
4464
+ id: "task-3.1-e2e-feature",
4465
+ title: "Implement first end-to-end CRUD feature",
4466
+ status: "todo",
4467
+ building_block: "api-controllers",
4468
+ quality_scenarios: ["PERF-02"],
4469
+ acceptance_criteria: [
4470
+ "Full CRUD operations from frontend through API to database",
4471
+ "Optimistic UI updates where appropriate",
4472
+ "Server-side validation reflected in frontend forms",
4473
+ "List, detail, create, edit, and delete views working"
3869
4474
  ]
3870
4475
  },
3871
4476
  {
3872
- id: "task-0.3-core-services",
3873
- title: "Set up core services and HTTP interceptor",
4477
+ id: "task-3.2-contract-sync",
4478
+ title: "Set up API contract synchronization",
3874
4479
  status: "todo",
3875
- building_block: "core-services",
3876
- quality_scenarios: [],
4480
+ building_block: "shared-contracts",
4481
+ quality_scenarios: ["MAINT-01"],
3877
4482
  acceptance_criteria: [
3878
- "HTTP interceptor for API base URL and error handling",
3879
- "Core services provided in root",
3880
- "Environment configuration set up"
4483
+ "OpenAPI spec or shared type definitions in contracts directory",
4484
+ "Frontend types generated from or validated against API contract",
4485
+ "CI check prevents contract drift between frontend and backend",
4486
+ "Documentation for updating contracts when API changes"
3881
4487
  ]
3882
4488
  },
3883
4489
  {
3884
- id: "task-0.4-testing",
3885
- title: "Set up testing infrastructure",
4490
+ id: "task-3.3-integration-tests",
4491
+ title: "Write cross-service integration tests",
3886
4492
  status: "todo",
4493
+ building_block: "shared-contracts",
3887
4494
  quality_scenarios: ["MAINT-02"],
3888
4495
  acceptance_criteria: [
3889
- "Unit test framework configured (Vitest or Karma/Jasmine)",
3890
- "First component test passes",
3891
- "Test coverage reporting enabled"
4496
+ "Integration tests exercise frontend-to-API workflows",
4497
+ "Test environment starts both services",
4498
+ "Auth flow tested end-to-end",
4499
+ "Error scenarios covered (API down, validation failures)"
3892
4500
  ]
3893
4501
  }
3894
4502
  ]
3895
4503
  },
3896
- "phase-1-foundation": {
4504
+ "phase-4-production": {
3897
4505
  schema_version: 1,
3898
- phase_id: "phase-1-foundation",
4506
+ phase_id: "phase-4-production",
3899
4507
  tasks: [
3900
4508
  {
3901
- id: "task-1.1-shared-components",
3902
- title: "Build shared component library",
4509
+ id: "task-4.1-ci-cd",
4510
+ title: "Set up CI/CD pipeline for both services",
3903
4511
  status: "todo",
3904
- building_block: "shared-components",
3905
- quality_scenarios: ["A11Y-01"],
4512
+ quality_scenarios: ["MAINT-02"],
3906
4513
  acceptance_criteria: [
3907
- "Reusable UI components in shared/ directory",
3908
- "Components use OnPush change detection or signals",
3909
- "Components follow accessibility guidelines"
4514
+ "CI builds and tests both frontend and API",
4515
+ "Lint, type-check, and test steps for each service",
4516
+ "Build artifacts produced for deployment",
4517
+ "Pipeline fails fast on errors"
3910
4518
  ]
3911
4519
  },
3912
4520
  {
3913
- id: "task-1.2-auth",
3914
- title: "Implement authentication and route guards",
4521
+ id: "task-4.2-deployment",
4522
+ title: "Configure deployment for full stack",
3915
4523
  status: "todo",
3916
- building_block: "core-services",
3917
- quality_scenarios: ["SEC-01"],
4524
+ quality_scenarios: ["REL-01"],
3918
4525
  acceptance_criteria: [
3919
- "Auth service with login/logout/token management",
3920
- "Functional route guard protecting private routes",
3921
- "Auth interceptor attaching tokens to API requests"
4526
+ "Docker Compose for local full-stack development",
4527
+ "Dockerfiles for frontend and API",
4528
+ "Environment-specific configuration (dev, staging, prod)",
4529
+ "Database migration runs as part of deployment"
3922
4530
  ]
3923
4531
  },
3924
4532
  {
3925
- id: "task-1.3-api-client",
3926
- title: "Set up API client layer",
4533
+ id: "task-4.3-monitoring",
4534
+ title: "Add monitoring, logging, and rate limiting",
3927
4535
  status: "todo",
3928
- building_block: "api-client",
3929
- quality_scenarios: [],
4536
+ building_block: "api-services",
4537
+ quality_scenarios: ["PERF-02", "SEC-04"],
3930
4538
  acceptance_criteria: [
3931
- "Typed API service methods for backend endpoints",
3932
- "Error handling and retry logic",
3933
- "Loading/error state management"
4539
+ "Structured logging in API with Serilog or similar",
4540
+ "Frontend error tracking (Sentry or similar)",
4541
+ "Rate limiting configured on API endpoints",
4542
+ "Health check dashboard or monitoring alerts"
3934
4543
  ]
3935
4544
  },
3936
4545
  {
3937
- id: "task-1.4-document-decisions",
3938
- title: "Document architectural decisions as ADRs",
4546
+ id: "task-4.4-performance",
4547
+ title: "Optimize performance across the stack",
3939
4548
  status: "todo",
3940
- quality_scenarios: [],
4549
+ building_block: "frontend-shell",
4550
+ quality_scenarios: ["PERF-01", "PERF-03"],
3941
4551
  acceptance_criteria: [
3942
- "ADR for each significant architecture/pattern choice",
3943
- "ADRs linked to affected building blocks and code paths"
4552
+ "Frontend bundle size optimized (code splitting, tree shaking)",
4553
+ "API response times meet p95 < 200ms target",
4554
+ "Database queries optimized with proper indexing",
4555
+ "Static assets cached with appropriate headers"
3944
4556
  ]
3945
4557
  }
3946
4558
  ]
@@ -3956,7 +4568,8 @@ var planTemplates = {
3956
4568
  "api-service": { plan: phasePlanTemplate3, tasks: phaseTasksTemplate3 },
3957
4569
  "dotnet-webapi": { plan: phasePlanTemplate4, tasks: phaseTasksTemplate4 },
3958
4570
  "unity-game": { plan: phasePlanTemplate5, tasks: phaseTasksTemplate5 },
3959
- "angular-app": { plan: phasePlanTemplate6, tasks: phaseTasksTemplate6 }
4571
+ "angular-app": { plan: phasePlanTemplate6, tasks: phaseTasksTemplate6 },
4572
+ "fullstack-nextjs-dotnet": { plan: phasePlanTemplate7, tasks: phaseTasksTemplate7 }
3960
4573
  };
3961
4574
  function generatePlan(targetDir, input) {
3962
4575
  const planDir = join5(targetDir, ".arcbridge", "plan");
@@ -4630,7 +5243,7 @@ You cannot see screenshots, but you CAN reason about UI quality through code:
4630
5243
  }
4631
5244
 
4632
5245
  // src/generators/agent-generator.ts
4633
- var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "angular-app", "unity-game"]);
5246
+ var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "angular-app", "unity-game", "fullstack-nextjs-dotnet"]);
4634
5247
  function writeAgentRole(dir, role) {
4635
5248
  const { system_prompt, ...frontmatter } = role;
4636
5249
  const content = matter2.stringify(system_prompt, frontmatter);
@@ -5903,7 +6516,7 @@ function analyzeRoutes(projectRoot, db, service = "main") {
5903
6516
  }
5904
6517
  }
5905
6518
  walkAppDir(appDir, "/", routes, layoutStack, service, projectRoot);
5906
- writeRoutes(db, routes);
6519
+ writeRoutes(db, routes, service);
5907
6520
  return routes.length;
5908
6521
  }
5909
6522
  function walkAppDir(dir, routePath, routes, layoutStack, service, projectRoot) {
@@ -5986,9 +6599,9 @@ function extractHttpMethods(filePath) {
5986
6599
  }
5987
6600
  return methods;
5988
6601
  }
5989
- function writeRoutes(db, routes) {
6602
+ function writeRoutes(db, routes, service) {
6603
+ db.prepare("DELETE FROM routes WHERE service = ?").run(service);
5990
6604
  if (routes.length === 0) return;
5991
- db.prepare("DELETE FROM routes").run();
5992
6605
  const insert = db.prepare(`
5993
6606
  INSERT OR IGNORE INTO routes (
5994
6607
  id, route_path, kind, http_methods, has_auth, parent_layout, service
@@ -5997,7 +6610,7 @@ function writeRoutes(db, routes) {
5997
6610
  transaction(db, () => {
5998
6611
  for (const r of routes) {
5999
6612
  insert.run(
6000
- r.id,
6613
+ `${service}::${r.id}`,
6001
6614
  r.routePath,
6002
6615
  r.kind,
6003
6616
  JSON.stringify(r.httpMethods),
@@ -6303,7 +6916,7 @@ function indexDotnetProjectRoslyn(db, options) {
6303
6916
  transaction(db, () => {
6304
6917
  for (const route of output.routes) {
6305
6918
  insertRoute.run(
6306
- route.id,
6919
+ `${service}::${route.id}`,
6307
6920
  route.routePath,
6308
6921
  route.kind,
6309
6922
  JSON.stringify(route.httpMethods),
@@ -7247,7 +7860,7 @@ async function indexCSharpTreeSitter(db, options) {
7247
7860
  transaction(db, () => {
7248
7861
  for (const route of allRoutes) {
7249
7862
  insertRoute.run(
7250
- route.id,
7863
+ `${service}::${route.id}`,
7251
7864
  route.routePath,
7252
7865
  route.kind,
7253
7866
  JSON.stringify(route.httpMethods),
@@ -7726,6 +8339,144 @@ function isTypeContext2(node) {
7726
8339
  parent.type === "typed_parameter" && node !== parent.childForFieldName("name") || parent.type === "typed_default_parameter" && node !== parent.childForFieldName("name");
7727
8340
  }
7728
8341
 
8342
+ // src/indexer/python/route-analyzer.ts
8343
+ var FASTAPI_HTTP_METHODS = /* @__PURE__ */ new Set([
8344
+ "get",
8345
+ "post",
8346
+ "put",
8347
+ "delete",
8348
+ "patch",
8349
+ "head",
8350
+ "options"
8351
+ ]);
8352
+ function extractPythonRoutes(tree, _relativePath) {
8353
+ const routes = [];
8354
+ const decoratedDefs = findAllNodes2(tree.rootNode, "decorated_definition");
8355
+ for (const decorated of decoratedDefs) {
8356
+ const decorators = decorated.namedChildren.filter(
8357
+ (c) => c.type === "decorator"
8358
+ );
8359
+ for (const decorator of decorators) {
8360
+ const routeInfo = parseDecorator(decorator);
8361
+ if (!routeInfo) continue;
8362
+ for (const method of routeInfo.methods) {
8363
+ const routePath = routeInfo.path.startsWith("/") ? routeInfo.path : `/${routeInfo.path}`;
8364
+ const id = `route::${routePath.slice(1)}::${method}`;
8365
+ routes.push({
8366
+ id,
8367
+ routePath,
8368
+ kind: "api-route",
8369
+ httpMethods: [method],
8370
+ hasAuth: routeInfo.hasAuth
8371
+ });
8372
+ }
8373
+ }
8374
+ }
8375
+ return routes;
8376
+ }
8377
+ function parseDecorator(decorator) {
8378
+ const callNode = findFirstChild(decorator, "call");
8379
+ if (!callNode) return null;
8380
+ const funcNode = callNode.childForFieldName("function");
8381
+ if (!funcNode || funcNode.type !== "attribute") return null;
8382
+ const attrName = funcNode.childForFieldName("attribute");
8383
+ if (!attrName) return null;
8384
+ const methodName = attrName.text;
8385
+ if (FASTAPI_HTTP_METHODS.has(methodName)) {
8386
+ const path = getFirstStringArgument2(callNode);
8387
+ if (path === null) return null;
8388
+ const hasAuth = detectAuthInCall(callNode);
8389
+ return {
8390
+ path,
8391
+ methods: [methodName.toUpperCase()],
8392
+ hasAuth
8393
+ };
8394
+ }
8395
+ if (methodName === "route") {
8396
+ const path = getFirstStringArgument2(callNode);
8397
+ if (path === null) return null;
8398
+ const methods = getMethodsKeywordArgument(callNode).map((m) => m.toUpperCase());
8399
+ const hasAuth = detectAuthInCall(callNode);
8400
+ return {
8401
+ path,
8402
+ methods: methods.length > 0 ? methods : ["GET"],
8403
+ hasAuth
8404
+ };
8405
+ }
8406
+ return null;
8407
+ }
8408
+ function detectAuthInCall(callNode) {
8409
+ const args = callNode.childForFieldName("arguments");
8410
+ if (!args) return false;
8411
+ const text = args.text;
8412
+ if (/Depends\s*\(\s*auth/.test(text)) return true;
8413
+ if (/login_required|auth_required|require_auth|jwt_required/.test(text))
8414
+ return true;
8415
+ return false;
8416
+ }
8417
+ function getFirstStringArgument2(callNode) {
8418
+ const args = callNode.childForFieldName("arguments");
8419
+ if (!args) return null;
8420
+ for (const child of args.namedChildren) {
8421
+ if (child.type === "keyword_argument") continue;
8422
+ if (child.type === "string") {
8423
+ return stripQuotes(child.text);
8424
+ }
8425
+ const strNode = findFirstChild(child, "string");
8426
+ if (strNode) {
8427
+ return stripQuotes(strNode.text);
8428
+ }
8429
+ }
8430
+ return null;
8431
+ }
8432
+ function getMethodsKeywordArgument(callNode) {
8433
+ const args = callNode.childForFieldName("arguments");
8434
+ if (!args) return [];
8435
+ for (const child of args.namedChildren) {
8436
+ if (child.type !== "keyword_argument") continue;
8437
+ const nameNode = child.childForFieldName("name");
8438
+ if (!nameNode || nameNode.text !== "methods") continue;
8439
+ const valueNode = child.childForFieldName("value");
8440
+ if (!valueNode) continue;
8441
+ return extractStringListValues(valueNode);
8442
+ }
8443
+ return [];
8444
+ }
8445
+ function extractStringListValues(node) {
8446
+ const values = [];
8447
+ if (node.type === "list") {
8448
+ for (const child of node.namedChildren) {
8449
+ if (child.type === "string") {
8450
+ values.push(stripQuotes(child.text));
8451
+ }
8452
+ }
8453
+ }
8454
+ return values;
8455
+ }
8456
+ function findAllNodes2(root, type) {
8457
+ const results = [];
8458
+ function walk(node) {
8459
+ if (node.type === type) results.push(node);
8460
+ for (const child of node.namedChildren) {
8461
+ walk(child);
8462
+ }
8463
+ }
8464
+ walk(root);
8465
+ return results;
8466
+ }
8467
+ function findFirstChild(node, type) {
8468
+ for (const child of node.namedChildren) {
8469
+ if (child.type === type) return child;
8470
+ }
8471
+ for (const child of node.children) {
8472
+ if (child.type === type) return child;
8473
+ }
8474
+ return null;
8475
+ }
8476
+ function stripQuotes(text) {
8477
+ return text.replace(/^[fFrRbBuU]*["']|["']$/g, "");
8478
+ }
8479
+
7729
8480
  // src/indexer/python/indexer.ts
7730
8481
  async function indexPythonTreeSitter(db, options) {
7731
8482
  const start = Date.now();
@@ -7796,11 +8547,35 @@ async function indexPythonTreeSitter(db, options) {
7796
8547
  "DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ? AND language = 'python')"
7797
8548
  ).run(service);
7798
8549
  writeDependencies(db, allDeps);
8550
+ const allRoutes = [];
8551
+ for (const [relPath, cached] of fileCache) {
8552
+ const routes = extractPythonRoutes(cached.tree, relPath);
8553
+ allRoutes.push(...routes);
8554
+ }
8555
+ db.prepare("DELETE FROM routes WHERE service = ?").run(service);
8556
+ if (allRoutes.length > 0) {
8557
+ const insertRoute = db.prepare(`
8558
+ INSERT OR REPLACE INTO routes (id, route_path, kind, http_methods, has_auth, service)
8559
+ VALUES (?, ?, ?, ?, ?, ?)
8560
+ `);
8561
+ transaction(db, () => {
8562
+ for (const route of allRoutes) {
8563
+ insertRoute.run(
8564
+ `${service}::${route.id}`,
8565
+ route.routePath,
8566
+ route.kind,
8567
+ JSON.stringify(route.httpMethods),
8568
+ route.hasAuth ? 1 : 0,
8569
+ service
8570
+ );
8571
+ }
8572
+ });
8573
+ }
7799
8574
  return {
7800
8575
  symbolsIndexed: allNewSymbols.length,
7801
8576
  dependenciesIndexed: allDeps.length,
7802
8577
  componentsAnalyzed: 0,
7803
- routesAnalyzed: 0,
8578
+ routesAnalyzed: allRoutes.length,
7804
8579
  filesProcessed: changedFiles.length,
7805
8580
  filesSkipped,
7806
8581
  filesRemoved: removed.length,
@@ -8299,6 +9074,236 @@ function collectTypeIdentifiersRecursive3(node, names) {
8299
9074
  }
8300
9075
  }
8301
9076
 
9077
+ // src/indexer/go/route-analyzer.ts
9078
+ var GIN_METHODS = {
9079
+ GET: "GET",
9080
+ POST: "POST",
9081
+ PUT: "PUT",
9082
+ DELETE: "DELETE",
9083
+ PATCH: "PATCH",
9084
+ HEAD: "HEAD",
9085
+ OPTIONS: "OPTIONS"
9086
+ };
9087
+ var CHI_METHODS = {
9088
+ Get: "GET",
9089
+ Post: "POST",
9090
+ Put: "PUT",
9091
+ Delete: "DELETE",
9092
+ Patch: "PATCH",
9093
+ Head: "HEAD",
9094
+ Options: "OPTIONS"
9095
+ };
9096
+ var NET_HTTP_METHODS = /* @__PURE__ */ new Set(["HandleFunc", "Handle"]);
9097
+ function extractGoRoutes(tree, _relativePath) {
9098
+ const routes = [];
9099
+ const scopedPrefixes = extractGroupPrefixes2(tree.rootNode);
9100
+ const authScopes = findAuthUseScopes(tree.rootNode);
9101
+ const calls = findAllNodes3(tree.rootNode, "call_expression");
9102
+ for (const call of calls) {
9103
+ const funcNode = call.childForFieldName("function");
9104
+ if (!funcNode || funcNode.type !== "selector_expression") continue;
9105
+ const fieldNode = funcNode.childForFieldName("field");
9106
+ if (!fieldNode) continue;
9107
+ const methodName = fieldNode.text;
9108
+ const receiverNode = funcNode.childForFieldName("operand");
9109
+ const receiverName = receiverNode?.type === "identifier" ? receiverNode.text : null;
9110
+ let httpMethods = null;
9111
+ let isNetHttp = false;
9112
+ if (GIN_METHODS[methodName]) {
9113
+ httpMethods = [GIN_METHODS[methodName]];
9114
+ } else if (CHI_METHODS[methodName]) {
9115
+ httpMethods = [CHI_METHODS[methodName]];
9116
+ } else if (NET_HTTP_METHODS.has(methodName)) {
9117
+ httpMethods = [];
9118
+ isNetHttp = true;
9119
+ }
9120
+ if (httpMethods === null) continue;
9121
+ const routeTemplate = getFirstStringArgument3(call);
9122
+ if (routeTemplate === null) continue;
9123
+ const prefix = receiverName ? resolvePrefix(receiverName, call, scopedPrefixes) : "";
9124
+ let routePath;
9125
+ if (prefix && routeTemplate) {
9126
+ routePath = `${prefix}/${routeTemplate}`.replace(/\/{2,}/g, "/");
9127
+ } else if (prefix) {
9128
+ routePath = prefix;
9129
+ } else {
9130
+ routePath = routeTemplate;
9131
+ }
9132
+ routePath = routePath.startsWith("/") ? routePath : `/${routePath}`;
9133
+ routePath = routePath.replace(/\/{2,}/g, "/").replace(/\/+$/, "") || "/";
9134
+ let hasAuth = false;
9135
+ if (!isNetHttp) {
9136
+ hasAuth = hasAuthArgument(call);
9137
+ if (!hasAuth && authScopes.length > 0) {
9138
+ hasAuth = isInsideAuthScope(call, authScopes);
9139
+ }
9140
+ }
9141
+ const cleanPath = routePath.slice(1) || "";
9142
+ const idMethod = httpMethods.length > 0 ? httpMethods.join(",") : "ANY";
9143
+ const id = `route::${cleanPath}::${idMethod}`;
9144
+ routes.push({
9145
+ id,
9146
+ routePath,
9147
+ kind: "api-route",
9148
+ httpMethods,
9149
+ hasAuth
9150
+ });
9151
+ }
9152
+ return routes;
9153
+ }
9154
+ function extractGroupPrefixes2(root) {
9155
+ const prefixes = [];
9156
+ const calls = findAllNodes3(root, "call_expression");
9157
+ for (const call of calls) {
9158
+ const funcNode = call.childForFieldName("function");
9159
+ if (funcNode?.type !== "selector_expression") continue;
9160
+ const fieldNode = funcNode.childForFieldName("field");
9161
+ const methodName = fieldNode?.text;
9162
+ if (methodName !== "Group" && methodName !== "Route") continue;
9163
+ const prefix = getFirstStringArgument3(call);
9164
+ if (prefix === null) continue;
9165
+ const assignNode = findAncestor(call, ["short_var_declaration", "assignment_statement"]);
9166
+ if (assignNode) {
9167
+ const left = assignNode.childForFieldName("left");
9168
+ if (left) {
9169
+ const varNode = left.type === "expression_list" ? left.namedChildren[0] : left;
9170
+ if (varNode?.type === "identifier") {
9171
+ const scope = findAncestor(call, [
9172
+ "func_literal",
9173
+ "function_declaration",
9174
+ "method_declaration"
9175
+ ]);
9176
+ prefixes.push({ varName: varNode.text, prefix, scope });
9177
+ }
9178
+ }
9179
+ }
9180
+ if (methodName === "Route") {
9181
+ const argList = findChild7(call, "argument_list");
9182
+ if (argList) {
9183
+ for (const arg of argList.namedChildren) {
9184
+ if (arg.type === "func_literal") {
9185
+ const paramList = arg.childForFieldName("parameters");
9186
+ if (paramList) {
9187
+ const firstParam = paramList.namedChildren[0];
9188
+ if (firstParam?.type === "parameter_declaration") {
9189
+ const paramName = firstParam.childForFieldName("name");
9190
+ if (paramName?.type === "identifier") {
9191
+ prefixes.push({ varName: paramName.text, prefix, scope: arg });
9192
+ }
9193
+ }
9194
+ }
9195
+ }
9196
+ }
9197
+ }
9198
+ }
9199
+ }
9200
+ return prefixes;
9201
+ }
9202
+ function resolvePrefix(receiverName, callNode, scopedPrefixes) {
9203
+ let best = null;
9204
+ for (const sp of scopedPrefixes) {
9205
+ if (sp.varName !== receiverName) continue;
9206
+ if (sp.scope === null) {
9207
+ if (!best) best = sp;
9208
+ } else {
9209
+ if (isDescendantOf(callNode, sp.scope)) {
9210
+ if (!best || best.scope === null || isDescendantOf(sp.scope, best.scope)) {
9211
+ best = sp;
9212
+ }
9213
+ }
9214
+ }
9215
+ }
9216
+ return best?.prefix ?? "";
9217
+ }
9218
+ function isSameNode(a, b) {
9219
+ return a.type === b.type && a.startPosition.row === b.startPosition.row && a.startPosition.column === b.startPosition.column && a.endPosition.row === b.endPosition.row && a.endPosition.column === b.endPosition.column;
9220
+ }
9221
+ function isDescendantOf(node, ancestor) {
9222
+ let current = node.parent;
9223
+ while (current) {
9224
+ if (isSameNode(current, ancestor)) return true;
9225
+ current = current.parent;
9226
+ }
9227
+ return false;
9228
+ }
9229
+ function findAncestor(node, types) {
9230
+ let current = node.parent;
9231
+ while (current) {
9232
+ if (types.includes(current.type)) return current;
9233
+ current = current.parent;
9234
+ }
9235
+ return null;
9236
+ }
9237
+ function findAuthUseScopes(root) {
9238
+ const scopes = [];
9239
+ const calls = findAllNodes3(root, "call_expression");
9240
+ for (const call of calls) {
9241
+ const funcNode = call.childForFieldName("function");
9242
+ if (funcNode?.type !== "selector_expression") continue;
9243
+ const fieldNode = funcNode.childForFieldName("field");
9244
+ if (fieldNode?.text !== "Use") continue;
9245
+ if (hasAuthArgument(call)) {
9246
+ let scope = call.parent;
9247
+ while (scope && scope.type !== "func_literal" && scope.type !== "function_declaration" && scope.type !== "method_declaration") {
9248
+ scope = scope.parent;
9249
+ }
9250
+ if (scope) {
9251
+ scopes.push(scope);
9252
+ }
9253
+ }
9254
+ }
9255
+ return scopes;
9256
+ }
9257
+ function isInsideAuthScope(node, scopes) {
9258
+ let current = node.parent;
9259
+ while (current) {
9260
+ for (const scope of scopes) {
9261
+ if (isSameNode(current, scope)) return true;
9262
+ }
9263
+ current = current.parent;
9264
+ }
9265
+ return false;
9266
+ }
9267
+ function hasAuthArgument(call) {
9268
+ const argList = findChild7(call, "argument_list");
9269
+ if (!argList) return false;
9270
+ for (const arg of argList.namedChildren) {
9271
+ const text = arg.text;
9272
+ if (/[Aa]uth/.test(text)) return true;
9273
+ }
9274
+ return false;
9275
+ }
9276
+ function findChild7(node, type) {
9277
+ for (const child of node.namedChildren) {
9278
+ if (child.type === type) return child;
9279
+ }
9280
+ return null;
9281
+ }
9282
+ function findAllNodes3(root, type) {
9283
+ const results = [];
9284
+ function walk(node) {
9285
+ if (node.type === type) results.push(node);
9286
+ for (const child of node.namedChildren) {
9287
+ walk(child);
9288
+ }
9289
+ }
9290
+ walk(root);
9291
+ return results;
9292
+ }
9293
+ function getFirstStringArgument3(node) {
9294
+ const argList = findChild7(node, "argument_list");
9295
+ if (!argList) return null;
9296
+ for (const arg of argList.namedChildren) {
9297
+ if (arg.type === "interpreted_string_literal") {
9298
+ return arg.text.replace(/^"|"$/g, "");
9299
+ }
9300
+ if (arg.type === "raw_string_literal") {
9301
+ return arg.text.replace(/^`|`$/g, "");
9302
+ }
9303
+ }
9304
+ return null;
9305
+ }
9306
+
8302
9307
  // src/indexer/go/indexer.ts
8303
9308
  async function indexGoTreeSitter(db, options) {
8304
9309
  const start = Date.now();
@@ -8367,11 +9372,35 @@ async function indexGoTreeSitter(db, options) {
8367
9372
  "DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ? AND language = 'go')"
8368
9373
  ).run(service);
8369
9374
  writeDependencies(db, allDeps);
9375
+ const allRoutes = [];
9376
+ for (const [relPath, cached] of fileCache) {
9377
+ const routes = extractGoRoutes(cached.tree, relPath);
9378
+ allRoutes.push(...routes);
9379
+ }
9380
+ db.prepare("DELETE FROM routes WHERE service = ?").run(service);
9381
+ if (allRoutes.length > 0) {
9382
+ const insertRoute = db.prepare(`
9383
+ INSERT OR REPLACE INTO routes (id, route_path, kind, http_methods, has_auth, service)
9384
+ VALUES (?, ?, ?, ?, ?, ?)
9385
+ `);
9386
+ transaction(db, () => {
9387
+ for (const route of allRoutes) {
9388
+ insertRoute.run(
9389
+ `${service}::${route.id}`,
9390
+ route.routePath,
9391
+ route.kind,
9392
+ JSON.stringify(route.httpMethods),
9393
+ route.hasAuth ? 1 : 0,
9394
+ service
9395
+ );
9396
+ }
9397
+ });
9398
+ }
8370
9399
  return {
8371
9400
  symbolsIndexed: allNewSymbols.length,
8372
9401
  dependenciesIndexed: allDeps.length,
8373
9402
  componentsAnalyzed: 0,
8374
- routesAnalyzed: 0,
9403
+ routesAnalyzed: allRoutes.length,
8375
9404
  filesProcessed: changedFiles.length,
8376
9405
  filesSkipped,
8377
9406
  filesRemoved: removed.length,