@arcbridge/core 0.3.3 → 0.4.1

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
@@ -41,7 +41,7 @@ var QualityScenariosFileSchema = z.object({
41
41
  var ServiceSchema = z2.object({
42
42
  name: z2.string().min(1),
43
43
  path: z2.string().default("."),
44
- type: z2.enum(["nextjs", "react", "fastify", "express", "hono", "dotnet", "unity"]),
44
+ type: z2.enum(["nextjs", "react", "fastify", "express", "hono", "dotnet", "unity", "angular"]),
45
45
  tsconfig: z2.string().optional(),
46
46
  csproj: z2.string().optional()
47
47
  });
@@ -53,7 +53,8 @@ var ArcBridgeConfigSchema = z2.object({
53
53
  "react-vite",
54
54
  "api-service",
55
55
  "dotnet-webapi",
56
- "unity-game"
56
+ "unity-game",
57
+ "angular-app"
57
58
  ]).default("nextjs-app-router"),
58
59
  services: z2.array(ServiceSchema).default([]),
59
60
  platforms: z2.array(z2.enum(["claude", "copilot", "gemini", "codex"])).default(["claude"]),
@@ -291,7 +292,7 @@ function transaction(db, fn) {
291
292
  }
292
293
 
293
294
  // src/db/schema.ts
294
- var CURRENT_SCHEMA_VERSION = 3;
295
+ var CURRENT_SCHEMA_VERSION = 4;
295
296
  var SCHEMA_SQL = `
296
297
  -- Metadata
297
298
  CREATE TABLE IF NOT EXISTS arcbridge_meta (
@@ -446,6 +447,9 @@ CREATE TABLE IF NOT EXISTS tasks (
446
447
  completed_at TEXT
447
448
  );
448
449
 
450
+ CREATE INDEX IF NOT EXISTS idx_tasks_phase ON tasks(phase_id);
451
+ CREATE INDEX IF NOT EXISTS idx_phases_status ON phases(status);
452
+
449
453
  CREATE TABLE IF NOT EXISTS drift_log (
450
454
  id INTEGER PRIMARY KEY AUTOINCREMENT,
451
455
  detected_at TEXT NOT NULL,
@@ -559,6 +563,15 @@ var migrations = [
559
563
  ALTER TABLE tasks_new RENAME TO tasks;
560
564
  `);
561
565
  }
566
+ },
567
+ {
568
+ version: 4,
569
+ up: (db) => {
570
+ db.exec(`
571
+ CREATE INDEX IF NOT EXISTS idx_tasks_phase ON tasks(phase_id);
572
+ CREATE INDEX IF NOT EXISTS idx_phases_status ON phases(status);
573
+ `);
574
+ }
562
575
  }
563
576
  ];
564
577
  function migrate(db) {
@@ -764,13 +777,45 @@ function configTemplate5(input) {
764
777
  };
765
778
  }
766
779
 
780
+ // src/templates/config/angular-app.ts
781
+ function configTemplate6(input) {
782
+ return {
783
+ schema_version: 1,
784
+ project_name: input.name,
785
+ project_type: input.template,
786
+ services: [{ name: "main", path: ".", type: "angular" }],
787
+ platforms: input.platforms,
788
+ quality_priorities: input.quality_priorities,
789
+ indexing: {
790
+ include: ["src/**/*"],
791
+ exclude: ["node_modules", "dist", ".angular", "coverage"],
792
+ default_mode: "fast",
793
+ csharp_indexer: "auto"
794
+ },
795
+ testing: {
796
+ test_command: "npx ng test --watch=false",
797
+ timeout_ms: 12e4
798
+ },
799
+ drift: {
800
+ ignore_paths: []
801
+ },
802
+ metrics: { auto_record: false },
803
+ sync: {
804
+ auto_detect_drift: true,
805
+ drift_severity_threshold: "warning",
806
+ propose_updates_on: "phase-complete"
807
+ }
808
+ };
809
+ }
810
+
767
811
  // src/generators/config-generator.ts
768
812
  var configTemplates = {
769
813
  "nextjs-app-router": configTemplate,
770
814
  "react-vite": configTemplate2,
771
815
  "api-service": configTemplate3,
772
816
  "dotnet-webapi": configTemplate4,
773
- "unity-game": configTemplate5
817
+ "unity-game": configTemplate5,
818
+ "angular-app": configTemplate6
774
819
  };
775
820
  function generateConfig(targetDir, input) {
776
821
  const templateFn = configTemplates[input.template] ?? configTemplate;
@@ -799,7 +844,7 @@ function introductionTemplate(input) {
799
844
 
800
845
  ## Requirements Overview
801
846
 
802
- ${input.name} is a ${input.template === "nextjs-app-router" ? "Next.js application using the App Router" : input.template === "dotnet-webapi" ? "ASP.NET Core Web API" : input.template === "unity-game" ? "Unity game built with a code-heavy C# architecture" : "web application"}.
847
+ ${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"}.
803
848
 
804
849
  ### Key Features
805
850
 
@@ -875,6 +920,10 @@ function techStack(template) {
875
920
  return `- **Engine:** Unity
876
921
  - **Language:** C#
877
922
  - **Runtime:** Mono / IL2CPP`;
923
+ case "angular-app":
924
+ return `- **Framework:** Angular
925
+ - **Language:** TypeScript
926
+ - **Runtime:** Node.js (build) / Browser (runtime)`;
878
927
  default:
879
928
  return `- **Framework:** Next.js (App Router)
880
929
  - **Language:** TypeScript
@@ -986,6 +1035,8 @@ function getEntrypoints(template, srcPrefix, appPrefix) {
986
1035
  return [`${srcPrefix}index.ts`, `${srcPrefix}app.ts`, `${srcPrefix}server.ts`];
987
1036
  case "unity-game":
988
1037
  return ["Assets/Scripts/Core/GameManager.cs"];
1038
+ case "angular-app":
1039
+ return [`${srcPrefix}main.ts`, `${srcPrefix}app/app.component.ts`, `${srcPrefix}app/app.routes.ts`];
989
1040
  default:
990
1041
  return [`${srcPrefix}index.ts`];
991
1042
  }
@@ -995,7 +1046,7 @@ function getEntrypoints(template, srcPrefix, appPrefix) {
995
1046
  function buildingBlocksTemplate(input) {
996
1047
  const now = (/* @__PURE__ */ new Date()).toISOString();
997
1048
  const layout = detectProjectLayout(input.projectRoot, input.template);
998
- const defaultBlocks = input.template === "dotnet-webapi" ? buildDotnetBlocks(input) : input.template === "unity-game" ? buildUnityBlocks() : buildJsBlocks(input, layout);
1049
+ const defaultBlocks = input.template === "dotnet-webapi" ? buildDotnetBlocks(input) : input.template === "unity-game" ? buildUnityBlocks() : input.template === "angular-app" ? buildAngularBlocks(layout) : buildJsBlocks(input, layout);
999
1050
  function buildJsBlocks(inp, lt) {
1000
1051
  const src = lt.srcPrefix;
1001
1052
  const entries = lt.entrypoints;
@@ -1283,6 +1334,77 @@ function buildingBlocksTemplate(input) {
1283
1334
  }
1284
1335
  ];
1285
1336
  }
1337
+ function buildAngularBlocks(lt) {
1338
+ const src = lt.srcPrefix;
1339
+ return [
1340
+ {
1341
+ id: "app-shell",
1342
+ name: "App Shell",
1343
+ level: 1,
1344
+ code_paths: [`${src}main.ts`, `${src}app/app.component.ts`, `${src}app/app.config.ts`, `${src}app/app.routes.ts`],
1345
+ interfaces: [],
1346
+ quality_scenarios: [],
1347
+ adrs: [],
1348
+ responsibility: "Application bootstrap, root component, top-level routing and providers",
1349
+ service: "main"
1350
+ },
1351
+ {
1352
+ id: "core-services",
1353
+ name: "Core Services",
1354
+ level: 1,
1355
+ code_paths: [`${src}app/core/`],
1356
+ interfaces: [],
1357
+ quality_scenarios: [],
1358
+ adrs: [],
1359
+ responsibility: "Singleton services, guards, interceptors, and app-wide infrastructure",
1360
+ service: "main"
1361
+ },
1362
+ {
1363
+ id: "shared-components",
1364
+ name: "Shared Components",
1365
+ level: 1,
1366
+ code_paths: [`${src}app/shared/`],
1367
+ interfaces: [],
1368
+ quality_scenarios: ["A11Y-01"],
1369
+ adrs: [],
1370
+ responsibility: "Reusable components, directives, and pipes shared across features",
1371
+ service: "main"
1372
+ },
1373
+ {
1374
+ id: "feature-modules",
1375
+ name: "Feature Modules",
1376
+ level: 1,
1377
+ code_paths: [`${src}app/features/`],
1378
+ interfaces: ["core-services", "shared-components"],
1379
+ quality_scenarios: ["PERF-05"],
1380
+ adrs: [],
1381
+ responsibility: "Self-contained feature areas with own routes, components, and services",
1382
+ service: "main"
1383
+ },
1384
+ {
1385
+ id: "models",
1386
+ name: "Models & Types",
1387
+ level: 1,
1388
+ code_paths: [`${src}app/models/`],
1389
+ interfaces: [],
1390
+ quality_scenarios: [],
1391
+ adrs: [],
1392
+ responsibility: "Shared interfaces, types, enums, and DTOs",
1393
+ service: "main"
1394
+ },
1395
+ {
1396
+ id: "api-client",
1397
+ name: "API Client",
1398
+ level: 1,
1399
+ code_paths: [`${src}app/core/http/`, `${src}app/core/services/`],
1400
+ interfaces: [],
1401
+ quality_scenarios: [],
1402
+ adrs: [],
1403
+ responsibility: "HTTP client layer, interceptors, and API service abstractions",
1404
+ service: "main"
1405
+ }
1406
+ ];
1407
+ }
1286
1408
  return {
1287
1409
  frontmatter: {
1288
1410
  section: "building-blocks",
@@ -1317,6 +1439,12 @@ Client \u2192 Kestrel \u2192 Middleware Pipeline \u2192 Controller / Endpoint
1317
1439
  Client \u2192 HTTP Server \u2192 Middleware \u2192 Route Handler
1318
1440
  \u2192 Services
1319
1441
  \u2192 Database / External APIs
1442
+ \`\`\``;
1443
+ case "angular-app":
1444
+ return `\`\`\`
1445
+ Browser \u2192 Angular Router \u2192 Feature Component (lazy-loaded)
1446
+ \u2192 Services (DI)
1447
+ \u2192 HTTP Interceptors \u2192 API
1320
1448
  \`\`\``;
1321
1449
  case "unity-game":
1322
1450
  return `\`\`\`
@@ -1369,7 +1497,12 @@ function deploymentTemplate(input) {
1369
1497
 
1370
1498
  ### Deployment Options
1371
1499
 
1372
- ${input.template === "dotnet-webapi" ? `| Platform | Description | Notes |
1500
+ ${input.template === "angular-app" ? `| Platform | Description | Notes |
1501
+ |----------|-------------|-------|
1502
+ | Vercel / Netlify | Static hosting | For SSG or prerendered apps |
1503
+ | Firebase Hosting | Google Cloud | Integrated with Angular Fire |
1504
+ | Docker | Container-based | For self-hosted environments |
1505
+ | Cloud Run / App Engine | Google Cloud PaaS | For SSR with Angular Universal |` : input.template === "dotnet-webapi" ? `| Platform | Description | Notes |
1373
1506
  |----------|-------------|-------|
1374
1507
  | Azure App Service | Managed PaaS for .NET | Recommended for ASP.NET Core |
1375
1508
  | Docker / Kubernetes | Container-based | For self-hosted or multi-cloud |
@@ -1406,6 +1539,8 @@ function firstAdrTemplate(input) {
1406
1539
  return apiServiceAdr(input, now);
1407
1540
  case "unity-game":
1408
1541
  return unityAdr(input, now);
1542
+ case "angular-app":
1543
+ return angularAdr(input, now);
1409
1544
  default:
1410
1545
  return nextjsAdr(input, now);
1411
1546
  }
@@ -1510,6 +1645,44 @@ Use a Node.js HTTP framework (Express, Fastify, or Hono) as the API service foun
1510
1645
  `
1511
1646
  };
1512
1647
  }
1648
+ function angularAdr(input, date) {
1649
+ const { srcPrefix } = detectProjectLayout(input.projectRoot, input.template);
1650
+ return {
1651
+ filename: "001-angular-standalone.md",
1652
+ frontmatter: {
1653
+ id: "001-angular-standalone",
1654
+ title: "Use Angular with Standalone Components",
1655
+ status: "accepted",
1656
+ date,
1657
+ affected_blocks: ["app-shell", "feature-modules"],
1658
+ affected_files: [srcPrefix || "./"],
1659
+ quality_scenarios: []
1660
+ },
1661
+ body: `# ADR-001: Use Angular with Standalone Components
1662
+
1663
+ ## Context
1664
+
1665
+ ${input.name} needs a modern, opinionated frontend framework with strong TypeScript support, dependency injection, and a structured approach to building large applications.
1666
+
1667
+ ## Decision
1668
+
1669
+ Use Angular with standalone components (default since Angular v17). Use the signals API for reactive state management and functional guards/resolvers for routing.
1670
+
1671
+ ## Consequences
1672
+
1673
+ - **Positive:** Strong TypeScript integration with decorators and DI
1674
+ - **Positive:** Standalone components simplify the module system and improve tree-shaking
1675
+ - **Positive:** Signals provide fine-grained reactivity without Zone.js overhead
1676
+ - **Positive:** Opinionated structure reduces architectural decision fatigue
1677
+ - **Negative:** Steeper learning curve compared to lighter frameworks
1678
+ - **Negative:** Larger initial bundle size (mitigated by lazy loading)
1679
+
1680
+ ## Current Limitations
1681
+
1682
+ The TypeScript indexer fully indexes Angular symbols, dependencies, services, and \`@Component\` declarations. The \`arcbridge_get_component_graph\` tool lists detected Angular components with their selectors and imports, but **template-based relationship analysis** (which component renders which via template selectors) is not yet implemented. Component listing works; hierarchy tracking is a planned enhancement.
1683
+ `
1684
+ };
1685
+ }
1513
1686
  function unityAdr(input, date) {
1514
1687
  return {
1515
1688
  filename: "001-unity-code-heavy.md",
@@ -1989,9 +2162,122 @@ var UNITY_GAME_SCENARIOS = {
1989
2162
  }
1990
2163
  ]
1991
2164
  };
2165
+ var ANGULAR_SCENARIOS = {
2166
+ security: [
2167
+ {
2168
+ id: "SEC-02",
2169
+ name: "No secrets in client bundles",
2170
+ category: "security",
2171
+ priority: "must",
2172
+ scenario: "Production build output is analyzed",
2173
+ expected: "No API keys, tokens, or secrets found in client-side JavaScript bundles",
2174
+ linked_code: [],
2175
+ linked_tests: [],
2176
+ linked_blocks: [],
2177
+ verification: "automatic",
2178
+ status: "untested"
2179
+ },
2180
+ {
2181
+ id: "SEC-04",
2182
+ name: "No bypassSecurityTrust without review",
2183
+ category: "security",
2184
+ priority: "should",
2185
+ scenario: "Source code is scanned for DomSanitizer bypass calls",
2186
+ expected: "All bypassSecurityTrust* calls have a documented justification in an ADR or code comment",
2187
+ linked_code: [],
2188
+ linked_tests: [],
2189
+ linked_blocks: [],
2190
+ verification: "semi-automatic",
2191
+ status: "untested"
2192
+ }
2193
+ ],
2194
+ performance: [
2195
+ {
2196
+ id: "PERF-01",
2197
+ name: "Initial page load under 3s",
2198
+ category: "performance",
2199
+ priority: "should",
2200
+ scenario: "User loads the landing page on a 3G connection",
2201
+ expected: "Largest Contentful Paint (LCP) is under 3 seconds",
2202
+ linked_code: [],
2203
+ linked_tests: [],
2204
+ linked_blocks: [],
2205
+ verification: "semi-automatic",
2206
+ status: "untested"
2207
+ },
2208
+ {
2209
+ id: "PERF-03",
2210
+ name: "Main bundle under 200KB gzipped",
2211
+ category: "performance",
2212
+ priority: "should",
2213
+ scenario: "Production build is analyzed",
2214
+ expected: "Main JavaScript bundle is under 200KB gzipped",
2215
+ linked_code: [],
2216
+ linked_tests: [],
2217
+ linked_blocks: [],
2218
+ verification: "semi-automatic",
2219
+ status: "untested"
2220
+ },
2221
+ {
2222
+ id: "PERF-04",
2223
+ name: "OnPush or signal-based change detection",
2224
+ category: "performance",
2225
+ priority: "should",
2226
+ scenario: "Components are reviewed for change detection strategy",
2227
+ expected: "All components use OnPush strategy or signal-based inputs \u2014 no default change detection",
2228
+ linked_code: [],
2229
+ linked_tests: [],
2230
+ linked_blocks: [],
2231
+ verification: "semi-automatic",
2232
+ status: "untested"
2233
+ },
2234
+ {
2235
+ id: "PERF-05",
2236
+ name: "Lazy loading on feature routes",
2237
+ category: "performance",
2238
+ priority: "must",
2239
+ scenario: "Application routes are analyzed",
2240
+ expected: "All feature routes use loadComponent or loadChildren for lazy loading",
2241
+ linked_code: [],
2242
+ linked_tests: [],
2243
+ linked_blocks: ["feature-modules"],
2244
+ verification: "automatic",
2245
+ status: "untested"
2246
+ }
2247
+ ],
2248
+ accessibility: [
2249
+ {
2250
+ id: "A11Y-01",
2251
+ name: "WCAG 2.1 AA compliance",
2252
+ category: "accessibility",
2253
+ priority: "should",
2254
+ scenario: "All pages are audited with axe-core",
2255
+ expected: "No critical or serious accessibility violations",
2256
+ linked_code: [],
2257
+ linked_tests: [],
2258
+ linked_blocks: [],
2259
+ verification: "semi-automatic",
2260
+ status: "untested"
2261
+ },
2262
+ {
2263
+ id: "A11Y-02",
2264
+ name: "Keyboard navigation",
2265
+ category: "accessibility",
2266
+ priority: "should",
2267
+ scenario: "User navigates the entire application using only keyboard",
2268
+ expected: "All interactive elements are reachable and operable via keyboard",
2269
+ linked_code: [],
2270
+ linked_tests: [],
2271
+ linked_blocks: [],
2272
+ verification: "manual",
2273
+ status: "untested"
2274
+ }
2275
+ ]
2276
+ };
1992
2277
  var TEMPLATE_SCENARIOS = {
1993
2278
  "nextjs-app-router": FRONTEND_SCENARIOS,
1994
2279
  "react-vite": FRONTEND_SCENARIOS,
2280
+ "angular-app": ANGULAR_SCENARIOS,
1995
2281
  "api-service": API_SCENARIOS,
1996
2282
  "dotnet-webapi": DOTNET_SCENARIOS,
1997
2283
  "unity-game": UNITY_GAME_SCENARIOS
@@ -2297,7 +2583,7 @@ function unityConcepts() {
2297
2583
  }
2298
2584
  function crosscuttingTemplate(input) {
2299
2585
  const isDotnet = input.template === "dotnet-webapi";
2300
- const isFrontend = input.template === "nextjs-app-router" || input.template === "react-vite";
2586
+ const isFrontend = input.template === "nextjs-app-router" || input.template === "react-vite" || input.template === "angular-app";
2301
2587
  const isUnity = input.template === "unity-game";
2302
2588
  let concepts;
2303
2589
  if (isDotnet) {
@@ -3492,13 +3778,184 @@ function phaseTasksTemplate5(_input, phaseId) {
3492
3778
  return tasksByPhase[phaseId] ?? null;
3493
3779
  }
3494
3780
 
3781
+ // src/templates/phases/angular-app.ts
3782
+ function phasePlanTemplate6(_input) {
3783
+ const phases = [
3784
+ {
3785
+ id: "phase-0-setup",
3786
+ name: "Project Setup",
3787
+ phase_number: 0,
3788
+ status: "planned",
3789
+ description: "Initialize Angular project, configure strict TypeScript, set up tooling and testing infrastructure",
3790
+ gate_requirements: [
3791
+ "Angular project builds and serves without errors",
3792
+ "Strict TypeScript configuration enabled",
3793
+ "Testing infrastructure configured (Vitest or Karma)",
3794
+ "Linting and formatting in place"
3795
+ ]
3796
+ },
3797
+ {
3798
+ id: "phase-1-foundation",
3799
+ name: "Foundation",
3800
+ phase_number: 1,
3801
+ status: "planned",
3802
+ description: "App shell, routing with lazy loading, shared component library, core services (HTTP interceptor, auth guard)",
3803
+ gate_requirements: [
3804
+ "App shell with routing configured",
3805
+ "At least one lazy-loaded feature route",
3806
+ "HTTP interceptor for API calls",
3807
+ "Shared component library started",
3808
+ "Quality scenario PERF-05 (lazy loading) verified"
3809
+ ]
3810
+ },
3811
+ {
3812
+ id: "phase-2-features",
3813
+ name: "Core Features",
3814
+ phase_number: 2,
3815
+ status: "planned",
3816
+ description: "Feature implementation, state management with signals or NgRx, integration tests",
3817
+ gate_requirements: [
3818
+ "Core features complete and functional",
3819
+ "State management approach established",
3820
+ "Integration tests cover key workflows",
3821
+ "All feature routes lazy-loaded"
3822
+ ]
3823
+ },
3824
+ {
3825
+ id: "phase-3-polish",
3826
+ name: "Polish & Launch",
3827
+ phase_number: 3,
3828
+ status: "planned",
3829
+ description: "Performance audit (bundle size, change detection), accessibility, deployment configuration",
3830
+ gate_requirements: [
3831
+ "All quality scenarios passing",
3832
+ "Bundle size within budget",
3833
+ "Accessibility audit complete",
3834
+ "Production build and deployment successful"
3835
+ ]
3836
+ }
3837
+ ];
3838
+ return { schema_version: 1, phases };
3839
+ }
3840
+ function phaseTasksTemplate6(_input, phaseId) {
3841
+ const tasksByPhase = {
3842
+ "phase-0-setup": {
3843
+ schema_version: 1,
3844
+ phase_id: "phase-0-setup",
3845
+ tasks: [
3846
+ {
3847
+ id: "task-0.1-init-project",
3848
+ title: "Initialize Angular project with strict configuration",
3849
+ status: "todo",
3850
+ building_block: "app-shell",
3851
+ quality_scenarios: [],
3852
+ acceptance_criteria: [
3853
+ "Angular CLI project created with strict mode",
3854
+ "Standalone components enabled (default in Angular 17+)",
3855
+ "TypeScript strict mode enforced"
3856
+ ]
3857
+ },
3858
+ {
3859
+ id: "task-0.2-routing",
3860
+ title: "Set up routing with lazy loading",
3861
+ status: "todo",
3862
+ building_block: "app-shell",
3863
+ quality_scenarios: ["PERF-05"],
3864
+ acceptance_criteria: [
3865
+ "App routes defined in app.routes.ts",
3866
+ "At least one feature route uses loadComponent or loadChildren",
3867
+ "Route guards and resolvers use functional approach"
3868
+ ]
3869
+ },
3870
+ {
3871
+ id: "task-0.3-core-services",
3872
+ title: "Set up core services and HTTP interceptor",
3873
+ status: "todo",
3874
+ building_block: "core-services",
3875
+ quality_scenarios: [],
3876
+ acceptance_criteria: [
3877
+ "HTTP interceptor for API base URL and error handling",
3878
+ "Core services provided in root",
3879
+ "Environment configuration set up"
3880
+ ]
3881
+ },
3882
+ {
3883
+ id: "task-0.4-testing",
3884
+ title: "Set up testing infrastructure",
3885
+ status: "todo",
3886
+ quality_scenarios: ["MAINT-02"],
3887
+ acceptance_criteria: [
3888
+ "Unit test framework configured (Vitest or Karma/Jasmine)",
3889
+ "First component test passes",
3890
+ "Test coverage reporting enabled"
3891
+ ]
3892
+ }
3893
+ ]
3894
+ },
3895
+ "phase-1-foundation": {
3896
+ schema_version: 1,
3897
+ phase_id: "phase-1-foundation",
3898
+ tasks: [
3899
+ {
3900
+ id: "task-1.1-shared-components",
3901
+ title: "Build shared component library",
3902
+ status: "todo",
3903
+ building_block: "shared-components",
3904
+ quality_scenarios: ["A11Y-01"],
3905
+ acceptance_criteria: [
3906
+ "Reusable UI components in shared/ directory",
3907
+ "Components use OnPush change detection or signals",
3908
+ "Components follow accessibility guidelines"
3909
+ ]
3910
+ },
3911
+ {
3912
+ id: "task-1.2-auth",
3913
+ title: "Implement authentication and route guards",
3914
+ status: "todo",
3915
+ building_block: "core-services",
3916
+ quality_scenarios: ["SEC-01"],
3917
+ acceptance_criteria: [
3918
+ "Auth service with login/logout/token management",
3919
+ "Functional route guard protecting private routes",
3920
+ "Auth interceptor attaching tokens to API requests"
3921
+ ]
3922
+ },
3923
+ {
3924
+ id: "task-1.3-api-client",
3925
+ title: "Set up API client layer",
3926
+ status: "todo",
3927
+ building_block: "api-client",
3928
+ quality_scenarios: [],
3929
+ acceptance_criteria: [
3930
+ "Typed API service methods for backend endpoints",
3931
+ "Error handling and retry logic",
3932
+ "Loading/error state management"
3933
+ ]
3934
+ },
3935
+ {
3936
+ id: "task-1.4-document-decisions",
3937
+ title: "Document architectural decisions as ADRs",
3938
+ status: "todo",
3939
+ quality_scenarios: [],
3940
+ acceptance_criteria: [
3941
+ "ADR for each significant architecture/pattern choice",
3942
+ "ADRs linked to affected building blocks and code paths"
3943
+ ]
3944
+ }
3945
+ ]
3946
+ }
3947
+ };
3948
+ return tasksByPhase[phaseId] ?? null;
3949
+ }
3950
+
3495
3951
  // src/generators/plan-generator.ts
3496
3952
  var planTemplates = {
3497
3953
  "nextjs-app-router": { plan: phasePlanTemplate, tasks: phaseTasksTemplate },
3498
3954
  "react-vite": { plan: phasePlanTemplate2, tasks: phaseTasksTemplate2 },
3499
3955
  "api-service": { plan: phasePlanTemplate3, tasks: phaseTasksTemplate3 },
3500
3956
  "dotnet-webapi": { plan: phasePlanTemplate4, tasks: phaseTasksTemplate4 },
3501
- "unity-game": { plan: phasePlanTemplate5, tasks: phaseTasksTemplate5 }
3957
+ "unity-game": { plan: phasePlanTemplate5, tasks: phaseTasksTemplate5 },
3958
+ "angular-app": { plan: phasePlanTemplate6, tasks: phaseTasksTemplate6 }
3502
3959
  };
3503
3960
  function generatePlan(targetDir, input) {
3504
3961
  const planDir = join5(targetDir, ".arcbridge", "plan");
@@ -4172,7 +4629,7 @@ You cannot see screenshots, but you CAN reason about UI quality through code:
4172
4629
  }
4173
4630
 
4174
4631
  // src/generators/agent-generator.ts
4175
- var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "unity-game"]);
4632
+ var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "angular-app", "unity-game"]);
4176
4633
  function writeAgentRole(dir, role) {
4177
4634
  const { system_prompt, ...frontmatter } = role;
4178
4635
  const content = matter2.stringify(system_prompt, frontmatter);
@@ -4717,11 +5174,16 @@ function extractSymbols(sourceFile, checker, relativePath, contentHash) {
4717
5174
  }
4718
5175
  if (ts3.isClassDeclaration(node) && node.name) {
4719
5176
  const name = node.name.text;
5177
+ const decorators = typeof ts3.canHaveDecorators === "function" && typeof ts3.getDecorators === "function" && ts3.canHaveDecorators(node) ? ts3.getDecorators(node) ?? [] : node.modifiers?.filter(ts3.isDecorator) ?? [];
5178
+ const hasComponentDecorator = decorators.some(
5179
+ (d) => ts3.isCallExpression(d.expression) && ts3.isIdentifier(d.expression.expression) && d.expression.expression.text === "Component"
5180
+ );
5181
+ const kind = hasComponentDecorator ? "component" : "class";
4720
5182
  symbols.push({
4721
- id: makeId(name, "class"),
5183
+ id: makeId(name, kind),
4722
5184
  name,
4723
5185
  qualifiedName: name,
4724
- kind: "class",
5186
+ kind,
4725
5187
  filePath: relativePath,
4726
5188
  ...getLocation(node),
4727
5189
  signature: null,
@@ -5300,6 +5762,70 @@ function analyzeComponents(sourceFiles, checker, projectRoot, db, allClient = fa
5300
5762
  });
5301
5763
  }
5302
5764
  }
5765
+ if (ts5.isClassDeclaration(node) && node.name) {
5766
+ const decorators = typeof ts5.canHaveDecorators === "function" && typeof ts5.getDecorators === "function" && ts5.canHaveDecorators(node) ? ts5.getDecorators(node) ?? [] : node.modifiers?.filter(ts5.isDecorator) ?? [];
5767
+ const componentDecorator = decorators.find(
5768
+ (decorator) => ts5.isCallExpression(decorator.expression) && ts5.isIdentifier(decorator.expression.expression) && decorator.expression.expression.text === "Component"
5769
+ );
5770
+ if (componentDecorator && ts5.isCallExpression(componentDecorator.expression)) {
5771
+ const name = node.name.text;
5772
+ const symbolId = `${relPath}::${name}#component`;
5773
+ let propsType = null;
5774
+ const metaArg = componentDecorator.expression.arguments[0];
5775
+ if (metaArg && ts5.isObjectLiteralExpression(metaArg)) {
5776
+ const selectorProp = metaArg.properties.find(
5777
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === "selector"
5778
+ );
5779
+ if (selectorProp && ts5.isStringLiteral(selectorProp.initializer)) {
5780
+ propsType = `selector: ${selectorProp.initializer.text}`;
5781
+ }
5782
+ }
5783
+ let hasState = false;
5784
+ if (node.members) {
5785
+ for (const member of node.members) {
5786
+ if (ts5.isPropertyDeclaration(member) && member.initializer) {
5787
+ const init = ts5.isParenthesizedExpression(member.initializer) ? member.initializer.expression : member.initializer;
5788
+ if (ts5.isCallExpression(init)) {
5789
+ const callee = init.expression;
5790
+ const calleeName = ts5.isIdentifier(callee) ? callee.text : ts5.isPropertyAccessExpression(callee) ? callee.name.text : null;
5791
+ if (calleeName === "signal" || calleeName === "computed") {
5792
+ hasState = true;
5793
+ break;
5794
+ }
5795
+ }
5796
+ }
5797
+ }
5798
+ }
5799
+ if (metaArg && ts5.isObjectLiteralExpression(metaArg)) {
5800
+ const importsProp = metaArg.properties.find(
5801
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === "imports"
5802
+ );
5803
+ if (importsProp && ts5.isArrayLiteralExpression(importsProp.initializer)) {
5804
+ const importNames = [];
5805
+ for (const el of importsProp.initializer.elements) {
5806
+ if (ts5.isIdentifier(el)) {
5807
+ importNames.push(el.text);
5808
+ }
5809
+ }
5810
+ if (importNames.length > 0) {
5811
+ propsType = propsType ? `${propsType} | imports: ${importNames.join(", ")}` : `imports: ${importNames.join(", ")}`;
5812
+ }
5813
+ }
5814
+ }
5815
+ components.push({
5816
+ symbolId,
5817
+ isClient: true,
5818
+ // Angular components are always client-side
5819
+ isServerAction: false,
5820
+ hasState,
5821
+ contextProviders: [],
5822
+ // Angular uses DI, not context
5823
+ contextConsumers: [],
5824
+ // Not applicable for Angular
5825
+ propsType
5826
+ });
5827
+ }
5828
+ }
5303
5829
  });
5304
5830
  }
5305
5831
  writeComponents(db, components);
@@ -6950,7 +7476,7 @@ function indexTypeScriptProject(db, options) {
6950
7476
  });
6951
7477
  db.prepare("DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ?)").run(service);
6952
7478
  writeDependencies(db, allDeps);
6953
- const CLIENT_ONLY_TEMPLATES = /* @__PURE__ */ new Set(["react-vite"]);
7479
+ const CLIENT_ONLY_TEMPLATES = /* @__PURE__ */ new Set(["react-vite", "angular-app"]);
6954
7480
  const projectType = db.prepare("SELECT value FROM arcbridge_meta WHERE key = 'project_type'").get()?.value;
6955
7481
  const allClient = projectType ? CLIENT_ONLY_TEMPLATES.has(projectType) : false;
6956
7482
  const componentsAnalyzed = analyzeComponents(sourceFiles, checker, projectRoot, db, allClient);
@@ -6989,6 +7515,14 @@ var FRAMEWORK_IGNORES = {
6989
7515
  ],
6990
7516
  "react-vite": ["src/main.", "src/App.", "vite.config"],
6991
7517
  "api-service": ["src/index.", "src/app.", "src/server."],
7518
+ "angular-app": [
7519
+ ".angular/",
7520
+ "src/environments/",
7521
+ "src/main.ts",
7522
+ "src/index.html",
7523
+ "src/styles.",
7524
+ "angular.json"
7525
+ ],
6992
7526
  "dotnet-webapi": [
6993
7527
  "Program.",
6994
7528
  "Startup.",
@@ -7071,7 +7605,7 @@ function detectMissingModules(db, entries) {
7071
7605
  const paths = safeParseJson(block.code_paths, []);
7072
7606
  for (const cp of paths) {
7073
7607
  const prefix = normalizePath(cp);
7074
- const match = db.prepare("SELECT 1 FROM symbols WHERE file_path LIKE ? LIMIT 1").get(`${escapeLike(prefix)}%`);
7608
+ const match = db.prepare("SELECT 1 FROM symbols WHERE file_path LIKE ? ESCAPE '\\' LIMIT 1").get(`${escapeLike(prefix)}%`);
7075
7609
  if (!match) {
7076
7610
  entries.push({
7077
7611
  kind: "missing_module",
@@ -7175,7 +7709,7 @@ function detectStaleAdrs(db, entries) {
7175
7709
  if (files.length === 0) continue;
7176
7710
  for (const file of files) {
7177
7711
  const prefix = normalizePath(file);
7178
- const match = db.prepare("SELECT 1 FROM symbols WHERE file_path LIKE ? LIMIT 1").get(`${escapeLike(prefix)}%`);
7712
+ const match = db.prepare("SELECT 1 FROM symbols WHERE file_path LIKE ? ESCAPE '\\' LIMIT 1").get(`${escapeLike(prefix)}%`);
7179
7713
  if (!match) {
7180
7714
  entries.push({
7181
7715
  kind: "stale_adr",
@@ -7259,7 +7793,7 @@ function fileMatchesPath(filePath, prefix) {
7259
7793
  return filePath === prefix || filePath.startsWith(prefix);
7260
7794
  }
7261
7795
  function escapeLike(value) {
7262
- return value.replace(/%/g, "\\%").replace(/_/g, "\\_");
7796
+ return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
7263
7797
  }
7264
7798
  function safeParseJson(value, fallback) {
7265
7799
  if (value === null || value === void 0) return fallback;
@@ -7574,7 +8108,7 @@ function inferSingleTask(db, task) {
7574
8108
  return null;
7575
8109
  }
7576
8110
  function escapeLike2(value) {
7577
- return value.replace(/%/g, "\\%").replace(/_/g, "\\_");
8111
+ return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
7578
8112
  }
7579
8113
  function safeParseJson2(value, fallback) {
7580
8114
  if (value === null || value === void 0) return fallback;