@arcbridge/core 0.3.2 → 0.4.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
@@ -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"]),
@@ -764,13 +765,45 @@ function configTemplate5(input) {
764
765
  };
765
766
  }
766
767
 
768
+ // src/templates/config/angular-app.ts
769
+ function configTemplate6(input) {
770
+ return {
771
+ schema_version: 1,
772
+ project_name: input.name,
773
+ project_type: input.template,
774
+ services: [{ name: "main", path: ".", type: "angular" }],
775
+ platforms: input.platforms,
776
+ quality_priorities: input.quality_priorities,
777
+ indexing: {
778
+ include: ["src/**/*"],
779
+ exclude: ["node_modules", "dist", ".angular", "coverage"],
780
+ default_mode: "fast",
781
+ csharp_indexer: "auto"
782
+ },
783
+ testing: {
784
+ test_command: "npx ng test --watch=false",
785
+ timeout_ms: 12e4
786
+ },
787
+ drift: {
788
+ ignore_paths: []
789
+ },
790
+ metrics: { auto_record: false },
791
+ sync: {
792
+ auto_detect_drift: true,
793
+ drift_severity_threshold: "warning",
794
+ propose_updates_on: "phase-complete"
795
+ }
796
+ };
797
+ }
798
+
767
799
  // src/generators/config-generator.ts
768
800
  var configTemplates = {
769
801
  "nextjs-app-router": configTemplate,
770
802
  "react-vite": configTemplate2,
771
803
  "api-service": configTemplate3,
772
804
  "dotnet-webapi": configTemplate4,
773
- "unity-game": configTemplate5
805
+ "unity-game": configTemplate5,
806
+ "angular-app": configTemplate6
774
807
  };
775
808
  function generateConfig(targetDir, input) {
776
809
  const templateFn = configTemplates[input.template] ?? configTemplate;
@@ -799,7 +832,7 @@ function introductionTemplate(input) {
799
832
 
800
833
  ## Requirements Overview
801
834
 
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"}.
835
+ ${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
836
 
804
837
  ### Key Features
805
838
 
@@ -875,6 +908,10 @@ function techStack(template) {
875
908
  return `- **Engine:** Unity
876
909
  - **Language:** C#
877
910
  - **Runtime:** Mono / IL2CPP`;
911
+ case "angular-app":
912
+ return `- **Framework:** Angular
913
+ - **Language:** TypeScript
914
+ - **Runtime:** Node.js (build) / Browser (runtime)`;
878
915
  default:
879
916
  return `- **Framework:** Next.js (App Router)
880
917
  - **Language:** TypeScript
@@ -986,6 +1023,8 @@ function getEntrypoints(template, srcPrefix, appPrefix) {
986
1023
  return [`${srcPrefix}index.ts`, `${srcPrefix}app.ts`, `${srcPrefix}server.ts`];
987
1024
  case "unity-game":
988
1025
  return ["Assets/Scripts/Core/GameManager.cs"];
1026
+ case "angular-app":
1027
+ return [`${srcPrefix}main.ts`, `${srcPrefix}app/app.component.ts`, `${srcPrefix}app/app.routes.ts`];
989
1028
  default:
990
1029
  return [`${srcPrefix}index.ts`];
991
1030
  }
@@ -995,7 +1034,7 @@ function getEntrypoints(template, srcPrefix, appPrefix) {
995
1034
  function buildingBlocksTemplate(input) {
996
1035
  const now = (/* @__PURE__ */ new Date()).toISOString();
997
1036
  const layout = detectProjectLayout(input.projectRoot, input.template);
998
- const defaultBlocks = input.template === "dotnet-webapi" ? buildDotnetBlocks(input) : input.template === "unity-game" ? buildUnityBlocks() : buildJsBlocks(input, layout);
1037
+ const defaultBlocks = input.template === "dotnet-webapi" ? buildDotnetBlocks(input) : input.template === "unity-game" ? buildUnityBlocks() : input.template === "angular-app" ? buildAngularBlocks(layout) : buildJsBlocks(input, layout);
999
1038
  function buildJsBlocks(inp, lt) {
1000
1039
  const src = lt.srcPrefix;
1001
1040
  const entries = lt.entrypoints;
@@ -1283,6 +1322,77 @@ function buildingBlocksTemplate(input) {
1283
1322
  }
1284
1323
  ];
1285
1324
  }
1325
+ function buildAngularBlocks(lt) {
1326
+ const src = lt.srcPrefix;
1327
+ return [
1328
+ {
1329
+ id: "app-shell",
1330
+ name: "App Shell",
1331
+ level: 1,
1332
+ code_paths: [`${src}main.ts`, `${src}app/app.component.ts`, `${src}app/app.config.ts`, `${src}app/app.routes.ts`],
1333
+ interfaces: [],
1334
+ quality_scenarios: [],
1335
+ adrs: [],
1336
+ responsibility: "Application bootstrap, root component, top-level routing and providers",
1337
+ service: "main"
1338
+ },
1339
+ {
1340
+ id: "core-services",
1341
+ name: "Core Services",
1342
+ level: 1,
1343
+ code_paths: [`${src}app/core/`],
1344
+ interfaces: [],
1345
+ quality_scenarios: [],
1346
+ adrs: [],
1347
+ responsibility: "Singleton services, guards, interceptors, and app-wide infrastructure",
1348
+ service: "main"
1349
+ },
1350
+ {
1351
+ id: "shared-components",
1352
+ name: "Shared Components",
1353
+ level: 1,
1354
+ code_paths: [`${src}app/shared/`],
1355
+ interfaces: [],
1356
+ quality_scenarios: ["A11Y-01"],
1357
+ adrs: [],
1358
+ responsibility: "Reusable components, directives, and pipes shared across features",
1359
+ service: "main"
1360
+ },
1361
+ {
1362
+ id: "feature-modules",
1363
+ name: "Feature Modules",
1364
+ level: 1,
1365
+ code_paths: [`${src}app/features/`],
1366
+ interfaces: ["core-services", "shared-components"],
1367
+ quality_scenarios: ["PERF-05"],
1368
+ adrs: [],
1369
+ responsibility: "Self-contained feature areas with own routes, components, and services",
1370
+ service: "main"
1371
+ },
1372
+ {
1373
+ id: "models",
1374
+ name: "Models & Types",
1375
+ level: 1,
1376
+ code_paths: [`${src}app/models/`],
1377
+ interfaces: [],
1378
+ quality_scenarios: [],
1379
+ adrs: [],
1380
+ responsibility: "Shared interfaces, types, enums, and DTOs",
1381
+ service: "main"
1382
+ },
1383
+ {
1384
+ id: "api-client",
1385
+ name: "API Client",
1386
+ level: 1,
1387
+ code_paths: [`${src}app/core/http/`, `${src}app/core/services/`],
1388
+ interfaces: [],
1389
+ quality_scenarios: [],
1390
+ adrs: [],
1391
+ responsibility: "HTTP client layer, interceptors, and API service abstractions",
1392
+ service: "main"
1393
+ }
1394
+ ];
1395
+ }
1286
1396
  return {
1287
1397
  frontmatter: {
1288
1398
  section: "building-blocks",
@@ -1317,6 +1427,12 @@ Client \u2192 Kestrel \u2192 Middleware Pipeline \u2192 Controller / Endpoint
1317
1427
  Client \u2192 HTTP Server \u2192 Middleware \u2192 Route Handler
1318
1428
  \u2192 Services
1319
1429
  \u2192 Database / External APIs
1430
+ \`\`\``;
1431
+ case "angular-app":
1432
+ return `\`\`\`
1433
+ Browser \u2192 Angular Router \u2192 Feature Component (lazy-loaded)
1434
+ \u2192 Services (DI)
1435
+ \u2192 HTTP Interceptors \u2192 API
1320
1436
  \`\`\``;
1321
1437
  case "unity-game":
1322
1438
  return `\`\`\`
@@ -1369,7 +1485,12 @@ function deploymentTemplate(input) {
1369
1485
 
1370
1486
  ### Deployment Options
1371
1487
 
1372
- ${input.template === "dotnet-webapi" ? `| Platform | Description | Notes |
1488
+ ${input.template === "angular-app" ? `| Platform | Description | Notes |
1489
+ |----------|-------------|-------|
1490
+ | Vercel / Netlify | Static hosting | For SSG or prerendered apps |
1491
+ | Firebase Hosting | Google Cloud | Integrated with Angular Fire |
1492
+ | Docker | Container-based | For self-hosted environments |
1493
+ | Cloud Run / App Engine | Google Cloud PaaS | For SSR with Angular Universal |` : input.template === "dotnet-webapi" ? `| Platform | Description | Notes |
1373
1494
  |----------|-------------|-------|
1374
1495
  | Azure App Service | Managed PaaS for .NET | Recommended for ASP.NET Core |
1375
1496
  | Docker / Kubernetes | Container-based | For self-hosted or multi-cloud |
@@ -1406,6 +1527,8 @@ function firstAdrTemplate(input) {
1406
1527
  return apiServiceAdr(input, now);
1407
1528
  case "unity-game":
1408
1529
  return unityAdr(input, now);
1530
+ case "angular-app":
1531
+ return angularAdr(input, now);
1409
1532
  default:
1410
1533
  return nextjsAdr(input, now);
1411
1534
  }
@@ -1510,6 +1633,44 @@ Use a Node.js HTTP framework (Express, Fastify, or Hono) as the API service foun
1510
1633
  `
1511
1634
  };
1512
1635
  }
1636
+ function angularAdr(input, date) {
1637
+ const { srcPrefix } = detectProjectLayout(input.projectRoot, input.template);
1638
+ return {
1639
+ filename: "001-angular-standalone.md",
1640
+ frontmatter: {
1641
+ id: "001-angular-standalone",
1642
+ title: "Use Angular with Standalone Components",
1643
+ status: "accepted",
1644
+ date,
1645
+ affected_blocks: ["app-shell", "feature-modules"],
1646
+ affected_files: [srcPrefix || "./"],
1647
+ quality_scenarios: []
1648
+ },
1649
+ body: `# ADR-001: Use Angular with Standalone Components
1650
+
1651
+ ## Context
1652
+
1653
+ ${input.name} needs a modern, opinionated frontend framework with strong TypeScript support, dependency injection, and a structured approach to building large applications.
1654
+
1655
+ ## Decision
1656
+
1657
+ Use Angular with standalone components (default since Angular v17). Use the signals API for reactive state management and functional guards/resolvers for routing.
1658
+
1659
+ ## Consequences
1660
+
1661
+ - **Positive:** Strong TypeScript integration with decorators and DI
1662
+ - **Positive:** Standalone components simplify the module system and improve tree-shaking
1663
+ - **Positive:** Signals provide fine-grained reactivity without Zone.js overhead
1664
+ - **Positive:** Opinionated structure reduces architectural decision fatigue
1665
+ - **Negative:** Steeper learning curve compared to lighter frameworks
1666
+ - **Negative:** Larger initial bundle size (mitigated by lazy loading)
1667
+
1668
+ ## Current Limitations
1669
+
1670
+ 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.
1671
+ `
1672
+ };
1673
+ }
1513
1674
  function unityAdr(input, date) {
1514
1675
  return {
1515
1676
  filename: "001-unity-code-heavy.md",
@@ -1989,9 +2150,122 @@ var UNITY_GAME_SCENARIOS = {
1989
2150
  }
1990
2151
  ]
1991
2152
  };
2153
+ var ANGULAR_SCENARIOS = {
2154
+ security: [
2155
+ {
2156
+ id: "SEC-02",
2157
+ name: "No secrets in client bundles",
2158
+ category: "security",
2159
+ priority: "must",
2160
+ scenario: "Production build output is analyzed",
2161
+ expected: "No API keys, tokens, or secrets found in client-side JavaScript bundles",
2162
+ linked_code: [],
2163
+ linked_tests: [],
2164
+ linked_blocks: [],
2165
+ verification: "automatic",
2166
+ status: "untested"
2167
+ },
2168
+ {
2169
+ id: "SEC-04",
2170
+ name: "No bypassSecurityTrust without review",
2171
+ category: "security",
2172
+ priority: "should",
2173
+ scenario: "Source code is scanned for DomSanitizer bypass calls",
2174
+ expected: "All bypassSecurityTrust* calls have a documented justification in an ADR or code comment",
2175
+ linked_code: [],
2176
+ linked_tests: [],
2177
+ linked_blocks: [],
2178
+ verification: "semi-automatic",
2179
+ status: "untested"
2180
+ }
2181
+ ],
2182
+ performance: [
2183
+ {
2184
+ id: "PERF-01",
2185
+ name: "Initial page load under 3s",
2186
+ category: "performance",
2187
+ priority: "should",
2188
+ scenario: "User loads the landing page on a 3G connection",
2189
+ expected: "Largest Contentful Paint (LCP) is under 3 seconds",
2190
+ linked_code: [],
2191
+ linked_tests: [],
2192
+ linked_blocks: [],
2193
+ verification: "semi-automatic",
2194
+ status: "untested"
2195
+ },
2196
+ {
2197
+ id: "PERF-03",
2198
+ name: "Main bundle under 200KB gzipped",
2199
+ category: "performance",
2200
+ priority: "should",
2201
+ scenario: "Production build is analyzed",
2202
+ expected: "Main JavaScript bundle is under 200KB gzipped",
2203
+ linked_code: [],
2204
+ linked_tests: [],
2205
+ linked_blocks: [],
2206
+ verification: "semi-automatic",
2207
+ status: "untested"
2208
+ },
2209
+ {
2210
+ id: "PERF-04",
2211
+ name: "OnPush or signal-based change detection",
2212
+ category: "performance",
2213
+ priority: "should",
2214
+ scenario: "Components are reviewed for change detection strategy",
2215
+ expected: "All components use OnPush strategy or signal-based inputs \u2014 no default change detection",
2216
+ linked_code: [],
2217
+ linked_tests: [],
2218
+ linked_blocks: [],
2219
+ verification: "semi-automatic",
2220
+ status: "untested"
2221
+ },
2222
+ {
2223
+ id: "PERF-05",
2224
+ name: "Lazy loading on feature routes",
2225
+ category: "performance",
2226
+ priority: "must",
2227
+ scenario: "Application routes are analyzed",
2228
+ expected: "All feature routes use loadComponent or loadChildren for lazy loading",
2229
+ linked_code: [],
2230
+ linked_tests: [],
2231
+ linked_blocks: ["feature-modules"],
2232
+ verification: "automatic",
2233
+ status: "untested"
2234
+ }
2235
+ ],
2236
+ accessibility: [
2237
+ {
2238
+ id: "A11Y-01",
2239
+ name: "WCAG 2.1 AA compliance",
2240
+ category: "accessibility",
2241
+ priority: "should",
2242
+ scenario: "All pages are audited with axe-core",
2243
+ expected: "No critical or serious accessibility violations",
2244
+ linked_code: [],
2245
+ linked_tests: [],
2246
+ linked_blocks: [],
2247
+ verification: "semi-automatic",
2248
+ status: "untested"
2249
+ },
2250
+ {
2251
+ id: "A11Y-02",
2252
+ name: "Keyboard navigation",
2253
+ category: "accessibility",
2254
+ priority: "should",
2255
+ scenario: "User navigates the entire application using only keyboard",
2256
+ expected: "All interactive elements are reachable and operable via keyboard",
2257
+ linked_code: [],
2258
+ linked_tests: [],
2259
+ linked_blocks: [],
2260
+ verification: "manual",
2261
+ status: "untested"
2262
+ }
2263
+ ]
2264
+ };
1992
2265
  var TEMPLATE_SCENARIOS = {
1993
2266
  "nextjs-app-router": FRONTEND_SCENARIOS,
1994
2267
  "react-vite": FRONTEND_SCENARIOS,
2268
+ "angular-app": ANGULAR_SCENARIOS,
1995
2269
  "api-service": API_SCENARIOS,
1996
2270
  "dotnet-webapi": DOTNET_SCENARIOS,
1997
2271
  "unity-game": UNITY_GAME_SCENARIOS
@@ -2297,7 +2571,7 @@ function unityConcepts() {
2297
2571
  }
2298
2572
  function crosscuttingTemplate(input) {
2299
2573
  const isDotnet = input.template === "dotnet-webapi";
2300
- const isFrontend = input.template === "nextjs-app-router" || input.template === "react-vite";
2574
+ const isFrontend = input.template === "nextjs-app-router" || input.template === "react-vite" || input.template === "angular-app";
2301
2575
  const isUnity = input.template === "unity-game";
2302
2576
  let concepts;
2303
2577
  if (isDotnet) {
@@ -3492,13 +3766,184 @@ function phaseTasksTemplate5(_input, phaseId) {
3492
3766
  return tasksByPhase[phaseId] ?? null;
3493
3767
  }
3494
3768
 
3769
+ // src/templates/phases/angular-app.ts
3770
+ function phasePlanTemplate6(_input) {
3771
+ const phases = [
3772
+ {
3773
+ id: "phase-0-setup",
3774
+ name: "Project Setup",
3775
+ phase_number: 0,
3776
+ status: "planned",
3777
+ description: "Initialize Angular project, configure strict TypeScript, set up tooling and testing infrastructure",
3778
+ gate_requirements: [
3779
+ "Angular project builds and serves without errors",
3780
+ "Strict TypeScript configuration enabled",
3781
+ "Testing infrastructure configured (Vitest or Karma)",
3782
+ "Linting and formatting in place"
3783
+ ]
3784
+ },
3785
+ {
3786
+ id: "phase-1-foundation",
3787
+ name: "Foundation",
3788
+ phase_number: 1,
3789
+ status: "planned",
3790
+ description: "App shell, routing with lazy loading, shared component library, core services (HTTP interceptor, auth guard)",
3791
+ gate_requirements: [
3792
+ "App shell with routing configured",
3793
+ "At least one lazy-loaded feature route",
3794
+ "HTTP interceptor for API calls",
3795
+ "Shared component library started",
3796
+ "Quality scenario PERF-05 (lazy loading) verified"
3797
+ ]
3798
+ },
3799
+ {
3800
+ id: "phase-2-features",
3801
+ name: "Core Features",
3802
+ phase_number: 2,
3803
+ status: "planned",
3804
+ description: "Feature implementation, state management with signals or NgRx, integration tests",
3805
+ gate_requirements: [
3806
+ "Core features complete and functional",
3807
+ "State management approach established",
3808
+ "Integration tests cover key workflows",
3809
+ "All feature routes lazy-loaded"
3810
+ ]
3811
+ },
3812
+ {
3813
+ id: "phase-3-polish",
3814
+ name: "Polish & Launch",
3815
+ phase_number: 3,
3816
+ status: "planned",
3817
+ description: "Performance audit (bundle size, change detection), accessibility, deployment configuration",
3818
+ gate_requirements: [
3819
+ "All quality scenarios passing",
3820
+ "Bundle size within budget",
3821
+ "Accessibility audit complete",
3822
+ "Production build and deployment successful"
3823
+ ]
3824
+ }
3825
+ ];
3826
+ return { schema_version: 1, phases };
3827
+ }
3828
+ function phaseTasksTemplate6(_input, phaseId) {
3829
+ const tasksByPhase = {
3830
+ "phase-0-setup": {
3831
+ schema_version: 1,
3832
+ phase_id: "phase-0-setup",
3833
+ tasks: [
3834
+ {
3835
+ id: "task-0.1-init-project",
3836
+ title: "Initialize Angular project with strict configuration",
3837
+ status: "todo",
3838
+ building_block: "app-shell",
3839
+ quality_scenarios: [],
3840
+ acceptance_criteria: [
3841
+ "Angular CLI project created with strict mode",
3842
+ "Standalone components enabled (default in Angular 17+)",
3843
+ "TypeScript strict mode enforced"
3844
+ ]
3845
+ },
3846
+ {
3847
+ id: "task-0.2-routing",
3848
+ title: "Set up routing with lazy loading",
3849
+ status: "todo",
3850
+ building_block: "app-shell",
3851
+ quality_scenarios: ["PERF-05"],
3852
+ acceptance_criteria: [
3853
+ "App routes defined in app.routes.ts",
3854
+ "At least one feature route uses loadComponent or loadChildren",
3855
+ "Route guards and resolvers use functional approach"
3856
+ ]
3857
+ },
3858
+ {
3859
+ id: "task-0.3-core-services",
3860
+ title: "Set up core services and HTTP interceptor",
3861
+ status: "todo",
3862
+ building_block: "core-services",
3863
+ quality_scenarios: [],
3864
+ acceptance_criteria: [
3865
+ "HTTP interceptor for API base URL and error handling",
3866
+ "Core services provided in root",
3867
+ "Environment configuration set up"
3868
+ ]
3869
+ },
3870
+ {
3871
+ id: "task-0.4-testing",
3872
+ title: "Set up testing infrastructure",
3873
+ status: "todo",
3874
+ quality_scenarios: ["MAINT-02"],
3875
+ acceptance_criteria: [
3876
+ "Unit test framework configured (Vitest or Karma/Jasmine)",
3877
+ "First component test passes",
3878
+ "Test coverage reporting enabled"
3879
+ ]
3880
+ }
3881
+ ]
3882
+ },
3883
+ "phase-1-foundation": {
3884
+ schema_version: 1,
3885
+ phase_id: "phase-1-foundation",
3886
+ tasks: [
3887
+ {
3888
+ id: "task-1.1-shared-components",
3889
+ title: "Build shared component library",
3890
+ status: "todo",
3891
+ building_block: "shared-components",
3892
+ quality_scenarios: ["A11Y-01"],
3893
+ acceptance_criteria: [
3894
+ "Reusable UI components in shared/ directory",
3895
+ "Components use OnPush change detection or signals",
3896
+ "Components follow accessibility guidelines"
3897
+ ]
3898
+ },
3899
+ {
3900
+ id: "task-1.2-auth",
3901
+ title: "Implement authentication and route guards",
3902
+ status: "todo",
3903
+ building_block: "core-services",
3904
+ quality_scenarios: ["SEC-01"],
3905
+ acceptance_criteria: [
3906
+ "Auth service with login/logout/token management",
3907
+ "Functional route guard protecting private routes",
3908
+ "Auth interceptor attaching tokens to API requests"
3909
+ ]
3910
+ },
3911
+ {
3912
+ id: "task-1.3-api-client",
3913
+ title: "Set up API client layer",
3914
+ status: "todo",
3915
+ building_block: "api-client",
3916
+ quality_scenarios: [],
3917
+ acceptance_criteria: [
3918
+ "Typed API service methods for backend endpoints",
3919
+ "Error handling and retry logic",
3920
+ "Loading/error state management"
3921
+ ]
3922
+ },
3923
+ {
3924
+ id: "task-1.4-document-decisions",
3925
+ title: "Document architectural decisions as ADRs",
3926
+ status: "todo",
3927
+ quality_scenarios: [],
3928
+ acceptance_criteria: [
3929
+ "ADR for each significant architecture/pattern choice",
3930
+ "ADRs linked to affected building blocks and code paths"
3931
+ ]
3932
+ }
3933
+ ]
3934
+ }
3935
+ };
3936
+ return tasksByPhase[phaseId] ?? null;
3937
+ }
3938
+
3495
3939
  // src/generators/plan-generator.ts
3496
3940
  var planTemplates = {
3497
3941
  "nextjs-app-router": { plan: phasePlanTemplate, tasks: phaseTasksTemplate },
3498
3942
  "react-vite": { plan: phasePlanTemplate2, tasks: phaseTasksTemplate2 },
3499
3943
  "api-service": { plan: phasePlanTemplate3, tasks: phaseTasksTemplate3 },
3500
3944
  "dotnet-webapi": { plan: phasePlanTemplate4, tasks: phaseTasksTemplate4 },
3501
- "unity-game": { plan: phasePlanTemplate5, tasks: phaseTasksTemplate5 }
3945
+ "unity-game": { plan: phasePlanTemplate5, tasks: phaseTasksTemplate5 },
3946
+ "angular-app": { plan: phasePlanTemplate6, tasks: phaseTasksTemplate6 }
3502
3947
  };
3503
3948
  function generatePlan(targetDir, input) {
3504
3949
  const planDir = join5(targetDir, ".arcbridge", "plan");
@@ -3551,6 +3996,7 @@ function architectTemplate() {
3551
3996
  "arcbridge_get_route_map",
3552
3997
  "arcbridge_get_boundary_analysis",
3553
3998
  "arcbridge_propose_arc42_update",
3999
+ "arcbridge_update_arc42_section",
3554
4000
  "arcbridge_check_drift",
3555
4001
  "arcbridge_get_open_questions"
3556
4002
  ],
@@ -4171,7 +4617,7 @@ You cannot see screenshots, but you CAN reason about UI quality through code:
4171
4617
  }
4172
4618
 
4173
4619
  // src/generators/agent-generator.ts
4174
- var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "unity-game"]);
4620
+ var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "angular-app", "unity-game"]);
4175
4621
  function writeAgentRole(dir, role) {
4176
4622
  const { system_prompt, ...frontmatter } = role;
4177
4623
  const content = matter2.stringify(system_prompt, frontmatter);
@@ -4477,6 +4923,7 @@ function generateDatabase(targetDir, input) {
4477
4923
  );
4478
4924
  upsert.run("project_name", input.name);
4479
4925
  upsert.run("project_type", input.template);
4926
+ upsert.run("platforms", input.platforms.join(", "));
4480
4927
  upsert.run("last_full_index", (/* @__PURE__ */ new Date()).toISOString());
4481
4928
  transaction(db, () => {
4482
4929
  allWarnings.push(...populateBuildingBlocks(db, targetDir));
@@ -4715,11 +5162,16 @@ function extractSymbols(sourceFile, checker, relativePath, contentHash) {
4715
5162
  }
4716
5163
  if (ts3.isClassDeclaration(node) && node.name) {
4717
5164
  const name = node.name.text;
5165
+ const decorators = typeof ts3.canHaveDecorators === "function" && typeof ts3.getDecorators === "function" && ts3.canHaveDecorators(node) ? ts3.getDecorators(node) ?? [] : node.modifiers?.filter(ts3.isDecorator) ?? [];
5166
+ const hasComponentDecorator = decorators.some(
5167
+ (d) => ts3.isCallExpression(d.expression) && ts3.isIdentifier(d.expression.expression) && d.expression.expression.text === "Component"
5168
+ );
5169
+ const kind = hasComponentDecorator ? "component" : "class";
4718
5170
  symbols.push({
4719
- id: makeId(name, "class"),
5171
+ id: makeId(name, kind),
4720
5172
  name,
4721
5173
  qualifiedName: name,
4722
- kind: "class",
5174
+ kind,
4723
5175
  filePath: relativePath,
4724
5176
  ...getLocation(node),
4725
5177
  signature: null,
@@ -5237,13 +5689,13 @@ function getPropsType(node, checker) {
5237
5689
  if (typeStr === "{}" || typeStr === "any") return null;
5238
5690
  return typeStr;
5239
5691
  }
5240
- function analyzeComponents(sourceFiles, checker, projectRoot, db) {
5692
+ function analyzeComponents(sourceFiles, checker, projectRoot, db, allClient = false) {
5241
5693
  const components = [];
5242
5694
  for (const sf of sourceFiles) {
5243
5695
  const relPath = relative2(projectRoot, sf.fileName);
5244
5696
  const directive = getFileDirective(sf);
5245
- const isClient = directive === "use client";
5246
- const isServerAction = directive === "use server";
5697
+ const isClient = allClient || directive === "use client";
5698
+ const isServerAction = !allClient && directive === "use server";
5247
5699
  ts5.forEachChild(sf, (node) => {
5248
5700
  if (ts5.isFunctionDeclaration(node) && node.name) {
5249
5701
  const name = node.name.text;
@@ -5298,6 +5750,70 @@ function analyzeComponents(sourceFiles, checker, projectRoot, db) {
5298
5750
  });
5299
5751
  }
5300
5752
  }
5753
+ if (ts5.isClassDeclaration(node) && node.name) {
5754
+ const decorators = typeof ts5.canHaveDecorators === "function" && typeof ts5.getDecorators === "function" && ts5.canHaveDecorators(node) ? ts5.getDecorators(node) ?? [] : node.modifiers?.filter(ts5.isDecorator) ?? [];
5755
+ const componentDecorator = decorators.find(
5756
+ (decorator) => ts5.isCallExpression(decorator.expression) && ts5.isIdentifier(decorator.expression.expression) && decorator.expression.expression.text === "Component"
5757
+ );
5758
+ if (componentDecorator && ts5.isCallExpression(componentDecorator.expression)) {
5759
+ const name = node.name.text;
5760
+ const symbolId = `${relPath}::${name}#component`;
5761
+ let propsType = null;
5762
+ const metaArg = componentDecorator.expression.arguments[0];
5763
+ if (metaArg && ts5.isObjectLiteralExpression(metaArg)) {
5764
+ const selectorProp = metaArg.properties.find(
5765
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === "selector"
5766
+ );
5767
+ if (selectorProp && ts5.isStringLiteral(selectorProp.initializer)) {
5768
+ propsType = `selector: ${selectorProp.initializer.text}`;
5769
+ }
5770
+ }
5771
+ let hasState = false;
5772
+ if (node.members) {
5773
+ for (const member of node.members) {
5774
+ if (ts5.isPropertyDeclaration(member) && member.initializer) {
5775
+ const init = ts5.isParenthesizedExpression(member.initializer) ? member.initializer.expression : member.initializer;
5776
+ if (ts5.isCallExpression(init)) {
5777
+ const callee = init.expression;
5778
+ const calleeName = ts5.isIdentifier(callee) ? callee.text : ts5.isPropertyAccessExpression(callee) ? callee.name.text : null;
5779
+ if (calleeName === "signal" || calleeName === "computed") {
5780
+ hasState = true;
5781
+ break;
5782
+ }
5783
+ }
5784
+ }
5785
+ }
5786
+ }
5787
+ if (metaArg && ts5.isObjectLiteralExpression(metaArg)) {
5788
+ const importsProp = metaArg.properties.find(
5789
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === "imports"
5790
+ );
5791
+ if (importsProp && ts5.isArrayLiteralExpression(importsProp.initializer)) {
5792
+ const importNames = [];
5793
+ for (const el of importsProp.initializer.elements) {
5794
+ if (ts5.isIdentifier(el)) {
5795
+ importNames.push(el.text);
5796
+ }
5797
+ }
5798
+ if (importNames.length > 0) {
5799
+ propsType = propsType ? `${propsType} | imports: ${importNames.join(", ")}` : `imports: ${importNames.join(", ")}`;
5800
+ }
5801
+ }
5802
+ }
5803
+ components.push({
5804
+ symbolId,
5805
+ isClient: true,
5806
+ // Angular components are always client-side
5807
+ isServerAction: false,
5808
+ hasState,
5809
+ contextProviders: [],
5810
+ // Angular uses DI, not context
5811
+ contextConsumers: [],
5812
+ // Not applicable for Angular
5813
+ propsType
5814
+ });
5815
+ }
5816
+ }
5301
5817
  });
5302
5818
  }
5303
5819
  writeComponents(db, components);
@@ -6948,7 +7464,10 @@ function indexTypeScriptProject(db, options) {
6948
7464
  });
6949
7465
  db.prepare("DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ?)").run(service);
6950
7466
  writeDependencies(db, allDeps);
6951
- const componentsAnalyzed = analyzeComponents(sourceFiles, checker, projectRoot, db);
7467
+ const CLIENT_ONLY_TEMPLATES = /* @__PURE__ */ new Set(["react-vite", "angular-app"]);
7468
+ const projectType = db.prepare("SELECT value FROM arcbridge_meta WHERE key = 'project_type'").get()?.value;
7469
+ const allClient = projectType ? CLIENT_ONLY_TEMPLATES.has(projectType) : false;
7470
+ const componentsAnalyzed = analyzeComponents(sourceFiles, checker, projectRoot, db, allClient);
6952
7471
  const routesAnalyzed = analyzeRoutes(projectRoot, db, service);
6953
7472
  return {
6954
7473
  symbolsIndexed: allSymbols.length,
@@ -6984,6 +7503,14 @@ var FRAMEWORK_IGNORES = {
6984
7503
  ],
6985
7504
  "react-vite": ["src/main.", "src/App.", "vite.config"],
6986
7505
  "api-service": ["src/index.", "src/app.", "src/server."],
7506
+ "angular-app": [
7507
+ ".angular/",
7508
+ "src/environments/",
7509
+ "src/main.ts",
7510
+ "src/index.html",
7511
+ "src/styles.",
7512
+ "angular.json"
7513
+ ],
6987
7514
  "dotnet-webapi": [
6988
7515
  "Program.",
6989
7516
  "Startup.",