@arcbridge/core 0.3.3 → 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.d.ts +12 -12
- package/dist/index.js +534 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 === "
|
|
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");
|
|
@@ -4172,7 +4617,7 @@ You cannot see screenshots, but you CAN reason about UI quality through code:
|
|
|
4172
4617
|
}
|
|
4173
4618
|
|
|
4174
4619
|
// src/generators/agent-generator.ts
|
|
4175
|
-
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"]);
|
|
4176
4621
|
function writeAgentRole(dir, role) {
|
|
4177
4622
|
const { system_prompt, ...frontmatter } = role;
|
|
4178
4623
|
const content = matter2.stringify(system_prompt, frontmatter);
|
|
@@ -4717,11 +5162,16 @@ function extractSymbols(sourceFile, checker, relativePath, contentHash) {
|
|
|
4717
5162
|
}
|
|
4718
5163
|
if (ts3.isClassDeclaration(node) && node.name) {
|
|
4719
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";
|
|
4720
5170
|
symbols.push({
|
|
4721
|
-
id: makeId(name,
|
|
5171
|
+
id: makeId(name, kind),
|
|
4722
5172
|
name,
|
|
4723
5173
|
qualifiedName: name,
|
|
4724
|
-
kind
|
|
5174
|
+
kind,
|
|
4725
5175
|
filePath: relativePath,
|
|
4726
5176
|
...getLocation(node),
|
|
4727
5177
|
signature: null,
|
|
@@ -5300,6 +5750,70 @@ function analyzeComponents(sourceFiles, checker, projectRoot, db, allClient = fa
|
|
|
5300
5750
|
});
|
|
5301
5751
|
}
|
|
5302
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
|
+
}
|
|
5303
5817
|
});
|
|
5304
5818
|
}
|
|
5305
5819
|
writeComponents(db, components);
|
|
@@ -6950,7 +7464,7 @@ function indexTypeScriptProject(db, options) {
|
|
|
6950
7464
|
});
|
|
6951
7465
|
db.prepare("DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ?)").run(service);
|
|
6952
7466
|
writeDependencies(db, allDeps);
|
|
6953
|
-
const CLIENT_ONLY_TEMPLATES = /* @__PURE__ */ new Set(["react-vite"]);
|
|
7467
|
+
const CLIENT_ONLY_TEMPLATES = /* @__PURE__ */ new Set(["react-vite", "angular-app"]);
|
|
6954
7468
|
const projectType = db.prepare("SELECT value FROM arcbridge_meta WHERE key = 'project_type'").get()?.value;
|
|
6955
7469
|
const allClient = projectType ? CLIENT_ONLY_TEMPLATES.has(projectType) : false;
|
|
6956
7470
|
const componentsAnalyzed = analyzeComponents(sourceFiles, checker, projectRoot, db, allClient);
|
|
@@ -6989,6 +7503,14 @@ var FRAMEWORK_IGNORES = {
|
|
|
6989
7503
|
],
|
|
6990
7504
|
"react-vite": ["src/main.", "src/App.", "vite.config"],
|
|
6991
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
|
+
],
|
|
6992
7514
|
"dotnet-webapi": [
|
|
6993
7515
|
"Program.",
|
|
6994
7516
|
"Startup.",
|