@arcbridge/core 0.1.3 → 0.1.5
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 +27 -12
- package/dist/index.js +688 -149
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -130,7 +130,8 @@ var TaskStatusSchema = z4.enum([
|
|
|
130
130
|
"todo",
|
|
131
131
|
"in-progress",
|
|
132
132
|
"done",
|
|
133
|
-
"blocked"
|
|
133
|
+
"blocked",
|
|
134
|
+
"cancelled"
|
|
134
135
|
]);
|
|
135
136
|
var PhaseSchema = z4.object({
|
|
136
137
|
id: z4.string().min(1),
|
|
@@ -300,7 +301,7 @@ function transaction(db, fn) {
|
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
// src/db/schema.ts
|
|
303
|
-
var CURRENT_SCHEMA_VERSION =
|
|
304
|
+
var CURRENT_SCHEMA_VERSION = 3;
|
|
304
305
|
var SCHEMA_SQL = `
|
|
305
306
|
-- Metadata
|
|
306
307
|
CREATE TABLE IF NOT EXISTS arcbridge_meta (
|
|
@@ -447,7 +448,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
447
448
|
phase_id TEXT NOT NULL REFERENCES phases(id),
|
|
448
449
|
title TEXT NOT NULL,
|
|
449
450
|
description TEXT,
|
|
450
|
-
status TEXT NOT NULL DEFAULT 'todo' CHECK(status IN ('todo','in-progress','done','blocked')),
|
|
451
|
+
status TEXT NOT NULL DEFAULT 'todo' CHECK(status IN ('todo','in-progress','done','blocked','cancelled')),
|
|
451
452
|
building_block TEXT REFERENCES building_blocks(id),
|
|
452
453
|
quality_scenarios TEXT NOT NULL DEFAULT '[]',
|
|
453
454
|
acceptance_criteria TEXT NOT NULL DEFAULT '[]',
|
|
@@ -545,6 +546,29 @@ var migrations = [
|
|
|
545
546
|
CREATE INDEX IF NOT EXISTS idx_activity_phase ON agent_activity(phase_id);
|
|
546
547
|
`);
|
|
547
548
|
}
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
version: 3,
|
|
552
|
+
up: (db) => {
|
|
553
|
+
db.exec(`
|
|
554
|
+
CREATE TABLE tasks_new (
|
|
555
|
+
id TEXT PRIMARY KEY,
|
|
556
|
+
phase_id TEXT NOT NULL REFERENCES phases(id),
|
|
557
|
+
title TEXT NOT NULL,
|
|
558
|
+
description TEXT,
|
|
559
|
+
status TEXT NOT NULL DEFAULT 'todo' CHECK(status IN ('todo','in-progress','done','blocked','cancelled')),
|
|
560
|
+
building_block TEXT REFERENCES building_blocks(id),
|
|
561
|
+
quality_scenarios TEXT NOT NULL DEFAULT '[]',
|
|
562
|
+
acceptance_criteria TEXT NOT NULL DEFAULT '[]',
|
|
563
|
+
created_at TEXT NOT NULL,
|
|
564
|
+
completed_at TEXT
|
|
565
|
+
);
|
|
566
|
+
INSERT INTO tasks_new (id, phase_id, title, description, status, building_block, quality_scenarios, acceptance_criteria, created_at, completed_at)
|
|
567
|
+
SELECT id, phase_id, title, description, status, building_block, quality_scenarios, acceptance_criteria, created_at, completed_at FROM tasks;
|
|
568
|
+
DROP TABLE tasks;
|
|
569
|
+
ALTER TABLE tasks_new RENAME TO tasks;
|
|
570
|
+
`);
|
|
571
|
+
}
|
|
548
572
|
}
|
|
549
573
|
];
|
|
550
574
|
function migrate(db) {
|
|
@@ -738,7 +762,7 @@ function generateConfig(targetDir, input) {
|
|
|
738
762
|
|
|
739
763
|
// src/generators/arc42-generator.ts
|
|
740
764
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
741
|
-
import { join as
|
|
765
|
+
import { join as join4 } from "path";
|
|
742
766
|
import matter from "gray-matter";
|
|
743
767
|
import { stringify as stringify2 } from "yaml";
|
|
744
768
|
|
|
@@ -826,6 +850,10 @@ ${techStack(input.template)}
|
|
|
826
850
|
};
|
|
827
851
|
}
|
|
828
852
|
|
|
853
|
+
// src/templates/arc42/05-building-blocks.ts
|
|
854
|
+
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
855
|
+
import { join as join3 } from "path";
|
|
856
|
+
|
|
829
857
|
// src/templates/arc42/detect-layout.ts
|
|
830
858
|
import { existsSync } from "fs";
|
|
831
859
|
import { join as join2 } from "path";
|
|
@@ -959,13 +987,31 @@ function buildingBlocksTemplate(input) {
|
|
|
959
987
|
}
|
|
960
988
|
return blocks;
|
|
961
989
|
}
|
|
962
|
-
function buildDotnetBlocks(
|
|
990
|
+
function buildDotnetBlocks(inp) {
|
|
991
|
+
const root = inp.projectRoot ?? ".";
|
|
992
|
+
let prefix = "";
|
|
993
|
+
try {
|
|
994
|
+
const primaryService = inp.dotnetServices?.find((s) => !s.path.includes("Test"));
|
|
995
|
+
if (primaryService && primaryService.path !== ".") {
|
|
996
|
+
prefix = primaryService.path.endsWith("/") ? primaryService.path : `${primaryService.path}/`;
|
|
997
|
+
} else {
|
|
998
|
+
const srcDir = join3(root, "src");
|
|
999
|
+
if (existsSync2(srcDir)) {
|
|
1000
|
+
const entries = readdirSync(srcDir).sort();
|
|
1001
|
+
const projDir = entries.find(
|
|
1002
|
+
(e) => existsSync2(join3(srcDir, e, `${e}.csproj`)) || existsSync2(join3(srcDir, e, "Program.cs"))
|
|
1003
|
+
);
|
|
1004
|
+
if (projDir) prefix = `src/${projDir}/`;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
} catch {
|
|
1008
|
+
}
|
|
963
1009
|
const blocks = [
|
|
964
1010
|
{
|
|
965
1011
|
id: "api-host",
|
|
966
1012
|
name: "API Host",
|
|
967
1013
|
level: 1,
|
|
968
|
-
code_paths: [
|
|
1014
|
+
code_paths: [`${prefix}Program.cs`, `${prefix}Extensions/`],
|
|
969
1015
|
interfaces: [],
|
|
970
1016
|
quality_scenarios: [],
|
|
971
1017
|
adrs: [],
|
|
@@ -976,7 +1022,7 @@ function buildingBlocksTemplate(input) {
|
|
|
976
1022
|
id: "controllers",
|
|
977
1023
|
name: "Controllers / Endpoints",
|
|
978
1024
|
level: 1,
|
|
979
|
-
code_paths: [
|
|
1025
|
+
code_paths: [`${prefix}Controllers/`, `${prefix}Endpoints/`],
|
|
980
1026
|
interfaces: [],
|
|
981
1027
|
quality_scenarios: ["SEC-03"],
|
|
982
1028
|
adrs: [],
|
|
@@ -987,7 +1033,7 @@ function buildingBlocksTemplate(input) {
|
|
|
987
1033
|
id: "domain",
|
|
988
1034
|
name: "Domain",
|
|
989
1035
|
level: 1,
|
|
990
|
-
code_paths: [
|
|
1036
|
+
code_paths: [`${prefix}Domain/`, `${prefix}Models/`],
|
|
991
1037
|
interfaces: [],
|
|
992
1038
|
quality_scenarios: [],
|
|
993
1039
|
adrs: [],
|
|
@@ -998,7 +1044,7 @@ function buildingBlocksTemplate(input) {
|
|
|
998
1044
|
id: "services",
|
|
999
1045
|
name: "Application Services",
|
|
1000
1046
|
level: 1,
|
|
1001
|
-
code_paths: [
|
|
1047
|
+
code_paths: [`${prefix}Services/`],
|
|
1002
1048
|
interfaces: ["controllers"],
|
|
1003
1049
|
quality_scenarios: [],
|
|
1004
1050
|
adrs: [],
|
|
@@ -1009,7 +1055,7 @@ function buildingBlocksTemplate(input) {
|
|
|
1009
1055
|
id: "middleware",
|
|
1010
1056
|
name: "Middleware",
|
|
1011
1057
|
level: 1,
|
|
1012
|
-
code_paths: [
|
|
1058
|
+
code_paths: [`${prefix}Middleware/`],
|
|
1013
1059
|
interfaces: [],
|
|
1014
1060
|
quality_scenarios: ["REL-01"],
|
|
1015
1061
|
adrs: [],
|
|
@@ -1021,7 +1067,7 @@ function buildingBlocksTemplate(input) {
|
|
|
1021
1067
|
id: "auth-module",
|
|
1022
1068
|
name: "Authentication & Authorization",
|
|
1023
1069
|
level: 1,
|
|
1024
|
-
code_paths: [
|
|
1070
|
+
code_paths: [`${prefix}Auth/`],
|
|
1025
1071
|
interfaces: [],
|
|
1026
1072
|
quality_scenarios: ["SEC-01", "SEC-02"],
|
|
1027
1073
|
adrs: [],
|
|
@@ -1032,7 +1078,7 @@ function buildingBlocksTemplate(input) {
|
|
|
1032
1078
|
id: "data-access",
|
|
1033
1079
|
name: "Data Access",
|
|
1034
1080
|
level: 1,
|
|
1035
|
-
code_paths: [
|
|
1081
|
+
code_paths: [`${prefix}Data/`, `${prefix}Repositories/`, `${prefix}Migrations/`],
|
|
1036
1082
|
interfaces: ["domain"],
|
|
1037
1083
|
quality_scenarios: [],
|
|
1038
1084
|
adrs: [],
|
|
@@ -1140,10 +1186,16 @@ ${input.template === "dotnet-webapi" ? `| Platform | Description | Notes |
|
|
|
1140
1186
|
// src/templates/arc42/09-decisions.ts
|
|
1141
1187
|
function firstAdrTemplate(input) {
|
|
1142
1188
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1143
|
-
|
|
1144
|
-
|
|
1189
|
+
switch (input.template) {
|
|
1190
|
+
case "dotnet-webapi":
|
|
1191
|
+
return dotnetAdr(input, now);
|
|
1192
|
+
case "react-vite":
|
|
1193
|
+
return reactViteAdr(input, now);
|
|
1194
|
+
case "api-service":
|
|
1195
|
+
return apiServiceAdr(input, now);
|
|
1196
|
+
default:
|
|
1197
|
+
return nextjsAdr(input, now);
|
|
1145
1198
|
}
|
|
1146
|
-
return nextjsAdr(input, now);
|
|
1147
1199
|
}
|
|
1148
1200
|
function nextjsAdr(input, date) {
|
|
1149
1201
|
const { appPrefix } = detectProjectLayout(input.projectRoot, input.template);
|
|
@@ -1178,6 +1230,73 @@ Use Next.js with the App Router (introduced in Next.js 13+) as the application f
|
|
|
1178
1230
|
`
|
|
1179
1231
|
};
|
|
1180
1232
|
}
|
|
1233
|
+
function reactViteAdr(input, date) {
|
|
1234
|
+
const { srcPrefix } = detectProjectLayout(input.projectRoot, input.template);
|
|
1235
|
+
return {
|
|
1236
|
+
filename: "001-react-vite.md",
|
|
1237
|
+
frontmatter: {
|
|
1238
|
+
id: "001-react-vite",
|
|
1239
|
+
title: "Use React with Vite",
|
|
1240
|
+
status: "accepted",
|
|
1241
|
+
date,
|
|
1242
|
+
affected_blocks: ["app-shell"],
|
|
1243
|
+
affected_files: [srcPrefix || "./"],
|
|
1244
|
+
quality_scenarios: []
|
|
1245
|
+
},
|
|
1246
|
+
body: `# ADR-001: Use React with Vite
|
|
1247
|
+
|
|
1248
|
+
## Context
|
|
1249
|
+
|
|
1250
|
+
${input.name} needs a fast, modern frontend framework for building a single-page application (SPA) with TypeScript.
|
|
1251
|
+
|
|
1252
|
+
## Decision
|
|
1253
|
+
|
|
1254
|
+
Use React with Vite as the build tool and development server.
|
|
1255
|
+
|
|
1256
|
+
## Consequences
|
|
1257
|
+
|
|
1258
|
+
- **Positive:** Extremely fast dev server with hot module replacement (HMR)
|
|
1259
|
+
- **Positive:** Optimized production builds with tree shaking and code splitting
|
|
1260
|
+
- **Positive:** Simple configuration, no SSR complexity
|
|
1261
|
+
- **Positive:** Large React ecosystem of libraries and components
|
|
1262
|
+
- **Negative:** Client-side only \u2014 no built-in server-side rendering (add later if needed)
|
|
1263
|
+
- **Negative:** Requires separate backend service for API endpoints
|
|
1264
|
+
`
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
function apiServiceAdr(input, date) {
|
|
1268
|
+
const { srcPrefix } = detectProjectLayout(input.projectRoot, input.template);
|
|
1269
|
+
return {
|
|
1270
|
+
filename: "001-api-service.md",
|
|
1271
|
+
frontmatter: {
|
|
1272
|
+
id: "001-api-service",
|
|
1273
|
+
title: "Use Node.js API Service",
|
|
1274
|
+
status: "accepted",
|
|
1275
|
+
date,
|
|
1276
|
+
affected_blocks: ["app-shell"],
|
|
1277
|
+
affected_files: [srcPrefix || "./"],
|
|
1278
|
+
quality_scenarios: []
|
|
1279
|
+
},
|
|
1280
|
+
body: `# ADR-001: Use Node.js API Service
|
|
1281
|
+
|
|
1282
|
+
## Context
|
|
1283
|
+
|
|
1284
|
+
${input.name} needs a backend API service with TypeScript support, good performance, and a simple architecture.
|
|
1285
|
+
|
|
1286
|
+
## Decision
|
|
1287
|
+
|
|
1288
|
+
Use a Node.js HTTP framework (Express, Fastify, or Hono) as the API service foundation.
|
|
1289
|
+
|
|
1290
|
+
## Consequences
|
|
1291
|
+
|
|
1292
|
+
- **Positive:** TypeScript-first with full type safety
|
|
1293
|
+
- **Positive:** Rich middleware ecosystem for auth, validation, logging
|
|
1294
|
+
- **Positive:** Easy to deploy to containers, serverless, or traditional hosts
|
|
1295
|
+
- **Negative:** Single-threaded \u2014 CPU-intensive operations need worker threads or external services
|
|
1296
|
+
- **Negative:** No built-in ORM \u2014 need to choose data access strategy
|
|
1297
|
+
`
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1181
1300
|
function dotnetAdr(input, date) {
|
|
1182
1301
|
return {
|
|
1183
1302
|
filename: "001-aspnet-core-webapi.md",
|
|
@@ -1774,8 +1893,8 @@ function writeMarkdownWithFrontmatter(filePath, frontmatter, body) {
|
|
|
1774
1893
|
writeFileSync2(filePath, content, "utf-8");
|
|
1775
1894
|
}
|
|
1776
1895
|
function generateArc42(targetDir, input) {
|
|
1777
|
-
const arc42Dir =
|
|
1778
|
-
const decisionsDir =
|
|
1896
|
+
const arc42Dir = join4(targetDir, ".arcbridge", "arc42");
|
|
1897
|
+
const decisionsDir = join4(arc42Dir, "09-decisions");
|
|
1779
1898
|
mkdirSync2(arc42Dir, { recursive: true });
|
|
1780
1899
|
mkdirSync2(decisionsDir, { recursive: true });
|
|
1781
1900
|
const sections = [
|
|
@@ -1790,17 +1909,17 @@ function generateArc42(targetDir, input) {
|
|
|
1790
1909
|
const inputWithRoot = { ...input, projectRoot: targetDir };
|
|
1791
1910
|
for (const { file, template } of sections) {
|
|
1792
1911
|
const { frontmatter, body } = template(inputWithRoot);
|
|
1793
|
-
writeMarkdownWithFrontmatter(
|
|
1912
|
+
writeMarkdownWithFrontmatter(join4(arc42Dir, file), frontmatter, body);
|
|
1794
1913
|
}
|
|
1795
1914
|
const adr = firstAdrTemplate(inputWithRoot);
|
|
1796
1915
|
writeMarkdownWithFrontmatter(
|
|
1797
|
-
|
|
1916
|
+
join4(decisionsDir, adr.filename),
|
|
1798
1917
|
adr.frontmatter,
|
|
1799
1918
|
adr.body
|
|
1800
1919
|
);
|
|
1801
1920
|
const qualityScenarios = qualityScenariosTemplate(input);
|
|
1802
1921
|
writeFileSync2(
|
|
1803
|
-
|
|
1922
|
+
join4(arc42Dir, "10-quality-scenarios.yaml"),
|
|
1804
1923
|
stringify2(qualityScenarios),
|
|
1805
1924
|
"utf-8"
|
|
1806
1925
|
);
|
|
@@ -1808,7 +1927,7 @@ function generateArc42(targetDir, input) {
|
|
|
1808
1927
|
|
|
1809
1928
|
// src/generators/plan-generator.ts
|
|
1810
1929
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1811
|
-
import { join as
|
|
1930
|
+
import { join as join5 } from "path";
|
|
1812
1931
|
import { stringify as stringify3 } from "yaml";
|
|
1813
1932
|
|
|
1814
1933
|
// src/templates/phases/nextjs-app-router.ts
|
|
@@ -1961,6 +2080,116 @@ function phaseTasksTemplate(_input, phaseId) {
|
|
|
1961
2080
|
]
|
|
1962
2081
|
}
|
|
1963
2082
|
]
|
|
2083
|
+
},
|
|
2084
|
+
"phase-2-features": {
|
|
2085
|
+
schema_version: 1,
|
|
2086
|
+
phase_id: "phase-2-features",
|
|
2087
|
+
tasks: [
|
|
2088
|
+
{
|
|
2089
|
+
id: "task-2.1-core-pages",
|
|
2090
|
+
title: "Implement core application pages",
|
|
2091
|
+
status: "todo",
|
|
2092
|
+
quality_scenarios: ["PERF-01"],
|
|
2093
|
+
acceptance_criteria: [
|
|
2094
|
+
"Core pages created with proper data fetching",
|
|
2095
|
+
"Server/client components properly separated",
|
|
2096
|
+
"Loading and error states handled"
|
|
2097
|
+
]
|
|
2098
|
+
},
|
|
2099
|
+
{
|
|
2100
|
+
id: "task-2.2-api-routes",
|
|
2101
|
+
title: "Implement API routes",
|
|
2102
|
+
status: "todo",
|
|
2103
|
+
quality_scenarios: ["SEC-01", "PERF-02"],
|
|
2104
|
+
acceptance_criteria: [
|
|
2105
|
+
"API routes created for core operations",
|
|
2106
|
+
"Input validation on all endpoints",
|
|
2107
|
+
"Error responses standardized"
|
|
2108
|
+
]
|
|
2109
|
+
},
|
|
2110
|
+
{
|
|
2111
|
+
id: "task-2.3-data-layer",
|
|
2112
|
+
title: "Set up data access layer",
|
|
2113
|
+
status: "todo",
|
|
2114
|
+
quality_scenarios: ["MAINT-01"],
|
|
2115
|
+
acceptance_criteria: [
|
|
2116
|
+
"Data fetching patterns established",
|
|
2117
|
+
"Caching strategy implemented",
|
|
2118
|
+
"Type-safe data access"
|
|
2119
|
+
]
|
|
2120
|
+
},
|
|
2121
|
+
{
|
|
2122
|
+
id: "task-2.4-integration-tests",
|
|
2123
|
+
title: "Write integration tests for core flows",
|
|
2124
|
+
status: "todo",
|
|
2125
|
+
quality_scenarios: ["MAINT-02"],
|
|
2126
|
+
acceptance_criteria: [
|
|
2127
|
+
"Happy path tested for each core feature",
|
|
2128
|
+
"API route tests written",
|
|
2129
|
+
"Test coverage meets threshold"
|
|
2130
|
+
]
|
|
2131
|
+
},
|
|
2132
|
+
{
|
|
2133
|
+
id: "task-2.5-document-decisions",
|
|
2134
|
+
title: "Document Phase 2 architectural decisions",
|
|
2135
|
+
status: "todo",
|
|
2136
|
+
quality_scenarios: [],
|
|
2137
|
+
acceptance_criteria: [
|
|
2138
|
+
"ADRs for data fetching and API design choices",
|
|
2139
|
+
"Building blocks updated with new code paths"
|
|
2140
|
+
]
|
|
2141
|
+
}
|
|
2142
|
+
]
|
|
2143
|
+
},
|
|
2144
|
+
"phase-3-polish": {
|
|
2145
|
+
schema_version: 1,
|
|
2146
|
+
phase_id: "phase-3-polish",
|
|
2147
|
+
tasks: [
|
|
2148
|
+
{
|
|
2149
|
+
id: "task-3.1-error-handling",
|
|
2150
|
+
title: "Implement comprehensive error handling",
|
|
2151
|
+
status: "todo",
|
|
2152
|
+
quality_scenarios: ["SEC-01"],
|
|
2153
|
+
acceptance_criteria: [
|
|
2154
|
+
"Error boundaries at route level",
|
|
2155
|
+
"Custom error.tsx and not-found.tsx pages",
|
|
2156
|
+
"API error responses standardized"
|
|
2157
|
+
]
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
id: "task-3.2-accessibility",
|
|
2161
|
+
title: "Accessibility audit and fixes",
|
|
2162
|
+
status: "todo",
|
|
2163
|
+
quality_scenarios: ["A11Y-01", "A11Y-02"],
|
|
2164
|
+
acceptance_criteria: [
|
|
2165
|
+
"Keyboard navigation works for all interactive elements",
|
|
2166
|
+
"Screen reader compatible (ARIA labels, roles)",
|
|
2167
|
+
"WCAG 2.1 AA compliance verified"
|
|
2168
|
+
]
|
|
2169
|
+
},
|
|
2170
|
+
{
|
|
2171
|
+
id: "task-3.3-performance",
|
|
2172
|
+
title: "Performance optimization",
|
|
2173
|
+
status: "todo",
|
|
2174
|
+
quality_scenarios: ["PERF-01"],
|
|
2175
|
+
acceptance_criteria: [
|
|
2176
|
+
"Bundle size optimized (dynamic imports, tree shaking)",
|
|
2177
|
+
"Core Web Vitals meet thresholds (LCP < 2.5s)",
|
|
2178
|
+
"Server-side rendering verified for SEO pages"
|
|
2179
|
+
]
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
id: "task-3.4-deployment",
|
|
2183
|
+
title: "Configure production deployment",
|
|
2184
|
+
status: "todo",
|
|
2185
|
+
quality_scenarios: [],
|
|
2186
|
+
acceptance_criteria: [
|
|
2187
|
+
"Production build configuration verified",
|
|
2188
|
+
"Environment variables documented",
|
|
2189
|
+
"Deployment to Vercel/hosting configured"
|
|
2190
|
+
]
|
|
2191
|
+
}
|
|
2192
|
+
]
|
|
1964
2193
|
}
|
|
1965
2194
|
};
|
|
1966
2195
|
return tasksByPhase[phaseId] ?? null;
|
|
@@ -2128,6 +2357,105 @@ function phaseTasksTemplate2(_input, phaseId) {
|
|
|
2128
2357
|
]
|
|
2129
2358
|
}
|
|
2130
2359
|
]
|
|
2360
|
+
},
|
|
2361
|
+
"phase-2-features": {
|
|
2362
|
+
schema_version: 1,
|
|
2363
|
+
phase_id: "phase-2-features",
|
|
2364
|
+
tasks: [
|
|
2365
|
+
{
|
|
2366
|
+
id: "task-2.1-core-feature",
|
|
2367
|
+
title: "Implement primary feature",
|
|
2368
|
+
status: "todo",
|
|
2369
|
+
quality_scenarios: ["PERF-01"],
|
|
2370
|
+
acceptance_criteria: [
|
|
2371
|
+
"Core user flow works end-to-end",
|
|
2372
|
+
"Unit and component tests written",
|
|
2373
|
+
"Meets performance budgets"
|
|
2374
|
+
]
|
|
2375
|
+
},
|
|
2376
|
+
{
|
|
2377
|
+
id: "task-2.2-state-management",
|
|
2378
|
+
title: "Set up state management for complex data",
|
|
2379
|
+
status: "todo",
|
|
2380
|
+
quality_scenarios: ["MAINT-01"],
|
|
2381
|
+
acceptance_criteria: [
|
|
2382
|
+
"State management pattern established",
|
|
2383
|
+
"Data fetching/caching strategy implemented",
|
|
2384
|
+
"Loading and error states handled"
|
|
2385
|
+
]
|
|
2386
|
+
},
|
|
2387
|
+
{
|
|
2388
|
+
id: "task-2.3-integration-tests",
|
|
2389
|
+
title: "Write integration tests for core flows",
|
|
2390
|
+
status: "todo",
|
|
2391
|
+
quality_scenarios: ["MAINT-02"],
|
|
2392
|
+
acceptance_criteria: [
|
|
2393
|
+
"Happy path tested for each core feature",
|
|
2394
|
+
"Error scenarios tested",
|
|
2395
|
+
"Test coverage meets threshold"
|
|
2396
|
+
]
|
|
2397
|
+
},
|
|
2398
|
+
{
|
|
2399
|
+
id: "task-2.4-document-decisions",
|
|
2400
|
+
title: "Document Phase 2 architectural decisions",
|
|
2401
|
+
status: "todo",
|
|
2402
|
+
quality_scenarios: [],
|
|
2403
|
+
acceptance_criteria: [
|
|
2404
|
+
"ADRs for state management and data fetching choices",
|
|
2405
|
+
"Building blocks updated with new code paths"
|
|
2406
|
+
]
|
|
2407
|
+
}
|
|
2408
|
+
]
|
|
2409
|
+
},
|
|
2410
|
+
"phase-3-polish": {
|
|
2411
|
+
schema_version: 1,
|
|
2412
|
+
phase_id: "phase-3-polish",
|
|
2413
|
+
tasks: [
|
|
2414
|
+
{
|
|
2415
|
+
id: "task-3.1-error-handling",
|
|
2416
|
+
title: "Implement comprehensive error handling",
|
|
2417
|
+
status: "todo",
|
|
2418
|
+
quality_scenarios: ["SEC-01"],
|
|
2419
|
+
acceptance_criteria: [
|
|
2420
|
+
"Error boundaries at route level",
|
|
2421
|
+
"User-friendly error messages",
|
|
2422
|
+
"Error logging configured"
|
|
2423
|
+
]
|
|
2424
|
+
},
|
|
2425
|
+
{
|
|
2426
|
+
id: "task-3.2-accessibility",
|
|
2427
|
+
title: "Accessibility audit and fixes",
|
|
2428
|
+
status: "todo",
|
|
2429
|
+
quality_scenarios: ["A11Y-01", "A11Y-02"],
|
|
2430
|
+
acceptance_criteria: [
|
|
2431
|
+
"Keyboard navigation works for all interactive elements",
|
|
2432
|
+
"Screen reader compatible (ARIA labels, roles)",
|
|
2433
|
+
"WCAG 2.1 AA compliance verified"
|
|
2434
|
+
]
|
|
2435
|
+
},
|
|
2436
|
+
{
|
|
2437
|
+
id: "task-3.3-performance",
|
|
2438
|
+
title: "Performance optimization",
|
|
2439
|
+
status: "todo",
|
|
2440
|
+
quality_scenarios: ["PERF-01"],
|
|
2441
|
+
acceptance_criteria: [
|
|
2442
|
+
"Bundle size optimized (code splitting, tree shaking)",
|
|
2443
|
+
"Lighthouse performance score >= 90",
|
|
2444
|
+
"No unnecessary re-renders in hot paths"
|
|
2445
|
+
]
|
|
2446
|
+
},
|
|
2447
|
+
{
|
|
2448
|
+
id: "task-3.4-deployment",
|
|
2449
|
+
title: "Configure production build and deployment",
|
|
2450
|
+
status: "todo",
|
|
2451
|
+
quality_scenarios: [],
|
|
2452
|
+
acceptance_criteria: [
|
|
2453
|
+
"Production build configuration verified",
|
|
2454
|
+
"Deployment pipeline configured",
|
|
2455
|
+
"Environment variables documented"
|
|
2456
|
+
]
|
|
2457
|
+
}
|
|
2458
|
+
]
|
|
2131
2459
|
}
|
|
2132
2460
|
};
|
|
2133
2461
|
return tasksByPhase[phaseId] ?? null;
|
|
@@ -2523,25 +2851,25 @@ var planTemplates = {
|
|
|
2523
2851
|
"dotnet-webapi": { plan: phasePlanTemplate4, tasks: phaseTasksTemplate4 }
|
|
2524
2852
|
};
|
|
2525
2853
|
function generatePlan(targetDir, input) {
|
|
2526
|
-
const planDir =
|
|
2527
|
-
const tasksDir =
|
|
2854
|
+
const planDir = join5(targetDir, ".arcbridge", "plan");
|
|
2855
|
+
const tasksDir = join5(planDir, "tasks");
|
|
2528
2856
|
mkdirSync3(planDir, { recursive: true });
|
|
2529
2857
|
mkdirSync3(tasksDir, { recursive: true });
|
|
2530
2858
|
const tmpl = planTemplates[input.template] ?? planTemplates["nextjs-app-router"];
|
|
2531
2859
|
const phasePlan = tmpl.plan(input);
|
|
2532
|
-
writeFileSync3(
|
|
2860
|
+
writeFileSync3(join5(planDir, "phases.yaml"), stringify3(phasePlan), "utf-8");
|
|
2533
2861
|
for (const phase of phasePlan.phases) {
|
|
2534
2862
|
const taskFile = tmpl.tasks(input, phase.id);
|
|
2535
2863
|
if (taskFile) {
|
|
2536
2864
|
writeFileSync3(
|
|
2537
|
-
|
|
2865
|
+
join5(tasksDir, `${phase.id}.yaml`),
|
|
2538
2866
|
stringify3(taskFile),
|
|
2539
2867
|
"utf-8"
|
|
2540
2868
|
);
|
|
2541
2869
|
}
|
|
2542
2870
|
}
|
|
2543
2871
|
writeFileSync3(
|
|
2544
|
-
|
|
2872
|
+
join5(planDir, "sync-log.md"),
|
|
2545
2873
|
`# Sync Log
|
|
2546
2874
|
|
|
2547
2875
|
Architecture sync events are recorded here.
|
|
@@ -2552,7 +2880,7 @@ Architecture sync events are recorded here.
|
|
|
2552
2880
|
|
|
2553
2881
|
// src/generators/agent-generator.ts
|
|
2554
2882
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2555
|
-
import { join as
|
|
2883
|
+
import { join as join6 } from "path";
|
|
2556
2884
|
import matter2 from "gray-matter";
|
|
2557
2885
|
|
|
2558
2886
|
// src/templates/agents/architect.ts
|
|
@@ -2646,6 +2974,19 @@ You are responsible for maintaining these sections in \`.arcbridge/arc42/\`. Upd
|
|
|
2646
2974
|
- All ADRs and their status
|
|
2647
2975
|
- Building block \u2192 code mapping
|
|
2648
2976
|
|
|
2977
|
+
## Project Planning
|
|
2978
|
+
|
|
2979
|
+
At the start of a project (after init), plan the full roadmap:
|
|
2980
|
+
- **ArcBridge generates 4 phases as a starting template** (Setup, Foundation, Features, Polish). These are a baseline \u2014 adapt them to your project's actual scope and complexity.
|
|
2981
|
+
- **For larger projects, add more phases** by editing \`.arcbridge/plan/phases.yaml\` and running \`arcbridge_reindex\`. Split "Core Features" into multiple phases if needed (e.g., "Phase 2: Auth & Users", "Phase 3: Core Business Logic", "Phase 4: Integrations", "Phase 5: Polish").
|
|
2982
|
+
- **Phase 0-1 tasks are ready to use** \u2014 they cover setup and foundation for this template
|
|
2983
|
+
- **Phase 2+ tasks are examples** \u2014 replace them with real tasks derived from the project's requirements and specifications
|
|
2984
|
+
- Review the phase plan with \`arcbridge_get_phase_plan\` and replace example tasks with project-specific ones
|
|
2985
|
+
- Create tasks using \`arcbridge_create_task\` with the phase ID shown in the plan
|
|
2986
|
+
- Keep each phase reasonably scoped \u2014 3-6 tasks per phase is ideal
|
|
2987
|
+
- Map tasks to building blocks so drift detection tracks coverage
|
|
2988
|
+
- Link tasks to quality scenarios so gate checks are meaningful
|
|
2989
|
+
|
|
2649
2990
|
## Working Style
|
|
2650
2991
|
|
|
2651
2992
|
Think at the system level. Before making changes, consider:
|
|
@@ -2795,11 +3136,13 @@ function qualityGuardianTemplate() {
|
|
|
2795
3136
|
"arcbridge_search_symbols",
|
|
2796
3137
|
"arcbridge_get_symbol",
|
|
2797
3138
|
"arcbridge_get_component_graph",
|
|
2798
|
-
"arcbridge_get_boundary_analysis"
|
|
2799
|
-
|
|
3139
|
+
"arcbridge_get_boundary_analysis",
|
|
3140
|
+
"arcbridge_update_scenario_status",
|
|
3141
|
+
"arcbridge_verify_scenarios",
|
|
3142
|
+
"arcbridge_run_role_check"
|
|
2800
3143
|
],
|
|
2801
3144
|
denied_tools: [],
|
|
2802
|
-
read_only:
|
|
3145
|
+
read_only: false,
|
|
2803
3146
|
quality_focus: [
|
|
2804
3147
|
"performance",
|
|
2805
3148
|
"accessibility",
|
|
@@ -2830,6 +3173,23 @@ function qualityGuardianTemplate() {
|
|
|
2830
3173
|
- Every quality scenario with status "untested" needs attention
|
|
2831
3174
|
- Performance budgets are hard limits, not guidelines
|
|
2832
3175
|
|
|
3176
|
+
## Linking Tests to Quality Scenarios
|
|
3177
|
+
|
|
3178
|
+
Quality scenarios need \`linked_tests\` to be verifiable via \`arcbridge_verify_scenarios\`. To link tests:
|
|
3179
|
+
|
|
3180
|
+
1. Edit \`.arcbridge/arc42/10-quality-scenarios.yaml\`
|
|
3181
|
+
2. Add test file paths to the \`linked_tests\` array for each scenario:
|
|
3182
|
+
\`\`\`yaml
|
|
3183
|
+
- id: SEC-01
|
|
3184
|
+
linked_tests:
|
|
3185
|
+
- "src/__tests__/auth.test.ts"
|
|
3186
|
+
- "src/__tests__/middleware.test.ts"
|
|
3187
|
+
\`\`\`
|
|
3188
|
+
3. Use glob patterns for broader matching: \`"src/**/*.security.test.ts"\`
|
|
3189
|
+
4. After linking, run \`arcbridge_verify_scenarios\` to execute the tests and update scenario status
|
|
3190
|
+
|
|
3191
|
+
Without \`linked_tests\`, scenarios remain "untested" \u2014 so link tests early, ideally when creating the test file.
|
|
3192
|
+
|
|
2833
3193
|
## Review Checklist
|
|
2834
3194
|
|
|
2835
3195
|
1. **Performance:** Bundle size, LCP, API latency against defined budgets
|
|
@@ -2851,7 +3211,11 @@ function phaseManagerTemplate() {
|
|
|
2851
3211
|
"arcbridge_get_phase_plan",
|
|
2852
3212
|
"arcbridge_get_current_tasks",
|
|
2853
3213
|
"arcbridge_update_task",
|
|
3214
|
+
"arcbridge_delete_task",
|
|
3215
|
+
"arcbridge_create_task",
|
|
2854
3216
|
"arcbridge_check_drift",
|
|
3217
|
+
"arcbridge_verify_scenarios",
|
|
3218
|
+
"arcbridge_update_scenario_status",
|
|
2855
3219
|
"arcbridge_get_open_questions",
|
|
2856
3220
|
"arcbridge_propose_arc42_update"
|
|
2857
3221
|
],
|
|
@@ -2883,13 +3247,28 @@ function phaseManagerTemplate() {
|
|
|
2883
3247
|
- Tasks must be "done" before a phase can complete
|
|
2884
3248
|
- Quality scenarios linked to phase tasks must be verified
|
|
2885
3249
|
|
|
3250
|
+
## Task Planning
|
|
3251
|
+
|
|
3252
|
+
Before starting any phase, ensure proper task planning:
|
|
3253
|
+
- **ArcBridge generates 4 phases as a starting template.** For larger projects, add more phases by editing \`.arcbridge/plan/phases.yaml\` and running \`arcbridge_reindex\`.
|
|
3254
|
+
- **Phase 0-1 tasks are concrete** \u2014 they cover project setup and foundation. Follow them as-is.
|
|
3255
|
+
- **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.
|
|
3256
|
+
- **At project start, plan ALL phases** \u2014 review \`arcbridge_get_phase_plan\`, delete example tasks in Phase 2+ using \`arcbridge_delete_task\`, and create real tasks based on the product spec.
|
|
3257
|
+
- **Delete vs cancel** \u2014 use \`arcbridge_delete_task\` for example/template tasks that should be removed. Use \`arcbridge_update_task\` with status \`cancelled\` for planned tasks that turned out to be unnecessary (preserves the decision trail).
|
|
3258
|
+
- **Keep phases small and focused** \u2014 if a phase has more than 6-8 tasks, split it into sub-phases
|
|
3259
|
+
- **Tasks should be concrete and verifiable** \u2014 each task needs clear acceptance criteria
|
|
3260
|
+
- **Link tasks to building blocks** \u2014 this enables drift detection and progress tracking
|
|
3261
|
+
- **Use \`arcbridge_create_task\` with the phase ID** shown in \`arcbridge_get_phase_plan\` output (e.g., \`phase-2-features\`)
|
|
3262
|
+
|
|
2886
3263
|
## Phase Transition Process
|
|
2887
3264
|
|
|
2888
3265
|
1. Verify all tasks in current phase are "done"
|
|
2889
|
-
2.
|
|
2890
|
-
3.
|
|
2891
|
-
4.
|
|
2892
|
-
5.
|
|
3266
|
+
2. Review task coverage for the NEXT phase \u2014 create tasks if empty
|
|
3267
|
+
3. Run drift detection (\`arcbridge_check_drift\`)
|
|
3268
|
+
4. Propose arc42 updates if drift is detected
|
|
3269
|
+
5. Ensure quality scenarios have \`linked_tests\` \u2014 edit \`10-quality-scenarios.yaml\` to add test file paths so \`arcbridge_verify_scenarios\` can run them
|
|
3270
|
+
6. Check quality gate requirements
|
|
3271
|
+
7. Mark phase complete or report blockers`
|
|
2893
3272
|
};
|
|
2894
3273
|
}
|
|
2895
3274
|
|
|
@@ -3116,10 +3495,10 @@ var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite"]);
|
|
|
3116
3495
|
function writeAgentRole(dir, role) {
|
|
3117
3496
|
const { system_prompt, ...frontmatter } = role;
|
|
3118
3497
|
const content = matter2.stringify(system_prompt, frontmatter);
|
|
3119
|
-
writeFileSync4(
|
|
3498
|
+
writeFileSync4(join6(dir, `${role.role_id}.md`), content, "utf-8");
|
|
3120
3499
|
}
|
|
3121
3500
|
function generateAgentRoles(targetDir, template) {
|
|
3122
|
-
const agentsDir =
|
|
3501
|
+
const agentsDir = join6(targetDir, ".arcbridge", "agents");
|
|
3123
3502
|
mkdirSync4(agentsDir, { recursive: true });
|
|
3124
3503
|
const roles = [
|
|
3125
3504
|
architectTemplate(),
|
|
@@ -3140,19 +3519,19 @@ function generateAgentRoles(targetDir, template) {
|
|
|
3140
3519
|
}
|
|
3141
3520
|
|
|
3142
3521
|
// src/generators/db-generator.ts
|
|
3143
|
-
import { join as
|
|
3144
|
-
import { existsSync as
|
|
3522
|
+
import { join as join7 } from "path";
|
|
3523
|
+
import { existsSync as existsSync3, readFileSync, readdirSync as readdirSync2, appendFileSync } from "fs";
|
|
3145
3524
|
import { parse } from "yaml";
|
|
3146
3525
|
import matter3 from "gray-matter";
|
|
3147
3526
|
function populateBuildingBlocks(db, targetDir) {
|
|
3148
3527
|
const warnings = [];
|
|
3149
|
-
const filePath =
|
|
3528
|
+
const filePath = join7(
|
|
3150
3529
|
targetDir,
|
|
3151
3530
|
".arcbridge",
|
|
3152
3531
|
"arc42",
|
|
3153
3532
|
"05-building-blocks.md"
|
|
3154
3533
|
);
|
|
3155
|
-
if (!
|
|
3534
|
+
if (!existsSync3(filePath)) {
|
|
3156
3535
|
warnings.push("Building blocks file not found, skipping");
|
|
3157
3536
|
return warnings;
|
|
3158
3537
|
}
|
|
@@ -3161,7 +3540,7 @@ function populateBuildingBlocks(db, targetDir) {
|
|
|
3161
3540
|
const result = BuildingBlocksFrontmatterSchema.safeParse(data);
|
|
3162
3541
|
if (!result.success) {
|
|
3163
3542
|
warnings.push(
|
|
3164
|
-
`Invalid building blocks frontmatter: ${result.error.issues.map((i) => i.message).join(", ")}`
|
|
3543
|
+
`Invalid building blocks frontmatter: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
|
|
3165
3544
|
);
|
|
3166
3545
|
return warnings;
|
|
3167
3546
|
}
|
|
@@ -3188,13 +3567,13 @@ function populateBuildingBlocks(db, targetDir) {
|
|
|
3188
3567
|
}
|
|
3189
3568
|
function populateQualityScenarios(db, targetDir) {
|
|
3190
3569
|
const warnings = [];
|
|
3191
|
-
const filePath =
|
|
3570
|
+
const filePath = join7(
|
|
3192
3571
|
targetDir,
|
|
3193
3572
|
".arcbridge",
|
|
3194
3573
|
"arc42",
|
|
3195
3574
|
"10-quality-scenarios.yaml"
|
|
3196
3575
|
);
|
|
3197
|
-
if (!
|
|
3576
|
+
if (!existsSync3(filePath)) {
|
|
3198
3577
|
warnings.push("Quality scenarios file not found, skipping");
|
|
3199
3578
|
return warnings;
|
|
3200
3579
|
}
|
|
@@ -3203,7 +3582,7 @@ function populateQualityScenarios(db, targetDir) {
|
|
|
3203
3582
|
const result = QualityScenariosFileSchema.safeParse(parsed);
|
|
3204
3583
|
if (!result.success) {
|
|
3205
3584
|
warnings.push(
|
|
3206
|
-
`Invalid quality scenarios: ${result.error.issues.map((i) => i.message).join(", ")}`
|
|
3585
|
+
`Invalid quality scenarios: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
|
|
3207
3586
|
);
|
|
3208
3587
|
return warnings;
|
|
3209
3588
|
}
|
|
@@ -3230,8 +3609,8 @@ function populateQualityScenarios(db, targetDir) {
|
|
|
3230
3609
|
}
|
|
3231
3610
|
function populatePhases(db, targetDir) {
|
|
3232
3611
|
const warnings = [];
|
|
3233
|
-
const phasesPath =
|
|
3234
|
-
if (!
|
|
3612
|
+
const phasesPath = join7(targetDir, ".arcbridge", "plan", "phases.yaml");
|
|
3613
|
+
if (!existsSync3(phasesPath)) {
|
|
3235
3614
|
warnings.push("Phases file not found, skipping");
|
|
3236
3615
|
return warnings;
|
|
3237
3616
|
}
|
|
@@ -3240,7 +3619,7 @@ function populatePhases(db, targetDir) {
|
|
|
3240
3619
|
const result = PhasesFileSchema.safeParse(parsed);
|
|
3241
3620
|
if (!result.success) {
|
|
3242
3621
|
warnings.push(
|
|
3243
|
-
`Invalid phases file: ${result.error.issues.map((i) => i.message).join(", ")}`
|
|
3622
|
+
`Invalid phases file: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
|
|
3244
3623
|
);
|
|
3245
3624
|
return warnings;
|
|
3246
3625
|
}
|
|
@@ -3264,20 +3643,20 @@ function populatePhases(db, targetDir) {
|
|
|
3264
3643
|
phase.started_at ?? null,
|
|
3265
3644
|
phase.completed_at ?? null
|
|
3266
3645
|
);
|
|
3267
|
-
const taskPath =
|
|
3646
|
+
const taskPath = join7(
|
|
3268
3647
|
targetDir,
|
|
3269
3648
|
".arcbridge",
|
|
3270
3649
|
"plan",
|
|
3271
3650
|
"tasks",
|
|
3272
3651
|
`${phase.id}.yaml`
|
|
3273
3652
|
);
|
|
3274
|
-
if (!
|
|
3653
|
+
if (!existsSync3(taskPath)) continue;
|
|
3275
3654
|
const taskRaw = readFileSync(taskPath, "utf-8");
|
|
3276
3655
|
const taskParsed = parse(taskRaw);
|
|
3277
3656
|
const taskResult = TaskFileSchema.safeParse(taskParsed);
|
|
3278
3657
|
if (!taskResult.success) {
|
|
3279
3658
|
warnings.push(
|
|
3280
|
-
`Invalid task file for ${phase.id}: ${taskResult.error.issues.map((i) => i.message).join(", ")}`
|
|
3659
|
+
`Invalid task file for ${phase.id}: ${taskResult.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
|
|
3281
3660
|
);
|
|
3282
3661
|
continue;
|
|
3283
3662
|
}
|
|
@@ -3299,27 +3678,27 @@ function populatePhases(db, targetDir) {
|
|
|
3299
3678
|
}
|
|
3300
3679
|
function populateAdrs(db, targetDir) {
|
|
3301
3680
|
const warnings = [];
|
|
3302
|
-
const decisionsDir =
|
|
3681
|
+
const decisionsDir = join7(
|
|
3303
3682
|
targetDir,
|
|
3304
3683
|
".arcbridge",
|
|
3305
3684
|
"arc42",
|
|
3306
3685
|
"09-decisions"
|
|
3307
3686
|
);
|
|
3308
|
-
if (!
|
|
3687
|
+
if (!existsSync3(decisionsDir)) {
|
|
3309
3688
|
return warnings;
|
|
3310
3689
|
}
|
|
3311
|
-
const files =
|
|
3690
|
+
const files = readdirSync2(decisionsDir).filter((f) => f.endsWith(".md"));
|
|
3312
3691
|
const insert = db.prepare(`
|
|
3313
3692
|
INSERT OR IGNORE INTO adrs (id, title, status, date, context, decision, consequences, affected_blocks, affected_files, quality_scenarios)
|
|
3314
3693
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3315
3694
|
`);
|
|
3316
3695
|
for (const file of files) {
|
|
3317
|
-
const raw = readFileSync(
|
|
3696
|
+
const raw = readFileSync(join7(decisionsDir, file), "utf-8");
|
|
3318
3697
|
const { data, content } = matter3(raw);
|
|
3319
3698
|
const result = AdrFrontmatterSchema.safeParse(data);
|
|
3320
3699
|
if (!result.success) {
|
|
3321
3700
|
warnings.push(
|
|
3322
|
-
`Invalid ADR ${file}: ${result.error.issues.map((i) => i.message).join(", ")}`
|
|
3701
|
+
`Invalid ADR ${file}: ${result.error.issues.map((i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message).join(", ")}`
|
|
3323
3702
|
);
|
|
3324
3703
|
continue;
|
|
3325
3704
|
}
|
|
@@ -3353,6 +3732,7 @@ function refreshFromDocs(db, targetDir) {
|
|
|
3353
3732
|
const scenarioStatusMap = new Map(
|
|
3354
3733
|
existingScenarios.map((s) => [s.id, s.status])
|
|
3355
3734
|
);
|
|
3735
|
+
db.exec("PRAGMA foreign_keys = OFF");
|
|
3356
3736
|
const refresh = () => transaction(db, () => {
|
|
3357
3737
|
db.prepare("DELETE FROM tasks").run();
|
|
3358
3738
|
db.prepare("DELETE FROM phases").run();
|
|
@@ -3365,6 +3745,16 @@ function refreshFromDocs(db, targetDir) {
|
|
|
3365
3745
|
warnings.push(...populateQualityScenarios(db, targetDir));
|
|
3366
3746
|
warnings.push(...populatePhases(db, targetDir));
|
|
3367
3747
|
warnings.push(...populateAdrs(db, targetDir));
|
|
3748
|
+
const orphaned = db.prepare(`
|
|
3749
|
+
UPDATE tasks SET building_block = NULL
|
|
3750
|
+
WHERE building_block IS NOT NULL
|
|
3751
|
+
AND building_block NOT IN (SELECT id FROM building_blocks)
|
|
3752
|
+
`).run();
|
|
3753
|
+
if (orphaned.changes > 0) {
|
|
3754
|
+
warnings.push(
|
|
3755
|
+
`${orphaned.changes} task(s) referenced removed building blocks (references cleared)`
|
|
3756
|
+
);
|
|
3757
|
+
}
|
|
3368
3758
|
const restoreTask = db.prepare(
|
|
3369
3759
|
"UPDATE tasks SET status = ?, completed_at = ? WHERE id = ?"
|
|
3370
3760
|
);
|
|
@@ -3390,11 +3780,15 @@ function refreshFromDocs(db, targetDir) {
|
|
|
3390
3780
|
}
|
|
3391
3781
|
}
|
|
3392
3782
|
});
|
|
3393
|
-
|
|
3783
|
+
try {
|
|
3784
|
+
refresh();
|
|
3785
|
+
} finally {
|
|
3786
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
3787
|
+
}
|
|
3394
3788
|
return warnings;
|
|
3395
3789
|
}
|
|
3396
3790
|
function generateDatabase(targetDir, input) {
|
|
3397
|
-
const dbPath =
|
|
3791
|
+
const dbPath = join7(targetDir, ".arcbridge", "index.db");
|
|
3398
3792
|
const db = openDatabase(dbPath);
|
|
3399
3793
|
initializeSchema(db);
|
|
3400
3794
|
const allWarnings = [];
|
|
@@ -3414,9 +3808,9 @@ function generateDatabase(targetDir, input) {
|
|
|
3414
3808
|
return { db, warnings: allWarnings };
|
|
3415
3809
|
}
|
|
3416
3810
|
function ensureGitignore(targetDir) {
|
|
3417
|
-
const gitignorePath =
|
|
3811
|
+
const gitignorePath = join7(targetDir, ".gitignore");
|
|
3418
3812
|
const marker = ".arcbridge/index.db";
|
|
3419
|
-
if (
|
|
3813
|
+
if (existsSync3(gitignorePath)) {
|
|
3420
3814
|
const content = readFileSync(gitignorePath, "utf-8");
|
|
3421
3815
|
if (content.includes(marker)) return;
|
|
3422
3816
|
}
|
|
@@ -3430,14 +3824,14 @@ function ensureGitignore(targetDir) {
|
|
|
3430
3824
|
}
|
|
3431
3825
|
|
|
3432
3826
|
// src/indexer/index.ts
|
|
3433
|
-
import { relative as relative5, join as
|
|
3434
|
-
import { existsSync as
|
|
3827
|
+
import { relative as relative5, join as join14 } from "path";
|
|
3828
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
3435
3829
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3436
3830
|
import YAML from "yaml";
|
|
3437
3831
|
|
|
3438
3832
|
// src/indexer/program.ts
|
|
3439
3833
|
import ts from "typescript";
|
|
3440
|
-
import { join as
|
|
3834
|
+
import { join as join8, dirname } from "path";
|
|
3441
3835
|
function createTsProgram(options) {
|
|
3442
3836
|
const projectRoot = options.projectRoot;
|
|
3443
3837
|
const configPath = options.tsconfigPath ?? ts.findConfigFile(projectRoot, ts.sys.fileExists, "tsconfig.json");
|
|
@@ -3446,7 +3840,7 @@ function createTsProgram(options) {
|
|
|
3446
3840
|
`No tsconfig.json found in ${projectRoot}. TypeScript indexing requires a tsconfig.json.`
|
|
3447
3841
|
);
|
|
3448
3842
|
}
|
|
3449
|
-
|
|
3843
|
+
let configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
3450
3844
|
if (configFile.error) {
|
|
3451
3845
|
const message = ts.flattenDiagnosticMessageText(
|
|
3452
3846
|
configFile.error.messageText,
|
|
@@ -3454,12 +3848,45 @@ function createTsProgram(options) {
|
|
|
3454
3848
|
);
|
|
3455
3849
|
throw new Error(`Failed to read tsconfig.json: ${message}`);
|
|
3456
3850
|
}
|
|
3851
|
+
let resolvedConfigPath = configPath;
|
|
3852
|
+
const config = configFile.config;
|
|
3853
|
+
const hasOwnFiles = config.include && config.include.length > 0 || config.files && config.files.length > 0;
|
|
3854
|
+
if (config.references && !hasOwnFiles) {
|
|
3855
|
+
for (const ref of config.references) {
|
|
3856
|
+
const refRelPath = typeof ref === "string" ? ref : ref.path;
|
|
3857
|
+
if (!refRelPath) continue;
|
|
3858
|
+
const refFullPath = join8(dirname(configPath), refRelPath);
|
|
3859
|
+
const refConfigPath = refFullPath.endsWith(".json") ? refFullPath : join8(refFullPath, "tsconfig.json");
|
|
3860
|
+
if (ts.sys.fileExists(refConfigPath)) {
|
|
3861
|
+
const refConfig = ts.readConfigFile(refConfigPath, ts.sys.readFile);
|
|
3862
|
+
const rc = refConfig.config;
|
|
3863
|
+
if (!refConfig.error && (rc.include?.length > 0 || rc.files?.length > 0)) {
|
|
3864
|
+
configFile = refConfig;
|
|
3865
|
+
resolvedConfigPath = refConfigPath;
|
|
3866
|
+
break;
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
if (resolvedConfigPath === configPath) {
|
|
3871
|
+
for (const candidate of ["tsconfig.app.json", "tsconfig.src.json"]) {
|
|
3872
|
+
const refPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, candidate);
|
|
3873
|
+
if (refPath) {
|
|
3874
|
+
const refConfig = ts.readConfigFile(refPath, ts.sys.readFile);
|
|
3875
|
+
if (!refConfig.error) {
|
|
3876
|
+
configFile = refConfig;
|
|
3877
|
+
resolvedConfigPath = refPath;
|
|
3878
|
+
break;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3457
3884
|
const parsed = ts.parseJsonConfigFileContent(
|
|
3458
3885
|
configFile.config,
|
|
3459
3886
|
ts.sys,
|
|
3460
|
-
|
|
3887
|
+
join8(projectRoot),
|
|
3461
3888
|
{ noEmit: true },
|
|
3462
|
-
|
|
3889
|
+
resolvedConfigPath
|
|
3463
3890
|
);
|
|
3464
3891
|
const program = ts.createProgram({
|
|
3465
3892
|
rootNames: parsed.fileNames,
|
|
@@ -3467,7 +3894,7 @@ function createTsProgram(options) {
|
|
|
3467
3894
|
});
|
|
3468
3895
|
const checker = program.getTypeChecker();
|
|
3469
3896
|
const sourceFiles = program.getSourceFiles().filter(
|
|
3470
|
-
(sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules")
|
|
3897
|
+
(sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules") && !sf.fileName.includes(".next/") && !sf.fileName.includes(".next\\")
|
|
3471
3898
|
);
|
|
3472
3899
|
return { program, checker, sourceFiles, projectRoot };
|
|
3473
3900
|
}
|
|
@@ -4225,8 +4652,8 @@ function writeComponents(db, components) {
|
|
|
4225
4652
|
}
|
|
4226
4653
|
|
|
4227
4654
|
// src/indexer/route-analyzer.ts
|
|
4228
|
-
import { existsSync as
|
|
4229
|
-
import { join as
|
|
4655
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync } from "fs";
|
|
4656
|
+
import { join as join9, relative as relative3 } from "path";
|
|
4230
4657
|
var FILE_KIND_MAP = {
|
|
4231
4658
|
"page": "page",
|
|
4232
4659
|
"layout": "layout",
|
|
@@ -4237,15 +4664,22 @@ var FILE_KIND_MAP = {
|
|
|
4237
4664
|
};
|
|
4238
4665
|
var TS_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"];
|
|
4239
4666
|
function analyzeRoutes(projectRoot, db, service = "main") {
|
|
4240
|
-
|
|
4241
|
-
|
|
4667
|
+
let appDir = join9(projectRoot, "app");
|
|
4668
|
+
try {
|
|
4669
|
+
if (!existsSync4(appDir) || !statSync(appDir).isDirectory()) {
|
|
4670
|
+
appDir = join9(projectRoot, "src", "app");
|
|
4671
|
+
if (!existsSync4(appDir) || !statSync(appDir).isDirectory()) {
|
|
4672
|
+
return 0;
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
} catch {
|
|
4242
4676
|
return 0;
|
|
4243
4677
|
}
|
|
4244
4678
|
const routes = [];
|
|
4245
4679
|
const layoutStack = [];
|
|
4246
4680
|
for (const ext of TS_EXTENSIONS) {
|
|
4247
|
-
const middlewarePath =
|
|
4248
|
-
if (
|
|
4681
|
+
const middlewarePath = join9(projectRoot, `middleware${ext}`);
|
|
4682
|
+
if (existsSync4(middlewarePath)) {
|
|
4249
4683
|
routes.push({
|
|
4250
4684
|
id: `route::middleware`,
|
|
4251
4685
|
routePath: "/",
|
|
@@ -4265,15 +4699,15 @@ function analyzeRoutes(projectRoot, db, service = "main") {
|
|
|
4265
4699
|
function walkAppDir(dir, routePath, routes, layoutStack, service, projectRoot) {
|
|
4266
4700
|
let entries;
|
|
4267
4701
|
try {
|
|
4268
|
-
entries =
|
|
4702
|
+
entries = readdirSync3(dir);
|
|
4269
4703
|
} catch {
|
|
4270
4704
|
return;
|
|
4271
4705
|
}
|
|
4272
4706
|
const currentLayout = layoutStack.length > 0 ? layoutStack[layoutStack.length - 1] : null;
|
|
4273
4707
|
for (const [convention, kind] of Object.entries(FILE_KIND_MAP)) {
|
|
4274
4708
|
for (const ext of TS_EXTENSIONS) {
|
|
4275
|
-
const filePath =
|
|
4276
|
-
if (
|
|
4709
|
+
const filePath = join9(dir, `${convention}${ext}`);
|
|
4710
|
+
if (existsSync4(filePath)) {
|
|
4277
4711
|
const relPath = relative3(projectRoot, dir);
|
|
4278
4712
|
const routeId = `route::${relPath}/${convention}`;
|
|
4279
4713
|
const route = {
|
|
@@ -4297,7 +4731,7 @@ function walkAppDir(dir, routePath, routes, layoutStack, service, projectRoot) {
|
|
|
4297
4731
|
}
|
|
4298
4732
|
}
|
|
4299
4733
|
for (const entry of entries.sort()) {
|
|
4300
|
-
const fullPath =
|
|
4734
|
+
const fullPath = join9(dir, entry);
|
|
4301
4735
|
try {
|
|
4302
4736
|
if (!statSync(fullPath).isDirectory()) continue;
|
|
4303
4737
|
} catch {
|
|
@@ -4463,16 +4897,16 @@ function writeDependencies(db, dependencies) {
|
|
|
4463
4897
|
|
|
4464
4898
|
// src/indexer/dotnet-indexer.ts
|
|
4465
4899
|
import { execFileSync } from "child_process";
|
|
4466
|
-
import { resolve, join as
|
|
4467
|
-
import { readdirSync as
|
|
4900
|
+
import { resolve, join as join10, dirname as dirname2, relative as relative4, basename } from "path";
|
|
4901
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync3, existsSync as existsSync5, accessSync, constants } from "fs";
|
|
4468
4902
|
import { fileURLToPath } from "url";
|
|
4469
4903
|
function findDotnetProject(projectRoot) {
|
|
4470
4904
|
try {
|
|
4471
|
-
const entries =
|
|
4905
|
+
const entries = readdirSync4(projectRoot);
|
|
4472
4906
|
const sln = entries.find((e) => e.endsWith(".sln"));
|
|
4473
|
-
if (sln) return
|
|
4907
|
+
if (sln) return join10(projectRoot, sln);
|
|
4474
4908
|
const csproj = entries.find((e) => e.endsWith(".csproj"));
|
|
4475
|
-
if (csproj) return
|
|
4909
|
+
if (csproj) return join10(projectRoot, csproj);
|
|
4476
4910
|
return null;
|
|
4477
4911
|
} catch {
|
|
4478
4912
|
return null;
|
|
@@ -4480,16 +4914,16 @@ function findDotnetProject(projectRoot) {
|
|
|
4480
4914
|
}
|
|
4481
4915
|
function parseSolutionProjects(slnPath) {
|
|
4482
4916
|
const content = readFileSync3(slnPath, "utf-8");
|
|
4483
|
-
const slnDir =
|
|
4917
|
+
const slnDir = dirname2(slnPath);
|
|
4484
4918
|
const projects = [];
|
|
4485
4919
|
const projectPattern = /Project\("\{[^}]+\}"\)\s*=\s*"([^"]+)",\s*"([^"]+\.csproj)"/g;
|
|
4486
4920
|
let match;
|
|
4487
4921
|
while ((match = projectPattern.exec(content)) !== null) {
|
|
4488
4922
|
const name = match[1];
|
|
4489
4923
|
const relativeCsprojPath = match[2].replace(/\\/g, "/");
|
|
4490
|
-
const fullCsprojPath = resolve(
|
|
4491
|
-
if (!
|
|
4492
|
-
const projectDir = relative4(slnDir,
|
|
4924
|
+
const fullCsprojPath = resolve(join10(slnDir, relativeCsprojPath));
|
|
4925
|
+
if (!existsSync5(fullCsprojPath)) continue;
|
|
4926
|
+
const projectDir = relative4(slnDir, dirname2(fullCsprojPath)).replace(/\\/g, "/") || ".";
|
|
4493
4927
|
const isTestProject = /[.\x2d]tests?$/i.test(name) || /[.\x2d](unit|integration|functional|e2e)tests?$/i.test(name);
|
|
4494
4928
|
projects.push({
|
|
4495
4929
|
name,
|
|
@@ -4524,7 +4958,7 @@ function hasGlobalTool() {
|
|
|
4524
4958
|
if (!dir) continue;
|
|
4525
4959
|
for (const ext of extensions) {
|
|
4526
4960
|
try {
|
|
4527
|
-
accessSync(
|
|
4961
|
+
accessSync(join10(dir, `${name}${ext}`), constants.X_OK);
|
|
4528
4962
|
return true;
|
|
4529
4963
|
} catch {
|
|
4530
4964
|
continue;
|
|
@@ -4534,14 +4968,14 @@ function hasGlobalTool() {
|
|
|
4534
4968
|
return false;
|
|
4535
4969
|
}
|
|
4536
4970
|
function resolveIndexerProject() {
|
|
4537
|
-
const currentDir2 =
|
|
4971
|
+
const currentDir2 = dirname2(fileURLToPath(import.meta.url));
|
|
4538
4972
|
const candidates = [
|
|
4539
4973
|
resolve(currentDir2, "../../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
|
|
4540
4974
|
resolve(currentDir2, "../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
|
|
4541
4975
|
resolve(currentDir2, "../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj")
|
|
4542
4976
|
];
|
|
4543
4977
|
for (const candidate of candidates) {
|
|
4544
|
-
if (
|
|
4978
|
+
if (existsSync5(candidate)) return candidate;
|
|
4545
4979
|
}
|
|
4546
4980
|
return null;
|
|
4547
4981
|
}
|
|
@@ -4683,15 +5117,15 @@ function indexDotnetProjectRoslyn(db, options) {
|
|
|
4683
5117
|
|
|
4684
5118
|
// src/indexer/csharp/indexer.ts
|
|
4685
5119
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4686
|
-
import { join as
|
|
5120
|
+
import { join as join11 } from "path";
|
|
4687
5121
|
import { globbySync } from "globby";
|
|
4688
5122
|
|
|
4689
5123
|
// src/indexer/csharp/parser.ts
|
|
4690
5124
|
import { accessSync as accessSync2, constants as constants2 } from "fs";
|
|
4691
|
-
import { dirname as
|
|
5125
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
4692
5126
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4693
5127
|
import "web-tree-sitter";
|
|
4694
|
-
var currentDir =
|
|
5128
|
+
var currentDir = dirname3(fileURLToPath2(import.meta.url));
|
|
4695
5129
|
var cachedParser = null;
|
|
4696
5130
|
var initPromise = null;
|
|
4697
5131
|
function resolveGrammarPath() {
|
|
@@ -5535,7 +5969,7 @@ async function indexCSharpTreeSitter(db, options) {
|
|
|
5535
5969
|
for (const filePath of csFiles) {
|
|
5536
5970
|
const relPath = filePath.replace(/\\/g, "/");
|
|
5537
5971
|
currentPaths.add(relPath);
|
|
5538
|
-
const fullPath =
|
|
5972
|
+
const fullPath = join11(projectRoot, relPath);
|
|
5539
5973
|
const content = readFileSync4(fullPath, "utf-8");
|
|
5540
5974
|
const hash = hashContent(content);
|
|
5541
5975
|
const tree = parseCSharp(content);
|
|
@@ -5611,12 +6045,12 @@ async function indexCSharpTreeSitter(db, options) {
|
|
|
5611
6045
|
}
|
|
5612
6046
|
|
|
5613
6047
|
// src/indexer/package-deps.ts
|
|
5614
|
-
import { join as
|
|
5615
|
-
import { existsSync as
|
|
6048
|
+
import { join as join12 } from "path";
|
|
6049
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5 } from "fs";
|
|
5616
6050
|
function indexPackageDependencies(db, projectRoot, service = "main") {
|
|
5617
6051
|
const deps = [];
|
|
5618
|
-
const pkgJsonPath =
|
|
5619
|
-
if (
|
|
6052
|
+
const pkgJsonPath = join12(projectRoot, "package.json");
|
|
6053
|
+
if (existsSync6(pkgJsonPath)) {
|
|
5620
6054
|
deps.push(...parsePackageJson(pkgJsonPath));
|
|
5621
6055
|
}
|
|
5622
6056
|
const csprojFiles = findCsprojFiles(projectRoot);
|
|
@@ -5678,10 +6112,10 @@ function findCsprojFiles(dir, maxDepth = 4) {
|
|
|
5678
6112
|
function walk(currentDir2, depth) {
|
|
5679
6113
|
if (depth > maxDepth) return;
|
|
5680
6114
|
try {
|
|
5681
|
-
const entries =
|
|
6115
|
+
const entries = readdirSync5(currentDir2, { withFileTypes: true });
|
|
5682
6116
|
for (const entry of entries) {
|
|
5683
6117
|
if (entry.name === "bin" || entry.name === "obj" || entry.name === "node_modules" || entry.name === ".git") continue;
|
|
5684
|
-
const fullPath =
|
|
6118
|
+
const fullPath = join12(currentDir2, entry.name);
|
|
5685
6119
|
if (entry.isFile() && entry.name.endsWith(".csproj")) {
|
|
5686
6120
|
results.push(fullPath);
|
|
5687
6121
|
} else if (entry.isDirectory()) {
|
|
@@ -5697,10 +6131,10 @@ function findCsprojFiles(dir, maxDepth = 4) {
|
|
|
5697
6131
|
|
|
5698
6132
|
// src/config/loader.ts
|
|
5699
6133
|
import { readFileSync as readFileSync6 } from "fs";
|
|
5700
|
-
import { join as
|
|
6134
|
+
import { join as join13 } from "path";
|
|
5701
6135
|
import yaml from "yaml";
|
|
5702
6136
|
function loadConfig(projectRoot) {
|
|
5703
|
-
const configPath =
|
|
6137
|
+
const configPath = join13(projectRoot, ".arcbridge", "config.yaml");
|
|
5704
6138
|
try {
|
|
5705
6139
|
const raw = readFileSync6(configPath, "utf-8");
|
|
5706
6140
|
const parsed = ArcBridgeConfigSchema.safeParse(yaml.parse(raw));
|
|
@@ -5719,8 +6153,8 @@ function loadConfig(projectRoot) {
|
|
|
5719
6153
|
|
|
5720
6154
|
// src/indexer/index.ts
|
|
5721
6155
|
function detectProjectLanguage(projectRoot) {
|
|
5722
|
-
if (
|
|
5723
|
-
if (
|
|
6156
|
+
if (existsSync7(join14(projectRoot, "tsconfig.json"))) return "typescript";
|
|
6157
|
+
if (existsSync7(join14(projectRoot, "package.json"))) return "typescript";
|
|
5724
6158
|
if (findDotnetProject(projectRoot)) return "csharp";
|
|
5725
6159
|
return "typescript";
|
|
5726
6160
|
}
|
|
@@ -5748,7 +6182,7 @@ function resolveCSharpBackend(projectRoot) {
|
|
|
5748
6182
|
let setting = config?.indexing?.csharp_indexer;
|
|
5749
6183
|
if (!setting && error) {
|
|
5750
6184
|
try {
|
|
5751
|
-
const raw = readFileSync7(
|
|
6185
|
+
const raw = readFileSync7(join14(projectRoot, ".arcbridge", "config.yaml"), "utf-8");
|
|
5752
6186
|
const parsed = YAML.parse(raw);
|
|
5753
6187
|
const rawSetting = parsed?.indexing?.csharp_indexer;
|
|
5754
6188
|
if (rawSetting === "roslyn" || rawSetting === "tree-sitter") {
|
|
@@ -6047,7 +6481,17 @@ function detectNewDependencies(db, entries) {
|
|
|
6047
6481
|
const adrs = db.prepare("SELECT id, title, context, decision FROM adrs WHERE status != 'superseded'").all();
|
|
6048
6482
|
const adrText = adrs.map((a) => `${a.title} ${a.context ?? ""} ${a.decision ?? ""}`.toLowerCase()).join(" ");
|
|
6049
6483
|
const trivialPackages = /* @__PURE__ */ new Set([
|
|
6050
|
-
// npm
|
|
6484
|
+
// npm — frameworks (core deps that don't need ADRs)
|
|
6485
|
+
"react",
|
|
6486
|
+
"react-dom",
|
|
6487
|
+
"next",
|
|
6488
|
+
"vite",
|
|
6489
|
+
"@vitejs/plugin-react",
|
|
6490
|
+
"express",
|
|
6491
|
+
"fastify",
|
|
6492
|
+
"hono",
|
|
6493
|
+
"koa",
|
|
6494
|
+
// npm — dev tooling
|
|
6051
6495
|
"typescript",
|
|
6052
6496
|
"eslint",
|
|
6053
6497
|
"prettier",
|
|
@@ -6055,15 +6499,28 @@ function detectNewDependencies(db, entries) {
|
|
|
6055
6499
|
"jest",
|
|
6056
6500
|
"@types/node",
|
|
6057
6501
|
"@types/react",
|
|
6502
|
+
"@types/react-dom",
|
|
6058
6503
|
"tsup",
|
|
6059
6504
|
"tsx",
|
|
6060
|
-
|
|
6505
|
+
"ts-node",
|
|
6506
|
+
"nodemon",
|
|
6507
|
+
"@eslint/js",
|
|
6508
|
+
"typescript-eslint",
|
|
6509
|
+
// npm — build/bundler
|
|
6510
|
+
"esbuild",
|
|
6511
|
+
"rollup",
|
|
6512
|
+
"webpack",
|
|
6513
|
+
"postcss",
|
|
6514
|
+
"tailwindcss",
|
|
6515
|
+
"autoprefixer",
|
|
6516
|
+
// nuget — test
|
|
6061
6517
|
"microsoft.net.test.sdk",
|
|
6062
6518
|
"xunit",
|
|
6063
6519
|
"xunit.runner.visualstudio",
|
|
6064
6520
|
"nunit",
|
|
6065
6521
|
"nunit3testadapter",
|
|
6066
6522
|
"coverlet.collector",
|
|
6523
|
+
// nuget — framework
|
|
6067
6524
|
"microsoft.aspnetcore.openapi",
|
|
6068
6525
|
"swashbuckle.aspnetcore"
|
|
6069
6526
|
]);
|
|
@@ -6101,18 +6558,18 @@ function safeParseJson(value, fallback) {
|
|
|
6101
6558
|
}
|
|
6102
6559
|
|
|
6103
6560
|
// src/sync/yaml-writer.ts
|
|
6104
|
-
import { join as
|
|
6105
|
-
import { existsSync as
|
|
6561
|
+
import { join as join15 } from "path";
|
|
6562
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
|
|
6106
6563
|
import { parse as parse2, stringify as stringify4 } from "yaml";
|
|
6107
6564
|
function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
|
|
6108
|
-
const taskPath =
|
|
6565
|
+
const taskPath = join15(
|
|
6109
6566
|
projectRoot,
|
|
6110
6567
|
".arcbridge",
|
|
6111
6568
|
"plan",
|
|
6112
6569
|
"tasks",
|
|
6113
6570
|
`${phaseId}.yaml`
|
|
6114
6571
|
);
|
|
6115
|
-
if (!
|
|
6572
|
+
if (!existsSync8(taskPath)) return;
|
|
6116
6573
|
const raw = readFileSync8(taskPath, "utf-8");
|
|
6117
6574
|
const parsed = parse2(raw);
|
|
6118
6575
|
const result = TaskFileSchema.safeParse(parsed);
|
|
@@ -6129,11 +6586,11 @@ function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
|
|
|
6129
6586
|
writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
|
|
6130
6587
|
}
|
|
6131
6588
|
function addTaskToYaml(projectRoot, phaseId, task) {
|
|
6132
|
-
const tasksDir =
|
|
6133
|
-
const taskPath =
|
|
6589
|
+
const tasksDir = join15(projectRoot, ".arcbridge", "plan", "tasks");
|
|
6590
|
+
const taskPath = join15(tasksDir, `${phaseId}.yaml`);
|
|
6134
6591
|
mkdirSync5(tasksDir, { recursive: true });
|
|
6135
6592
|
let taskFile;
|
|
6136
|
-
if (
|
|
6593
|
+
if (existsSync8(taskPath)) {
|
|
6137
6594
|
const raw = readFileSync8(taskPath, "utf-8");
|
|
6138
6595
|
const parsed = parse2(raw);
|
|
6139
6596
|
const result = TaskFileSchema.safeParse(parsed);
|
|
@@ -6151,13 +6608,13 @@ function addTaskToYaml(projectRoot, phaseId, task) {
|
|
|
6151
6608
|
writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
|
|
6152
6609
|
}
|
|
6153
6610
|
function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
|
|
6154
|
-
const phasesPath =
|
|
6611
|
+
const phasesPath = join15(
|
|
6155
6612
|
projectRoot,
|
|
6156
6613
|
".arcbridge",
|
|
6157
6614
|
"plan",
|
|
6158
6615
|
"phases.yaml"
|
|
6159
6616
|
);
|
|
6160
|
-
if (!
|
|
6617
|
+
if (!existsSync8(phasesPath)) return;
|
|
6161
6618
|
const raw = readFileSync8(phasesPath, "utf-8");
|
|
6162
6619
|
const parsed = parse2(raw);
|
|
6163
6620
|
const result = PhasesFileSchema.safeParse(parsed);
|
|
@@ -6170,14 +6627,14 @@ function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
|
|
|
6170
6627
|
if (completedAt) phase.completed_at = completedAt;
|
|
6171
6628
|
writeFileSync5(phasesPath, stringify4(phasesFile), "utf-8");
|
|
6172
6629
|
}
|
|
6173
|
-
function syncScenarioToYaml(projectRoot, scenarioId, status) {
|
|
6174
|
-
const scenarioPath =
|
|
6630
|
+
function syncScenarioToYaml(projectRoot, scenarioId, status, linkedTests, verification) {
|
|
6631
|
+
const scenarioPath = join15(
|
|
6175
6632
|
projectRoot,
|
|
6176
6633
|
".arcbridge",
|
|
6177
6634
|
"arc42",
|
|
6178
6635
|
"10-quality-scenarios.yaml"
|
|
6179
6636
|
);
|
|
6180
|
-
if (!
|
|
6637
|
+
if (!existsSync8(scenarioPath)) return;
|
|
6181
6638
|
const raw = readFileSync8(scenarioPath, "utf-8");
|
|
6182
6639
|
const parsed = parse2(raw);
|
|
6183
6640
|
const result = QualityScenariosFileSchema.safeParse(parsed);
|
|
@@ -6186,14 +6643,55 @@ function syncScenarioToYaml(projectRoot, scenarioId, status) {
|
|
|
6186
6643
|
const scenario = scenariosFile.scenarios.find((s) => s.id === scenarioId);
|
|
6187
6644
|
if (!scenario) return;
|
|
6188
6645
|
scenario.status = status;
|
|
6646
|
+
if (linkedTests) {
|
|
6647
|
+
scenario.linked_tests = linkedTests;
|
|
6648
|
+
}
|
|
6649
|
+
if (verification) {
|
|
6650
|
+
scenario.verification = verification;
|
|
6651
|
+
}
|
|
6189
6652
|
writeFileSync5(scenarioPath, stringify4(scenariosFile), "utf-8");
|
|
6190
6653
|
}
|
|
6654
|
+
function deleteTaskFromYaml(projectRoot, phaseId, taskId) {
|
|
6655
|
+
try {
|
|
6656
|
+
const taskPath = join15(
|
|
6657
|
+
projectRoot,
|
|
6658
|
+
".arcbridge",
|
|
6659
|
+
"plan",
|
|
6660
|
+
"tasks",
|
|
6661
|
+
`${phaseId}.yaml`
|
|
6662
|
+
);
|
|
6663
|
+
if (!existsSync8(taskPath)) {
|
|
6664
|
+
return { success: true };
|
|
6665
|
+
}
|
|
6666
|
+
const raw = readFileSync8(taskPath, "utf-8");
|
|
6667
|
+
const result = TaskFileSchema.safeParse(parse2(raw));
|
|
6668
|
+
if (!result.success) {
|
|
6669
|
+
return {
|
|
6670
|
+
success: false,
|
|
6671
|
+
warning: `Could not parse ${phaseId}.yaml \u2014 task may reappear after reindex`
|
|
6672
|
+
};
|
|
6673
|
+
}
|
|
6674
|
+
const taskFile = result.data;
|
|
6675
|
+
const before = taskFile.tasks.length;
|
|
6676
|
+
taskFile.tasks = taskFile.tasks.filter((t) => t.id !== taskId);
|
|
6677
|
+
if (taskFile.tasks.length === before) {
|
|
6678
|
+
return { success: true };
|
|
6679
|
+
}
|
|
6680
|
+
writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
|
|
6681
|
+
return { success: true };
|
|
6682
|
+
} catch (err) {
|
|
6683
|
+
return {
|
|
6684
|
+
success: false,
|
|
6685
|
+
warning: `YAML update failed: ${err instanceof Error ? err.message : String(err)} \u2014 task may reappear after reindex`
|
|
6686
|
+
};
|
|
6687
|
+
}
|
|
6688
|
+
}
|
|
6191
6689
|
|
|
6192
6690
|
// src/sync/task-inference.ts
|
|
6193
6691
|
function inferTaskStatuses(db, phaseId) {
|
|
6194
6692
|
const results = [];
|
|
6195
6693
|
const tasks = db.prepare(
|
|
6196
|
-
"SELECT id, title, status, building_block, quality_scenarios, acceptance_criteria FROM tasks WHERE phase_id = ? AND status
|
|
6694
|
+
"SELECT id, title, status, building_block, quality_scenarios, acceptance_criteria FROM tasks WHERE phase_id = ? AND status NOT IN ('done', 'cancelled')"
|
|
6197
6695
|
).all(phaseId);
|
|
6198
6696
|
for (const task of tasks) {
|
|
6199
6697
|
const inference = inferSingleTask(db, task);
|
|
@@ -6318,7 +6816,7 @@ function safeParseJson2(value, fallback) {
|
|
|
6318
6816
|
|
|
6319
6817
|
// src/generators/sync-generator.ts
|
|
6320
6818
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
6321
|
-
import { join as
|
|
6819
|
+
import { join as join16, dirname as dirname4 } from "path";
|
|
6322
6820
|
|
|
6323
6821
|
// src/templates/sync/claude-skill.ts
|
|
6324
6822
|
function claudeSkillTemplate(config) {
|
|
@@ -6496,21 +6994,21 @@ To enable automatic sync:
|
|
|
6496
6994
|
function generateSyncFiles(targetDir, config) {
|
|
6497
6995
|
const generated = [];
|
|
6498
6996
|
const action = githubActionTemplate(config);
|
|
6499
|
-
const actionPath =
|
|
6500
|
-
mkdirSync6(
|
|
6997
|
+
const actionPath = join16(targetDir, action.relativePath);
|
|
6998
|
+
mkdirSync6(dirname4(actionPath), { recursive: true });
|
|
6501
6999
|
writeFileSync6(actionPath, action.content, "utf-8");
|
|
6502
7000
|
generated.push(action.relativePath);
|
|
6503
7001
|
if (config.platforms.includes("claude")) {
|
|
6504
7002
|
const skill = claudeSkillTemplate(config);
|
|
6505
|
-
const skillPath =
|
|
6506
|
-
mkdirSync6(
|
|
7003
|
+
const skillPath = join16(targetDir, skill.relativePath);
|
|
7004
|
+
mkdirSync6(dirname4(skillPath), { recursive: true });
|
|
6507
7005
|
writeFileSync6(skillPath, skill.content, "utf-8");
|
|
6508
7006
|
generated.push(skill.relativePath);
|
|
6509
7007
|
}
|
|
6510
7008
|
if (config.platforms.includes("copilot")) {
|
|
6511
7009
|
const hook = copilotHookTemplate(config);
|
|
6512
|
-
const hookPath =
|
|
6513
|
-
mkdirSync6(
|
|
7010
|
+
const hookPath = join16(targetDir, hook.relativePath);
|
|
7011
|
+
mkdirSync6(dirname4(hookPath), { recursive: true });
|
|
6514
7012
|
writeFileSync6(hookPath, hook.content, "utf-8");
|
|
6515
7013
|
generated.push(hook.relativePath);
|
|
6516
7014
|
}
|
|
@@ -6519,7 +7017,7 @@ function generateSyncFiles(targetDir, config) {
|
|
|
6519
7017
|
|
|
6520
7018
|
// src/metrics/activity.ts
|
|
6521
7019
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
|
|
6522
|
-
import { join as
|
|
7020
|
+
import { join as join17 } from "path";
|
|
6523
7021
|
function insertActivity(db, params) {
|
|
6524
7022
|
const totalTokens = params.totalTokens ?? (params.inputTokens != null && params.outputTokens != null ? params.inputTokens + params.outputTokens : null);
|
|
6525
7023
|
const stmt = db.prepare(`
|
|
@@ -6677,7 +7175,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
|
|
|
6677
7175
|
});
|
|
6678
7176
|
const rows = result.rows;
|
|
6679
7177
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
6680
|
-
const dir =
|
|
7178
|
+
const dir = join17(projectRoot, ".arcbridge", "metrics");
|
|
6681
7179
|
mkdirSync7(dir, { recursive: true });
|
|
6682
7180
|
let content;
|
|
6683
7181
|
let filename;
|
|
@@ -6774,7 +7272,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
|
|
|
6774
7272
|
break;
|
|
6775
7273
|
}
|
|
6776
7274
|
}
|
|
6777
|
-
const filePath =
|
|
7275
|
+
const filePath = join17(dir, filename);
|
|
6778
7276
|
writeFileSync7(filePath, content, "utf-8");
|
|
6779
7277
|
return filePath;
|
|
6780
7278
|
}
|
|
@@ -6842,6 +7340,8 @@ function esc(val) {
|
|
|
6842
7340
|
|
|
6843
7341
|
// src/git/helpers.ts
|
|
6844
7342
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
7343
|
+
import { realpathSync } from "fs";
|
|
7344
|
+
import { relative as relative6 } from "path";
|
|
6845
7345
|
function resolveRef(projectRoot, since, db) {
|
|
6846
7346
|
switch (since) {
|
|
6847
7347
|
case "last-commit":
|
|
@@ -6866,6 +7366,7 @@ function resolveRef(projectRoot, since, db) {
|
|
|
6866
7366
|
}
|
|
6867
7367
|
}
|
|
6868
7368
|
function getChangedFiles(projectRoot, ref) {
|
|
7369
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6869
7370
|
try {
|
|
6870
7371
|
execFileSync3("git", ["rev-parse", "--verify", ref], {
|
|
6871
7372
|
cwd: projectRoot,
|
|
@@ -6878,16 +7379,24 @@ function getChangedFiles(projectRoot, ref) {
|
|
|
6878
7379
|
["diff", "--name-status", "--no-renames", ref, "HEAD"],
|
|
6879
7380
|
{ cwd: projectRoot, encoding: "utf-8", timeout: 1e4 }
|
|
6880
7381
|
).trim();
|
|
6881
|
-
if (
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
}
|
|
7382
|
+
if (output) {
|
|
7383
|
+
for (const line of output.split("\n")) {
|
|
7384
|
+
const [statusCode, ...pathParts] = line.split(" ");
|
|
7385
|
+
const path = pathParts.join(" ");
|
|
7386
|
+
byPath.set(path, { status: parseStatusCode(statusCode ?? "M"), path });
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
6888
7389
|
} catch {
|
|
6889
|
-
return getUncommittedChanges(projectRoot);
|
|
6890
7390
|
}
|
|
7391
|
+
for (const change of getUncommittedChanges(projectRoot)) {
|
|
7392
|
+
const existing = byPath.get(change.path);
|
|
7393
|
+
if (!existing) {
|
|
7394
|
+
byPath.set(change.path, change);
|
|
7395
|
+
} else if (change.status === "deleted") {
|
|
7396
|
+
byPath.set(change.path, change);
|
|
7397
|
+
}
|
|
7398
|
+
}
|
|
7399
|
+
return Array.from(byPath.values());
|
|
6891
7400
|
}
|
|
6892
7401
|
function getUncommittedChanges(projectRoot) {
|
|
6893
7402
|
try {
|
|
@@ -6895,11 +7404,12 @@ function getUncommittedChanges(projectRoot) {
|
|
|
6895
7404
|
"git",
|
|
6896
7405
|
["status", "--porcelain", "-uno"],
|
|
6897
7406
|
{ cwd: projectRoot, encoding: "utf-8", timeout: 5e3 }
|
|
6898
|
-
)
|
|
6899
|
-
|
|
6900
|
-
|
|
7407
|
+
);
|
|
7408
|
+
const lines = output.split("\n").filter((l) => l.length >= 3);
|
|
7409
|
+
if (lines.length === 0) return [];
|
|
7410
|
+
return lines.map((line) => {
|
|
6901
7411
|
const statusCode = line.slice(0, 2).trim();
|
|
6902
|
-
const path = line.slice(3);
|
|
7412
|
+
const path = line.slice(3).replace(/\r$/, "");
|
|
6903
7413
|
const status = statusCode === "D" ? "deleted" : statusCode === "A" ? "added" : "modified";
|
|
6904
7414
|
return { status, path };
|
|
6905
7415
|
});
|
|
@@ -6923,6 +7433,33 @@ function setSyncCommit(db, key, sha) {
|
|
|
6923
7433
|
"INSERT OR REPLACE INTO arcbridge_meta (key, value) VALUES (?, ?)"
|
|
6924
7434
|
).run(key, sha);
|
|
6925
7435
|
}
|
|
7436
|
+
function getRepoRoot(projectRoot) {
|
|
7437
|
+
try {
|
|
7438
|
+
return execFileSync3(
|
|
7439
|
+
"git",
|
|
7440
|
+
["rev-parse", "--show-toplevel"],
|
|
7441
|
+
{ cwd: projectRoot, encoding: "utf-8", timeout: 5e3 }
|
|
7442
|
+
).trim();
|
|
7443
|
+
} catch {
|
|
7444
|
+
return null;
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7447
|
+
function scopeToProject(changedFiles, projectRoot) {
|
|
7448
|
+
const repoRoot = getRepoRoot(projectRoot);
|
|
7449
|
+
if (!repoRoot) return changedFiles;
|
|
7450
|
+
let projectRel;
|
|
7451
|
+
try {
|
|
7452
|
+
projectRel = relative6(realpathSync(repoRoot), realpathSync(projectRoot));
|
|
7453
|
+
} catch {
|
|
7454
|
+
return changedFiles;
|
|
7455
|
+
}
|
|
7456
|
+
if (!projectRel || projectRel === "." || projectRel.startsWith("..")) return changedFiles;
|
|
7457
|
+
const prefix = projectRel.replace(/\\/g, "/");
|
|
7458
|
+
return changedFiles.filter((f) => f.path.startsWith(prefix + "/") || f.path === prefix).map((f) => ({
|
|
7459
|
+
...f,
|
|
7460
|
+
path: f.path.startsWith(prefix + "/") ? f.path.slice(prefix.length + 1) : f.path
|
|
7461
|
+
}));
|
|
7462
|
+
}
|
|
6926
7463
|
function parseStatusCode(code) {
|
|
6927
7464
|
switch (code.charAt(0)) {
|
|
6928
7465
|
case "A":
|
|
@@ -6938,7 +7475,7 @@ function parseStatusCode(code) {
|
|
|
6938
7475
|
|
|
6939
7476
|
// src/testing/runner.ts
|
|
6940
7477
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
6941
|
-
import { existsSync as
|
|
7478
|
+
import { existsSync as existsSync9 } from "fs";
|
|
6942
7479
|
import { resolve as resolve3 } from "path";
|
|
6943
7480
|
function verifyScenarios(db, projectRoot, options) {
|
|
6944
7481
|
const results = [];
|
|
@@ -6976,7 +7513,7 @@ function verifyScenarios(db, projectRoot, options) {
|
|
|
6976
7513
|
}
|
|
6977
7514
|
if (testPaths.length === 0) continue;
|
|
6978
7515
|
const missingPaths = testPaths.filter(
|
|
6979
|
-
(tp) => !
|
|
7516
|
+
(tp) => !existsSync9(resolve3(projectRoot, tp))
|
|
6980
7517
|
);
|
|
6981
7518
|
if (missingPaths.length === testPaths.length) {
|
|
6982
7519
|
results.push({
|
|
@@ -6991,7 +7528,7 @@ function verifyScenarios(db, projectRoot, options) {
|
|
|
6991
7528
|
continue;
|
|
6992
7529
|
}
|
|
6993
7530
|
const existingPaths = testPaths.filter(
|
|
6994
|
-
(tp) =>
|
|
7531
|
+
(tp) => existsSync9(resolve3(projectRoot, tp))
|
|
6995
7532
|
);
|
|
6996
7533
|
const start = Date.now();
|
|
6997
7534
|
let passed;
|
|
@@ -7045,21 +7582,21 @@ ${output}`;
|
|
|
7045
7582
|
}
|
|
7046
7583
|
|
|
7047
7584
|
// src/roles/loader.ts
|
|
7048
|
-
import { readdirSync as
|
|
7049
|
-
import { join as
|
|
7585
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
|
|
7586
|
+
import { join as join18 } from "path";
|
|
7050
7587
|
import matter4 from "gray-matter";
|
|
7051
7588
|
function loadRoles(projectRoot) {
|
|
7052
|
-
const agentsDir =
|
|
7589
|
+
const agentsDir = join18(projectRoot, ".arcbridge", "agents");
|
|
7053
7590
|
const roles = [];
|
|
7054
7591
|
const errors = [];
|
|
7055
7592
|
let files;
|
|
7056
7593
|
try {
|
|
7057
|
-
files =
|
|
7594
|
+
files = readdirSync6(agentsDir).filter((f) => f.endsWith(".md")).sort();
|
|
7058
7595
|
} catch {
|
|
7059
7596
|
return { roles: [], errors: [`Agent directory not found: ${agentsDir}`] };
|
|
7060
7597
|
}
|
|
7061
7598
|
for (const file of files) {
|
|
7062
|
-
const filePath =
|
|
7599
|
+
const filePath = join18(agentsDir, file);
|
|
7063
7600
|
try {
|
|
7064
7601
|
const raw = readFileSync9(filePath, "utf-8");
|
|
7065
7602
|
const parsed = matter4(raw);
|
|
@@ -7086,7 +7623,7 @@ function loadRole(projectRoot, roleId) {
|
|
|
7086
7623
|
if (!/^[a-z0-9-]+$/.test(roleId)) {
|
|
7087
7624
|
return { role: null, error: `Invalid role ID: "${roleId}" (must be kebab-case)` };
|
|
7088
7625
|
}
|
|
7089
|
-
const filePath =
|
|
7626
|
+
const filePath = join18(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
|
|
7090
7627
|
try {
|
|
7091
7628
|
const raw = readFileSync9(filePath, "utf-8");
|
|
7092
7629
|
const parsed = matter4(raw);
|
|
@@ -7125,6 +7662,7 @@ export {
|
|
|
7125
7662
|
TaskSchema,
|
|
7126
7663
|
addTaskToYaml,
|
|
7127
7664
|
applyInferences,
|
|
7665
|
+
deleteTaskFromYaml,
|
|
7128
7666
|
detectDrift,
|
|
7129
7667
|
detectProjectLanguage,
|
|
7130
7668
|
discoverDotnetServices,
|
|
@@ -7153,6 +7691,7 @@ export {
|
|
|
7153
7691
|
queryMetrics,
|
|
7154
7692
|
refreshFromDocs,
|
|
7155
7693
|
resolveRef,
|
|
7694
|
+
scopeToProject,
|
|
7156
7695
|
setSyncCommit,
|
|
7157
7696
|
suppressSqliteWarning,
|
|
7158
7697
|
syncPhaseToYaml,
|