@arcbridge/core 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -1
- package/dist/index.js +422 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1033,7 +1033,9 @@ interface GitRef {
|
|
|
1033
1033
|
*/
|
|
1034
1034
|
declare function resolveRef(projectRoot: string, since: string, db?: Database): GitRef;
|
|
1035
1035
|
/**
|
|
1036
|
-
* Get list of changed files between a ref and HEAD.
|
|
1036
|
+
* Get list of changed files between a ref and HEAD, including uncommitted changes.
|
|
1037
|
+
* Merges committed diffs with staged+unstaged working tree changes so that
|
|
1038
|
+
* practice reviews and drift checks see all work, not just committed code.
|
|
1037
1039
|
*/
|
|
1038
1040
|
declare function getChangedFiles(projectRoot: string, ref: string): ChangedFile[];
|
|
1039
1041
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1140,10 +1140,16 @@ ${input.template === "dotnet-webapi" ? `| Platform | Description | Notes |
|
|
|
1140
1140
|
// src/templates/arc42/09-decisions.ts
|
|
1141
1141
|
function firstAdrTemplate(input) {
|
|
1142
1142
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1143
|
-
|
|
1144
|
-
|
|
1143
|
+
switch (input.template) {
|
|
1144
|
+
case "dotnet-webapi":
|
|
1145
|
+
return dotnetAdr(input, now);
|
|
1146
|
+
case "react-vite":
|
|
1147
|
+
return reactViteAdr(input, now);
|
|
1148
|
+
case "api-service":
|
|
1149
|
+
return apiServiceAdr(input, now);
|
|
1150
|
+
default:
|
|
1151
|
+
return nextjsAdr(input, now);
|
|
1145
1152
|
}
|
|
1146
|
-
return nextjsAdr(input, now);
|
|
1147
1153
|
}
|
|
1148
1154
|
function nextjsAdr(input, date) {
|
|
1149
1155
|
const { appPrefix } = detectProjectLayout(input.projectRoot, input.template);
|
|
@@ -1178,6 +1184,73 @@ Use Next.js with the App Router (introduced in Next.js 13+) as the application f
|
|
|
1178
1184
|
`
|
|
1179
1185
|
};
|
|
1180
1186
|
}
|
|
1187
|
+
function reactViteAdr(input, date) {
|
|
1188
|
+
const { srcPrefix } = detectProjectLayout(input.projectRoot, input.template);
|
|
1189
|
+
return {
|
|
1190
|
+
filename: "001-react-vite.md",
|
|
1191
|
+
frontmatter: {
|
|
1192
|
+
id: "001-react-vite",
|
|
1193
|
+
title: "Use React with Vite",
|
|
1194
|
+
status: "accepted",
|
|
1195
|
+
date,
|
|
1196
|
+
affected_blocks: ["app-shell"],
|
|
1197
|
+
affected_files: [srcPrefix || "./"],
|
|
1198
|
+
quality_scenarios: []
|
|
1199
|
+
},
|
|
1200
|
+
body: `# ADR-001: Use React with Vite
|
|
1201
|
+
|
|
1202
|
+
## Context
|
|
1203
|
+
|
|
1204
|
+
${input.name} needs a fast, modern frontend framework for building a single-page application (SPA) with TypeScript.
|
|
1205
|
+
|
|
1206
|
+
## Decision
|
|
1207
|
+
|
|
1208
|
+
Use React with Vite as the build tool and development server.
|
|
1209
|
+
|
|
1210
|
+
## Consequences
|
|
1211
|
+
|
|
1212
|
+
- **Positive:** Extremely fast dev server with hot module replacement (HMR)
|
|
1213
|
+
- **Positive:** Optimized production builds with tree shaking and code splitting
|
|
1214
|
+
- **Positive:** Simple configuration, no SSR complexity
|
|
1215
|
+
- **Positive:** Large React ecosystem of libraries and components
|
|
1216
|
+
- **Negative:** Client-side only \u2014 no built-in server-side rendering (add later if needed)
|
|
1217
|
+
- **Negative:** Requires separate backend service for API endpoints
|
|
1218
|
+
`
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
function apiServiceAdr(input, date) {
|
|
1222
|
+
const { srcPrefix } = detectProjectLayout(input.projectRoot, input.template);
|
|
1223
|
+
return {
|
|
1224
|
+
filename: "001-api-service.md",
|
|
1225
|
+
frontmatter: {
|
|
1226
|
+
id: "001-api-service",
|
|
1227
|
+
title: "Use Node.js API Service",
|
|
1228
|
+
status: "accepted",
|
|
1229
|
+
date,
|
|
1230
|
+
affected_blocks: ["app-shell"],
|
|
1231
|
+
affected_files: [srcPrefix || "./"],
|
|
1232
|
+
quality_scenarios: []
|
|
1233
|
+
},
|
|
1234
|
+
body: `# ADR-001: Use Node.js API Service
|
|
1235
|
+
|
|
1236
|
+
## Context
|
|
1237
|
+
|
|
1238
|
+
${input.name} needs a backend API service with TypeScript support, good performance, and a simple architecture.
|
|
1239
|
+
|
|
1240
|
+
## Decision
|
|
1241
|
+
|
|
1242
|
+
Use a Node.js HTTP framework (Express, Fastify, or Hono) as the API service foundation.
|
|
1243
|
+
|
|
1244
|
+
## Consequences
|
|
1245
|
+
|
|
1246
|
+
- **Positive:** TypeScript-first with full type safety
|
|
1247
|
+
- **Positive:** Rich middleware ecosystem for auth, validation, logging
|
|
1248
|
+
- **Positive:** Easy to deploy to containers, serverless, or traditional hosts
|
|
1249
|
+
- **Negative:** Single-threaded \u2014 CPU-intensive operations need worker threads or external services
|
|
1250
|
+
- **Negative:** No built-in ORM \u2014 need to choose data access strategy
|
|
1251
|
+
`
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1181
1254
|
function dotnetAdr(input, date) {
|
|
1182
1255
|
return {
|
|
1183
1256
|
filename: "001-aspnet-core-webapi.md",
|
|
@@ -1961,6 +2034,116 @@ function phaseTasksTemplate(_input, phaseId) {
|
|
|
1961
2034
|
]
|
|
1962
2035
|
}
|
|
1963
2036
|
]
|
|
2037
|
+
},
|
|
2038
|
+
"phase-2-features": {
|
|
2039
|
+
schema_version: 1,
|
|
2040
|
+
phase_id: "phase-2-features",
|
|
2041
|
+
tasks: [
|
|
2042
|
+
{
|
|
2043
|
+
id: "task-2.1-core-pages",
|
|
2044
|
+
title: "Implement core application pages",
|
|
2045
|
+
status: "todo",
|
|
2046
|
+
quality_scenarios: ["PERF-01"],
|
|
2047
|
+
acceptance_criteria: [
|
|
2048
|
+
"Core pages created with proper data fetching",
|
|
2049
|
+
"Server/client components properly separated",
|
|
2050
|
+
"Loading and error states handled"
|
|
2051
|
+
]
|
|
2052
|
+
},
|
|
2053
|
+
{
|
|
2054
|
+
id: "task-2.2-api-routes",
|
|
2055
|
+
title: "Implement API routes",
|
|
2056
|
+
status: "todo",
|
|
2057
|
+
quality_scenarios: ["SEC-01", "PERF-02"],
|
|
2058
|
+
acceptance_criteria: [
|
|
2059
|
+
"API routes created for core operations",
|
|
2060
|
+
"Input validation on all endpoints",
|
|
2061
|
+
"Error responses standardized"
|
|
2062
|
+
]
|
|
2063
|
+
},
|
|
2064
|
+
{
|
|
2065
|
+
id: "task-2.3-data-layer",
|
|
2066
|
+
title: "Set up data access layer",
|
|
2067
|
+
status: "todo",
|
|
2068
|
+
quality_scenarios: ["MAINT-01"],
|
|
2069
|
+
acceptance_criteria: [
|
|
2070
|
+
"Data fetching patterns established",
|
|
2071
|
+
"Caching strategy implemented",
|
|
2072
|
+
"Type-safe data access"
|
|
2073
|
+
]
|
|
2074
|
+
},
|
|
2075
|
+
{
|
|
2076
|
+
id: "task-2.4-integration-tests",
|
|
2077
|
+
title: "Write integration tests for core flows",
|
|
2078
|
+
status: "todo",
|
|
2079
|
+
quality_scenarios: ["MAINT-02"],
|
|
2080
|
+
acceptance_criteria: [
|
|
2081
|
+
"Happy path tested for each core feature",
|
|
2082
|
+
"API route tests written",
|
|
2083
|
+
"Test coverage meets threshold"
|
|
2084
|
+
]
|
|
2085
|
+
},
|
|
2086
|
+
{
|
|
2087
|
+
id: "task-2.5-document-decisions",
|
|
2088
|
+
title: "Document Phase 2 architectural decisions",
|
|
2089
|
+
status: "todo",
|
|
2090
|
+
quality_scenarios: [],
|
|
2091
|
+
acceptance_criteria: [
|
|
2092
|
+
"ADRs for data fetching and API design choices",
|
|
2093
|
+
"Building blocks updated with new code paths"
|
|
2094
|
+
]
|
|
2095
|
+
}
|
|
2096
|
+
]
|
|
2097
|
+
},
|
|
2098
|
+
"phase-3-polish": {
|
|
2099
|
+
schema_version: 1,
|
|
2100
|
+
phase_id: "phase-3-polish",
|
|
2101
|
+
tasks: [
|
|
2102
|
+
{
|
|
2103
|
+
id: "task-3.1-error-handling",
|
|
2104
|
+
title: "Implement comprehensive error handling",
|
|
2105
|
+
status: "todo",
|
|
2106
|
+
quality_scenarios: ["SEC-01"],
|
|
2107
|
+
acceptance_criteria: [
|
|
2108
|
+
"Error boundaries at route level",
|
|
2109
|
+
"Custom error.tsx and not-found.tsx pages",
|
|
2110
|
+
"API error responses standardized"
|
|
2111
|
+
]
|
|
2112
|
+
},
|
|
2113
|
+
{
|
|
2114
|
+
id: "task-3.2-accessibility",
|
|
2115
|
+
title: "Accessibility audit and fixes",
|
|
2116
|
+
status: "todo",
|
|
2117
|
+
quality_scenarios: ["A11Y-01", "A11Y-02"],
|
|
2118
|
+
acceptance_criteria: [
|
|
2119
|
+
"Keyboard navigation works for all interactive elements",
|
|
2120
|
+
"Screen reader compatible (ARIA labels, roles)",
|
|
2121
|
+
"WCAG 2.1 AA compliance verified"
|
|
2122
|
+
]
|
|
2123
|
+
},
|
|
2124
|
+
{
|
|
2125
|
+
id: "task-3.3-performance",
|
|
2126
|
+
title: "Performance optimization",
|
|
2127
|
+
status: "todo",
|
|
2128
|
+
quality_scenarios: ["PERF-01"],
|
|
2129
|
+
acceptance_criteria: [
|
|
2130
|
+
"Bundle size optimized (dynamic imports, tree shaking)",
|
|
2131
|
+
"Core Web Vitals meet thresholds (LCP < 2.5s)",
|
|
2132
|
+
"Server-side rendering verified for SEO pages"
|
|
2133
|
+
]
|
|
2134
|
+
},
|
|
2135
|
+
{
|
|
2136
|
+
id: "task-3.4-deployment",
|
|
2137
|
+
title: "Configure production deployment",
|
|
2138
|
+
status: "todo",
|
|
2139
|
+
quality_scenarios: [],
|
|
2140
|
+
acceptance_criteria: [
|
|
2141
|
+
"Production build configuration verified",
|
|
2142
|
+
"Environment variables documented",
|
|
2143
|
+
"Deployment to Vercel/hosting configured"
|
|
2144
|
+
]
|
|
2145
|
+
}
|
|
2146
|
+
]
|
|
1964
2147
|
}
|
|
1965
2148
|
};
|
|
1966
2149
|
return tasksByPhase[phaseId] ?? null;
|
|
@@ -2128,6 +2311,105 @@ function phaseTasksTemplate2(_input, phaseId) {
|
|
|
2128
2311
|
]
|
|
2129
2312
|
}
|
|
2130
2313
|
]
|
|
2314
|
+
},
|
|
2315
|
+
"phase-2-features": {
|
|
2316
|
+
schema_version: 1,
|
|
2317
|
+
phase_id: "phase-2-features",
|
|
2318
|
+
tasks: [
|
|
2319
|
+
{
|
|
2320
|
+
id: "task-2.1-core-feature",
|
|
2321
|
+
title: "Implement primary feature",
|
|
2322
|
+
status: "todo",
|
|
2323
|
+
quality_scenarios: ["PERF-01"],
|
|
2324
|
+
acceptance_criteria: [
|
|
2325
|
+
"Core user flow works end-to-end",
|
|
2326
|
+
"Unit and component tests written",
|
|
2327
|
+
"Meets performance budgets"
|
|
2328
|
+
]
|
|
2329
|
+
},
|
|
2330
|
+
{
|
|
2331
|
+
id: "task-2.2-state-management",
|
|
2332
|
+
title: "Set up state management for complex data",
|
|
2333
|
+
status: "todo",
|
|
2334
|
+
quality_scenarios: ["MAINT-01"],
|
|
2335
|
+
acceptance_criteria: [
|
|
2336
|
+
"State management pattern established",
|
|
2337
|
+
"Data fetching/caching strategy implemented",
|
|
2338
|
+
"Loading and error states handled"
|
|
2339
|
+
]
|
|
2340
|
+
},
|
|
2341
|
+
{
|
|
2342
|
+
id: "task-2.3-integration-tests",
|
|
2343
|
+
title: "Write integration tests for core flows",
|
|
2344
|
+
status: "todo",
|
|
2345
|
+
quality_scenarios: ["MAINT-02"],
|
|
2346
|
+
acceptance_criteria: [
|
|
2347
|
+
"Happy path tested for each core feature",
|
|
2348
|
+
"Error scenarios tested",
|
|
2349
|
+
"Test coverage meets threshold"
|
|
2350
|
+
]
|
|
2351
|
+
},
|
|
2352
|
+
{
|
|
2353
|
+
id: "task-2.4-document-decisions",
|
|
2354
|
+
title: "Document Phase 2 architectural decisions",
|
|
2355
|
+
status: "todo",
|
|
2356
|
+
quality_scenarios: [],
|
|
2357
|
+
acceptance_criteria: [
|
|
2358
|
+
"ADRs for state management and data fetching choices",
|
|
2359
|
+
"Building blocks updated with new code paths"
|
|
2360
|
+
]
|
|
2361
|
+
}
|
|
2362
|
+
]
|
|
2363
|
+
},
|
|
2364
|
+
"phase-3-polish": {
|
|
2365
|
+
schema_version: 1,
|
|
2366
|
+
phase_id: "phase-3-polish",
|
|
2367
|
+
tasks: [
|
|
2368
|
+
{
|
|
2369
|
+
id: "task-3.1-error-handling",
|
|
2370
|
+
title: "Implement comprehensive error handling",
|
|
2371
|
+
status: "todo",
|
|
2372
|
+
quality_scenarios: ["SEC-01"],
|
|
2373
|
+
acceptance_criteria: [
|
|
2374
|
+
"Error boundaries at route level",
|
|
2375
|
+
"User-friendly error messages",
|
|
2376
|
+
"Error logging configured"
|
|
2377
|
+
]
|
|
2378
|
+
},
|
|
2379
|
+
{
|
|
2380
|
+
id: "task-3.2-accessibility",
|
|
2381
|
+
title: "Accessibility audit and fixes",
|
|
2382
|
+
status: "todo",
|
|
2383
|
+
quality_scenarios: ["A11Y-01", "A11Y-02"],
|
|
2384
|
+
acceptance_criteria: [
|
|
2385
|
+
"Keyboard navigation works for all interactive elements",
|
|
2386
|
+
"Screen reader compatible (ARIA labels, roles)",
|
|
2387
|
+
"WCAG 2.1 AA compliance verified"
|
|
2388
|
+
]
|
|
2389
|
+
},
|
|
2390
|
+
{
|
|
2391
|
+
id: "task-3.3-performance",
|
|
2392
|
+
title: "Performance optimization",
|
|
2393
|
+
status: "todo",
|
|
2394
|
+
quality_scenarios: ["PERF-01"],
|
|
2395
|
+
acceptance_criteria: [
|
|
2396
|
+
"Bundle size optimized (code splitting, tree shaking)",
|
|
2397
|
+
"Lighthouse performance score >= 90",
|
|
2398
|
+
"No unnecessary re-renders in hot paths"
|
|
2399
|
+
]
|
|
2400
|
+
},
|
|
2401
|
+
{
|
|
2402
|
+
id: "task-3.4-deployment",
|
|
2403
|
+
title: "Configure production build and deployment",
|
|
2404
|
+
status: "todo",
|
|
2405
|
+
quality_scenarios: [],
|
|
2406
|
+
acceptance_criteria: [
|
|
2407
|
+
"Production build configuration verified",
|
|
2408
|
+
"Deployment pipeline configured",
|
|
2409
|
+
"Environment variables documented"
|
|
2410
|
+
]
|
|
2411
|
+
}
|
|
2412
|
+
]
|
|
2131
2413
|
}
|
|
2132
2414
|
};
|
|
2133
2415
|
return tasksByPhase[phaseId] ?? null;
|
|
@@ -2646,6 +2928,18 @@ You are responsible for maintaining these sections in \`.arcbridge/arc42/\`. Upd
|
|
|
2646
2928
|
- All ADRs and their status
|
|
2647
2929
|
- Building block \u2192 code mapping
|
|
2648
2930
|
|
|
2931
|
+
## Project Planning
|
|
2932
|
+
|
|
2933
|
+
At the start of a project (after init), plan tasks across ALL phases:
|
|
2934
|
+
- **Phase 0-1 tasks are ready to use** \u2014 they cover setup and foundation for this template
|
|
2935
|
+
- **Phase 2+ tasks are examples** \u2014 replace them with real tasks derived from the project's requirements and specifications
|
|
2936
|
+
- Review the phase plan with \`arcbridge_get_phase_plan\` and replace example tasks with project-specific ones
|
|
2937
|
+
- Create tasks using \`arcbridge_create_task\` with the phase ID shown in the plan
|
|
2938
|
+
- Keep each phase reasonably scoped \u2014 3-6 tasks per phase is ideal
|
|
2939
|
+
- Map tasks to building blocks so drift detection tracks coverage
|
|
2940
|
+
- Link tasks to quality scenarios so gate checks are meaningful
|
|
2941
|
+
- Plan the full roadmap before diving into implementation \u2014 this prevents phases from becoming too large or unfocused
|
|
2942
|
+
|
|
2649
2943
|
## Working Style
|
|
2650
2944
|
|
|
2651
2945
|
Think at the system level. Before making changes, consider:
|
|
@@ -2883,13 +3177,25 @@ function phaseManagerTemplate() {
|
|
|
2883
3177
|
- Tasks must be "done" before a phase can complete
|
|
2884
3178
|
- Quality scenarios linked to phase tasks must be verified
|
|
2885
3179
|
|
|
3180
|
+
## Task Planning
|
|
3181
|
+
|
|
3182
|
+
Before starting any phase, ensure proper task planning:
|
|
3183
|
+
- **Phase 0-1 tasks are concrete** \u2014 they cover project setup and foundation. Follow them as-is.
|
|
3184
|
+
- **Phase 2+ tasks are examples only** \u2014 they show the *shape* of later phases but must be replaced with real tasks derived from the project's actual requirements and specs.
|
|
3185
|
+
- **At project start, plan ALL phases** \u2014 review \`arcbridge_get_phase_plan\`, delete example tasks in Phase 2+, and create real tasks based on the product spec and building blocks.
|
|
3186
|
+
- **Keep phases small and focused** \u2014 if a phase has more than 6-8 tasks, split it into sub-phases
|
|
3187
|
+
- **Tasks should be concrete and verifiable** \u2014 each task needs clear acceptance criteria
|
|
3188
|
+
- **Link tasks to building blocks** \u2014 this enables drift detection and progress tracking
|
|
3189
|
+
- **Use \`arcbridge_create_task\` with the phase ID** shown in \`arcbridge_get_phase_plan\` output (e.g., \`phase-2-features\`)
|
|
3190
|
+
|
|
2886
3191
|
## Phase Transition Process
|
|
2887
3192
|
|
|
2888
3193
|
1. Verify all tasks in current phase are "done"
|
|
2889
|
-
2.
|
|
2890
|
-
3.
|
|
2891
|
-
4.
|
|
2892
|
-
5.
|
|
3194
|
+
2. Review task coverage for the NEXT phase \u2014 create tasks if empty
|
|
3195
|
+
3. Run drift detection (\`arcbridge_check_drift\`)
|
|
3196
|
+
4. Propose arc42 updates if drift is detected
|
|
3197
|
+
5. Check quality gate requirements
|
|
3198
|
+
6. Mark phase complete or report blockers`
|
|
2893
3199
|
};
|
|
2894
3200
|
}
|
|
2895
3201
|
|
|
@@ -3353,6 +3659,7 @@ function refreshFromDocs(db, targetDir) {
|
|
|
3353
3659
|
const scenarioStatusMap = new Map(
|
|
3354
3660
|
existingScenarios.map((s) => [s.id, s.status])
|
|
3355
3661
|
);
|
|
3662
|
+
db.exec("PRAGMA foreign_keys = OFF");
|
|
3356
3663
|
const refresh = () => transaction(db, () => {
|
|
3357
3664
|
db.prepare("DELETE FROM tasks").run();
|
|
3358
3665
|
db.prepare("DELETE FROM phases").run();
|
|
@@ -3365,6 +3672,16 @@ function refreshFromDocs(db, targetDir) {
|
|
|
3365
3672
|
warnings.push(...populateQualityScenarios(db, targetDir));
|
|
3366
3673
|
warnings.push(...populatePhases(db, targetDir));
|
|
3367
3674
|
warnings.push(...populateAdrs(db, targetDir));
|
|
3675
|
+
const orphaned = db.prepare(`
|
|
3676
|
+
UPDATE tasks SET building_block = NULL
|
|
3677
|
+
WHERE building_block IS NOT NULL
|
|
3678
|
+
AND building_block NOT IN (SELECT id FROM building_blocks)
|
|
3679
|
+
`).run();
|
|
3680
|
+
if (orphaned.changes > 0) {
|
|
3681
|
+
warnings.push(
|
|
3682
|
+
`${orphaned.changes} task(s) referenced removed building blocks (references cleared)`
|
|
3683
|
+
);
|
|
3684
|
+
}
|
|
3368
3685
|
const restoreTask = db.prepare(
|
|
3369
3686
|
"UPDATE tasks SET status = ?, completed_at = ? WHERE id = ?"
|
|
3370
3687
|
);
|
|
@@ -3390,7 +3707,11 @@ function refreshFromDocs(db, targetDir) {
|
|
|
3390
3707
|
}
|
|
3391
3708
|
}
|
|
3392
3709
|
});
|
|
3393
|
-
|
|
3710
|
+
try {
|
|
3711
|
+
refresh();
|
|
3712
|
+
} finally {
|
|
3713
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
3714
|
+
}
|
|
3394
3715
|
return warnings;
|
|
3395
3716
|
}
|
|
3396
3717
|
function generateDatabase(targetDir, input) {
|
|
@@ -3437,7 +3758,7 @@ import YAML from "yaml";
|
|
|
3437
3758
|
|
|
3438
3759
|
// src/indexer/program.ts
|
|
3439
3760
|
import ts from "typescript";
|
|
3440
|
-
import { join as join7 } from "path";
|
|
3761
|
+
import { join as join7, dirname } from "path";
|
|
3441
3762
|
function createTsProgram(options) {
|
|
3442
3763
|
const projectRoot = options.projectRoot;
|
|
3443
3764
|
const configPath = options.tsconfigPath ?? ts.findConfigFile(projectRoot, ts.sys.fileExists, "tsconfig.json");
|
|
@@ -3446,7 +3767,7 @@ function createTsProgram(options) {
|
|
|
3446
3767
|
`No tsconfig.json found in ${projectRoot}. TypeScript indexing requires a tsconfig.json.`
|
|
3447
3768
|
);
|
|
3448
3769
|
}
|
|
3449
|
-
|
|
3770
|
+
let configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
3450
3771
|
if (configFile.error) {
|
|
3451
3772
|
const message = ts.flattenDiagnosticMessageText(
|
|
3452
3773
|
configFile.error.messageText,
|
|
@@ -3454,12 +3775,45 @@ function createTsProgram(options) {
|
|
|
3454
3775
|
);
|
|
3455
3776
|
throw new Error(`Failed to read tsconfig.json: ${message}`);
|
|
3456
3777
|
}
|
|
3778
|
+
let resolvedConfigPath = configPath;
|
|
3779
|
+
const config = configFile.config;
|
|
3780
|
+
const hasOwnFiles = config.include && config.include.length > 0 || config.files && config.files.length > 0;
|
|
3781
|
+
if (config.references && !hasOwnFiles) {
|
|
3782
|
+
for (const ref of config.references) {
|
|
3783
|
+
const refRelPath = typeof ref === "string" ? ref : ref.path;
|
|
3784
|
+
if (!refRelPath) continue;
|
|
3785
|
+
const refFullPath = join7(dirname(configPath), refRelPath);
|
|
3786
|
+
const refConfigPath = refFullPath.endsWith(".json") ? refFullPath : join7(refFullPath, "tsconfig.json");
|
|
3787
|
+
if (ts.sys.fileExists(refConfigPath)) {
|
|
3788
|
+
const refConfig = ts.readConfigFile(refConfigPath, ts.sys.readFile);
|
|
3789
|
+
const rc = refConfig.config;
|
|
3790
|
+
if (!refConfig.error && (rc.include?.length > 0 || rc.files?.length > 0)) {
|
|
3791
|
+
configFile = refConfig;
|
|
3792
|
+
resolvedConfigPath = refConfigPath;
|
|
3793
|
+
break;
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
if (resolvedConfigPath === configPath) {
|
|
3798
|
+
for (const candidate of ["tsconfig.app.json", "tsconfig.src.json"]) {
|
|
3799
|
+
const refPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, candidate);
|
|
3800
|
+
if (refPath) {
|
|
3801
|
+
const refConfig = ts.readConfigFile(refPath, ts.sys.readFile);
|
|
3802
|
+
if (!refConfig.error) {
|
|
3803
|
+
configFile = refConfig;
|
|
3804
|
+
resolvedConfigPath = refPath;
|
|
3805
|
+
break;
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3457
3811
|
const parsed = ts.parseJsonConfigFileContent(
|
|
3458
3812
|
configFile.config,
|
|
3459
3813
|
ts.sys,
|
|
3460
3814
|
join7(projectRoot),
|
|
3461
3815
|
{ noEmit: true },
|
|
3462
|
-
|
|
3816
|
+
resolvedConfigPath
|
|
3463
3817
|
);
|
|
3464
3818
|
const program = ts.createProgram({
|
|
3465
3819
|
rootNames: parsed.fileNames,
|
|
@@ -4463,7 +4817,7 @@ function writeDependencies(db, dependencies) {
|
|
|
4463
4817
|
|
|
4464
4818
|
// src/indexer/dotnet-indexer.ts
|
|
4465
4819
|
import { execFileSync } from "child_process";
|
|
4466
|
-
import { resolve, join as join9, dirname, relative as relative4, basename } from "path";
|
|
4820
|
+
import { resolve, join as join9, dirname as dirname2, relative as relative4, basename } from "path";
|
|
4467
4821
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4, accessSync, constants } from "fs";
|
|
4468
4822
|
import { fileURLToPath } from "url";
|
|
4469
4823
|
function findDotnetProject(projectRoot) {
|
|
@@ -4480,7 +4834,7 @@ function findDotnetProject(projectRoot) {
|
|
|
4480
4834
|
}
|
|
4481
4835
|
function parseSolutionProjects(slnPath) {
|
|
4482
4836
|
const content = readFileSync3(slnPath, "utf-8");
|
|
4483
|
-
const slnDir =
|
|
4837
|
+
const slnDir = dirname2(slnPath);
|
|
4484
4838
|
const projects = [];
|
|
4485
4839
|
const projectPattern = /Project\("\{[^}]+\}"\)\s*=\s*"([^"]+)",\s*"([^"]+\.csproj)"/g;
|
|
4486
4840
|
let match;
|
|
@@ -4489,7 +4843,7 @@ function parseSolutionProjects(slnPath) {
|
|
|
4489
4843
|
const relativeCsprojPath = match[2].replace(/\\/g, "/");
|
|
4490
4844
|
const fullCsprojPath = resolve(join9(slnDir, relativeCsprojPath));
|
|
4491
4845
|
if (!existsSync4(fullCsprojPath)) continue;
|
|
4492
|
-
const projectDir = relative4(slnDir,
|
|
4846
|
+
const projectDir = relative4(slnDir, dirname2(fullCsprojPath)).replace(/\\/g, "/") || ".";
|
|
4493
4847
|
const isTestProject = /[.\x2d]tests?$/i.test(name) || /[.\x2d](unit|integration|functional|e2e)tests?$/i.test(name);
|
|
4494
4848
|
projects.push({
|
|
4495
4849
|
name,
|
|
@@ -4534,7 +4888,7 @@ function hasGlobalTool() {
|
|
|
4534
4888
|
return false;
|
|
4535
4889
|
}
|
|
4536
4890
|
function resolveIndexerProject() {
|
|
4537
|
-
const currentDir2 =
|
|
4891
|
+
const currentDir2 = dirname2(fileURLToPath(import.meta.url));
|
|
4538
4892
|
const candidates = [
|
|
4539
4893
|
resolve(currentDir2, "../../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
|
|
4540
4894
|
resolve(currentDir2, "../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
|
|
@@ -4688,10 +5042,10 @@ import { globbySync } from "globby";
|
|
|
4688
5042
|
|
|
4689
5043
|
// src/indexer/csharp/parser.ts
|
|
4690
5044
|
import { accessSync as accessSync2, constants as constants2 } from "fs";
|
|
4691
|
-
import { dirname as
|
|
5045
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
4692
5046
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4693
5047
|
import "web-tree-sitter";
|
|
4694
|
-
var currentDir =
|
|
5048
|
+
var currentDir = dirname3(fileURLToPath2(import.meta.url));
|
|
4695
5049
|
var cachedParser = null;
|
|
4696
5050
|
var initPromise = null;
|
|
4697
5051
|
function resolveGrammarPath() {
|
|
@@ -6047,7 +6401,17 @@ function detectNewDependencies(db, entries) {
|
|
|
6047
6401
|
const adrs = db.prepare("SELECT id, title, context, decision FROM adrs WHERE status != 'superseded'").all();
|
|
6048
6402
|
const adrText = adrs.map((a) => `${a.title} ${a.context ?? ""} ${a.decision ?? ""}`.toLowerCase()).join(" ");
|
|
6049
6403
|
const trivialPackages = /* @__PURE__ */ new Set([
|
|
6050
|
-
// npm
|
|
6404
|
+
// npm — frameworks (core deps that don't need ADRs)
|
|
6405
|
+
"react",
|
|
6406
|
+
"react-dom",
|
|
6407
|
+
"next",
|
|
6408
|
+
"vite",
|
|
6409
|
+
"@vitejs/plugin-react",
|
|
6410
|
+
"express",
|
|
6411
|
+
"fastify",
|
|
6412
|
+
"hono",
|
|
6413
|
+
"koa",
|
|
6414
|
+
// npm — dev tooling
|
|
6051
6415
|
"typescript",
|
|
6052
6416
|
"eslint",
|
|
6053
6417
|
"prettier",
|
|
@@ -6055,15 +6419,28 @@ function detectNewDependencies(db, entries) {
|
|
|
6055
6419
|
"jest",
|
|
6056
6420
|
"@types/node",
|
|
6057
6421
|
"@types/react",
|
|
6422
|
+
"@types/react-dom",
|
|
6058
6423
|
"tsup",
|
|
6059
6424
|
"tsx",
|
|
6060
|
-
|
|
6425
|
+
"ts-node",
|
|
6426
|
+
"nodemon",
|
|
6427
|
+
"@eslint/js",
|
|
6428
|
+
"typescript-eslint",
|
|
6429
|
+
// npm — build/bundler
|
|
6430
|
+
"esbuild",
|
|
6431
|
+
"rollup",
|
|
6432
|
+
"webpack",
|
|
6433
|
+
"postcss",
|
|
6434
|
+
"tailwindcss",
|
|
6435
|
+
"autoprefixer",
|
|
6436
|
+
// nuget — test
|
|
6061
6437
|
"microsoft.net.test.sdk",
|
|
6062
6438
|
"xunit",
|
|
6063
6439
|
"xunit.runner.visualstudio",
|
|
6064
6440
|
"nunit",
|
|
6065
6441
|
"nunit3testadapter",
|
|
6066
6442
|
"coverlet.collector",
|
|
6443
|
+
// nuget — framework
|
|
6067
6444
|
"microsoft.aspnetcore.openapi",
|
|
6068
6445
|
"swashbuckle.aspnetcore"
|
|
6069
6446
|
]);
|
|
@@ -6318,7 +6695,7 @@ function safeParseJson2(value, fallback) {
|
|
|
6318
6695
|
|
|
6319
6696
|
// src/generators/sync-generator.ts
|
|
6320
6697
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
6321
|
-
import { join as join15, dirname as
|
|
6698
|
+
import { join as join15, dirname as dirname4 } from "path";
|
|
6322
6699
|
|
|
6323
6700
|
// src/templates/sync/claude-skill.ts
|
|
6324
6701
|
function claudeSkillTemplate(config) {
|
|
@@ -6497,20 +6874,20 @@ function generateSyncFiles(targetDir, config) {
|
|
|
6497
6874
|
const generated = [];
|
|
6498
6875
|
const action = githubActionTemplate(config);
|
|
6499
6876
|
const actionPath = join15(targetDir, action.relativePath);
|
|
6500
|
-
mkdirSync6(
|
|
6877
|
+
mkdirSync6(dirname4(actionPath), { recursive: true });
|
|
6501
6878
|
writeFileSync6(actionPath, action.content, "utf-8");
|
|
6502
6879
|
generated.push(action.relativePath);
|
|
6503
6880
|
if (config.platforms.includes("claude")) {
|
|
6504
6881
|
const skill = claudeSkillTemplate(config);
|
|
6505
6882
|
const skillPath = join15(targetDir, skill.relativePath);
|
|
6506
|
-
mkdirSync6(
|
|
6883
|
+
mkdirSync6(dirname4(skillPath), { recursive: true });
|
|
6507
6884
|
writeFileSync6(skillPath, skill.content, "utf-8");
|
|
6508
6885
|
generated.push(skill.relativePath);
|
|
6509
6886
|
}
|
|
6510
6887
|
if (config.platforms.includes("copilot")) {
|
|
6511
6888
|
const hook = copilotHookTemplate(config);
|
|
6512
6889
|
const hookPath = join15(targetDir, hook.relativePath);
|
|
6513
|
-
mkdirSync6(
|
|
6890
|
+
mkdirSync6(dirname4(hookPath), { recursive: true });
|
|
6514
6891
|
writeFileSync6(hookPath, hook.content, "utf-8");
|
|
6515
6892
|
generated.push(hook.relativePath);
|
|
6516
6893
|
}
|
|
@@ -6866,6 +7243,7 @@ function resolveRef(projectRoot, since, db) {
|
|
|
6866
7243
|
}
|
|
6867
7244
|
}
|
|
6868
7245
|
function getChangedFiles(projectRoot, ref) {
|
|
7246
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6869
7247
|
try {
|
|
6870
7248
|
execFileSync3("git", ["rev-parse", "--verify", ref], {
|
|
6871
7249
|
cwd: projectRoot,
|
|
@@ -6878,16 +7256,24 @@ function getChangedFiles(projectRoot, ref) {
|
|
|
6878
7256
|
["diff", "--name-status", "--no-renames", ref, "HEAD"],
|
|
6879
7257
|
{ cwd: projectRoot, encoding: "utf-8", timeout: 1e4 }
|
|
6880
7258
|
).trim();
|
|
6881
|
-
if (
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
}
|
|
7259
|
+
if (output) {
|
|
7260
|
+
for (const line of output.split("\n")) {
|
|
7261
|
+
const [statusCode, ...pathParts] = line.split(" ");
|
|
7262
|
+
const path = pathParts.join(" ");
|
|
7263
|
+
byPath.set(path, { status: parseStatusCode(statusCode ?? "M"), path });
|
|
7264
|
+
}
|
|
7265
|
+
}
|
|
6888
7266
|
} catch {
|
|
6889
|
-
return getUncommittedChanges(projectRoot);
|
|
6890
7267
|
}
|
|
7268
|
+
for (const change of getUncommittedChanges(projectRoot)) {
|
|
7269
|
+
const existing = byPath.get(change.path);
|
|
7270
|
+
if (!existing) {
|
|
7271
|
+
byPath.set(change.path, change);
|
|
7272
|
+
} else if (change.status === "deleted") {
|
|
7273
|
+
byPath.set(change.path, change);
|
|
7274
|
+
}
|
|
7275
|
+
}
|
|
7276
|
+
return Array.from(byPath.values());
|
|
6891
7277
|
}
|
|
6892
7278
|
function getUncommittedChanges(projectRoot) {
|
|
6893
7279
|
try {
|
|
@@ -6895,11 +7281,12 @@ function getUncommittedChanges(projectRoot) {
|
|
|
6895
7281
|
"git",
|
|
6896
7282
|
["status", "--porcelain", "-uno"],
|
|
6897
7283
|
{ cwd: projectRoot, encoding: "utf-8", timeout: 5e3 }
|
|
6898
|
-
)
|
|
6899
|
-
|
|
6900
|
-
|
|
7284
|
+
);
|
|
7285
|
+
const lines = output.split("\n").filter((l) => l.length >= 3);
|
|
7286
|
+
if (lines.length === 0) return [];
|
|
7287
|
+
return lines.map((line) => {
|
|
6901
7288
|
const statusCode = line.slice(0, 2).trim();
|
|
6902
|
-
const path = line.slice(3);
|
|
7289
|
+
const path = line.slice(3).replace(/\r$/, "");
|
|
6903
7290
|
const status = statusCode === "D" ? "deleted" : statusCode === "A" ? "added" : "modified";
|
|
6904
7291
|
return { status, path };
|
|
6905
7292
|
});
|