@cubis/foundry 0.3.78 → 0.3.80
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/cli/build/commands.js +1 -1
- package/dist/cli/build/commands.js.map +1 -1
- package/dist/cli/core.js +752 -101
- package/dist/cli/core.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/build/commands.ts +1 -1
- package/src/cli/core.ts +884 -114
- package/workflows/workflows/agent-environment-setup/generated/route-manifest.json +2 -2
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/accessibility.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/architecture.toml +2 -2
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/backend.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/create.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/database.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/debug.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/devops.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/implement-track.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/migrate.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/mobile.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/onboard.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/orchestrate.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/plan.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/refactor.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/release.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/review.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/security.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/spec.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/test.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/vercel.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/architecture.md +20 -15
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/spec.md +2 -2
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/architecture.md +20 -15
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/spec.md +2 -2
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/architecture.md +20 -15
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/spec.md +2 -2
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-accessibility.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-architecture.prompt.md +2 -2
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-backend.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-create.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-database.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-debug.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-devops.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-implement-track.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-migrate.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-mobile.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-onboard.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-orchestrate.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-plan.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-refactor.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-release.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-review.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-security.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-spec.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-test.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-vercel.prompt.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/architecture.md +20 -15
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/spec.md +2 -2
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/accessibility.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/architecture.toml +2 -2
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/backend.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/create.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/database.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/debug.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/devops.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/implement-track.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/migrate.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/mobile.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/onboard.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/orchestrate.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/plan.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/refactor.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/release.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/review.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/security.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/spec.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/test.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/vercel.toml +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/architecture.md +20 -15
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/spec.md +2 -2
- package/workflows/workflows/agent-environment-setup/shared/rules/STEERING.md +3 -3
- package/workflows/workflows/agent-environment-setup/shared/workflows/architecture.md +20 -15
- package/workflows/workflows/agent-environment-setup/shared/workflows/spec.md +2 -2
package/src/cli/core.ts
CHANGED
|
@@ -65,10 +65,24 @@ const ENGINEERING_ARCHITECTURE_BLOCK_START_RE =
|
|
|
65
65
|
/<!--\s*cbx:architecture:rules:start[^>]*-->/g;
|
|
66
66
|
const ENGINEERING_ARCHITECTURE_BLOCK_END_RE =
|
|
67
67
|
/<!--\s*cbx:architecture:rules:end\s*-->/g;
|
|
68
|
+
const PRODUCT_FOUNDATION_BLOCK_START_RE =
|
|
69
|
+
/<!--\s*cbx:product:foundation:start[^>]*-->/g;
|
|
70
|
+
const PRODUCT_FOUNDATION_BLOCK_END_RE =
|
|
71
|
+
/<!--\s*cbx:product:foundation:end\s*-->/g;
|
|
72
|
+
const ARCHITECTURE_DOC_BLOCK_START_RE =
|
|
73
|
+
/<!--\s*cbx:architecture:doc:start[^>]*-->/g;
|
|
74
|
+
const ARCHITECTURE_DOC_BLOCK_END_RE =
|
|
75
|
+
/<!--\s*cbx:architecture:doc:end\s*-->/g;
|
|
68
76
|
const TECH_ARCHITECTURE_BLOCK_START_RE =
|
|
69
77
|
/<!--\s*cbx:architecture:tech:start[^>]*-->/g;
|
|
70
78
|
const TECH_ARCHITECTURE_BLOCK_END_RE =
|
|
71
79
|
/<!--\s*cbx:architecture:tech:end\s*-->/g;
|
|
80
|
+
const ROADMAP_FOUNDATION_BLOCK_START_RE =
|
|
81
|
+
/<!--\s*cbx:roadmap:foundation:start[^>]*-->/g;
|
|
82
|
+
const ROADMAP_FOUNDATION_BLOCK_END_RE =
|
|
83
|
+
/<!--\s*cbx:roadmap:foundation:end\s*-->/g;
|
|
84
|
+
const FOUNDATION_DOCS_DIR = path.join("docs", "foundation");
|
|
85
|
+
const FOUNDATION_ADR_DIR = path.join(FOUNDATION_DOCS_DIR, "adr");
|
|
72
86
|
const COPILOT_ALLOWED_SKILL_FRONTMATTER_KEYS = new Set([
|
|
73
87
|
"compatibility",
|
|
74
88
|
"description",
|
|
@@ -1105,6 +1119,181 @@ function buildArchitectureMermaid(snapshot) {
|
|
|
1105
1119
|
return lines.join("\n");
|
|
1106
1120
|
}
|
|
1107
1121
|
|
|
1122
|
+
function inferProductFoundationProfile(snapshot, specRoots = []) {
|
|
1123
|
+
const appRoots = (snapshot.architectureByApp || [])
|
|
1124
|
+
.map((item) => item.rootPath)
|
|
1125
|
+
.filter((value) => value && value !== ".");
|
|
1126
|
+
const primarySurfaces = appRoots.length > 0 ? appRoots : snapshot.topDirs.slice(0, 6);
|
|
1127
|
+
const userPersonas = [];
|
|
1128
|
+
|
|
1129
|
+
if (snapshot.frameworks.includes("Flutter")) {
|
|
1130
|
+
userPersonas.push("End users interacting through mobile or desktop app surfaces");
|
|
1131
|
+
}
|
|
1132
|
+
if (
|
|
1133
|
+
snapshot.frameworks.includes("Next.js") ||
|
|
1134
|
+
snapshot.frameworks.includes("React") ||
|
|
1135
|
+
snapshot.topDirs.includes("web")
|
|
1136
|
+
) {
|
|
1137
|
+
userPersonas.push("Browser users and internal operators using web-facing flows");
|
|
1138
|
+
}
|
|
1139
|
+
if (snapshot.frameworks.includes("NestJS") || snapshot.topDirs.includes("api")) {
|
|
1140
|
+
userPersonas.push("Internal services, operators, or partner systems consuming API boundaries");
|
|
1141
|
+
}
|
|
1142
|
+
if (userPersonas.length === 0) {
|
|
1143
|
+
userPersonas.push(
|
|
1144
|
+
"Project stakeholders are not obvious from the repo alone; refine personas from product context before major feature work.",
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const coreJourneys = [];
|
|
1149
|
+
if (specRoots.length > 0) {
|
|
1150
|
+
coreJourneys.push(
|
|
1151
|
+
`Active implementation themes are reflected in current spec packs: ${specRoots.join(", ")}.`,
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
if (primarySurfaces.length > 0) {
|
|
1155
|
+
coreJourneys.push(
|
|
1156
|
+
`Primary product surfaces currently live in: ${primarySurfaces.join(", ")}.`,
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
if (snapshot.isMcpServer || snapshot.mcpSignals.length > 0) {
|
|
1160
|
+
coreJourneys.push("Tool-assisted and MCP-driven workflows are part of the operating model and should stay stable.");
|
|
1161
|
+
}
|
|
1162
|
+
if (coreJourneys.length === 0) {
|
|
1163
|
+
coreJourneys.push(
|
|
1164
|
+
"Repository evidence is thin; capture the primary user journeys here before scaling the codebase further.",
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const successSignals = [
|
|
1169
|
+
"Feature work should stay aligned to explicit user or operator value, not speculative abstractions.",
|
|
1170
|
+
"Architecture changes should reduce onboarding, debugging, and testing cost over time.",
|
|
1171
|
+
];
|
|
1172
|
+
if (snapshot.cicdSignals.length > 0) {
|
|
1173
|
+
successSignals.push(
|
|
1174
|
+
`Delivery flows already rely on ${snapshot.cicdSignals.join(", ")} signals; keep release friction low for that pipeline.`,
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return {
|
|
1179
|
+
productScope:
|
|
1180
|
+
snapshot.readmeExcerpt ||
|
|
1181
|
+
"No explicit product summary was detected from repo docs. Replace this with a concise product statement when better context exists.",
|
|
1182
|
+
primarySurfaces,
|
|
1183
|
+
userPersonas,
|
|
1184
|
+
coreJourneys,
|
|
1185
|
+
successSignals,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function buildProductFoundationSection(snapshot, specRoots = []) {
|
|
1190
|
+
const profile = inferProductFoundationProfile(snapshot, specRoots);
|
|
1191
|
+
const hash = hashStableObject(profile);
|
|
1192
|
+
|
|
1193
|
+
return [
|
|
1194
|
+
`<!-- cbx:product:foundation:start version=1 profile=${hash} -->`,
|
|
1195
|
+
"## Product Foundation (auto-managed)",
|
|
1196
|
+
"",
|
|
1197
|
+
"### Product Scope",
|
|
1198
|
+
profile.productScope,
|
|
1199
|
+
"",
|
|
1200
|
+
"### Primary Surfaces",
|
|
1201
|
+
...(profile.primarySurfaces.length > 0
|
|
1202
|
+
? profile.primarySurfaces.map((item) => `- ${item}`)
|
|
1203
|
+
: ["- No primary surfaces were detected automatically."]),
|
|
1204
|
+
"",
|
|
1205
|
+
"### Users and Operators",
|
|
1206
|
+
...profile.userPersonas.map((item) => `- ${item}`),
|
|
1207
|
+
"",
|
|
1208
|
+
"### Core Journeys",
|
|
1209
|
+
...profile.coreJourneys.map((item) => `- ${item}`),
|
|
1210
|
+
"",
|
|
1211
|
+
"### Success Signals",
|
|
1212
|
+
...profile.successSignals.map((item) => `- ${item}`),
|
|
1213
|
+
"",
|
|
1214
|
+
"### Product Guardrails",
|
|
1215
|
+
"- Keep product intent stable across future features so agents do not optimize for the wrong user outcome.",
|
|
1216
|
+
"- Refresh this managed section when scope, personas, operating model, or success metrics change materially.",
|
|
1217
|
+
"<!-- cbx:product:foundation:end -->",
|
|
1218
|
+
"",
|
|
1219
|
+
].join("\n");
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function inferArchitectureDocProfile(snapshot, specRoots = []) {
|
|
1223
|
+
const contract = inferArchitectureContractProfile(snapshot);
|
|
1224
|
+
const architectureSignals = (snapshot.architectureByApp || []).map((item) => {
|
|
1225
|
+
const label = item.rootPath === "." ? "repo root" : item.rootPath;
|
|
1226
|
+
const signals =
|
|
1227
|
+
item.architectureSignals && item.architectureSignals.length > 0
|
|
1228
|
+
? item.architectureSignals.join(", ")
|
|
1229
|
+
: "not enough signals to classify";
|
|
1230
|
+
return `${label}: ${signals}`;
|
|
1231
|
+
});
|
|
1232
|
+
const decisionAreas = [
|
|
1233
|
+
"Module boundaries and dependency direction",
|
|
1234
|
+
"Design-system ownership and reusable primitives",
|
|
1235
|
+
"Testing and validation expectations by runtime boundary",
|
|
1236
|
+
];
|
|
1237
|
+
if (specRoots.length > 0) {
|
|
1238
|
+
decisionAreas.push(
|
|
1239
|
+
`Active specs that may influence upcoming architecture work: ${specRoots.join(", ")}.`,
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
return {
|
|
1244
|
+
style: contract.style,
|
|
1245
|
+
designSystemSource: contract.designSystemSource,
|
|
1246
|
+
moduleBoundaries: contract.moduleBoundaries,
|
|
1247
|
+
architectureSignals,
|
|
1248
|
+
decisionAreas,
|
|
1249
|
+
scalingConstraints: contract.scalingConstraints,
|
|
1250
|
+
testingStrategy: contract.testingStrategy,
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function buildArchitectureDocSection(snapshot, specRoots = []) {
|
|
1255
|
+
const profile = inferArchitectureDocProfile(snapshot, specRoots);
|
|
1256
|
+
const hash = hashStableObject(profile);
|
|
1257
|
+
|
|
1258
|
+
return [
|
|
1259
|
+
`<!-- cbx:architecture:doc:start version=1 profile=${hash} -->`,
|
|
1260
|
+
"## Accepted Architecture Backbone (auto-managed)",
|
|
1261
|
+
"",
|
|
1262
|
+
"### System Overview",
|
|
1263
|
+
`- Accepted style: ${profile.style}.`,
|
|
1264
|
+
`- Design-system source of truth: ${profile.designSystemSource}`,
|
|
1265
|
+
"",
|
|
1266
|
+
"### Bounded Contexts and Module Boundaries",
|
|
1267
|
+
...(profile.moduleBoundaries.length > 0
|
|
1268
|
+
? profile.moduleBoundaries.map((item) => `- ${item}`)
|
|
1269
|
+
: ["- No strong top-level module boundaries were detected automatically."]),
|
|
1270
|
+
"",
|
|
1271
|
+
"### Architecture Signals by Surface",
|
|
1272
|
+
...(profile.architectureSignals.length > 0
|
|
1273
|
+
? profile.architectureSignals.map((item) => `- ${item}`)
|
|
1274
|
+
: ["- No app-level architecture signals were inferred from the repo scan."]),
|
|
1275
|
+
"",
|
|
1276
|
+
"### Decision Areas to Preserve",
|
|
1277
|
+
...profile.decisionAreas.map((item) => `- ${item}`),
|
|
1278
|
+
"",
|
|
1279
|
+
"### Scalability and Reliability Notes",
|
|
1280
|
+
...profile.scalingConstraints.map((item) => `- ${item}`),
|
|
1281
|
+
"",
|
|
1282
|
+
"### Testing and Operability",
|
|
1283
|
+
...profile.testingStrategy.map((item) => `- ${item}`),
|
|
1284
|
+
"",
|
|
1285
|
+
"### ADR Linkage",
|
|
1286
|
+
"- Keep durable architecture decisions in `docs/adr/` and summarize the active decision set here.",
|
|
1287
|
+
"",
|
|
1288
|
+
"### Mermaid Diagram",
|
|
1289
|
+
"```mermaid",
|
|
1290
|
+
buildArchitectureDocMermaid(snapshot),
|
|
1291
|
+
"```",
|
|
1292
|
+
"<!-- cbx:architecture:doc:end -->",
|
|
1293
|
+
"",
|
|
1294
|
+
].join("\n");
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1108
1297
|
function buildEngineeringArchitectureSection(snapshot) {
|
|
1109
1298
|
const profile = inferArchitectureContractProfile(snapshot);
|
|
1110
1299
|
const hash = hashStableObject(profile);
|
|
@@ -1125,12 +1314,32 @@ function buildEngineeringArchitectureSection(snapshot) {
|
|
|
1125
1314
|
...profile.testingStrategy.map((rule) => ` - ${rule}`),
|
|
1126
1315
|
"- Doc refresh policy:",
|
|
1127
1316
|
" - Update these managed sections when architecture, scale, boundaries, design-system rules, or testing strategy changes.",
|
|
1128
|
-
|
|
1317
|
+
` - For non-trivial work, read ${FOUNDATION_DOCS_DIR}/PRODUCT.md, ENGINEERING_RULES.md, ${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md, and ${FOUNDATION_DOCS_DIR}/TECH.md in that order when they exist.`,
|
|
1129
1318
|
"<!-- cbx:architecture:rules:end -->",
|
|
1130
1319
|
"",
|
|
1131
1320
|
].join("\n");
|
|
1132
1321
|
}
|
|
1133
1322
|
|
|
1323
|
+
function buildArchitectureDocMermaid(snapshot) {
|
|
1324
|
+
const topDirs = snapshot.topDirs.slice(0, 5);
|
|
1325
|
+
const lines = [
|
|
1326
|
+
"flowchart LR",
|
|
1327
|
+
' product["Product Intent"] --> rules["Engineering Rules"]',
|
|
1328
|
+
' product --> arch["Architecture Backbone"]',
|
|
1329
|
+
' arch --> tech["Current Tech Snapshot"]',
|
|
1330
|
+
];
|
|
1331
|
+
if (topDirs.length > 0) {
|
|
1332
|
+
for (let index = 0; index < topDirs.length; index += 1) {
|
|
1333
|
+
const dir = topDirs[index];
|
|
1334
|
+
const nodeName = `D${index}`;
|
|
1335
|
+
lines.push(` arch --> ${nodeName}["${dir}/"]`);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
lines.push(' arch --> adr["docs/adr"]');
|
|
1339
|
+
lines.push(' product --> specs["docs/specs"]');
|
|
1340
|
+
return lines.join("\n");
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1134
1343
|
function buildTechArchitectureSection(snapshot) {
|
|
1135
1344
|
const profile = inferArchitectureContractProfile(snapshot);
|
|
1136
1345
|
const payload = {
|
|
@@ -1186,6 +1395,56 @@ function buildTechArchitectureSection(snapshot) {
|
|
|
1186
1395
|
].join("\n");
|
|
1187
1396
|
}
|
|
1188
1397
|
|
|
1398
|
+
function buildRoadmapFoundationSection(snapshot, specRoots = []) {
|
|
1399
|
+
const payload = {
|
|
1400
|
+
topDirs: snapshot.topDirs,
|
|
1401
|
+
frameworks: snapshot.frameworks,
|
|
1402
|
+
specRoots,
|
|
1403
|
+
};
|
|
1404
|
+
const hash = hashStableObject(payload);
|
|
1405
|
+
const nowItems = specRoots.length > 0
|
|
1406
|
+
? specRoots.map((item) => `Track active change planning in \`${item}\`.`)
|
|
1407
|
+
: [
|
|
1408
|
+
"No active spec packs detected. Create a spec pack before starting the next non-trivial feature or migration.",
|
|
1409
|
+
];
|
|
1410
|
+
const nextItems = [];
|
|
1411
|
+
if (snapshot.frameworks.length > 0) {
|
|
1412
|
+
nextItems.push(
|
|
1413
|
+
`Keep shared conventions stable across the current stack: ${snapshot.frameworks.join(", ")}.`,
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
if (snapshot.cicdSignals.length > 0) {
|
|
1417
|
+
nextItems.push(
|
|
1418
|
+
`Preserve release compatibility with the detected delivery surfaces: ${snapshot.cicdSignals.join(", ")}.`,
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
if (nextItems.length === 0) {
|
|
1422
|
+
nextItems.push(
|
|
1423
|
+
"Document the next scaling milestones here once product direction and architecture constraints are clearer.",
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
return [
|
|
1428
|
+
`<!-- cbx:roadmap:foundation:start version=1 profile=${hash} -->`,
|
|
1429
|
+
"## Delivery Backbone (auto-managed)",
|
|
1430
|
+
"",
|
|
1431
|
+
"### Now",
|
|
1432
|
+
...nowItems.map((item) => `- ${item}`),
|
|
1433
|
+
"",
|
|
1434
|
+
"### Next",
|
|
1435
|
+
...nextItems.map((item) => `- ${item}`),
|
|
1436
|
+
"",
|
|
1437
|
+
"### Later",
|
|
1438
|
+
"- Use this section for medium-term scaling themes, major migrations, or cross-team architecture investments.",
|
|
1439
|
+
"",
|
|
1440
|
+
"### Backbone Maintenance",
|
|
1441
|
+
`- Keep ${FOUNDATION_DOCS_DIR}/PRODUCT.md, ${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md, ENGINEERING_RULES.md, and ${FOUNDATION_DOCS_DIR}/TECH.md aligned when direction or structure changes.`,
|
|
1442
|
+
"- Link major roadmap themes back to specs and ADRs instead of burying them in chat-only planning.",
|
|
1443
|
+
"<!-- cbx:roadmap:foundation:end -->",
|
|
1444
|
+
"",
|
|
1445
|
+
].join("\n");
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1189
1448
|
function buildEngineeringRulesTemplate() {
|
|
1190
1449
|
return [
|
|
1191
1450
|
"# Engineering Rules",
|
|
@@ -1288,14 +1547,144 @@ function buildEngineeringRulesTemplate() {
|
|
|
1288
1547
|
].join("\n");
|
|
1289
1548
|
}
|
|
1290
1549
|
|
|
1550
|
+
function buildProductTemplate(snapshot, specRoots = []) {
|
|
1551
|
+
return [
|
|
1552
|
+
"# Product",
|
|
1553
|
+
"",
|
|
1554
|
+
"This file is the durable product backbone for the project.",
|
|
1555
|
+
"",
|
|
1556
|
+
buildProductFoundationSection(snapshot, specRoots).trimEnd(),
|
|
1557
|
+
"",
|
|
1558
|
+
].join("\n");
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function buildArchitectureDocTemplate(snapshot, specRoots = []) {
|
|
1562
|
+
return [
|
|
1563
|
+
"# Architecture",
|
|
1564
|
+
"",
|
|
1565
|
+
"This file captures the accepted architecture backbone for the project.",
|
|
1566
|
+
"",
|
|
1567
|
+
buildArchitectureDocSection(snapshot, specRoots).trimEnd(),
|
|
1568
|
+
"",
|
|
1569
|
+
].join("\n");
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
function buildRoadmapTemplate(snapshot, specRoots = []) {
|
|
1573
|
+
return [
|
|
1574
|
+
"# Roadmap",
|
|
1575
|
+
"",
|
|
1576
|
+
"This file captures the living delivery backbone for medium-term product and architecture work.",
|
|
1577
|
+
"",
|
|
1578
|
+
buildRoadmapFoundationSection(snapshot, specRoots).trimEnd(),
|
|
1579
|
+
"",
|
|
1580
|
+
].join("\n");
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
function buildProductBuildSkeleton() {
|
|
1584
|
+
return [
|
|
1585
|
+
"# Product",
|
|
1586
|
+
"",
|
|
1587
|
+
"This file is managed by `cbx build architecture`.",
|
|
1588
|
+
"",
|
|
1589
|
+
"<!-- cbx:product:foundation:start version=1 profile=uninitialized -->",
|
|
1590
|
+
"Replace this managed section by running `cbx build architecture --platform <codex|claude|gemini|copilot>`.",
|
|
1591
|
+
"<!-- cbx:product:foundation:end -->",
|
|
1592
|
+
"",
|
|
1593
|
+
].join("\n");
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function buildArchitectureBuildSkeleton() {
|
|
1597
|
+
return [
|
|
1598
|
+
"# Architecture",
|
|
1599
|
+
"",
|
|
1600
|
+
"This file is managed by `cbx build architecture`.",
|
|
1601
|
+
"",
|
|
1602
|
+
"<!-- cbx:architecture:doc:start version=1 profile=uninitialized -->",
|
|
1603
|
+
"Replace this managed section by running `cbx build architecture --platform <codex|claude|gemini|copilot>`.",
|
|
1604
|
+
"<!-- cbx:architecture:doc:end -->",
|
|
1605
|
+
"",
|
|
1606
|
+
].join("\n");
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function buildTechBuildSkeleton() {
|
|
1610
|
+
return [
|
|
1611
|
+
"# TECH.md",
|
|
1612
|
+
"",
|
|
1613
|
+
"This file is managed by `cbx build architecture`.",
|
|
1614
|
+
"",
|
|
1615
|
+
"<!-- cbx:architecture:tech:start version=1 snapshot=uninitialized -->",
|
|
1616
|
+
"Replace this managed section by running `cbx build architecture --platform <codex|claude|gemini|copilot>`.",
|
|
1617
|
+
"<!-- cbx:architecture:tech:end -->",
|
|
1618
|
+
"",
|
|
1619
|
+
].join("\n");
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function buildAdrReadme() {
|
|
1623
|
+
return [
|
|
1624
|
+
"# Architecture Decision Records",
|
|
1625
|
+
"",
|
|
1626
|
+
"Use this directory for durable decisions that future contributors and agents need to preserve.",
|
|
1627
|
+
"",
|
|
1628
|
+
"## When to add an ADR",
|
|
1629
|
+
"",
|
|
1630
|
+
"- Architecture style or boundary changes",
|
|
1631
|
+
"- Data model or persistence strategy changes",
|
|
1632
|
+
"- Deployment or scaling model changes",
|
|
1633
|
+
"- Design-system ownership or shared UX pattern changes",
|
|
1634
|
+
"",
|
|
1635
|
+
"## Suggested format",
|
|
1636
|
+
"",
|
|
1637
|
+
"1. Context",
|
|
1638
|
+
"2. Decision",
|
|
1639
|
+
"3. Consequences",
|
|
1640
|
+
"4. Validation",
|
|
1641
|
+
"",
|
|
1642
|
+
"Start with `0000-template.md` and create numbered follow-up ADRs for accepted decisions.",
|
|
1643
|
+
"",
|
|
1644
|
+
].join("\n");
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
function buildAdrTemplate() {
|
|
1648
|
+
return [
|
|
1649
|
+
"# ADR 0000: Title",
|
|
1650
|
+
"",
|
|
1651
|
+
"## Status",
|
|
1652
|
+
"",
|
|
1653
|
+
"Proposed",
|
|
1654
|
+
"",
|
|
1655
|
+
"## Context",
|
|
1656
|
+
"",
|
|
1657
|
+
"- What problem or pressure led to this decision?",
|
|
1658
|
+
"",
|
|
1659
|
+
"## Decision",
|
|
1660
|
+
"",
|
|
1661
|
+
"- What is the chosen direction?",
|
|
1662
|
+
"",
|
|
1663
|
+
"## Consequences",
|
|
1664
|
+
"",
|
|
1665
|
+
"- What tradeoffs, benefits, or costs follow from this choice?",
|
|
1666
|
+
"",
|
|
1667
|
+
"## Validation",
|
|
1668
|
+
"",
|
|
1669
|
+
"- How will the team know this decision is working?",
|
|
1670
|
+
"",
|
|
1671
|
+
].join("\n");
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1291
1674
|
function buildEngineeringRulesManagedBlock({
|
|
1292
1675
|
platform,
|
|
1676
|
+
productFilePath,
|
|
1677
|
+
architectureFilePath,
|
|
1293
1678
|
engineeringRulesFilePath,
|
|
1294
1679
|
techMdFilePath,
|
|
1680
|
+
roadmapFilePath,
|
|
1295
1681
|
ruleFilePath,
|
|
1296
1682
|
}) {
|
|
1683
|
+
const productRef = toPosixPath(path.resolve(productFilePath));
|
|
1684
|
+
const architectureRef = toPosixPath(path.resolve(architectureFilePath));
|
|
1297
1685
|
const engineeringRef = toPosixPath(path.resolve(engineeringRulesFilePath));
|
|
1298
1686
|
const techRef = toPosixPath(path.resolve(techMdFilePath));
|
|
1687
|
+
const roadmapRef = toPosixPath(path.resolve(roadmapFilePath));
|
|
1299
1688
|
const ruleRef = toPosixPath(path.resolve(ruleFilePath));
|
|
1300
1689
|
|
|
1301
1690
|
return [
|
|
@@ -1303,8 +1692,11 @@ function buildEngineeringRulesManagedBlock({
|
|
|
1303
1692
|
"## Engineering Guardrails (auto-managed)",
|
|
1304
1693
|
"Apply these before planning, coding, review, and release:",
|
|
1305
1694
|
"",
|
|
1695
|
+
`- Product backbone: \`${productRef}\``,
|
|
1696
|
+
`- Accepted architecture: \`${architectureRef}\``,
|
|
1306
1697
|
`- Required baseline: \`${engineeringRef}\``,
|
|
1307
1698
|
`- Project tech map: \`${techRef}\``,
|
|
1699
|
+
`- Delivery roadmap: \`${roadmapRef}\``,
|
|
1308
1700
|
`- Active platform rule file: \`${ruleRef}\``,
|
|
1309
1701
|
"",
|
|
1310
1702
|
"Hard policy:",
|
|
@@ -1312,7 +1704,7 @@ function buildEngineeringRulesManagedBlock({
|
|
|
1312
1704
|
"2. Keep architecture simple (KISS) and avoid speculative work (YAGNI).",
|
|
1313
1705
|
"3. Apply SOLID pragmatically to reduce change risk, not add ceremony.",
|
|
1314
1706
|
"4. Use clear naming with focused responsibilities and explicit boundaries.",
|
|
1315
|
-
|
|
1707
|
+
`5. For non-trivial work, read ${FOUNDATION_DOCS_DIR}/PRODUCT.md, ENGINEERING_RULES.md, ${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md, and ${FOUNDATION_DOCS_DIR}/TECH.md in that order when they exist before planning or implementation.`,
|
|
1316
1708
|
"6. Require validation evidence (lint/types/tests) before merge.",
|
|
1317
1709
|
"7. Use Decision Log response style.",
|
|
1318
1710
|
"8. Every Decision Log must include a `Skills Used` section listing skill, workflow, or agent names.",
|
|
@@ -1437,14 +1829,20 @@ async function upsertEngineeringRulesFile({
|
|
|
1437
1829
|
async function upsertEngineeringRulesBlock({
|
|
1438
1830
|
ruleFilePath,
|
|
1439
1831
|
platform,
|
|
1832
|
+
productFilePath,
|
|
1833
|
+
architectureFilePath,
|
|
1440
1834
|
engineeringRulesFilePath,
|
|
1441
1835
|
techMdFilePath,
|
|
1836
|
+
roadmapFilePath,
|
|
1442
1837
|
dryRun = false,
|
|
1443
1838
|
}) {
|
|
1444
1839
|
const block = buildEngineeringRulesManagedBlock({
|
|
1445
1840
|
platform,
|
|
1841
|
+
productFilePath,
|
|
1842
|
+
architectureFilePath,
|
|
1446
1843
|
engineeringRulesFilePath,
|
|
1447
1844
|
techMdFilePath,
|
|
1845
|
+
roadmapFilePath,
|
|
1448
1846
|
ruleFilePath,
|
|
1449
1847
|
});
|
|
1450
1848
|
const exists = await pathExists(ruleFilePath);
|
|
@@ -1531,6 +1929,17 @@ async function upsertTaggedSectionInFile({
|
|
|
1531
1929
|
trimmed.length > 0 ? `${trimmed}\n\n${block}\n` : `${block}\n`;
|
|
1532
1930
|
}
|
|
1533
1931
|
|
|
1932
|
+
if (!exists) {
|
|
1933
|
+
if (!dryRun) {
|
|
1934
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
1935
|
+
await writeFile(targetPath, nextContent, "utf8");
|
|
1936
|
+
}
|
|
1937
|
+
return {
|
|
1938
|
+
action: dryRun ? "would-create" : "created",
|
|
1939
|
+
filePath: targetPath,
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1534
1943
|
if (nextContent === original) {
|
|
1535
1944
|
return {
|
|
1536
1945
|
action: "unchanged",
|
|
@@ -1558,28 +1967,51 @@ async function upsertTaggedSectionInFile({
|
|
|
1558
1967
|
function buildArchitectureBuildMetadata({
|
|
1559
1968
|
platform,
|
|
1560
1969
|
researchMode,
|
|
1561
|
-
|
|
1562
|
-
techSnapshotHash,
|
|
1970
|
+
managedDocs,
|
|
1563
1971
|
}) {
|
|
1564
1972
|
return {
|
|
1565
|
-
schemaVersion:
|
|
1973
|
+
schemaVersion: 3,
|
|
1566
1974
|
generatedBy: "cbx build architecture",
|
|
1567
1975
|
generatedAt: new Date().toISOString(),
|
|
1568
1976
|
platform,
|
|
1569
1977
|
researchMode,
|
|
1570
|
-
|
|
1571
|
-
techSnapshotHash,
|
|
1978
|
+
managedDocs,
|
|
1572
1979
|
};
|
|
1573
1980
|
}
|
|
1574
1981
|
|
|
1575
1982
|
async function ensureArchitectureDocScaffold({
|
|
1576
1983
|
workspaceRoot,
|
|
1577
1984
|
snapshot,
|
|
1985
|
+
specRoots = [],
|
|
1578
1986
|
overwrite = false,
|
|
1579
1987
|
dryRun = false,
|
|
1580
1988
|
}) {
|
|
1989
|
+
const productPath = path.join(workspaceRoot, "PRODUCT.md");
|
|
1990
|
+
const architectureDocPath = path.join(workspaceRoot, "ARCHITECTURE.md");
|
|
1581
1991
|
const engineeringRulesPath = path.join(workspaceRoot, "ENGINEERING_RULES.md");
|
|
1582
1992
|
const techMdPath = path.join(workspaceRoot, "TECH.md");
|
|
1993
|
+
const roadmapPath = path.join(workspaceRoot, "ROADMAP.md");
|
|
1994
|
+
const adrDir = path.join(workspaceRoot, "docs", "adr");
|
|
1995
|
+
const adrReadmePath = path.join(adrDir, "README.md");
|
|
1996
|
+
const adrTemplatePath = path.join(adrDir, "0000-template.md");
|
|
1997
|
+
|
|
1998
|
+
const productResult = await upsertTaggedSectionInFile({
|
|
1999
|
+
targetPath: productPath,
|
|
2000
|
+
initialContent: `${buildProductTemplate(snapshot, specRoots)}\n`,
|
|
2001
|
+
block: buildProductFoundationSection(snapshot, specRoots),
|
|
2002
|
+
startPattern: PRODUCT_FOUNDATION_BLOCK_START_RE,
|
|
2003
|
+
endPattern: PRODUCT_FOUNDATION_BLOCK_END_RE,
|
|
2004
|
+
dryRun,
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
const architectureDocResult = await upsertTaggedSectionInFile({
|
|
2008
|
+
targetPath: architectureDocPath,
|
|
2009
|
+
initialContent: `${buildArchitectureDocTemplate(snapshot, specRoots)}\n`,
|
|
2010
|
+
block: buildArchitectureDocSection(snapshot, specRoots),
|
|
2011
|
+
startPattern: ARCHITECTURE_DOC_BLOCK_START_RE,
|
|
2012
|
+
endPattern: ARCHITECTURE_DOC_BLOCK_END_RE,
|
|
2013
|
+
dryRun,
|
|
2014
|
+
});
|
|
1583
2015
|
|
|
1584
2016
|
const rulesTemplate = buildEngineeringRulesTemplate();
|
|
1585
2017
|
const rulesFileResult = await upsertEngineeringRulesFile({
|
|
@@ -1613,13 +2045,126 @@ async function ensureArchitectureDocScaffold({
|
|
|
1613
2045
|
dryRun,
|
|
1614
2046
|
});
|
|
1615
2047
|
|
|
2048
|
+
const roadmapResult = await upsertTaggedSectionInFile({
|
|
2049
|
+
targetPath: roadmapPath,
|
|
2050
|
+
initialContent: `${buildRoadmapTemplate(snapshot, specRoots)}\n`,
|
|
2051
|
+
block: buildRoadmapFoundationSection(snapshot, specRoots),
|
|
2052
|
+
startPattern: ROADMAP_FOUNDATION_BLOCK_START_RE,
|
|
2053
|
+
endPattern: ROADMAP_FOUNDATION_BLOCK_END_RE,
|
|
2054
|
+
dryRun,
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
const adrReadmeResult = await writeTextFile({
|
|
2058
|
+
targetPath: adrReadmePath,
|
|
2059
|
+
content: `${buildAdrReadme()}\n`,
|
|
2060
|
+
overwrite,
|
|
2061
|
+
dryRun,
|
|
2062
|
+
});
|
|
2063
|
+
const adrTemplateResult = await writeTextFile({
|
|
2064
|
+
targetPath: adrTemplatePath,
|
|
2065
|
+
content: `${buildAdrTemplate()}\n`,
|
|
2066
|
+
overwrite,
|
|
2067
|
+
dryRun,
|
|
2068
|
+
});
|
|
2069
|
+
|
|
1616
2070
|
return {
|
|
2071
|
+
productPath,
|
|
2072
|
+
architectureDocPath,
|
|
1617
2073
|
engineeringRulesPath,
|
|
1618
2074
|
techMdPath,
|
|
2075
|
+
roadmapPath,
|
|
2076
|
+
adrReadmePath,
|
|
2077
|
+
adrTemplatePath,
|
|
2078
|
+
productResult,
|
|
2079
|
+
architectureDocResult,
|
|
1619
2080
|
rulesFileResult,
|
|
1620
2081
|
rulesArchitectureResult,
|
|
1621
2082
|
techResult,
|
|
1622
2083
|
techArchitectureResult,
|
|
2084
|
+
roadmapResult,
|
|
2085
|
+
adrReadmeResult,
|
|
2086
|
+
adrTemplateResult,
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
async function ensureArchitectureBuildScaffold({
|
|
2091
|
+
workspaceRoot,
|
|
2092
|
+
dryRun = false,
|
|
2093
|
+
}) {
|
|
2094
|
+
const foundationRoot = path.join(workspaceRoot, FOUNDATION_DOCS_DIR);
|
|
2095
|
+
const productPath = path.join(foundationRoot, "PRODUCT.md");
|
|
2096
|
+
const architectureDocPath = path.join(foundationRoot, "ARCHITECTURE.md");
|
|
2097
|
+
const techMdPath = path.join(foundationRoot, "TECH.md");
|
|
2098
|
+
const adrDir = path.join(workspaceRoot, FOUNDATION_ADR_DIR);
|
|
2099
|
+
const adrReadmePath = path.join(adrDir, "README.md");
|
|
2100
|
+
const adrTemplatePath = path.join(adrDir, "0000-template.md");
|
|
2101
|
+
|
|
2102
|
+
const productResult = await upsertTaggedSectionInFile({
|
|
2103
|
+
targetPath: productPath,
|
|
2104
|
+
initialContent: `${buildProductBuildSkeleton()}\n`,
|
|
2105
|
+
block: [
|
|
2106
|
+
"<!-- cbx:product:foundation:start version=1 profile=uninitialized -->",
|
|
2107
|
+
"Replace this managed section by running `cbx build architecture --platform <codex|claude|gemini|copilot>`.",
|
|
2108
|
+
"<!-- cbx:product:foundation:end -->",
|
|
2109
|
+
"",
|
|
2110
|
+
].join("\n"),
|
|
2111
|
+
startPattern: PRODUCT_FOUNDATION_BLOCK_START_RE,
|
|
2112
|
+
endPattern: PRODUCT_FOUNDATION_BLOCK_END_RE,
|
|
2113
|
+
dryRun,
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
const architectureDocResult = await upsertTaggedSectionInFile({
|
|
2117
|
+
targetPath: architectureDocPath,
|
|
2118
|
+
initialContent: `${buildArchitectureBuildSkeleton()}\n`,
|
|
2119
|
+
block: [
|
|
2120
|
+
"<!-- cbx:architecture:doc:start version=1 profile=uninitialized -->",
|
|
2121
|
+
"Replace this managed section by running `cbx build architecture --platform <codex|claude|gemini|copilot>`.",
|
|
2122
|
+
"<!-- cbx:architecture:doc:end -->",
|
|
2123
|
+
"",
|
|
2124
|
+
].join("\n"),
|
|
2125
|
+
startPattern: ARCHITECTURE_DOC_BLOCK_START_RE,
|
|
2126
|
+
endPattern: ARCHITECTURE_DOC_BLOCK_END_RE,
|
|
2127
|
+
dryRun,
|
|
2128
|
+
});
|
|
2129
|
+
|
|
2130
|
+
const techResult = await upsertTaggedSectionInFile({
|
|
2131
|
+
targetPath: techMdPath,
|
|
2132
|
+
initialContent: `${buildTechBuildSkeleton()}\n`,
|
|
2133
|
+
block: [
|
|
2134
|
+
"<!-- cbx:architecture:tech:start version=1 snapshot=uninitialized -->",
|
|
2135
|
+
"Replace this managed section by running `cbx build architecture --platform <codex|claude|gemini|copilot>`.",
|
|
2136
|
+
"<!-- cbx:architecture:tech:end -->",
|
|
2137
|
+
"",
|
|
2138
|
+
].join("\n"),
|
|
2139
|
+
startPattern: TECH_ARCHITECTURE_BLOCK_START_RE,
|
|
2140
|
+
endPattern: TECH_ARCHITECTURE_BLOCK_END_RE,
|
|
2141
|
+
dryRun,
|
|
2142
|
+
});
|
|
2143
|
+
|
|
2144
|
+
const adrReadmeResult = await writeTextFile({
|
|
2145
|
+
targetPath: adrReadmePath,
|
|
2146
|
+
content: `${buildAdrReadme()}\n`,
|
|
2147
|
+
overwrite: false,
|
|
2148
|
+
dryRun,
|
|
2149
|
+
});
|
|
2150
|
+
const adrTemplateResult = await writeTextFile({
|
|
2151
|
+
targetPath: adrTemplatePath,
|
|
2152
|
+
content: `${buildAdrTemplate()}\n`,
|
|
2153
|
+
overwrite: false,
|
|
2154
|
+
dryRun,
|
|
2155
|
+
});
|
|
2156
|
+
|
|
2157
|
+
return {
|
|
2158
|
+
productPath,
|
|
2159
|
+
architectureDocPath,
|
|
2160
|
+
techMdPath,
|
|
2161
|
+
adrReadmePath,
|
|
2162
|
+
adrTemplatePath,
|
|
2163
|
+
productResult,
|
|
2164
|
+
architectureDocResult,
|
|
2165
|
+
techResult,
|
|
2166
|
+
adrReadmeResult,
|
|
2167
|
+
adrTemplateResult,
|
|
1623
2168
|
};
|
|
1624
2169
|
}
|
|
1625
2170
|
|
|
@@ -8755,14 +9300,6 @@ async function performWorkflowInstall(
|
|
|
8755
9300
|
dryRun,
|
|
8756
9301
|
cwd,
|
|
8757
9302
|
});
|
|
8758
|
-
const engineeringArtifactsResult = await upsertEngineeringArtifacts({
|
|
8759
|
-
platform,
|
|
8760
|
-
scope: ruleScope,
|
|
8761
|
-
overwrite: false,
|
|
8762
|
-
dryRun,
|
|
8763
|
-
skipTech: false,
|
|
8764
|
-
cwd,
|
|
8765
|
-
});
|
|
8766
9303
|
const postmanSetupResult = await configurePostmanInstallArtifacts({
|
|
8767
9304
|
platform,
|
|
8768
9305
|
scope,
|
|
@@ -8808,7 +9345,7 @@ async function performWorkflowInstall(
|
|
|
8808
9345
|
bundleId,
|
|
8809
9346
|
installResult,
|
|
8810
9347
|
syncResult,
|
|
8811
|
-
engineeringArtifactsResult,
|
|
9348
|
+
engineeringArtifactsResult: null,
|
|
8812
9349
|
postmanSetupResult,
|
|
8813
9350
|
terminalVerificationRuleResult,
|
|
8814
9351
|
};
|
|
@@ -8838,10 +9375,7 @@ async function runWorkflowInstall(options) {
|
|
|
8838
9375
|
dryRun: result.dryRun,
|
|
8839
9376
|
});
|
|
8840
9377
|
printRuleSyncResult(result.syncResult);
|
|
8841
|
-
|
|
8842
|
-
engineeringResults: result.engineeringArtifactsResult.engineeringResults,
|
|
8843
|
-
techResult: result.engineeringArtifactsResult.techResult,
|
|
8844
|
-
});
|
|
9378
|
+
printInstallDocumentationNotice();
|
|
8845
9379
|
printPostmanSetupSummary({
|
|
8846
9380
|
postmanSetup: result.postmanSetupResult,
|
|
8847
9381
|
});
|
|
@@ -12296,6 +12830,16 @@ function printInstallEngineeringSummary({ engineeringResults, techResult }) {
|
|
|
12296
12830
|
}
|
|
12297
12831
|
}
|
|
12298
12832
|
|
|
12833
|
+
function printInstallDocumentationNotice() {
|
|
12834
|
+
console.log("\nProject backbone docs:");
|
|
12835
|
+
console.log(
|
|
12836
|
+
"- Install only wires the rule references and workflow assets.",
|
|
12837
|
+
);
|
|
12838
|
+
console.log(
|
|
12839
|
+
`- Use \`cbx rules init\` to scaffold ENGINEERING_RULES.md and TECH.md, or \`cbx build architecture --platform <codex|claude|gemini|copilot>\` to generate ${FOUNDATION_DOCS_DIR}/PRODUCT.md, ${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md, ${FOUNDATION_DOCS_DIR}/TECH.md, and ADR scaffolds.`,
|
|
12840
|
+
);
|
|
12841
|
+
}
|
|
12842
|
+
|
|
12299
12843
|
async function upsertEngineeringArtifacts({
|
|
12300
12844
|
platform,
|
|
12301
12845
|
scope,
|
|
@@ -12313,10 +12857,10 @@ async function upsertEngineeringArtifacts({
|
|
|
12313
12857
|
const scaffold = await ensureArchitectureDocScaffold({
|
|
12314
12858
|
workspaceRoot,
|
|
12315
12859
|
snapshot,
|
|
12860
|
+
specRoots: [],
|
|
12316
12861
|
overwrite,
|
|
12317
12862
|
dryRun,
|
|
12318
12863
|
});
|
|
12319
|
-
const techMdPath = path.join(workspaceRoot, "TECH.md");
|
|
12320
12864
|
const targets = [{ ruleFilePath }];
|
|
12321
12865
|
|
|
12322
12866
|
if (scope === "global") {
|
|
@@ -12341,8 +12885,11 @@ async function upsertEngineeringArtifacts({
|
|
|
12341
12885
|
const blockResult = await upsertEngineeringRulesBlock({
|
|
12342
12886
|
ruleFilePath: target.ruleFilePath,
|
|
12343
12887
|
platform,
|
|
12888
|
+
productFilePath: scaffold.productPath,
|
|
12889
|
+
architectureFilePath: scaffold.architectureDocPath,
|
|
12344
12890
|
engineeringRulesFilePath: scaffold.engineeringRulesPath,
|
|
12345
|
-
techMdFilePath: techMdPath,
|
|
12891
|
+
techMdFilePath: scaffold.techMdPath,
|
|
12892
|
+
roadmapFilePath: scaffold.roadmapPath,
|
|
12346
12893
|
dryRun,
|
|
12347
12894
|
});
|
|
12348
12895
|
engineeringResults.push({
|
|
@@ -12474,6 +13021,68 @@ async function listSpecPackRoots(workspaceRoot) {
|
|
|
12474
13021
|
.slice(0, 8);
|
|
12475
13022
|
}
|
|
12476
13023
|
|
|
13024
|
+
async function resolveArchitectureInspectionAnchors(
|
|
13025
|
+
workspaceRoot,
|
|
13026
|
+
snapshot,
|
|
13027
|
+
specRoots,
|
|
13028
|
+
) {
|
|
13029
|
+
const seen = new Set();
|
|
13030
|
+
const ordered = [];
|
|
13031
|
+
const pushCandidate = async (relativePath) => {
|
|
13032
|
+
const normalized = toPosixPath(relativePath);
|
|
13033
|
+
if (!normalized || seen.has(normalized)) return;
|
|
13034
|
+
if (!(await pathExists(path.join(workspaceRoot, relativePath)))) return;
|
|
13035
|
+
seen.add(normalized);
|
|
13036
|
+
ordered.push(normalized);
|
|
13037
|
+
};
|
|
13038
|
+
|
|
13039
|
+
for (const candidate of [
|
|
13040
|
+
"README.md",
|
|
13041
|
+
"package.json",
|
|
13042
|
+
"pubspec.yaml",
|
|
13043
|
+
"go.mod",
|
|
13044
|
+
"pyproject.toml",
|
|
13045
|
+
"Cargo.toml",
|
|
13046
|
+
"Dockerfile",
|
|
13047
|
+
"docker-compose.yml",
|
|
13048
|
+
"docker-compose.yaml",
|
|
13049
|
+
"compose.yaml",
|
|
13050
|
+
"cbx_config.json",
|
|
13051
|
+
".vscode/mcp.json",
|
|
13052
|
+
".gemini/settings.json",
|
|
13053
|
+
]) {
|
|
13054
|
+
await pushCandidate(candidate);
|
|
13055
|
+
}
|
|
13056
|
+
|
|
13057
|
+
for (const specRoot of specRoots.slice(0, 4)) {
|
|
13058
|
+
await pushCandidate(specRoot);
|
|
13059
|
+
}
|
|
13060
|
+
|
|
13061
|
+
for (const app of snapshot.architectureByApp || []) {
|
|
13062
|
+
if (!app?.rootPath || app.rootPath === ".") continue;
|
|
13063
|
+
await pushCandidate(app.rootPath);
|
|
13064
|
+
for (const child of [
|
|
13065
|
+
"README.md",
|
|
13066
|
+
"src",
|
|
13067
|
+
"lib",
|
|
13068
|
+
"app",
|
|
13069
|
+
"prisma",
|
|
13070
|
+
"migrations",
|
|
13071
|
+
"test",
|
|
13072
|
+
"tests",
|
|
13073
|
+
"docs",
|
|
13074
|
+
]) {
|
|
13075
|
+
await pushCandidate(path.join(app.rootPath, child));
|
|
13076
|
+
}
|
|
13077
|
+
}
|
|
13078
|
+
|
|
13079
|
+
for (const dir of snapshot.topDirs || []) {
|
|
13080
|
+
await pushCandidate(dir);
|
|
13081
|
+
}
|
|
13082
|
+
|
|
13083
|
+
return ordered.slice(0, 18);
|
|
13084
|
+
}
|
|
13085
|
+
|
|
12477
13086
|
function resolveArchitectureConditionalSkills(snapshot, specRoots, researchMode) {
|
|
12478
13087
|
const conditional = [];
|
|
12479
13088
|
const frameworks = new Set(snapshot.frameworks || []);
|
|
@@ -12536,13 +13145,17 @@ function buildArchitecturePrompt({
|
|
|
12536
13145
|
workspaceRoot,
|
|
12537
13146
|
snapshot,
|
|
12538
13147
|
specRoots,
|
|
13148
|
+
inspectionAnchors,
|
|
12539
13149
|
researchMode,
|
|
12540
13150
|
coreSkills,
|
|
12541
13151
|
conditionalSkills,
|
|
12542
13152
|
skillPathHints,
|
|
12543
13153
|
}) {
|
|
12544
|
-
const
|
|
12545
|
-
const
|
|
13154
|
+
const productPath = `${FOUNDATION_DOCS_DIR}/PRODUCT.md`;
|
|
13155
|
+
const architecturePath = `${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md`;
|
|
13156
|
+
const techPath = `${FOUNDATION_DOCS_DIR}/TECH.md`;
|
|
13157
|
+
const adrReadmePath = `${FOUNDATION_ADR_DIR}/README.md`;
|
|
13158
|
+
const adrTemplatePath = `${FOUNDATION_ADR_DIR}/0000-template.md`;
|
|
12546
13159
|
const architectureSignals = snapshot.architectureByApp
|
|
12547
13160
|
.filter((item) => (item.architectureSignals || []).length > 0)
|
|
12548
13161
|
.map((item) => {
|
|
@@ -12554,9 +13167,9 @@ function buildArchitecturePrompt({
|
|
|
12554
13167
|
`You are running inside ${platform}.`,
|
|
12555
13168
|
"",
|
|
12556
13169
|
"Objective:",
|
|
12557
|
-
`- Inspect the repository at ${toPosixPath(workspaceRoot)} and refresh the
|
|
12558
|
-
"-
|
|
12559
|
-
"- Preserve manual content outside the managed `cbx
|
|
13170
|
+
`- Inspect the repository at ${toPosixPath(workspaceRoot)} and author or refresh the core foundation docs in ${productPath}, ${architecturePath}, ${techPath}, ${adrReadmePath}, and ${adrTemplatePath}.`,
|
|
13171
|
+
"- The content should be primarily AI-authored from repository inspection, not copied from placeholder scaffolding.",
|
|
13172
|
+
"- Preserve manual content outside the managed `cbx:*` markers.",
|
|
12560
13173
|
"",
|
|
12561
13174
|
"Required skill bundle:",
|
|
12562
13175
|
`- Load these exact skill IDs first: ${coreSkills.map((skillId) => `\`${skillId}\``).join(", ")}`,
|
|
@@ -12575,24 +13188,33 @@ function buildArchitecturePrompt({
|
|
|
12575
13188
|
architectureSignals.length > 0
|
|
12576
13189
|
? `- Architecture signals: ${architectureSignals.join(" | ")}`
|
|
12577
13190
|
: "- Architecture signals: none confidently inferred from the repo scan",
|
|
13191
|
+
`- Entry points: ${snapshot.entryPoints.length > 0 ? snapshot.entryPoints.slice(0, 8).join(" | ") : "none detected"}`,
|
|
13192
|
+
`- Key scripts: ${snapshot.keyScripts.length > 0 ? snapshot.keyScripts.slice(0, 8).map((item) => `${item.name}=${item.command}`).join(" | ") : "none detected"}`,
|
|
13193
|
+
`- Inspection anchors: ${inspectionAnchors.length > 0 ? inspectionAnchors.join(", ") : "no concrete anchors detected; inspect the repo root, main source trees, and manifest files manually"}`,
|
|
12578
13194
|
"",
|
|
12579
13195
|
"Execution contract:",
|
|
12580
|
-
"1.
|
|
12581
|
-
"2.
|
|
12582
|
-
"3.
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
13196
|
+
"1. Inspect the repository first before writing any backbone doc content. Derive structure, product surfaces, runtime boundaries, and technical constraints from the actual codebase.",
|
|
13197
|
+
"2. Complete a real inspection pass before drafting. At minimum, inspect the concrete anchors listed above, plus any adjacent directories needed to understand the main execution paths, data boundaries, and integration surfaces.",
|
|
13198
|
+
"3. Do not infer architecture from filenames alone when you can open representative files. Read enough source to validate the main app boundaries, runtime flows, and persistence/integration patterns.",
|
|
13199
|
+
`4. Then read ${productPath}, ${architecturePath}, and ${techPath} in that order when they exist so you can preserve useful manual context and update existing managed sections cleanly.`,
|
|
13200
|
+
`5. Replace or update only the content between the existing managed markers in ${productPath}, ${architecturePath}, and ${techPath}. Do not append a second marker block.`,
|
|
13201
|
+
`6. In ${productPath}, write a concrete product foundation: product purpose, primary users/operators, main journeys, business capabilities, operational constraints, and what future contributors must preserve.`,
|
|
13202
|
+
`7. In ${architecturePath}, write a lean but detailed architecture backbone in a pragmatic arc42/C4 style: system purpose and constraints, bounded contexts, major building blocks, dependency rules, data and integration boundaries, runtime flows, deployment/operability notes, testing/debugging strategy, and only the diagram levels that add real value.`,
|
|
13203
|
+
`8. In ${techPath}, write the developer-facing technical map: stack, repo layout, key commands, entrypoints, data stores, external services, environment/config surfaces, MCP/tooling footprint, and change hotspots future agents should inspect before editing code.`,
|
|
13204
|
+
"9. Every major claim should be grounded in repository evidence. Mention concrete repo paths in the docs when a structural claim would otherwise be ambiguous.",
|
|
13205
|
+
"10. Avoid placeholder filler, generic checklists, and duplicated content across files. Each doc should have a clear job.",
|
|
13206
|
+
"11. Do not create ROADMAP.md, ENGINEERING_RULES.md, or other extra docs unless the prompt explicitly asks for them.",
|
|
12586
13207
|
researchMode === "never"
|
|
12587
|
-
? "
|
|
12588
|
-
: "
|
|
13208
|
+
? "12. Stay repo-only. Do not use outside research."
|
|
13209
|
+
: "12. Use repo evidence first. Use official docs when needed. Treat Reddit or community sources only as labeled secondary evidence.",
|
|
12589
13210
|
researchMode === "always"
|
|
12590
|
-
?
|
|
12591
|
-
: "
|
|
12592
|
-
|
|
13211
|
+
? `13. Include an external research evidence subsection in ${techPath} with clearly labeled primary and secondary evidence.`
|
|
13212
|
+
: "13. Include external research notes only if they materially informed the architecture update.",
|
|
13213
|
+
`14. If the project clearly follows Clean Architecture, feature-first modules, DDD, modular monolith, or another stable structure, make that explicit in ${architecturePath} with evidence from the repo.`,
|
|
13214
|
+
`15. Ensure ${adrReadmePath} and ${adrTemplatePath} exist as ADR entrypoints, but keep them lean.`,
|
|
12593
13215
|
"",
|
|
12594
13216
|
"Return one JSON object on the last line with this shape:",
|
|
12595
|
-
|
|
13217
|
+
`{"files_written":["${productPath}","${architecturePath}","${techPath}","${adrReadmePath}","${adrTemplatePath}"],"research_used":false,"gaps":[],"next_actions":[]}`,
|
|
12596
13218
|
"",
|
|
12597
13219
|
"Do not emit placeholder TODOs in the managed sections.",
|
|
12598
13220
|
].join("\n");
|
|
@@ -12622,6 +13244,95 @@ async function execFileCapture(command, args, options = {}) {
|
|
|
12622
13244
|
}
|
|
12623
13245
|
}
|
|
12624
13246
|
|
|
13247
|
+
async function spawnCapture(command, args, options = {}) {
|
|
13248
|
+
const { cwd, env, streamOutput = false } = options;
|
|
13249
|
+
return await new Promise((resolve, reject) => {
|
|
13250
|
+
let stdout = "";
|
|
13251
|
+
let stderr = "";
|
|
13252
|
+
const child = spawn(command, args, {
|
|
13253
|
+
cwd,
|
|
13254
|
+
env,
|
|
13255
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13256
|
+
});
|
|
13257
|
+
|
|
13258
|
+
child.stdout.on("data", (chunk) => {
|
|
13259
|
+
const text = chunk.toString();
|
|
13260
|
+
stdout += text;
|
|
13261
|
+
if (streamOutput) process.stdout.write(text);
|
|
13262
|
+
});
|
|
13263
|
+
|
|
13264
|
+
child.stderr.on("data", (chunk) => {
|
|
13265
|
+
const text = chunk.toString();
|
|
13266
|
+
stderr += text;
|
|
13267
|
+
if (streamOutput) process.stderr.write(text);
|
|
13268
|
+
});
|
|
13269
|
+
|
|
13270
|
+
child.on("error", (error) => {
|
|
13271
|
+
if (error?.code === "ENOENT") {
|
|
13272
|
+
reject(
|
|
13273
|
+
new Error(`Required CLI '${command}' is not installed or not on PATH.`),
|
|
13274
|
+
);
|
|
13275
|
+
return;
|
|
13276
|
+
}
|
|
13277
|
+
reject(error);
|
|
13278
|
+
});
|
|
13279
|
+
|
|
13280
|
+
child.on("close", (code) => {
|
|
13281
|
+
resolve({
|
|
13282
|
+
ok: code === 0,
|
|
13283
|
+
stdout,
|
|
13284
|
+
stderr,
|
|
13285
|
+
code: code ?? 1,
|
|
13286
|
+
});
|
|
13287
|
+
});
|
|
13288
|
+
});
|
|
13289
|
+
}
|
|
13290
|
+
|
|
13291
|
+
function explainArchitectureBuildFailure(platform, execution) {
|
|
13292
|
+
const combined = String(
|
|
13293
|
+
`${execution.stderr || ""}\n${execution.stdout || ""}`.trim(),
|
|
13294
|
+
);
|
|
13295
|
+
const notes = [];
|
|
13296
|
+
|
|
13297
|
+
if (platform === "gemini") {
|
|
13298
|
+
if (
|
|
13299
|
+
combined.includes("Error during discovery for MCP server") ||
|
|
13300
|
+
combined.includes("[MCP error]")
|
|
13301
|
+
) {
|
|
13302
|
+
notes.push(
|
|
13303
|
+
"Gemini CLI is failing while loading MCP servers from your Gemini settings. Start the required MCP runtime(s) first or disable the broken server entries in `.gemini/settings.json` before retrying.",
|
|
13304
|
+
);
|
|
13305
|
+
}
|
|
13306
|
+
if (
|
|
13307
|
+
combined.includes("cloudaicompanion.companions.generateChat") ||
|
|
13308
|
+
combined.includes("PERMISSION_DENIED") ||
|
|
13309
|
+
combined.includes("403")
|
|
13310
|
+
) {
|
|
13311
|
+
notes.push(
|
|
13312
|
+
"Gemini CLI reached Google auth, but the active account or project cannot generate chat content. Re-authenticate Gemini CLI with a permitted account or configure a supported Gemini API credential and project before retrying.",
|
|
13313
|
+
);
|
|
13314
|
+
}
|
|
13315
|
+
}
|
|
13316
|
+
|
|
13317
|
+
if (platform === "claude" && combined.includes("permission")) {
|
|
13318
|
+
notes.push(
|
|
13319
|
+
"Claude CLI appears to be blocked by its own permission model. Re-run in a Claude environment that allows non-interactive edits for this workspace.",
|
|
13320
|
+
);
|
|
13321
|
+
}
|
|
13322
|
+
|
|
13323
|
+
if (notes.length === 0) {
|
|
13324
|
+
return `Architecture build failed via ${platform}. ${combined}`.trim();
|
|
13325
|
+
}
|
|
13326
|
+
|
|
13327
|
+
return [
|
|
13328
|
+
`Architecture build failed via ${platform}.`,
|
|
13329
|
+
...notes.map((note) => `- ${note}`),
|
|
13330
|
+
combined ? `Raw CLI output:\n${combined}` : "",
|
|
13331
|
+
]
|
|
13332
|
+
.filter(Boolean)
|
|
13333
|
+
.join("\n");
|
|
13334
|
+
}
|
|
13335
|
+
|
|
12625
13336
|
async function probeArchitectureAdapter(platform, cwd) {
|
|
12626
13337
|
if (platform === "codex") {
|
|
12627
13338
|
const help = await execFileCapture("codex", ["exec", "--help"], { cwd });
|
|
@@ -12710,9 +13421,8 @@ async function probeArchitectureAdapter(platform, cwd) {
|
|
|
12710
13421
|
function normalizeArchitectureResult({
|
|
12711
13422
|
stdout,
|
|
12712
13423
|
workspaceRoot,
|
|
12713
|
-
rulesPath,
|
|
12714
|
-
techPath,
|
|
12715
13424
|
researchMode,
|
|
13425
|
+
changedFiles = [],
|
|
12716
13426
|
}) {
|
|
12717
13427
|
const trimmed = String(stdout || "").trim();
|
|
12718
13428
|
if (trimmed) {
|
|
@@ -12722,9 +13432,12 @@ function normalizeArchitectureResult({
|
|
|
12722
13432
|
const parsed = JSON.parse(lastLine);
|
|
12723
13433
|
return {
|
|
12724
13434
|
outputRoot: workspaceRoot,
|
|
12725
|
-
filesWritten:
|
|
12726
|
-
|
|
12727
|
-
|
|
13435
|
+
filesWritten:
|
|
13436
|
+
changedFiles.length > 0
|
|
13437
|
+
? changedFiles
|
|
13438
|
+
: Array.isArray(parsed.files_written)
|
|
13439
|
+
? parsed.files_written
|
|
13440
|
+
: [],
|
|
12728
13441
|
researchUsed:
|
|
12729
13442
|
typeof parsed.research_used === "boolean"
|
|
12730
13443
|
? parsed.research_used
|
|
@@ -12743,7 +13456,7 @@ function normalizeArchitectureResult({
|
|
|
12743
13456
|
|
|
12744
13457
|
return {
|
|
12745
13458
|
outputRoot: workspaceRoot,
|
|
12746
|
-
filesWritten:
|
|
13459
|
+
filesWritten: changedFiles,
|
|
12747
13460
|
researchUsed: researchMode === "always",
|
|
12748
13461
|
gaps: [],
|
|
12749
13462
|
nextActions: [],
|
|
@@ -12751,51 +13464,76 @@ function normalizeArchitectureResult({
|
|
|
12751
13464
|
};
|
|
12752
13465
|
}
|
|
12753
13466
|
|
|
13467
|
+
async function captureFileContents(filePaths) {
|
|
13468
|
+
const snapshot = {};
|
|
13469
|
+
for (const filePath of filePaths) {
|
|
13470
|
+
if (await pathExists(filePath)) {
|
|
13471
|
+
snapshot[filePath] = await readFile(filePath, "utf8");
|
|
13472
|
+
} else {
|
|
13473
|
+
snapshot[filePath] = null;
|
|
13474
|
+
}
|
|
13475
|
+
}
|
|
13476
|
+
return snapshot;
|
|
13477
|
+
}
|
|
13478
|
+
|
|
12754
13479
|
async function readArchitectureDriftStatus(workspaceRoot, snapshot) {
|
|
12755
|
-
const
|
|
12756
|
-
const
|
|
13480
|
+
const productPath = path.join(workspaceRoot, FOUNDATION_DOCS_DIR, "PRODUCT.md");
|
|
13481
|
+
const architecturePath = path.join(
|
|
13482
|
+
workspaceRoot,
|
|
13483
|
+
FOUNDATION_DOCS_DIR,
|
|
13484
|
+
"ARCHITECTURE.md",
|
|
13485
|
+
);
|
|
13486
|
+
const techPath = path.join(workspaceRoot, FOUNDATION_DOCS_DIR, "TECH.md");
|
|
13487
|
+
const adrReadmePath = path.join(workspaceRoot, FOUNDATION_ADR_DIR, "README.md");
|
|
12757
13488
|
const metadataPath = path.join(
|
|
12758
13489
|
workspaceRoot,
|
|
12759
13490
|
".cbx",
|
|
12760
13491
|
ARCHITECTURE_BUILD_METADATA_FILENAME,
|
|
12761
13492
|
);
|
|
12762
|
-
const
|
|
13493
|
+
const productExists = await pathExists(productPath);
|
|
13494
|
+
const architectureExists = await pathExists(architecturePath);
|
|
12763
13495
|
const techExists = await pathExists(techPath);
|
|
12764
|
-
|
|
12765
|
-
const expectedRulesHash = hashStableObject(
|
|
12766
|
-
inferArchitectureContractProfile(snapshot),
|
|
12767
|
-
);
|
|
12768
|
-
const expectedTechHash = hashStableObject({
|
|
12769
|
-
style: inferArchitectureContractProfile(snapshot).style,
|
|
12770
|
-
topDirs: snapshot.topDirs,
|
|
12771
|
-
frameworks: snapshot.frameworks,
|
|
12772
|
-
architectureByApp: snapshot.architectureByApp,
|
|
12773
|
-
});
|
|
13496
|
+
const adrReadmeExists = await pathExists(adrReadmePath);
|
|
12774
13497
|
|
|
12775
13498
|
const findings = [];
|
|
12776
|
-
let
|
|
13499
|
+
let actualProductHash = null;
|
|
13500
|
+
let actualArchitectureHash = null;
|
|
12777
13501
|
let actualTechHash = null;
|
|
12778
13502
|
|
|
12779
|
-
if (!
|
|
12780
|
-
findings.push(
|
|
13503
|
+
if (!productExists) {
|
|
13504
|
+
findings.push(`${FOUNDATION_DOCS_DIR}/PRODUCT.md is missing.`);
|
|
13505
|
+
} else {
|
|
13506
|
+
const content = await readFile(productPath, "utf8");
|
|
13507
|
+
actualProductHash = extractTaggedMarkerAttribute(
|
|
13508
|
+
content,
|
|
13509
|
+
PRODUCT_FOUNDATION_BLOCK_START_RE,
|
|
13510
|
+
"profile",
|
|
13511
|
+
);
|
|
13512
|
+
if (!actualProductHash) {
|
|
13513
|
+
findings.push(
|
|
13514
|
+
`${FOUNDATION_DOCS_DIR}/PRODUCT.md is missing the managed product foundation block.`,
|
|
13515
|
+
);
|
|
13516
|
+
}
|
|
13517
|
+
}
|
|
13518
|
+
|
|
13519
|
+
if (!architectureExists) {
|
|
13520
|
+
findings.push(`${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md is missing.`);
|
|
12781
13521
|
} else {
|
|
12782
|
-
const content = await readFile(
|
|
12783
|
-
|
|
13522
|
+
const content = await readFile(architecturePath, "utf8");
|
|
13523
|
+
actualArchitectureHash = extractTaggedMarkerAttribute(
|
|
12784
13524
|
content,
|
|
12785
|
-
|
|
13525
|
+
ARCHITECTURE_DOC_BLOCK_START_RE,
|
|
12786
13526
|
"profile",
|
|
12787
13527
|
);
|
|
12788
|
-
if (!
|
|
12789
|
-
findings.push("ENGINEERING_RULES.md is missing the managed architecture contract block.");
|
|
12790
|
-
} else if (actualRulesHash !== expectedRulesHash) {
|
|
13528
|
+
if (!actualArchitectureHash) {
|
|
12791
13529
|
findings.push(
|
|
12792
|
-
|
|
13530
|
+
`${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md is missing the managed architecture backbone block.`,
|
|
12793
13531
|
);
|
|
12794
13532
|
}
|
|
12795
13533
|
}
|
|
12796
13534
|
|
|
12797
13535
|
if (!techExists) {
|
|
12798
|
-
findings.push(
|
|
13536
|
+
findings.push(`${FOUNDATION_DOCS_DIR}/TECH.md is missing.`);
|
|
12799
13537
|
} else {
|
|
12800
13538
|
const content = await readFile(techPath, "utf8");
|
|
12801
13539
|
actualTechHash = extractTaggedMarkerAttribute(
|
|
@@ -12804,14 +13542,16 @@ async function readArchitectureDriftStatus(workspaceRoot, snapshot) {
|
|
|
12804
13542
|
"snapshot",
|
|
12805
13543
|
);
|
|
12806
13544
|
if (!actualTechHash) {
|
|
12807
|
-
findings.push("TECH.md is missing the managed architecture snapshot block.");
|
|
12808
|
-
} else if (actualTechHash !== expectedTechHash) {
|
|
12809
13545
|
findings.push(
|
|
12810
|
-
|
|
13546
|
+
`${FOUNDATION_DOCS_DIR}/TECH.md is missing the managed architecture snapshot block.`,
|
|
12811
13547
|
);
|
|
12812
13548
|
}
|
|
12813
13549
|
}
|
|
12814
13550
|
|
|
13551
|
+
if (!adrReadmeExists) {
|
|
13552
|
+
findings.push(`${FOUNDATION_ADR_DIR}/README.md is missing.`);
|
|
13553
|
+
}
|
|
13554
|
+
|
|
12815
13555
|
const metadata = await readJsonFileIfExists(metadataPath);
|
|
12816
13556
|
if (!metadata.exists) {
|
|
12817
13557
|
findings.push("Architecture build metadata is missing.");
|
|
@@ -12820,12 +13560,13 @@ async function readArchitectureDriftStatus(workspaceRoot, snapshot) {
|
|
|
12820
13560
|
return {
|
|
12821
13561
|
stale: findings.length > 0,
|
|
12822
13562
|
findings,
|
|
12823
|
-
|
|
13563
|
+
productPath,
|
|
13564
|
+
architecturePath,
|
|
12824
13565
|
techPath,
|
|
13566
|
+
adrReadmePath,
|
|
12825
13567
|
metadataPath,
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
actualRulesHash,
|
|
13568
|
+
actualProductHash,
|
|
13569
|
+
actualArchitectureHash,
|
|
12829
13570
|
actualTechHash,
|
|
12830
13571
|
};
|
|
12831
13572
|
}
|
|
@@ -12841,6 +13582,7 @@ async function runBuildArchitecture(options) {
|
|
|
12841
13582
|
const cwd = process.cwd();
|
|
12842
13583
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
12843
13584
|
const snapshot = await collectTechSnapshot(workspaceRoot);
|
|
13585
|
+
const specRoots = await listSpecPackRoots(workspaceRoot);
|
|
12844
13586
|
|
|
12845
13587
|
if (checkOnly) {
|
|
12846
13588
|
const drift = await readArchitectureDriftStatus(workspaceRoot, snapshot);
|
|
@@ -12850,6 +13592,9 @@ async function runBuildArchitecture(options) {
|
|
|
12850
13592
|
console.log(`Platform: ${platform}`);
|
|
12851
13593
|
console.log(`Workspace: ${toPosixPath(workspaceRoot)}`);
|
|
12852
13594
|
console.log(`Status: ${drift.stale ? "stale" : "fresh"}`);
|
|
13595
|
+
console.log(
|
|
13596
|
+
`Backbone docs: ${FOUNDATION_DOCS_DIR}/PRODUCT.md, ${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md, ${FOUNDATION_DOCS_DIR}/TECH.md, ${FOUNDATION_ADR_DIR}/README.md`,
|
|
13597
|
+
);
|
|
12853
13598
|
if (drift.findings.length > 0) {
|
|
12854
13599
|
console.log("Findings:");
|
|
12855
13600
|
for (const finding of drift.findings) {
|
|
@@ -12861,13 +13606,13 @@ async function runBuildArchitecture(options) {
|
|
|
12861
13606
|
return;
|
|
12862
13607
|
}
|
|
12863
13608
|
|
|
12864
|
-
const
|
|
12865
|
-
workspaceRoot,
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
13609
|
+
const managedFilePaths = [
|
|
13610
|
+
path.join(workspaceRoot, FOUNDATION_DOCS_DIR, "PRODUCT.md"),
|
|
13611
|
+
path.join(workspaceRoot, FOUNDATION_DOCS_DIR, "ARCHITECTURE.md"),
|
|
13612
|
+
path.join(workspaceRoot, FOUNDATION_DOCS_DIR, "TECH.md"),
|
|
13613
|
+
path.join(workspaceRoot, FOUNDATION_ADR_DIR, "README.md"),
|
|
13614
|
+
path.join(workspaceRoot, FOUNDATION_ADR_DIR, "0000-template.md"),
|
|
13615
|
+
];
|
|
12871
13616
|
const coreSkills = [
|
|
12872
13617
|
"architecture-doc",
|
|
12873
13618
|
"system-design",
|
|
@@ -12885,11 +13630,17 @@ async function runBuildArchitecture(options) {
|
|
|
12885
13630
|
cwd,
|
|
12886
13631
|
skillBundle,
|
|
12887
13632
|
);
|
|
13633
|
+
const inspectionAnchors = await resolveArchitectureInspectionAnchors(
|
|
13634
|
+
workspaceRoot,
|
|
13635
|
+
snapshot,
|
|
13636
|
+
specRoots,
|
|
13637
|
+
);
|
|
12888
13638
|
const prompt = buildArchitecturePrompt({
|
|
12889
13639
|
platform,
|
|
12890
13640
|
workspaceRoot,
|
|
12891
13641
|
snapshot,
|
|
12892
13642
|
specRoots,
|
|
13643
|
+
inspectionAnchors,
|
|
12893
13644
|
researchMode,
|
|
12894
13645
|
coreSkills,
|
|
12895
13646
|
conditionalSkills,
|
|
@@ -12897,6 +13648,13 @@ async function runBuildArchitecture(options) {
|
|
|
12897
13648
|
});
|
|
12898
13649
|
const adapter = await probeArchitectureAdapter(platform, workspaceRoot);
|
|
12899
13650
|
const args = adapter.buildInvocation(prompt);
|
|
13651
|
+
const managedTargets = [
|
|
13652
|
+
path.join(workspaceRoot, FOUNDATION_DOCS_DIR, "PRODUCT.md"),
|
|
13653
|
+
path.join(workspaceRoot, FOUNDATION_DOCS_DIR, "ARCHITECTURE.md"),
|
|
13654
|
+
path.join(workspaceRoot, FOUNDATION_DOCS_DIR, "TECH.md"),
|
|
13655
|
+
path.join(workspaceRoot, FOUNDATION_ADR_DIR, "README.md"),
|
|
13656
|
+
path.join(workspaceRoot, FOUNDATION_ADR_DIR, "0000-template.md"),
|
|
13657
|
+
].map((filePath) => toPosixPath(filePath));
|
|
12900
13658
|
|
|
12901
13659
|
if (dryRun) {
|
|
12902
13660
|
const summary = {
|
|
@@ -12905,10 +13663,7 @@ async function runBuildArchitecture(options) {
|
|
|
12905
13663
|
adapter: adapter.binary,
|
|
12906
13664
|
invocation: [adapter.binary, ...args],
|
|
12907
13665
|
researchMode,
|
|
12908
|
-
managedTargets
|
|
12909
|
-
toPosixPath(scaffold.engineeringRulesPath),
|
|
12910
|
-
toPosixPath(scaffold.techMdPath),
|
|
12911
|
-
],
|
|
13666
|
+
managedTargets,
|
|
12912
13667
|
skillBundle,
|
|
12913
13668
|
};
|
|
12914
13669
|
if (emitJson) {
|
|
@@ -12919,7 +13674,7 @@ async function runBuildArchitecture(options) {
|
|
|
12919
13674
|
console.log(`Adapter: ${adapter.binary}`);
|
|
12920
13675
|
console.log(`Research mode: ${researchMode}`);
|
|
12921
13676
|
console.log(
|
|
12922
|
-
`Managed targets: ${
|
|
13677
|
+
`Managed targets: ${summary.managedTargets.join(", ")}`,
|
|
12923
13678
|
);
|
|
12924
13679
|
console.log(`Skill bundle: ${skillBundle.join(", ")}`);
|
|
12925
13680
|
console.log(`Invocation: ${[adapter.binary, ...args].join(" ")}`);
|
|
@@ -12927,18 +13682,38 @@ async function runBuildArchitecture(options) {
|
|
|
12927
13682
|
return;
|
|
12928
13683
|
}
|
|
12929
13684
|
|
|
12930
|
-
const
|
|
13685
|
+
const filesBefore = await captureFileContents(managedFilePaths);
|
|
13686
|
+
const scaffold = await ensureArchitectureBuildScaffold({
|
|
13687
|
+
workspaceRoot,
|
|
13688
|
+
dryRun,
|
|
13689
|
+
});
|
|
13690
|
+
|
|
13691
|
+
if (!emitJson) {
|
|
13692
|
+
console.log(`Streaming ${adapter.binary} output...`);
|
|
13693
|
+
}
|
|
13694
|
+
|
|
13695
|
+
const execution = await spawnCapture(adapter.binary, args, {
|
|
12931
13696
|
cwd: workspaceRoot,
|
|
12932
13697
|
env: process.env,
|
|
13698
|
+
streamOutput: !emitJson,
|
|
12933
13699
|
});
|
|
12934
13700
|
if (!execution.ok) {
|
|
12935
|
-
throw new Error(
|
|
12936
|
-
`Architecture build failed via ${adapter.binary}. ${String(execution.stderr || execution.stdout || "").trim()}`,
|
|
12937
|
-
);
|
|
13701
|
+
throw new Error(explainArchitectureBuildFailure(platform, execution));
|
|
12938
13702
|
}
|
|
12939
13703
|
|
|
12940
|
-
const
|
|
12941
|
-
const
|
|
13704
|
+
const filesAfter = await captureFileContents(managedFilePaths);
|
|
13705
|
+
const changedFiles = managedFilePaths
|
|
13706
|
+
.filter((filePath) => filesBefore[filePath] !== filesAfter[filePath])
|
|
13707
|
+
.map((filePath) => toPosixPath(path.relative(workspaceRoot, filePath)));
|
|
13708
|
+
|
|
13709
|
+
const techContent =
|
|
13710
|
+
filesAfter[scaffold.techMdPath] ?? (await readFile(scaffold.techMdPath, "utf8"));
|
|
13711
|
+
const productContent =
|
|
13712
|
+
filesAfter[scaffold.productPath] ?? (await readFile(scaffold.productPath, "utf8"));
|
|
13713
|
+
const architectureContent =
|
|
13714
|
+
filesAfter[scaffold.architectureDocPath] ??
|
|
13715
|
+
(await readFile(scaffold.architectureDocPath, "utf8"));
|
|
13716
|
+
|
|
12942
13717
|
const metadataPath = path.join(
|
|
12943
13718
|
workspaceRoot,
|
|
12944
13719
|
".cbx",
|
|
@@ -12947,18 +13722,13 @@ async function runBuildArchitecture(options) {
|
|
|
12947
13722
|
const metadata = buildArchitectureBuildMetadata({
|
|
12948
13723
|
platform,
|
|
12949
13724
|
researchMode,
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
extractTaggedMarkerAttribute(
|
|
12958
|
-
techContent,
|
|
12959
|
-
TECH_ARCHITECTURE_BLOCK_START_RE,
|
|
12960
|
-
"snapshot",
|
|
12961
|
-
) || "unknown",
|
|
13725
|
+
managedDocs: [
|
|
13726
|
+
`${FOUNDATION_DOCS_DIR}/PRODUCT.md`,
|
|
13727
|
+
`${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md`,
|
|
13728
|
+
`${FOUNDATION_DOCS_DIR}/TECH.md`,
|
|
13729
|
+
`${FOUNDATION_ADR_DIR}/README.md`,
|
|
13730
|
+
`${FOUNDATION_ADR_DIR}/0000-template.md`,
|
|
13731
|
+
],
|
|
12962
13732
|
});
|
|
12963
13733
|
await mkdir(path.dirname(metadataPath), { recursive: true });
|
|
12964
13734
|
await writeFile(
|
|
@@ -12970,9 +13740,8 @@ async function runBuildArchitecture(options) {
|
|
|
12970
13740
|
const result = normalizeArchitectureResult({
|
|
12971
13741
|
stdout: execution.stdout,
|
|
12972
13742
|
workspaceRoot: toPosixPath(workspaceRoot),
|
|
12973
|
-
rulesPath: "ENGINEERING_RULES.md",
|
|
12974
|
-
techPath: "TECH.md",
|
|
12975
13743
|
researchMode,
|
|
13744
|
+
changedFiles: [...new Set(changedFiles)],
|
|
12976
13745
|
});
|
|
12977
13746
|
|
|
12978
13747
|
if (emitJson) {
|
|
@@ -12994,7 +13763,12 @@ async function runBuildArchitecture(options) {
|
|
|
12994
13763
|
console.log(`Platform: ${platform}`);
|
|
12995
13764
|
console.log(`Adapter: ${adapter.binary}`);
|
|
12996
13765
|
console.log(`Workspace: ${toPosixPath(workspaceRoot)}`);
|
|
12997
|
-
console.log(
|
|
13766
|
+
console.log(
|
|
13767
|
+
`Managed docs: ${FOUNDATION_DOCS_DIR}/PRODUCT.md, ${FOUNDATION_DOCS_DIR}/ARCHITECTURE.md, ${FOUNDATION_DOCS_DIR}/TECH.md`,
|
|
13768
|
+
);
|
|
13769
|
+
console.log(
|
|
13770
|
+
`ADR scaffold: ${FOUNDATION_ADR_DIR}/README.md, ${FOUNDATION_ADR_DIR}/0000-template.md`,
|
|
13771
|
+
);
|
|
12998
13772
|
console.log(`Skill bundle: ${skillBundle.join(", ")}`);
|
|
12999
13773
|
console.log(
|
|
13000
13774
|
`Files written: ${(result.filesWritten || []).join(", ") || "(none reported)"}`,
|
|
@@ -13273,11 +14047,7 @@ async function runInitWizard(options) {
|
|
|
13273
14047
|
dryRun: installOutcome.dryRun,
|
|
13274
14048
|
});
|
|
13275
14049
|
printRuleSyncResult(installOutcome.syncResult);
|
|
13276
|
-
|
|
13277
|
-
engineeringResults:
|
|
13278
|
-
installOutcome.engineeringArtifactsResult.engineeringResults,
|
|
13279
|
-
techResult: installOutcome.engineeringArtifactsResult.techResult,
|
|
13280
|
-
});
|
|
14050
|
+
printInstallDocumentationNotice();
|
|
13281
14051
|
printPostmanSetupSummary({
|
|
13282
14052
|
postmanSetup: installOutcome.postmanSetupResult,
|
|
13283
14053
|
});
|