@cubis/foundry 0.3.77 → 0.3.79
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 +19 -0
- package/dist/cli/build/commands.js.map +1 -0
- package/dist/cli/commands/register.js +4 -0
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/core.js +1355 -46
- package/dist/cli/core.js.map +1 -1
- package/package.json +4 -3
- package/src/cli/build/commands.ts +39 -0
- package/src/cli/commands/register.ts +6 -0
- package/src/cli/core.ts +1689 -75
- package/workflows/skills/generated/skill-audit.json +11 -2
- package/workflows/skills/generated/skill-catalog.json +38 -4
- package/workflows/skills/skills_index.json +34 -0
- package/workflows/skills/spec-driven-delivery/SKILL.md +63 -0
- package/workflows/workflows/agent-environment-setup/generated/route-manifest.json +117 -4
- package/workflows/workflows/agent-environment-setup/manifest.json +21 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/accessibility.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/architecture.toml +19 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/backend.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/create.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/database.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/debug.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/devops.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/implement-track.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/migrate.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/mobile.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/onboard.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/orchestrate.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/plan.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/refactor.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/release.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/review.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/security.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/spec.toml +19 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/test.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/commands/vercel.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md +3 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/skills/spec-driven-delivery/SKILL.md +65 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/architecture.md +87 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/backend.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/create.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/database.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/implement-track.md +7 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/migrate.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/mobile.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/onboard.md +1 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/orchestrate.md +6 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/plan.md +12 -2
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/refactor.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/release.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/spec.md +81 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/rules/CLAUDE.md +3 -1
- package/workflows/workflows/agent-environment-setup/platforms/claude/skills/skills_index.json +34 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/skills/spec-driven-delivery/SKILL.md +66 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/architecture.md +85 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/backend.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/create.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/database.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/implement-track.md +7 -1
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/migrate.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/mobile.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/onboard.md +1 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/orchestrate.md +6 -1
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/plan.md +12 -2
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/refactor.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/release.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/claude/workflows/spec.md +79 -0
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/backend-specialist.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/code-archaeologist.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/database-architect.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/debugger.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/devops-engineer.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/documentation-writer.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/frontend-specialist.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/game-developer.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/mobile-developer.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/orchestrator.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/penetration-tester.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/performance-optimizer.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/product-manager.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/project-planner.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/qa-automation-engineer.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/researcher.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/security-auditor.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/seo-specialist.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/sre-engineer.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/test-engineer.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/validator.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/agents/vercel-expert.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md +3 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/skills/spec-driven-delivery/SKILL.md +65 -0
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/accessibility.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/architecture.md +87 -0
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/backend.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/create.md +5 -2
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/database.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/debug.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/devops.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/implement-track.md +8 -2
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/migrate.md +5 -2
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/mobile.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/onboard.md +2 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/orchestrate.md +7 -2
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/plan.md +13 -3
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/refactor.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/release.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/review.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/security.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/spec.md +81 -0
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/test.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/vercel.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-accessibility.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-architecture.prompt.md +18 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-backend.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-create.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-database.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-debug.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-devops.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-implement-track.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-migrate.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-mobile.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-onboard.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-orchestrate.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-plan.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-refactor.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-release.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-review.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-security.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-spec.prompt.md +18 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-test.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/prompts/workflow-vercel.prompt.md +9 -4
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +3 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/skills_index.json +34 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/spec-driven-delivery/SKILL.md +70 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/architecture.md +85 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/backend.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/create.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/database.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/implement-track.md +7 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/migrate.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/mobile.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/onboard.md +1 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/orchestrate.md +6 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/plan.md +12 -2
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/refactor.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/release.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/spec.md +79 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/accessibility.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/architecture.toml +19 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/backend.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/create.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/database.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/debug.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/devops.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/implement-track.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/migrate.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/mobile.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/onboard.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/orchestrate.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/plan.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/refactor.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/release.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/review.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/security.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/spec.toml +19 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/test.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/commands/vercel.toml +10 -5
- package/workflows/workflows/agent-environment-setup/platforms/gemini/rules/GEMINI.md +3 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/skills/spec-driven-delivery/SKILL.md +65 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/architecture.md +87 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/backend.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/create.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/database.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/implement-track.md +7 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/migrate.md +4 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/mobile.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/onboard.md +1 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/orchestrate.md +6 -1
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/plan.md +12 -2
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/refactor.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/release.md +3 -0
- package/workflows/workflows/agent-environment-setup/platforms/gemini/workflows/spec.md +81 -0
- package/workflows/workflows/agent-environment-setup/shared/agents/backend-specialist.md +5 -5
- package/workflows/workflows/agent-environment-setup/shared/agents/database-architect.md +3 -3
- package/workflows/workflows/agent-environment-setup/shared/agents/orchestrator.md +8 -8
- package/workflows/workflows/agent-environment-setup/shared/agents/penetration-tester.md +3 -3
- package/workflows/workflows/agent-environment-setup/shared/agents/product-manager.md +6 -6
- package/workflows/workflows/agent-environment-setup/shared/agents/project-planner.md +8 -8
- package/workflows/workflows/agent-environment-setup/shared/agents/researcher.md +8 -8
- package/workflows/workflows/agent-environment-setup/shared/agents/security-auditor.md +2 -2
- package/workflows/workflows/agent-environment-setup/shared/rules/STEERING.md +13 -0
- package/workflows/workflows/agent-environment-setup/shared/rules/overrides/codex.md +3 -3
- package/workflows/workflows/agent-environment-setup/shared/workflows/architecture.md +85 -0
- package/workflows/workflows/agent-environment-setup/shared/workflows/backend.md +8 -5
- package/workflows/workflows/agent-environment-setup/shared/workflows/create.md +5 -2
- package/workflows/workflows/agent-environment-setup/shared/workflows/database.md +6 -3
- package/workflows/workflows/agent-environment-setup/shared/workflows/implement-track.md +12 -6
- package/workflows/workflows/agent-environment-setup/shared/workflows/migrate.md +7 -4
- package/workflows/workflows/agent-environment-setup/shared/workflows/mobile.md +3 -0
- package/workflows/workflows/agent-environment-setup/shared/workflows/onboard.md +6 -5
- package/workflows/workflows/agent-environment-setup/shared/workflows/orchestrate.md +10 -5
- package/workflows/workflows/agent-environment-setup/shared/workflows/plan.md +17 -7
- package/workflows/workflows/agent-environment-setup/shared/workflows/refactor.md +5 -2
- package/workflows/workflows/agent-environment-setup/shared/workflows/release.md +3 -0
- package/workflows/workflows/agent-environment-setup/shared/workflows/security.md +2 -2
- package/workflows/workflows/agent-environment-setup/shared/workflows/spec.md +79 -0
package/src/cli/core.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
symlink,
|
|
15
15
|
writeFile,
|
|
16
16
|
} from "node:fs/promises";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
17
18
|
import { createRequire } from "node:module";
|
|
18
19
|
import { spawn, execFile as execFileCallback } from "node:child_process";
|
|
19
20
|
import os from "node:os";
|
|
@@ -60,6 +61,26 @@ const ENGINEERING_RULES_FILE_BLOCK_START_RE =
|
|
|
60
61
|
/<!--\s*cbx:engineering:rules:start[^>]*-->/g;
|
|
61
62
|
const ENGINEERING_RULES_FILE_BLOCK_END_RE =
|
|
62
63
|
/<!--\s*cbx:engineering:rules:end\s*-->/g;
|
|
64
|
+
const ENGINEERING_ARCHITECTURE_BLOCK_START_RE =
|
|
65
|
+
/<!--\s*cbx:architecture:rules:start[^>]*-->/g;
|
|
66
|
+
const ENGINEERING_ARCHITECTURE_BLOCK_END_RE =
|
|
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;
|
|
76
|
+
const TECH_ARCHITECTURE_BLOCK_START_RE =
|
|
77
|
+
/<!--\s*cbx:architecture:tech:start[^>]*-->/g;
|
|
78
|
+
const TECH_ARCHITECTURE_BLOCK_END_RE =
|
|
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;
|
|
63
84
|
const COPILOT_ALLOWED_SKILL_FRONTMATTER_KEYS = new Set([
|
|
64
85
|
"compatibility",
|
|
65
86
|
"description",
|
|
@@ -253,11 +274,18 @@ const PLAYWRIGHT_MCP_URL = `http://localhost:${PLAYWRIGHT_DEFAULT_PORT}/mcp`;
|
|
|
253
274
|
const POSTMAN_WORKSPACE_MANUAL_CHOICE = "__postman_workspace_manual__";
|
|
254
275
|
const CBX_CONFIG_FILENAME = "cbx_config.json";
|
|
255
276
|
const CBX_CREDENTIALS_ENV_FILENAME = "credentials.env";
|
|
277
|
+
const ARCHITECTURE_BUILD_METADATA_FILENAME = "architecture-build.json";
|
|
256
278
|
const LEGACY_POSTMAN_CONFIG_FILENAME = ["postman", "setting.json"].join("_");
|
|
257
279
|
const DEFAULT_CREDENTIAL_PROFILE_NAME = "default";
|
|
258
280
|
const RESERVED_CREDENTIAL_PROFILE_NAMES = new Set(["all"]);
|
|
259
281
|
const CREDENTIAL_SERVICES = new Set(["postman", "stitch"]);
|
|
260
282
|
const MCP_RUNTIMES = new Set(["docker", "local"]);
|
|
283
|
+
const ARCHITECTURE_BUILD_PLATFORMS = new Set([
|
|
284
|
+
"codex",
|
|
285
|
+
"claude",
|
|
286
|
+
"gemini",
|
|
287
|
+
"copilot",
|
|
288
|
+
]);
|
|
261
289
|
const MCP_FALLBACKS = new Set(["local", "fail", "skip"]);
|
|
262
290
|
const MCP_UPDATE_POLICIES = new Set(["pinned", "latest"]);
|
|
263
291
|
const DEFAULT_MCP_RUNTIME = "docker";
|
|
@@ -957,6 +985,464 @@ function buildAntigravityTerminalVerificationBlock({
|
|
|
957
985
|
].join("\n");
|
|
958
986
|
}
|
|
959
987
|
|
|
988
|
+
function hashStableObject(value) {
|
|
989
|
+
return createHash("sha256")
|
|
990
|
+
.update(JSON.stringify(value))
|
|
991
|
+
.digest("hex")
|
|
992
|
+
.slice(0, 12);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function firstNonEmpty(values) {
|
|
996
|
+
for (const value of values || []) {
|
|
997
|
+
if (value) return value;
|
|
998
|
+
}
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function inferArchitectureContractProfile(snapshot) {
|
|
1003
|
+
const allSignals = [];
|
|
1004
|
+
for (const app of snapshot.architectureByApp || []) {
|
|
1005
|
+
for (const signal of app.architectureSignals || []) {
|
|
1006
|
+
if (!allSignals.includes(signal)) allSignals.push(signal);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const style =
|
|
1011
|
+
firstNonEmpty([
|
|
1012
|
+
allSignals.find((signal) => /clean architecture/i.test(signal)),
|
|
1013
|
+
allSignals.find((signal) => /hexagonal/i.test(signal)),
|
|
1014
|
+
allSignals.find((signal) => /feature-first/i.test(signal)),
|
|
1015
|
+
allSignals.find((signal) => /mvvm/i.test(signal)),
|
|
1016
|
+
allSignals.find((signal) => /mvc/i.test(signal)),
|
|
1017
|
+
]) || "Undeclared — align to current repository conventions";
|
|
1018
|
+
|
|
1019
|
+
const moduleBoundaries = snapshot.annotatedDirs
|
|
1020
|
+
.slice(0, 8)
|
|
1021
|
+
.map(({ name, purpose }) =>
|
|
1022
|
+
purpose ? `${name}/ (${purpose})` : `${name}/`,
|
|
1023
|
+
);
|
|
1024
|
+
|
|
1025
|
+
const dependencyRules = [];
|
|
1026
|
+
if (/clean architecture/i.test(style)) {
|
|
1027
|
+
dependencyRules.push(
|
|
1028
|
+
"Domain code must not depend on infrastructure, framework, or presentation layers.",
|
|
1029
|
+
);
|
|
1030
|
+
dependencyRules.push(
|
|
1031
|
+
"Adapters may depend inward on domain contracts, never the reverse.",
|
|
1032
|
+
);
|
|
1033
|
+
} else if (/hexagonal/i.test(style)) {
|
|
1034
|
+
dependencyRules.push(
|
|
1035
|
+
"Ports stay stable at the boundary; adapters implement them without leaking infrastructure concerns inward.",
|
|
1036
|
+
);
|
|
1037
|
+
} else {
|
|
1038
|
+
dependencyRules.push(
|
|
1039
|
+
"Respect existing feature and package boundaries; avoid hidden cross-module imports.",
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
dependencyRules.push(
|
|
1043
|
+
"Shared UI, data, and platform conventions belong in the declared common layer, not duplicated per feature.",
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1046
|
+
const designSystemSource =
|
|
1047
|
+
firstNonEmpty([
|
|
1048
|
+
snapshot.topDirs.includes("components")
|
|
1049
|
+
? "components/ is the primary shared UI surface."
|
|
1050
|
+
: null,
|
|
1051
|
+
snapshot.topDirs.includes("design-system")
|
|
1052
|
+
? "design-system/ is the primary shared UI surface."
|
|
1053
|
+
: null,
|
|
1054
|
+
snapshot.topDirs.includes("app")
|
|
1055
|
+
? "App-level UI patterns should be centralized and reused across screens."
|
|
1056
|
+
: null,
|
|
1057
|
+
]) || "No dedicated design-system directory detected; infer shared UI rules from current components and screens.";
|
|
1058
|
+
|
|
1059
|
+
const testingStrategy = [];
|
|
1060
|
+
if (snapshot.keyScripts.some((script) => script.name === "lint")) {
|
|
1061
|
+
testingStrategy.push("Linting is part of the baseline quality gate.");
|
|
1062
|
+
}
|
|
1063
|
+
if (snapshot.keyScripts.some((script) => script.name.includes("type"))) {
|
|
1064
|
+
testingStrategy.push(
|
|
1065
|
+
"Static typing or analysis should pass before merge when the stack supports it.",
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
if (snapshot.keyScripts.some((script) => script.name.startsWith("test"))) {
|
|
1069
|
+
testingStrategy.push(
|
|
1070
|
+
"Focused automated tests should cover the changed behavior before merge.",
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
if (testingStrategy.length === 0) {
|
|
1074
|
+
testingStrategy.push(
|
|
1075
|
+
"Add focused validation evidence for non-trivial changes even when no standard test command is detected.",
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const scalingConstraints = inferTechnicalConstraints(snapshot, {
|
|
1080
|
+
suggestedProfile: inferContextBudget(snapshot).suggestedProfile,
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
const docUpdateTriggers = [
|
|
1084
|
+
"feature additions that change module boundaries or shared UI rules",
|
|
1085
|
+
"migrations, scale changes, or deployment-shape changes",
|
|
1086
|
+
"refactors that establish new conventions future work should follow",
|
|
1087
|
+
];
|
|
1088
|
+
|
|
1089
|
+
return {
|
|
1090
|
+
style,
|
|
1091
|
+
moduleBoundaries,
|
|
1092
|
+
dependencyRules,
|
|
1093
|
+
designSystemSource,
|
|
1094
|
+
testingStrategy,
|
|
1095
|
+
scalingConstraints,
|
|
1096
|
+
docUpdateTriggers,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function buildArchitectureMermaid(snapshot) {
|
|
1101
|
+
const topDirs = snapshot.topDirs.slice(0, 4);
|
|
1102
|
+
const flowNodes = topDirs.length > 0 ? topDirs : ["src", "docs"];
|
|
1103
|
+
const lines = [
|
|
1104
|
+
"flowchart TD",
|
|
1105
|
+
' user["User / Entry Point"] --> app["Application Surface"]',
|
|
1106
|
+
];
|
|
1107
|
+
for (let index = 0; index < flowNodes.length; index += 1) {
|
|
1108
|
+
const nodeName = flowNodes[index].replace(/[^A-Za-z0-9]/g, "") || `N${index}`;
|
|
1109
|
+
lines.push(` app --> ${nodeName}["${flowNodes[index]}/"]`);
|
|
1110
|
+
}
|
|
1111
|
+
if (snapshot.isMcpServer || snapshot.mcpSignals.length > 0) {
|
|
1112
|
+
lines.push(' app --> mcp["MCP / Tooling Surface"]');
|
|
1113
|
+
}
|
|
1114
|
+
if (snapshot.cicdSignals.length > 0) {
|
|
1115
|
+
lines.push(' app --> deploy["Delivery / Runtime"]');
|
|
1116
|
+
}
|
|
1117
|
+
return lines.join("\n");
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function inferProductFoundationProfile(snapshot, specRoots = []) {
|
|
1121
|
+
const appRoots = (snapshot.architectureByApp || [])
|
|
1122
|
+
.map((item) => item.rootPath)
|
|
1123
|
+
.filter((value) => value && value !== ".");
|
|
1124
|
+
const primarySurfaces = appRoots.length > 0 ? appRoots : snapshot.topDirs.slice(0, 6);
|
|
1125
|
+
const userPersonas = [];
|
|
1126
|
+
|
|
1127
|
+
if (snapshot.frameworks.includes("Flutter")) {
|
|
1128
|
+
userPersonas.push("End users interacting through mobile or desktop app surfaces");
|
|
1129
|
+
}
|
|
1130
|
+
if (
|
|
1131
|
+
snapshot.frameworks.includes("Next.js") ||
|
|
1132
|
+
snapshot.frameworks.includes("React") ||
|
|
1133
|
+
snapshot.topDirs.includes("web")
|
|
1134
|
+
) {
|
|
1135
|
+
userPersonas.push("Browser users and internal operators using web-facing flows");
|
|
1136
|
+
}
|
|
1137
|
+
if (snapshot.frameworks.includes("NestJS") || snapshot.topDirs.includes("api")) {
|
|
1138
|
+
userPersonas.push("Internal services, operators, or partner systems consuming API boundaries");
|
|
1139
|
+
}
|
|
1140
|
+
if (userPersonas.length === 0) {
|
|
1141
|
+
userPersonas.push(
|
|
1142
|
+
"Project stakeholders are not obvious from the repo alone; refine personas from product context before major feature work.",
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const coreJourneys = [];
|
|
1147
|
+
if (specRoots.length > 0) {
|
|
1148
|
+
coreJourneys.push(
|
|
1149
|
+
`Active implementation themes are reflected in current spec packs: ${specRoots.join(", ")}.`,
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
if (primarySurfaces.length > 0) {
|
|
1153
|
+
coreJourneys.push(
|
|
1154
|
+
`Primary product surfaces currently live in: ${primarySurfaces.join(", ")}.`,
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
if (snapshot.isMcpServer || snapshot.mcpSignals.length > 0) {
|
|
1158
|
+
coreJourneys.push("Tool-assisted and MCP-driven workflows are part of the operating model and should stay stable.");
|
|
1159
|
+
}
|
|
1160
|
+
if (coreJourneys.length === 0) {
|
|
1161
|
+
coreJourneys.push(
|
|
1162
|
+
"Repository evidence is thin; capture the primary user journeys here before scaling the codebase further.",
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const successSignals = [
|
|
1167
|
+
"Feature work should stay aligned to explicit user or operator value, not speculative abstractions.",
|
|
1168
|
+
"Architecture changes should reduce onboarding, debugging, and testing cost over time.",
|
|
1169
|
+
];
|
|
1170
|
+
if (snapshot.cicdSignals.length > 0) {
|
|
1171
|
+
successSignals.push(
|
|
1172
|
+
`Delivery flows already rely on ${snapshot.cicdSignals.join(", ")} signals; keep release friction low for that pipeline.`,
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
return {
|
|
1177
|
+
productScope:
|
|
1178
|
+
snapshot.readmeExcerpt ||
|
|
1179
|
+
"No explicit product summary was detected from repo docs. Replace this with a concise product statement when better context exists.",
|
|
1180
|
+
primarySurfaces,
|
|
1181
|
+
userPersonas,
|
|
1182
|
+
coreJourneys,
|
|
1183
|
+
successSignals,
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function buildProductFoundationSection(snapshot, specRoots = []) {
|
|
1188
|
+
const profile = inferProductFoundationProfile(snapshot, specRoots);
|
|
1189
|
+
const hash = hashStableObject(profile);
|
|
1190
|
+
|
|
1191
|
+
return [
|
|
1192
|
+
`<!-- cbx:product:foundation:start version=1 profile=${hash} -->`,
|
|
1193
|
+
"## Product Foundation (auto-managed)",
|
|
1194
|
+
"",
|
|
1195
|
+
"### Product Scope",
|
|
1196
|
+
profile.productScope,
|
|
1197
|
+
"",
|
|
1198
|
+
"### Primary Surfaces",
|
|
1199
|
+
...(profile.primarySurfaces.length > 0
|
|
1200
|
+
? profile.primarySurfaces.map((item) => `- ${item}`)
|
|
1201
|
+
: ["- No primary surfaces were detected automatically."]),
|
|
1202
|
+
"",
|
|
1203
|
+
"### Users and Operators",
|
|
1204
|
+
...profile.userPersonas.map((item) => `- ${item}`),
|
|
1205
|
+
"",
|
|
1206
|
+
"### Core Journeys",
|
|
1207
|
+
...profile.coreJourneys.map((item) => `- ${item}`),
|
|
1208
|
+
"",
|
|
1209
|
+
"### Success Signals",
|
|
1210
|
+
...profile.successSignals.map((item) => `- ${item}`),
|
|
1211
|
+
"",
|
|
1212
|
+
"### Product Guardrails",
|
|
1213
|
+
"- Keep product intent stable across future features so agents do not optimize for the wrong user outcome.",
|
|
1214
|
+
"- Refresh this managed section when scope, personas, operating model, or success metrics change materially.",
|
|
1215
|
+
"<!-- cbx:product:foundation:end -->",
|
|
1216
|
+
"",
|
|
1217
|
+
].join("\n");
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function inferArchitectureDocProfile(snapshot, specRoots = []) {
|
|
1221
|
+
const contract = inferArchitectureContractProfile(snapshot);
|
|
1222
|
+
const architectureSignals = (snapshot.architectureByApp || []).map((item) => {
|
|
1223
|
+
const label = item.rootPath === "." ? "repo root" : item.rootPath;
|
|
1224
|
+
const signals =
|
|
1225
|
+
item.architectureSignals && item.architectureSignals.length > 0
|
|
1226
|
+
? item.architectureSignals.join(", ")
|
|
1227
|
+
: "not enough signals to classify";
|
|
1228
|
+
return `${label}: ${signals}`;
|
|
1229
|
+
});
|
|
1230
|
+
const decisionAreas = [
|
|
1231
|
+
"Module boundaries and dependency direction",
|
|
1232
|
+
"Design-system ownership and reusable primitives",
|
|
1233
|
+
"Testing and validation expectations by runtime boundary",
|
|
1234
|
+
];
|
|
1235
|
+
if (specRoots.length > 0) {
|
|
1236
|
+
decisionAreas.push(
|
|
1237
|
+
`Active specs that may influence upcoming architecture work: ${specRoots.join(", ")}.`,
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
return {
|
|
1242
|
+
style: contract.style,
|
|
1243
|
+
designSystemSource: contract.designSystemSource,
|
|
1244
|
+
moduleBoundaries: contract.moduleBoundaries,
|
|
1245
|
+
architectureSignals,
|
|
1246
|
+
decisionAreas,
|
|
1247
|
+
scalingConstraints: contract.scalingConstraints,
|
|
1248
|
+
testingStrategy: contract.testingStrategy,
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function buildArchitectureDocSection(snapshot, specRoots = []) {
|
|
1253
|
+
const profile = inferArchitectureDocProfile(snapshot, specRoots);
|
|
1254
|
+
const hash = hashStableObject(profile);
|
|
1255
|
+
|
|
1256
|
+
return [
|
|
1257
|
+
`<!-- cbx:architecture:doc:start version=1 profile=${hash} -->`,
|
|
1258
|
+
"## Accepted Architecture Backbone (auto-managed)",
|
|
1259
|
+
"",
|
|
1260
|
+
"### System Overview",
|
|
1261
|
+
`- Accepted style: ${profile.style}.`,
|
|
1262
|
+
`- Design-system source of truth: ${profile.designSystemSource}`,
|
|
1263
|
+
"",
|
|
1264
|
+
"### Bounded Contexts and Module Boundaries",
|
|
1265
|
+
...(profile.moduleBoundaries.length > 0
|
|
1266
|
+
? profile.moduleBoundaries.map((item) => `- ${item}`)
|
|
1267
|
+
: ["- No strong top-level module boundaries were detected automatically."]),
|
|
1268
|
+
"",
|
|
1269
|
+
"### Architecture Signals by Surface",
|
|
1270
|
+
...(profile.architectureSignals.length > 0
|
|
1271
|
+
? profile.architectureSignals.map((item) => `- ${item}`)
|
|
1272
|
+
: ["- No app-level architecture signals were inferred from the repo scan."]),
|
|
1273
|
+
"",
|
|
1274
|
+
"### Decision Areas to Preserve",
|
|
1275
|
+
...profile.decisionAreas.map((item) => `- ${item}`),
|
|
1276
|
+
"",
|
|
1277
|
+
"### Scalability and Reliability Notes",
|
|
1278
|
+
...profile.scalingConstraints.map((item) => `- ${item}`),
|
|
1279
|
+
"",
|
|
1280
|
+
"### Testing and Operability",
|
|
1281
|
+
...profile.testingStrategy.map((item) => `- ${item}`),
|
|
1282
|
+
"",
|
|
1283
|
+
"### ADR Linkage",
|
|
1284
|
+
"- Keep durable architecture decisions in `docs/adr/` and summarize the active decision set here.",
|
|
1285
|
+
"",
|
|
1286
|
+
"### Mermaid Diagram",
|
|
1287
|
+
"```mermaid",
|
|
1288
|
+
buildArchitectureDocMermaid(snapshot),
|
|
1289
|
+
"```",
|
|
1290
|
+
"<!-- cbx:architecture:doc:end -->",
|
|
1291
|
+
"",
|
|
1292
|
+
].join("\n");
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function buildEngineeringArchitectureSection(snapshot) {
|
|
1296
|
+
const profile = inferArchitectureContractProfile(snapshot);
|
|
1297
|
+
const hash = hashStableObject(profile);
|
|
1298
|
+
|
|
1299
|
+
return [
|
|
1300
|
+
`<!-- cbx:architecture:rules:start version=1 profile=${hash} -->`,
|
|
1301
|
+
"## 10) Architecture Contract (auto-managed)",
|
|
1302
|
+
"",
|
|
1303
|
+
`- Declared style: ${profile.style}.`,
|
|
1304
|
+
`- Design-system source of truth: ${profile.designSystemSource}`,
|
|
1305
|
+
"- Dependency direction rules:",
|
|
1306
|
+
...profile.dependencyRules.map((rule) => ` - ${rule}`),
|
|
1307
|
+
"- Module and package boundaries to preserve:",
|
|
1308
|
+
...(profile.moduleBoundaries.length > 0
|
|
1309
|
+
? profile.moduleBoundaries.map((rule) => ` - ${rule}`)
|
|
1310
|
+
: [" - No strong module boundary was detected automatically; keep new boundaries explicit in specs and ADRs."]),
|
|
1311
|
+
"- Testability expectations:",
|
|
1312
|
+
...profile.testingStrategy.map((rule) => ` - ${rule}`),
|
|
1313
|
+
"- Doc refresh policy:",
|
|
1314
|
+
" - Update these managed sections when architecture, scale, boundaries, design-system rules, or testing strategy changes.",
|
|
1315
|
+
" - For non-trivial work, read PRODUCT.md, ENGINEERING_RULES.md, ARCHITECTURE.md, and TECH.md in that order when they exist.",
|
|
1316
|
+
"<!-- cbx:architecture:rules:end -->",
|
|
1317
|
+
"",
|
|
1318
|
+
].join("\n");
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function buildArchitectureDocMermaid(snapshot) {
|
|
1322
|
+
const topDirs = snapshot.topDirs.slice(0, 5);
|
|
1323
|
+
const lines = [
|
|
1324
|
+
"flowchart LR",
|
|
1325
|
+
' product["Product Intent"] --> rules["Engineering Rules"]',
|
|
1326
|
+
' product --> arch["Architecture Backbone"]',
|
|
1327
|
+
' arch --> tech["Current Tech Snapshot"]',
|
|
1328
|
+
];
|
|
1329
|
+
if (topDirs.length > 0) {
|
|
1330
|
+
for (let index = 0; index < topDirs.length; index += 1) {
|
|
1331
|
+
const dir = topDirs[index];
|
|
1332
|
+
const nodeName = `D${index}`;
|
|
1333
|
+
lines.push(` arch --> ${nodeName}["${dir}/"]`);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
lines.push(' arch --> adr["docs/adr"]');
|
|
1337
|
+
lines.push(' product --> specs["docs/specs"]');
|
|
1338
|
+
return lines.join("\n");
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function buildTechArchitectureSection(snapshot) {
|
|
1342
|
+
const profile = inferArchitectureContractProfile(snapshot);
|
|
1343
|
+
const payload = {
|
|
1344
|
+
style: profile.style,
|
|
1345
|
+
topDirs: snapshot.topDirs,
|
|
1346
|
+
frameworks: snapshot.frameworks,
|
|
1347
|
+
architectureByApp: snapshot.architectureByApp,
|
|
1348
|
+
};
|
|
1349
|
+
const hash = hashStableObject(payload);
|
|
1350
|
+
const architectureSignals = profile.style
|
|
1351
|
+
? [`- Primary inferred style: ${profile.style}.`]
|
|
1352
|
+
: [];
|
|
1353
|
+
|
|
1354
|
+
return [
|
|
1355
|
+
`<!-- cbx:architecture:tech:start version=1 snapshot=${hash} -->`,
|
|
1356
|
+
"## Architecture Snapshot (auto-managed)",
|
|
1357
|
+
...(architectureSignals.length > 0 ? ["", ...architectureSignals] : []),
|
|
1358
|
+
...(snapshot.architectureByApp.length > 0
|
|
1359
|
+
? [
|
|
1360
|
+
"- App-level structure signals:",
|
|
1361
|
+
...snapshot.architectureByApp.slice(0, 6).map((item) => {
|
|
1362
|
+
const label = item.rootPath === "." ? "repo root" : item.rootPath;
|
|
1363
|
+
const signals =
|
|
1364
|
+
item.architectureSignals.length > 0
|
|
1365
|
+
? item.architectureSignals.join(", ")
|
|
1366
|
+
: "not enough signals to classify";
|
|
1367
|
+
return ` - ${label}: ${signals}`;
|
|
1368
|
+
}),
|
|
1369
|
+
]
|
|
1370
|
+
: ["- No app-level architecture signals detected automatically."]),
|
|
1371
|
+
"",
|
|
1372
|
+
"### Module / App Topology",
|
|
1373
|
+
...(profile.moduleBoundaries.length > 0
|
|
1374
|
+
? profile.moduleBoundaries.map((item) => `- ${item}`)
|
|
1375
|
+
: ["- No significant top-level module boundaries detected automatically."]),
|
|
1376
|
+
"",
|
|
1377
|
+
"### Flow Narratives",
|
|
1378
|
+
"- Describe the primary request, data, and background-job flows here when architecture generation runs.",
|
|
1379
|
+
"- Keep this section current when scale changes or new integration boundaries are introduced.",
|
|
1380
|
+
"",
|
|
1381
|
+
"### Mermaid Diagram",
|
|
1382
|
+
"```mermaid",
|
|
1383
|
+
buildArchitectureMermaid(snapshot),
|
|
1384
|
+
"```",
|
|
1385
|
+
"",
|
|
1386
|
+
"### Scaling / Deployment / Testing Snapshot",
|
|
1387
|
+
...profile.scalingConstraints.map((item) => `- ${item}`),
|
|
1388
|
+
"",
|
|
1389
|
+
"### External Research Evidence",
|
|
1390
|
+
"- Reserved for `cbx build architecture --research auto|always` when outside evidence informs the architecture notes.",
|
|
1391
|
+
"<!-- cbx:architecture:tech:end -->",
|
|
1392
|
+
"",
|
|
1393
|
+
].join("\n");
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function buildRoadmapFoundationSection(snapshot, specRoots = []) {
|
|
1397
|
+
const payload = {
|
|
1398
|
+
topDirs: snapshot.topDirs,
|
|
1399
|
+
frameworks: snapshot.frameworks,
|
|
1400
|
+
specRoots,
|
|
1401
|
+
};
|
|
1402
|
+
const hash = hashStableObject(payload);
|
|
1403
|
+
const nowItems = specRoots.length > 0
|
|
1404
|
+
? specRoots.map((item) => `Track active change planning in \`${item}\`.`)
|
|
1405
|
+
: [
|
|
1406
|
+
"No active spec packs detected. Create a spec pack before starting the next non-trivial feature or migration.",
|
|
1407
|
+
];
|
|
1408
|
+
const nextItems = [];
|
|
1409
|
+
if (snapshot.frameworks.length > 0) {
|
|
1410
|
+
nextItems.push(
|
|
1411
|
+
`Keep shared conventions stable across the current stack: ${snapshot.frameworks.join(", ")}.`,
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
if (snapshot.cicdSignals.length > 0) {
|
|
1415
|
+
nextItems.push(
|
|
1416
|
+
`Preserve release compatibility with the detected delivery surfaces: ${snapshot.cicdSignals.join(", ")}.`,
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
if (nextItems.length === 0) {
|
|
1420
|
+
nextItems.push(
|
|
1421
|
+
"Document the next scaling milestones here once product direction and architecture constraints are clearer.",
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
return [
|
|
1426
|
+
`<!-- cbx:roadmap:foundation:start version=1 profile=${hash} -->`,
|
|
1427
|
+
"## Delivery Backbone (auto-managed)",
|
|
1428
|
+
"",
|
|
1429
|
+
"### Now",
|
|
1430
|
+
...nowItems.map((item) => `- ${item}`),
|
|
1431
|
+
"",
|
|
1432
|
+
"### Next",
|
|
1433
|
+
...nextItems.map((item) => `- ${item}`),
|
|
1434
|
+
"",
|
|
1435
|
+
"### Later",
|
|
1436
|
+
"- Use this section for medium-term scaling themes, major migrations, or cross-team architecture investments.",
|
|
1437
|
+
"",
|
|
1438
|
+
"### Backbone Maintenance",
|
|
1439
|
+
"- Keep PRODUCT.md, ARCHITECTURE.md, ENGINEERING_RULES.md, and TECH.md aligned when direction or structure changes.",
|
|
1440
|
+
"- Link major roadmap themes back to specs and ADRs instead of burying them in chat-only planning.",
|
|
1441
|
+
"<!-- cbx:roadmap:foundation:end -->",
|
|
1442
|
+
"",
|
|
1443
|
+
].join("\n");
|
|
1444
|
+
}
|
|
1445
|
+
|
|
960
1446
|
function buildEngineeringRulesTemplate() {
|
|
961
1447
|
return [
|
|
962
1448
|
"# Engineering Rules",
|
|
@@ -1039,19 +1525,125 @@ function buildEngineeringRulesTemplate() {
|
|
|
1039
1525
|
"",
|
|
1040
1526
|
"- `TECH.md` is generated from current codebase reality.",
|
|
1041
1527
|
"- Re-run `cbx rules tech-md --overwrite` after major stack or architecture changes.",
|
|
1528
|
+
"",
|
|
1529
|
+
"<!-- cbx:architecture:rules:start version=1 profile=bootstrap -->",
|
|
1530
|
+
"## 10) Architecture Contract (auto-managed)",
|
|
1531
|
+
"",
|
|
1532
|
+
"- Declared style: bootstrap placeholder. Re-run `cbx build architecture --platform <codex|claude|gemini|copilot>` to refresh this contract from the repo.",
|
|
1533
|
+
"- Design-system source of truth: bootstrap placeholder.",
|
|
1534
|
+
"- Dependency direction rules:",
|
|
1535
|
+
" - Replace this placeholder with the managed architecture block when architecture generation runs.",
|
|
1536
|
+
"- Module and package boundaries to preserve:",
|
|
1537
|
+
" - Replace this placeholder with the managed architecture block when architecture generation runs.",
|
|
1538
|
+
"- Testability expectations:",
|
|
1539
|
+
" - Replace this placeholder with the managed architecture block when architecture generation runs.",
|
|
1540
|
+
"- Doc refresh policy:",
|
|
1541
|
+
" - Update this section when architecture, scale, boundaries, design-system rules, or testing strategy changes.",
|
|
1542
|
+
"<!-- cbx:architecture:rules:end -->",
|
|
1042
1543
|
"<!-- cbx:engineering:rules:end -->",
|
|
1043
1544
|
"",
|
|
1044
1545
|
].join("\n");
|
|
1045
1546
|
}
|
|
1046
1547
|
|
|
1548
|
+
function buildProductTemplate(snapshot, specRoots = []) {
|
|
1549
|
+
return [
|
|
1550
|
+
"# Product",
|
|
1551
|
+
"",
|
|
1552
|
+
"This file is the durable product backbone for the project.",
|
|
1553
|
+
"",
|
|
1554
|
+
buildProductFoundationSection(snapshot, specRoots).trimEnd(),
|
|
1555
|
+
"",
|
|
1556
|
+
].join("\n");
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function buildArchitectureDocTemplate(snapshot, specRoots = []) {
|
|
1560
|
+
return [
|
|
1561
|
+
"# Architecture",
|
|
1562
|
+
"",
|
|
1563
|
+
"This file captures the accepted architecture backbone for the project.",
|
|
1564
|
+
"",
|
|
1565
|
+
buildArchitectureDocSection(snapshot, specRoots).trimEnd(),
|
|
1566
|
+
"",
|
|
1567
|
+
].join("\n");
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
function buildRoadmapTemplate(snapshot, specRoots = []) {
|
|
1571
|
+
return [
|
|
1572
|
+
"# Roadmap",
|
|
1573
|
+
"",
|
|
1574
|
+
"This file captures the living delivery backbone for medium-term product and architecture work.",
|
|
1575
|
+
"",
|
|
1576
|
+
buildRoadmapFoundationSection(snapshot, specRoots).trimEnd(),
|
|
1577
|
+
"",
|
|
1578
|
+
].join("\n");
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function buildAdrReadme() {
|
|
1582
|
+
return [
|
|
1583
|
+
"# Architecture Decision Records",
|
|
1584
|
+
"",
|
|
1585
|
+
"Use this directory for durable decisions that future contributors and agents need to preserve.",
|
|
1586
|
+
"",
|
|
1587
|
+
"## When to add an ADR",
|
|
1588
|
+
"",
|
|
1589
|
+
"- Architecture style or boundary changes",
|
|
1590
|
+
"- Data model or persistence strategy changes",
|
|
1591
|
+
"- Deployment or scaling model changes",
|
|
1592
|
+
"- Design-system ownership or shared UX pattern changes",
|
|
1593
|
+
"",
|
|
1594
|
+
"## Suggested format",
|
|
1595
|
+
"",
|
|
1596
|
+
"1. Context",
|
|
1597
|
+
"2. Decision",
|
|
1598
|
+
"3. Consequences",
|
|
1599
|
+
"4. Validation",
|
|
1600
|
+
"",
|
|
1601
|
+
"Start with `0000-template.md` and create numbered follow-up ADRs for accepted decisions.",
|
|
1602
|
+
"",
|
|
1603
|
+
].join("\n");
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function buildAdrTemplate() {
|
|
1607
|
+
return [
|
|
1608
|
+
"# ADR 0000: Title",
|
|
1609
|
+
"",
|
|
1610
|
+
"## Status",
|
|
1611
|
+
"",
|
|
1612
|
+
"Proposed",
|
|
1613
|
+
"",
|
|
1614
|
+
"## Context",
|
|
1615
|
+
"",
|
|
1616
|
+
"- What problem or pressure led to this decision?",
|
|
1617
|
+
"",
|
|
1618
|
+
"## Decision",
|
|
1619
|
+
"",
|
|
1620
|
+
"- What is the chosen direction?",
|
|
1621
|
+
"",
|
|
1622
|
+
"## Consequences",
|
|
1623
|
+
"",
|
|
1624
|
+
"- What tradeoffs, benefits, or costs follow from this choice?",
|
|
1625
|
+
"",
|
|
1626
|
+
"## Validation",
|
|
1627
|
+
"",
|
|
1628
|
+
"- How will the team know this decision is working?",
|
|
1629
|
+
"",
|
|
1630
|
+
].join("\n");
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1047
1633
|
function buildEngineeringRulesManagedBlock({
|
|
1048
1634
|
platform,
|
|
1635
|
+
productFilePath,
|
|
1636
|
+
architectureFilePath,
|
|
1049
1637
|
engineeringRulesFilePath,
|
|
1050
1638
|
techMdFilePath,
|
|
1639
|
+
roadmapFilePath,
|
|
1051
1640
|
ruleFilePath,
|
|
1052
1641
|
}) {
|
|
1642
|
+
const productRef = toPosixPath(path.resolve(productFilePath));
|
|
1643
|
+
const architectureRef = toPosixPath(path.resolve(architectureFilePath));
|
|
1053
1644
|
const engineeringRef = toPosixPath(path.resolve(engineeringRulesFilePath));
|
|
1054
1645
|
const techRef = toPosixPath(path.resolve(techMdFilePath));
|
|
1646
|
+
const roadmapRef = toPosixPath(path.resolve(roadmapFilePath));
|
|
1055
1647
|
const ruleRef = toPosixPath(path.resolve(ruleFilePath));
|
|
1056
1648
|
|
|
1057
1649
|
return [
|
|
@@ -1059,8 +1651,11 @@ function buildEngineeringRulesManagedBlock({
|
|
|
1059
1651
|
"## Engineering Guardrails (auto-managed)",
|
|
1060
1652
|
"Apply these before planning, coding, review, and release:",
|
|
1061
1653
|
"",
|
|
1654
|
+
`- Product backbone: \`${productRef}\``,
|
|
1655
|
+
`- Accepted architecture: \`${architectureRef}\``,
|
|
1062
1656
|
`- Required baseline: \`${engineeringRef}\``,
|
|
1063
1657
|
`- Project tech map: \`${techRef}\``,
|
|
1658
|
+
`- Delivery roadmap: \`${roadmapRef}\``,
|
|
1064
1659
|
`- Active platform rule file: \`${ruleRef}\``,
|
|
1065
1660
|
"",
|
|
1066
1661
|
"Hard policy:",
|
|
@@ -1068,10 +1663,11 @@ function buildEngineeringRulesManagedBlock({
|
|
|
1068
1663
|
"2. Keep architecture simple (KISS) and avoid speculative work (YAGNI).",
|
|
1069
1664
|
"3. Apply SOLID pragmatically to reduce change risk, not add ceremony.",
|
|
1070
1665
|
"4. Use clear naming with focused responsibilities and explicit boundaries.",
|
|
1071
|
-
"5.
|
|
1072
|
-
"6.
|
|
1073
|
-
"7.
|
|
1074
|
-
"8.
|
|
1666
|
+
"5. For non-trivial work, read PRODUCT.md, ENGINEERING_RULES.md, ARCHITECTURE.md, and TECH.md in that order when they exist before planning or implementation.",
|
|
1667
|
+
"6. Require validation evidence (lint/types/tests) before merge.",
|
|
1668
|
+
"7. Use Decision Log response style.",
|
|
1669
|
+
"8. Every Decision Log must include a `Skills Used` section listing skill, workflow, or agent names.",
|
|
1670
|
+
"9. If no skill loaded, `Skills Used: none` is mandatory.",
|
|
1075
1671
|
"",
|
|
1076
1672
|
"<!-- cbx:engineering:auto:end -->",
|
|
1077
1673
|
].join("\n");
|
|
@@ -1192,14 +1788,20 @@ async function upsertEngineeringRulesFile({
|
|
|
1192
1788
|
async function upsertEngineeringRulesBlock({
|
|
1193
1789
|
ruleFilePath,
|
|
1194
1790
|
platform,
|
|
1791
|
+
productFilePath,
|
|
1792
|
+
architectureFilePath,
|
|
1195
1793
|
engineeringRulesFilePath,
|
|
1196
1794
|
techMdFilePath,
|
|
1795
|
+
roadmapFilePath,
|
|
1197
1796
|
dryRun = false,
|
|
1198
1797
|
}) {
|
|
1199
1798
|
const block = buildEngineeringRulesManagedBlock({
|
|
1200
1799
|
platform,
|
|
1800
|
+
productFilePath,
|
|
1801
|
+
architectureFilePath,
|
|
1201
1802
|
engineeringRulesFilePath,
|
|
1202
1803
|
techMdFilePath,
|
|
1804
|
+
roadmapFilePath,
|
|
1203
1805
|
ruleFilePath,
|
|
1204
1806
|
});
|
|
1205
1807
|
const exists = await pathExists(ruleFilePath);
|
|
@@ -1254,40 +1856,227 @@ async function upsertEngineeringRulesBlock({
|
|
|
1254
1856
|
};
|
|
1255
1857
|
}
|
|
1256
1858
|
|
|
1257
|
-
function
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
.toLowerCase();
|
|
1263
|
-
return normalized || null;
|
|
1859
|
+
function extractTaggedMarkerAttribute(content, startPattern, key) {
|
|
1860
|
+
const match = String(content || "").match(startPattern);
|
|
1861
|
+
if (!match || !match[0]) return null;
|
|
1862
|
+
const attributeMatch = match[0].match(new RegExp(`${key}=([^\\s>]+)`));
|
|
1863
|
+
return attributeMatch?.[1] || null;
|
|
1264
1864
|
}
|
|
1265
1865
|
|
|
1266
|
-
function
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1866
|
+
async function upsertTaggedSectionInFile({
|
|
1867
|
+
targetPath,
|
|
1868
|
+
initialContent,
|
|
1869
|
+
block,
|
|
1870
|
+
startPattern,
|
|
1871
|
+
endPattern,
|
|
1872
|
+
dryRun = false,
|
|
1873
|
+
}) {
|
|
1874
|
+
const exists = await pathExists(targetPath);
|
|
1875
|
+
const original = exists ? await readFile(targetPath, "utf8") : initialContent;
|
|
1876
|
+
const analysis = analyzeTaggedBlock(original, startPattern, endPattern);
|
|
1877
|
+
let nextContent = original;
|
|
1270
1878
|
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1879
|
+
if (!exists || analysis.status === "absent") {
|
|
1880
|
+
const trimmed = String(original || "").trimEnd();
|
|
1881
|
+
nextContent =
|
|
1882
|
+
trimmed.length > 0 ? `${trimmed}\n\n${block}\n` : `${block}\n`;
|
|
1883
|
+
} else if (analysis.range) {
|
|
1884
|
+
nextContent = `${original.slice(0, analysis.range.start)}${block}${original.slice(analysis.range.end)}`;
|
|
1885
|
+
} else {
|
|
1886
|
+
const trimmed = String(original || "").trimEnd();
|
|
1887
|
+
nextContent =
|
|
1888
|
+
trimmed.length > 0 ? `${trimmed}\n\n${block}\n` : `${block}\n`;
|
|
1279
1889
|
}
|
|
1280
1890
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1891
|
+
if (nextContent === original) {
|
|
1892
|
+
return {
|
|
1893
|
+
action: "unchanged",
|
|
1894
|
+
filePath: targetPath,
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1283
1897
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
}
|
|
1898
|
+
if (!dryRun) {
|
|
1899
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
1900
|
+
await writeFile(targetPath, nextContent, "utf8");
|
|
1901
|
+
}
|
|
1289
1902
|
|
|
1290
|
-
|
|
1903
|
+
return {
|
|
1904
|
+
action: exists
|
|
1905
|
+
? dryRun
|
|
1906
|
+
? "would-patch"
|
|
1907
|
+
: "patched"
|
|
1908
|
+
: dryRun
|
|
1909
|
+
? "would-create"
|
|
1910
|
+
: "created",
|
|
1911
|
+
filePath: targetPath,
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
function buildArchitectureBuildMetadata({
|
|
1916
|
+
platform,
|
|
1917
|
+
researchMode,
|
|
1918
|
+
productProfileHash,
|
|
1919
|
+
architectureDocHash,
|
|
1920
|
+
rulesProfileHash,
|
|
1921
|
+
techSnapshotHash,
|
|
1922
|
+
roadmapProfileHash,
|
|
1923
|
+
}) {
|
|
1924
|
+
return {
|
|
1925
|
+
schemaVersion: 2,
|
|
1926
|
+
generatedBy: "cbx build architecture",
|
|
1927
|
+
generatedAt: new Date().toISOString(),
|
|
1928
|
+
platform,
|
|
1929
|
+
researchMode,
|
|
1930
|
+
productProfileHash,
|
|
1931
|
+
architectureDocHash,
|
|
1932
|
+
rulesProfileHash,
|
|
1933
|
+
techSnapshotHash,
|
|
1934
|
+
roadmapProfileHash,
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
async function ensureArchitectureDocScaffold({
|
|
1939
|
+
workspaceRoot,
|
|
1940
|
+
snapshot,
|
|
1941
|
+
specRoots = [],
|
|
1942
|
+
overwrite = false,
|
|
1943
|
+
dryRun = false,
|
|
1944
|
+
}) {
|
|
1945
|
+
const productPath = path.join(workspaceRoot, "PRODUCT.md");
|
|
1946
|
+
const architectureDocPath = path.join(workspaceRoot, "ARCHITECTURE.md");
|
|
1947
|
+
const engineeringRulesPath = path.join(workspaceRoot, "ENGINEERING_RULES.md");
|
|
1948
|
+
const techMdPath = path.join(workspaceRoot, "TECH.md");
|
|
1949
|
+
const roadmapPath = path.join(workspaceRoot, "ROADMAP.md");
|
|
1950
|
+
const adrDir = path.join(workspaceRoot, "docs", "adr");
|
|
1951
|
+
const adrReadmePath = path.join(adrDir, "README.md");
|
|
1952
|
+
const adrTemplatePath = path.join(adrDir, "0000-template.md");
|
|
1953
|
+
|
|
1954
|
+
const productResult = await upsertTaggedSectionInFile({
|
|
1955
|
+
targetPath: productPath,
|
|
1956
|
+
initialContent: `${buildProductTemplate(snapshot, specRoots)}\n`,
|
|
1957
|
+
block: buildProductFoundationSection(snapshot, specRoots),
|
|
1958
|
+
startPattern: PRODUCT_FOUNDATION_BLOCK_START_RE,
|
|
1959
|
+
endPattern: PRODUCT_FOUNDATION_BLOCK_END_RE,
|
|
1960
|
+
dryRun,
|
|
1961
|
+
});
|
|
1962
|
+
|
|
1963
|
+
const architectureDocResult = await upsertTaggedSectionInFile({
|
|
1964
|
+
targetPath: architectureDocPath,
|
|
1965
|
+
initialContent: `${buildArchitectureDocTemplate(snapshot, specRoots)}\n`,
|
|
1966
|
+
block: buildArchitectureDocSection(snapshot, specRoots),
|
|
1967
|
+
startPattern: ARCHITECTURE_DOC_BLOCK_START_RE,
|
|
1968
|
+
endPattern: ARCHITECTURE_DOC_BLOCK_END_RE,
|
|
1969
|
+
dryRun,
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
const rulesTemplate = buildEngineeringRulesTemplate();
|
|
1973
|
+
const rulesFileResult = await upsertEngineeringRulesFile({
|
|
1974
|
+
targetPath: engineeringRulesPath,
|
|
1975
|
+
template: rulesTemplate,
|
|
1976
|
+
overwrite,
|
|
1977
|
+
dryRun,
|
|
1978
|
+
});
|
|
1979
|
+
const rulesArchitectureResult = await upsertTaggedSectionInFile({
|
|
1980
|
+
targetPath: engineeringRulesPath,
|
|
1981
|
+
initialContent: `${rulesTemplate}\n`,
|
|
1982
|
+
block: buildEngineeringArchitectureSection(snapshot),
|
|
1983
|
+
startPattern: ENGINEERING_ARCHITECTURE_BLOCK_START_RE,
|
|
1984
|
+
endPattern: ENGINEERING_ARCHITECTURE_BLOCK_END_RE,
|
|
1985
|
+
dryRun,
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
const techContent = `${buildTechMd(snapshot)}\n`;
|
|
1989
|
+
const techResult = await writeTextFile({
|
|
1990
|
+
targetPath: techMdPath,
|
|
1991
|
+
content: techContent,
|
|
1992
|
+
overwrite,
|
|
1993
|
+
dryRun,
|
|
1994
|
+
});
|
|
1995
|
+
const techArchitectureResult = await upsertTaggedSectionInFile({
|
|
1996
|
+
targetPath: techMdPath,
|
|
1997
|
+
initialContent: techContent,
|
|
1998
|
+
block: buildTechArchitectureSection(snapshot),
|
|
1999
|
+
startPattern: TECH_ARCHITECTURE_BLOCK_START_RE,
|
|
2000
|
+
endPattern: TECH_ARCHITECTURE_BLOCK_END_RE,
|
|
2001
|
+
dryRun,
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
const roadmapResult = await upsertTaggedSectionInFile({
|
|
2005
|
+
targetPath: roadmapPath,
|
|
2006
|
+
initialContent: `${buildRoadmapTemplate(snapshot, specRoots)}\n`,
|
|
2007
|
+
block: buildRoadmapFoundationSection(snapshot, specRoots),
|
|
2008
|
+
startPattern: ROADMAP_FOUNDATION_BLOCK_START_RE,
|
|
2009
|
+
endPattern: ROADMAP_FOUNDATION_BLOCK_END_RE,
|
|
2010
|
+
dryRun,
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
const adrReadmeResult = await writeTextFile({
|
|
2014
|
+
targetPath: adrReadmePath,
|
|
2015
|
+
content: `${buildAdrReadme()}\n`,
|
|
2016
|
+
overwrite,
|
|
2017
|
+
dryRun,
|
|
2018
|
+
});
|
|
2019
|
+
const adrTemplateResult = await writeTextFile({
|
|
2020
|
+
targetPath: adrTemplatePath,
|
|
2021
|
+
content: `${buildAdrTemplate()}\n`,
|
|
2022
|
+
overwrite,
|
|
2023
|
+
dryRun,
|
|
2024
|
+
});
|
|
2025
|
+
|
|
2026
|
+
return {
|
|
2027
|
+
productPath,
|
|
2028
|
+
architectureDocPath,
|
|
2029
|
+
engineeringRulesPath,
|
|
2030
|
+
techMdPath,
|
|
2031
|
+
roadmapPath,
|
|
2032
|
+
adrReadmePath,
|
|
2033
|
+
adrTemplatePath,
|
|
2034
|
+
productResult,
|
|
2035
|
+
architectureDocResult,
|
|
2036
|
+
rulesFileResult,
|
|
2037
|
+
rulesArchitectureResult,
|
|
2038
|
+
techResult,
|
|
2039
|
+
techArchitectureResult,
|
|
2040
|
+
roadmapResult,
|
|
2041
|
+
adrReadmeResult,
|
|
2042
|
+
adrTemplateResult,
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
function normalizeTechPackageName(value) {
|
|
2047
|
+
if (value === undefined || value === null) return null;
|
|
2048
|
+
const normalized = String(value)
|
|
2049
|
+
.trim()
|
|
2050
|
+
.replace(/^['"]|['"]$/g, "")
|
|
2051
|
+
.toLowerCase();
|
|
2052
|
+
return normalized || null;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
function parseTomlSections(content) {
|
|
2056
|
+
const sections = new Map();
|
|
2057
|
+
let currentSection = "";
|
|
2058
|
+
sections.set(currentSection, []);
|
|
2059
|
+
|
|
2060
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2061
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
2062
|
+
if (sectionMatch) {
|
|
2063
|
+
currentSection = sectionMatch[1].trim();
|
|
2064
|
+
if (!sections.has(currentSection)) sections.set(currentSection, []);
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
2067
|
+
sections.get(currentSection).push(line);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
return sections;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
function escapeTomlBasicString(value) {
|
|
2074
|
+
return String(value ?? "")
|
|
2075
|
+
.replace(/\\/g, "\\\\")
|
|
2076
|
+
.replace(/"/g, '\\"');
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
async function patchCodexPostmanHttpHeaders({
|
|
1291
2080
|
configPath,
|
|
1292
2081
|
mcpUrl,
|
|
1293
2082
|
bearerToken,
|
|
@@ -2233,7 +3022,7 @@ function inferRecommendedSkills(snapshot) {
|
|
|
2233
3022
|
hasFramework("Mongoose") ||
|
|
2234
3023
|
hasFramework("SQLAlchemy")
|
|
2235
3024
|
) {
|
|
2236
|
-
recommended.add("database-
|
|
3025
|
+
recommended.add("database-design");
|
|
2237
3026
|
}
|
|
2238
3027
|
|
|
2239
3028
|
if (recommended.size === 0) {
|
|
@@ -2564,6 +3353,9 @@ function buildTechMd(snapshot, { compact = false } = {}) {
|
|
|
2564
3353
|
);
|
|
2565
3354
|
lines.push("");
|
|
2566
3355
|
|
|
3356
|
+
lines.push(buildTechArchitectureSection(snapshot).trimEnd());
|
|
3357
|
+
lines.push("");
|
|
3358
|
+
|
|
2567
3359
|
if (!compact) {
|
|
2568
3360
|
lines.push("## Package Signals");
|
|
2569
3361
|
appendTechPackageSection(
|
|
@@ -8383,14 +9175,6 @@ async function performWorkflowInstall(
|
|
|
8383
9175
|
dryRun,
|
|
8384
9176
|
cwd,
|
|
8385
9177
|
});
|
|
8386
|
-
const engineeringArtifactsResult = await upsertEngineeringArtifacts({
|
|
8387
|
-
platform,
|
|
8388
|
-
scope: ruleScope,
|
|
8389
|
-
overwrite: false,
|
|
8390
|
-
dryRun,
|
|
8391
|
-
skipTech: false,
|
|
8392
|
-
cwd,
|
|
8393
|
-
});
|
|
8394
9178
|
const postmanSetupResult = await configurePostmanInstallArtifacts({
|
|
8395
9179
|
platform,
|
|
8396
9180
|
scope,
|
|
@@ -8436,7 +9220,7 @@ async function performWorkflowInstall(
|
|
|
8436
9220
|
bundleId,
|
|
8437
9221
|
installResult,
|
|
8438
9222
|
syncResult,
|
|
8439
|
-
engineeringArtifactsResult,
|
|
9223
|
+
engineeringArtifactsResult: null,
|
|
8440
9224
|
postmanSetupResult,
|
|
8441
9225
|
terminalVerificationRuleResult,
|
|
8442
9226
|
};
|
|
@@ -8466,10 +9250,7 @@ async function runWorkflowInstall(options) {
|
|
|
8466
9250
|
dryRun: result.dryRun,
|
|
8467
9251
|
});
|
|
8468
9252
|
printRuleSyncResult(result.syncResult);
|
|
8469
|
-
|
|
8470
|
-
engineeringResults: result.engineeringArtifactsResult.engineeringResults,
|
|
8471
|
-
techResult: result.engineeringArtifactsResult.techResult,
|
|
8472
|
-
});
|
|
9253
|
+
printInstallDocumentationNotice();
|
|
8473
9254
|
printPostmanSetupSummary({
|
|
8474
9255
|
postmanSetup: result.postmanSetupResult,
|
|
8475
9256
|
});
|
|
@@ -11924,6 +12705,16 @@ function printInstallEngineeringSummary({ engineeringResults, techResult }) {
|
|
|
11924
12705
|
}
|
|
11925
12706
|
}
|
|
11926
12707
|
|
|
12708
|
+
function printInstallDocumentationNotice() {
|
|
12709
|
+
console.log("\nProject backbone docs:");
|
|
12710
|
+
console.log(
|
|
12711
|
+
"- Install only wires the rule references and workflow assets.",
|
|
12712
|
+
);
|
|
12713
|
+
console.log(
|
|
12714
|
+
"- Use `cbx rules init` to scaffold ENGINEERING_RULES.md and TECH.md, or `cbx build architecture --platform <codex|claude|gemini|copilot>` to generate PRODUCT.md, ARCHITECTURE.md, ENGINEERING_RULES.md, TECH.md, ROADMAP.md, and ADR scaffolds.",
|
|
12715
|
+
);
|
|
12716
|
+
}
|
|
12717
|
+
|
|
11927
12718
|
async function upsertEngineeringArtifacts({
|
|
11928
12719
|
platform,
|
|
11929
12720
|
scope,
|
|
@@ -11937,7 +12728,14 @@ async function upsertEngineeringArtifacts({
|
|
|
11937
12728
|
throw new Error(`No rule file configured for platform '${platform}'.`);
|
|
11938
12729
|
|
|
11939
12730
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
11940
|
-
const
|
|
12731
|
+
const snapshot = await collectTechSnapshot(workspaceRoot);
|
|
12732
|
+
const scaffold = await ensureArchitectureDocScaffold({
|
|
12733
|
+
workspaceRoot,
|
|
12734
|
+
snapshot,
|
|
12735
|
+
specRoots: [],
|
|
12736
|
+
overwrite,
|
|
12737
|
+
dryRun,
|
|
12738
|
+
});
|
|
11941
12739
|
const targets = [{ ruleFilePath }];
|
|
11942
12740
|
|
|
11943
12741
|
if (scope === "global") {
|
|
@@ -11957,46 +12755,32 @@ async function upsertEngineeringArtifacts({
|
|
|
11957
12755
|
}
|
|
11958
12756
|
}
|
|
11959
12757
|
|
|
11960
|
-
const template = buildEngineeringRulesTemplate();
|
|
11961
12758
|
const engineeringResults = [];
|
|
11962
12759
|
for (const target of targets) {
|
|
11963
|
-
const rulesFilePath = path.join(
|
|
11964
|
-
path.dirname(target.ruleFilePath),
|
|
11965
|
-
"ENGINEERING_RULES.md",
|
|
11966
|
-
);
|
|
11967
|
-
const rulesFileResult = await upsertEngineeringRulesFile({
|
|
11968
|
-
targetPath: rulesFilePath,
|
|
11969
|
-
template,
|
|
11970
|
-
overwrite,
|
|
11971
|
-
dryRun,
|
|
11972
|
-
});
|
|
11973
12760
|
const blockResult = await upsertEngineeringRulesBlock({
|
|
11974
12761
|
ruleFilePath: target.ruleFilePath,
|
|
11975
12762
|
platform,
|
|
11976
|
-
|
|
11977
|
-
|
|
12763
|
+
productFilePath: scaffold.productPath,
|
|
12764
|
+
architectureFilePath: scaffold.architectureDocPath,
|
|
12765
|
+
engineeringRulesFilePath: scaffold.engineeringRulesPath,
|
|
12766
|
+
techMdFilePath: scaffold.techMdPath,
|
|
12767
|
+
roadmapFilePath: scaffold.roadmapPath,
|
|
11978
12768
|
dryRun,
|
|
11979
12769
|
});
|
|
11980
12770
|
engineeringResults.push({
|
|
11981
12771
|
ruleFilePath: target.ruleFilePath,
|
|
11982
|
-
rulesFilePath,
|
|
11983
|
-
rulesFileResult
|
|
12772
|
+
rulesFilePath: scaffold.engineeringRulesPath,
|
|
12773
|
+
rulesFileResult: scaffold.rulesArchitectureResult.action === "unchanged"
|
|
12774
|
+
? scaffold.rulesFileResult
|
|
12775
|
+
: scaffold.rulesArchitectureResult,
|
|
11984
12776
|
blockResult,
|
|
11985
12777
|
});
|
|
11986
12778
|
}
|
|
11987
12779
|
|
|
11988
12780
|
let techResult = null;
|
|
11989
12781
|
if (!skipTech) {
|
|
11990
|
-
const snapshot = await collectTechSnapshot(workspaceRoot);
|
|
11991
|
-
const content = buildTechMd(snapshot);
|
|
11992
|
-
const fileResult = await writeTextFile({
|
|
11993
|
-
targetPath: techMdPath,
|
|
11994
|
-
content: `${content}\n`,
|
|
11995
|
-
overwrite,
|
|
11996
|
-
dryRun,
|
|
11997
|
-
});
|
|
11998
12782
|
techResult = {
|
|
11999
|
-
...
|
|
12783
|
+
...scaffold.techArchitectureResult,
|
|
12000
12784
|
snapshot,
|
|
12001
12785
|
};
|
|
12002
12786
|
}
|
|
@@ -12081,6 +12865,839 @@ async function runRulesTechMd(options) {
|
|
|
12081
12865
|
}
|
|
12082
12866
|
}
|
|
12083
12867
|
|
|
12868
|
+
function normalizeArchitectureBuildPlatform(value) {
|
|
12869
|
+
const normalized = normalizePlatform(value);
|
|
12870
|
+
if (!normalized || !ARCHITECTURE_BUILD_PLATFORMS.has(normalized)) {
|
|
12871
|
+
throw new Error(
|
|
12872
|
+
"Architecture build platform must be one of: codex, claude, gemini, copilot.",
|
|
12873
|
+
);
|
|
12874
|
+
}
|
|
12875
|
+
return normalized;
|
|
12876
|
+
}
|
|
12877
|
+
|
|
12878
|
+
function normalizeArchitectureResearchMode(value) {
|
|
12879
|
+
const normalized = String(value || "auto")
|
|
12880
|
+
.trim()
|
|
12881
|
+
.toLowerCase();
|
|
12882
|
+
if (!["auto", "always", "never"].includes(normalized)) {
|
|
12883
|
+
throw new Error("Research mode must be one of: auto, always, never.");
|
|
12884
|
+
}
|
|
12885
|
+
return normalized;
|
|
12886
|
+
}
|
|
12887
|
+
|
|
12888
|
+
async function listSpecPackRoots(workspaceRoot) {
|
|
12889
|
+
const specsRoot = path.join(workspaceRoot, "docs", "specs");
|
|
12890
|
+
if (!(await pathExists(specsRoot))) return [];
|
|
12891
|
+
const entries = await readdir(specsRoot, { withFileTypes: true });
|
|
12892
|
+
return entries
|
|
12893
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
|
|
12894
|
+
.map((entry) => `docs/specs/${entry.name}`)
|
|
12895
|
+
.sort((a, b) => a.localeCompare(b))
|
|
12896
|
+
.slice(0, 8);
|
|
12897
|
+
}
|
|
12898
|
+
|
|
12899
|
+
function resolveArchitectureConditionalSkills(snapshot, specRoots, researchMode) {
|
|
12900
|
+
const conditional = [];
|
|
12901
|
+
const frameworks = new Set(snapshot.frameworks || []);
|
|
12902
|
+
const topDirs = new Set(snapshot.topDirs || []);
|
|
12903
|
+
const jsPackages = new Set(snapshot.packageSignals?.javascript || []);
|
|
12904
|
+
|
|
12905
|
+
if (
|
|
12906
|
+
snapshot.isMcpServer ||
|
|
12907
|
+
frameworks.has("Next.js") ||
|
|
12908
|
+
frameworks.has("NestJS") ||
|
|
12909
|
+
frameworks.has("FastAPI") ||
|
|
12910
|
+
topDirs.has("api") ||
|
|
12911
|
+
topDirs.has("routes") ||
|
|
12912
|
+
topDirs.has("controllers")
|
|
12913
|
+
) {
|
|
12914
|
+
conditional.push("api-design");
|
|
12915
|
+
}
|
|
12916
|
+
|
|
12917
|
+
if (
|
|
12918
|
+
topDirs.has("db") ||
|
|
12919
|
+
topDirs.has("database") ||
|
|
12920
|
+
topDirs.has("migrations") ||
|
|
12921
|
+
jsPackages.has("prisma") ||
|
|
12922
|
+
jsPackages.has("drizzle-orm") ||
|
|
12923
|
+
frameworks.has("SQLAlchemy")
|
|
12924
|
+
) {
|
|
12925
|
+
conditional.push("database-design");
|
|
12926
|
+
}
|
|
12927
|
+
|
|
12928
|
+
if (specRoots.length > 0) {
|
|
12929
|
+
conditional.push("sadd");
|
|
12930
|
+
}
|
|
12931
|
+
|
|
12932
|
+
if (researchMode === "always") {
|
|
12933
|
+
conditional.push("deep-research");
|
|
12934
|
+
} else if (
|
|
12935
|
+
researchMode === "auto" &&
|
|
12936
|
+
!snapshot.readmeExcerpt &&
|
|
12937
|
+
snapshot.architectureByApp.every(
|
|
12938
|
+
(item) => (item.architectureSignals || []).length === 0,
|
|
12939
|
+
)
|
|
12940
|
+
) {
|
|
12941
|
+
conditional.push("deep-research");
|
|
12942
|
+
}
|
|
12943
|
+
|
|
12944
|
+
return [...new Set(conditional)];
|
|
12945
|
+
}
|
|
12946
|
+
|
|
12947
|
+
async function resolveArchitectureSkillPathHints(platform, cwd, skillIds) {
|
|
12948
|
+
const profilePaths = await resolveProfilePaths(platform, "project", cwd);
|
|
12949
|
+
const skillsDir = profilePaths.skillsDir;
|
|
12950
|
+
if (!skillsDir) return [];
|
|
12951
|
+
return skillIds
|
|
12952
|
+
.map((skillId) => path.join(skillsDir, skillId, "SKILL.md"))
|
|
12953
|
+
.map((filePath) => toPosixPath(path.relative(findWorkspaceRoot(cwd), filePath)));
|
|
12954
|
+
}
|
|
12955
|
+
|
|
12956
|
+
function buildArchitecturePrompt({
|
|
12957
|
+
platform,
|
|
12958
|
+
workspaceRoot,
|
|
12959
|
+
snapshot,
|
|
12960
|
+
specRoots,
|
|
12961
|
+
researchMode,
|
|
12962
|
+
coreSkills,
|
|
12963
|
+
conditionalSkills,
|
|
12964
|
+
skillPathHints,
|
|
12965
|
+
}) {
|
|
12966
|
+
const productPath = "PRODUCT.md";
|
|
12967
|
+
const architecturePath = "ARCHITECTURE.md";
|
|
12968
|
+
const rulesPath = "ENGINEERING_RULES.md";
|
|
12969
|
+
const techPath = "TECH.md";
|
|
12970
|
+
const roadmapPath = "ROADMAP.md";
|
|
12971
|
+
const adrReadmePath = "docs/adr/README.md";
|
|
12972
|
+
const architectureSignals = snapshot.architectureByApp
|
|
12973
|
+
.filter((item) => (item.architectureSignals || []).length > 0)
|
|
12974
|
+
.map((item) => {
|
|
12975
|
+
const label = item.rootPath === "." ? "repo root" : item.rootPath;
|
|
12976
|
+
return `${label}: ${item.architectureSignals.join(", ")}`;
|
|
12977
|
+
});
|
|
12978
|
+
|
|
12979
|
+
return [
|
|
12980
|
+
`You are running inside ${platform}.`,
|
|
12981
|
+
"",
|
|
12982
|
+
"Objective:",
|
|
12983
|
+
`- Inspect the repository at ${toPosixPath(workspaceRoot)} and refresh the scalable project backbone in ${productPath}, ${architecturePath}, ${rulesPath}, ${techPath}, and ${roadmapPath}.`,
|
|
12984
|
+
"- Keep PRODUCT.md focused on intent, ARCHITECTURE.md on accepted structure, ENGINEERING_RULES.md on normative rules, TECH.md on current-state evidence, and ROADMAP.md on delivery themes.",
|
|
12985
|
+
"- Preserve manual content outside the managed `cbx:*` markers.",
|
|
12986
|
+
"",
|
|
12987
|
+
"Required skill bundle:",
|
|
12988
|
+
`- Load these exact skill IDs first: ${coreSkills.map((skillId) => `\`${skillId}\``).join(", ")}`,
|
|
12989
|
+
conditionalSkills.length > 0
|
|
12990
|
+
? `- Additional skills to load now: ${conditionalSkills.map((skillId) => `\`${skillId}\``).join(", ")}`
|
|
12991
|
+
: "- Conditional skills to load now: none",
|
|
12992
|
+
skillPathHints.length > 0
|
|
12993
|
+
? `- Local skill file hints if installed: ${skillPathHints.map((hint) => `\`${hint}\``).join(", ")}`
|
|
12994
|
+
: "- Local skill file hints if installed: none detected",
|
|
12995
|
+
"- Treat the route and skill bundle as already resolved. Do not begin with route discovery.",
|
|
12996
|
+
"",
|
|
12997
|
+
"Repository context:",
|
|
12998
|
+
`- Frameworks: ${snapshot.frameworks.length > 0 ? snapshot.frameworks.join(", ") : "none detected"}`,
|
|
12999
|
+
`- Top directories: ${snapshot.topDirs.length > 0 ? snapshot.topDirs.join(", ") : "none detected"}`,
|
|
13000
|
+
`- Existing spec packs: ${specRoots.length > 0 ? specRoots.join(", ") : "none detected"}`,
|
|
13001
|
+
architectureSignals.length > 0
|
|
13002
|
+
? `- Architecture signals: ${architectureSignals.join(" | ")}`
|
|
13003
|
+
: "- Architecture signals: none confidently inferred from the repo scan",
|
|
13004
|
+
"",
|
|
13005
|
+
"Execution contract:",
|
|
13006
|
+
`1. Read ${productPath}, ${rulesPath}, ${architecturePath}, and ${techPath} in that order when they exist.`,
|
|
13007
|
+
"2. Inspect the repo before making architecture claims.",
|
|
13008
|
+
"3. Update only the content between the existing managed markers in the backbone docs and preserve the marker lines themselves, including their hash metadata.",
|
|
13009
|
+
"4. In PRODUCT.md, state product scope, primary surfaces, users or operators, core journeys, success signals, and product guardrails.",
|
|
13010
|
+
"5. In ARCHITECTURE.md, state accepted architecture style, bounded contexts, stable decision areas, ADR linkage, and add at least one Mermaid architecture diagram if the repo is non-trivial.",
|
|
13011
|
+
"6. In ENGINEERING_RULES.md, state architecture style, dependency rules, feature or module structure rules, design-system source of truth, testability expectations, and doc refresh policy.",
|
|
13012
|
+
"7. In TECH.md, update architecture snapshot, module or app topology, flow narratives, Mermaid diagrams, and scaling or deployment notes.",
|
|
13013
|
+
"8. In ROADMAP.md, capture current delivery themes, active spec-driven work, and major architecture follow-ups without turning it into a speculative wishlist.",
|
|
13014
|
+
researchMode === "never"
|
|
13015
|
+
? "9. Stay repo-only. Do not use outside research."
|
|
13016
|
+
: "9. Use repo evidence first. Use official docs when needed. Treat Reddit or community sources only as labeled secondary evidence.",
|
|
13017
|
+
researchMode === "always"
|
|
13018
|
+
? "10. Include an external research evidence subsection in TECH.md with clearly labeled primary and secondary evidence."
|
|
13019
|
+
: "10. Include external research notes only if they materially informed the architecture update.",
|
|
13020
|
+
"11. If the project clearly follows Clean Architecture, feature-first modules, or another stable structure, make that explicit so future implementation stays consistent.",
|
|
13021
|
+
`12. Ensure ${adrReadmePath} exists as the ADR entrypoint and mention ADR follow-up when the repo lacks decision history.`,
|
|
13022
|
+
"",
|
|
13023
|
+
"Return one JSON object on the last line with this shape:",
|
|
13024
|
+
'{"files_written":["PRODUCT.md","ARCHITECTURE.md","ENGINEERING_RULES.md","TECH.md","ROADMAP.md","docs/adr/README.md"],"research_used":false,"gaps":[],"next_actions":[]}',
|
|
13025
|
+
"",
|
|
13026
|
+
"Do not emit placeholder TODOs in the managed sections.",
|
|
13027
|
+
].join("\n");
|
|
13028
|
+
}
|
|
13029
|
+
|
|
13030
|
+
async function execFileCapture(command, args, options = {}) {
|
|
13031
|
+
try {
|
|
13032
|
+
const result = await execFile(command, args, {
|
|
13033
|
+
...options,
|
|
13034
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
13035
|
+
});
|
|
13036
|
+
return {
|
|
13037
|
+
ok: true,
|
|
13038
|
+
stdout: result.stdout || "",
|
|
13039
|
+
stderr: result.stderr || "",
|
|
13040
|
+
};
|
|
13041
|
+
} catch (error) {
|
|
13042
|
+
if (error?.code === "ENOENT") {
|
|
13043
|
+
throw new Error(`Required CLI '${command}' is not installed or not on PATH.`);
|
|
13044
|
+
}
|
|
13045
|
+
return {
|
|
13046
|
+
ok: false,
|
|
13047
|
+
stdout: error?.stdout || "",
|
|
13048
|
+
stderr: error?.stderr || "",
|
|
13049
|
+
code: error?.code || 1,
|
|
13050
|
+
};
|
|
13051
|
+
}
|
|
13052
|
+
}
|
|
13053
|
+
|
|
13054
|
+
async function spawnCapture(command, args, options = {}) {
|
|
13055
|
+
const { cwd, env, streamOutput = false } = options;
|
|
13056
|
+
return await new Promise((resolve, reject) => {
|
|
13057
|
+
let stdout = "";
|
|
13058
|
+
let stderr = "";
|
|
13059
|
+
const child = spawn(command, args, {
|
|
13060
|
+
cwd,
|
|
13061
|
+
env,
|
|
13062
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13063
|
+
});
|
|
13064
|
+
|
|
13065
|
+
child.stdout.on("data", (chunk) => {
|
|
13066
|
+
const text = chunk.toString();
|
|
13067
|
+
stdout += text;
|
|
13068
|
+
if (streamOutput) process.stdout.write(text);
|
|
13069
|
+
});
|
|
13070
|
+
|
|
13071
|
+
child.stderr.on("data", (chunk) => {
|
|
13072
|
+
const text = chunk.toString();
|
|
13073
|
+
stderr += text;
|
|
13074
|
+
if (streamOutput) process.stderr.write(text);
|
|
13075
|
+
});
|
|
13076
|
+
|
|
13077
|
+
child.on("error", (error) => {
|
|
13078
|
+
if (error?.code === "ENOENT") {
|
|
13079
|
+
reject(
|
|
13080
|
+
new Error(`Required CLI '${command}' is not installed or not on PATH.`),
|
|
13081
|
+
);
|
|
13082
|
+
return;
|
|
13083
|
+
}
|
|
13084
|
+
reject(error);
|
|
13085
|
+
});
|
|
13086
|
+
|
|
13087
|
+
child.on("close", (code) => {
|
|
13088
|
+
resolve({
|
|
13089
|
+
ok: code === 0,
|
|
13090
|
+
stdout,
|
|
13091
|
+
stderr,
|
|
13092
|
+
code: code ?? 1,
|
|
13093
|
+
});
|
|
13094
|
+
});
|
|
13095
|
+
});
|
|
13096
|
+
}
|
|
13097
|
+
|
|
13098
|
+
function explainArchitectureBuildFailure(platform, execution) {
|
|
13099
|
+
const combined = String(
|
|
13100
|
+
`${execution.stderr || ""}\n${execution.stdout || ""}`.trim(),
|
|
13101
|
+
);
|
|
13102
|
+
const notes = [];
|
|
13103
|
+
|
|
13104
|
+
if (platform === "gemini") {
|
|
13105
|
+
if (
|
|
13106
|
+
combined.includes("Error during discovery for MCP server") ||
|
|
13107
|
+
combined.includes("[MCP error]")
|
|
13108
|
+
) {
|
|
13109
|
+
notes.push(
|
|
13110
|
+
"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.",
|
|
13111
|
+
);
|
|
13112
|
+
}
|
|
13113
|
+
if (
|
|
13114
|
+
combined.includes("cloudaicompanion.companions.generateChat") ||
|
|
13115
|
+
combined.includes("PERMISSION_DENIED") ||
|
|
13116
|
+
combined.includes("403")
|
|
13117
|
+
) {
|
|
13118
|
+
notes.push(
|
|
13119
|
+
"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.",
|
|
13120
|
+
);
|
|
13121
|
+
}
|
|
13122
|
+
}
|
|
13123
|
+
|
|
13124
|
+
if (platform === "claude" && combined.includes("permission")) {
|
|
13125
|
+
notes.push(
|
|
13126
|
+
"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.",
|
|
13127
|
+
);
|
|
13128
|
+
}
|
|
13129
|
+
|
|
13130
|
+
if (notes.length === 0) {
|
|
13131
|
+
return `Architecture build failed via ${platform}. ${combined}`.trim();
|
|
13132
|
+
}
|
|
13133
|
+
|
|
13134
|
+
return [
|
|
13135
|
+
`Architecture build failed via ${platform}.`,
|
|
13136
|
+
...notes.map((note) => `- ${note}`),
|
|
13137
|
+
combined ? `Raw CLI output:\n${combined}` : "",
|
|
13138
|
+
]
|
|
13139
|
+
.filter(Boolean)
|
|
13140
|
+
.join("\n");
|
|
13141
|
+
}
|
|
13142
|
+
|
|
13143
|
+
async function probeArchitectureAdapter(platform, cwd) {
|
|
13144
|
+
if (platform === "codex") {
|
|
13145
|
+
const help = await execFileCapture("codex", ["exec", "--help"], { cwd });
|
|
13146
|
+
return {
|
|
13147
|
+
platform,
|
|
13148
|
+
binary: "codex",
|
|
13149
|
+
helpText: `${help.stdout}\n${help.stderr}`.trim(),
|
|
13150
|
+
buildInvocation(prompt) {
|
|
13151
|
+
const args = ["exec"];
|
|
13152
|
+
if (this.helpText.includes("--skip-git-repo-check")) {
|
|
13153
|
+
args.push("--skip-git-repo-check");
|
|
13154
|
+
}
|
|
13155
|
+
args.push(prompt);
|
|
13156
|
+
return args;
|
|
13157
|
+
},
|
|
13158
|
+
};
|
|
13159
|
+
}
|
|
13160
|
+
|
|
13161
|
+
if (platform === "claude") {
|
|
13162
|
+
const help = await execFileCapture("claude", ["--help"], { cwd });
|
|
13163
|
+
const helpText = `${help.stdout}\n${help.stderr}`.trim();
|
|
13164
|
+
const printFlag = helpText.includes("--print")
|
|
13165
|
+
? "--print"
|
|
13166
|
+
: helpText.includes(" -p") || helpText.includes("\n-p")
|
|
13167
|
+
? "-p"
|
|
13168
|
+
: null;
|
|
13169
|
+
if (!printFlag) {
|
|
13170
|
+
throw new Error(
|
|
13171
|
+
"Claude CLI was found, but no headless print mode was detected. Install a Claude Code build that supports --print or -p.",
|
|
13172
|
+
);
|
|
13173
|
+
}
|
|
13174
|
+
return {
|
|
13175
|
+
platform,
|
|
13176
|
+
binary: "claude",
|
|
13177
|
+
helpText,
|
|
13178
|
+
buildInvocation(prompt) {
|
|
13179
|
+
return [printFlag, prompt];
|
|
13180
|
+
},
|
|
13181
|
+
};
|
|
13182
|
+
}
|
|
13183
|
+
|
|
13184
|
+
if (platform === "gemini") {
|
|
13185
|
+
const help = await execFileCapture("gemini", ["--help"], { cwd });
|
|
13186
|
+
const helpText = `${help.stdout}\n${help.stderr}`.trim();
|
|
13187
|
+
const promptFlag = helpText.includes("--prompt")
|
|
13188
|
+
? "--prompt"
|
|
13189
|
+
: helpText.includes(" -p") || helpText.includes("\n-p")
|
|
13190
|
+
? "-p"
|
|
13191
|
+
: null;
|
|
13192
|
+
if (!promptFlag) {
|
|
13193
|
+
throw new Error(
|
|
13194
|
+
"Gemini CLI was found, but no prompt flag was detected. Install a Gemini CLI build with --prompt or -p support.",
|
|
13195
|
+
);
|
|
13196
|
+
}
|
|
13197
|
+
return {
|
|
13198
|
+
platform,
|
|
13199
|
+
binary: "gemini",
|
|
13200
|
+
helpText,
|
|
13201
|
+
buildInvocation(prompt) {
|
|
13202
|
+
return [promptFlag, prompt];
|
|
13203
|
+
},
|
|
13204
|
+
};
|
|
13205
|
+
}
|
|
13206
|
+
|
|
13207
|
+
const help = await execFileCapture("copilot", ["--help"], { cwd });
|
|
13208
|
+
const helpText = `${help.stdout}\n${help.stderr}`.trim();
|
|
13209
|
+
const supportsPrompt = helpText.includes("--prompt");
|
|
13210
|
+
const supportsChatPrompt =
|
|
13211
|
+
helpText.includes("chat") && helpText.includes("--prompt");
|
|
13212
|
+
if (!supportsPrompt && !supportsChatPrompt) {
|
|
13213
|
+
throw new Error(
|
|
13214
|
+
"Copilot CLI was found, but no headless prompt mode was detected. This command requires a native Copilot CLI surface with --prompt support.",
|
|
13215
|
+
);
|
|
13216
|
+
}
|
|
13217
|
+
return {
|
|
13218
|
+
platform,
|
|
13219
|
+
binary: "copilot",
|
|
13220
|
+
helpText,
|
|
13221
|
+
buildInvocation(prompt) {
|
|
13222
|
+
if (supportsPrompt) return ["--prompt", prompt];
|
|
13223
|
+
return ["chat", "--prompt", prompt];
|
|
13224
|
+
},
|
|
13225
|
+
};
|
|
13226
|
+
}
|
|
13227
|
+
|
|
13228
|
+
function normalizeArchitectureResult({
|
|
13229
|
+
stdout,
|
|
13230
|
+
workspaceRoot,
|
|
13231
|
+
researchMode,
|
|
13232
|
+
changedFiles = [],
|
|
13233
|
+
}) {
|
|
13234
|
+
const trimmed = String(stdout || "").trim();
|
|
13235
|
+
if (trimmed) {
|
|
13236
|
+
const lastLine = trimmed.split(/\r?\n/).pop();
|
|
13237
|
+
if (lastLine && lastLine.startsWith("{") && lastLine.endsWith("}")) {
|
|
13238
|
+
try {
|
|
13239
|
+
const parsed = JSON.parse(lastLine);
|
|
13240
|
+
return {
|
|
13241
|
+
outputRoot: workspaceRoot,
|
|
13242
|
+
filesWritten:
|
|
13243
|
+
changedFiles.length > 0
|
|
13244
|
+
? changedFiles
|
|
13245
|
+
: Array.isArray(parsed.files_written)
|
|
13246
|
+
? parsed.files_written
|
|
13247
|
+
: [],
|
|
13248
|
+
researchUsed:
|
|
13249
|
+
typeof parsed.research_used === "boolean"
|
|
13250
|
+
? parsed.research_used
|
|
13251
|
+
: researchMode === "always",
|
|
13252
|
+
gaps: Array.isArray(parsed.gaps) ? parsed.gaps : [],
|
|
13253
|
+
nextActions: Array.isArray(parsed.next_actions)
|
|
13254
|
+
? parsed.next_actions
|
|
13255
|
+
: [],
|
|
13256
|
+
rawOutput: trimmed,
|
|
13257
|
+
};
|
|
13258
|
+
} catch {
|
|
13259
|
+
// fall through to default normalization
|
|
13260
|
+
}
|
|
13261
|
+
}
|
|
13262
|
+
}
|
|
13263
|
+
|
|
13264
|
+
return {
|
|
13265
|
+
outputRoot: workspaceRoot,
|
|
13266
|
+
filesWritten: changedFiles,
|
|
13267
|
+
researchUsed: researchMode === "always",
|
|
13268
|
+
gaps: [],
|
|
13269
|
+
nextActions: [],
|
|
13270
|
+
rawOutput: trimmed,
|
|
13271
|
+
};
|
|
13272
|
+
}
|
|
13273
|
+
|
|
13274
|
+
async function captureFileContents(filePaths) {
|
|
13275
|
+
const snapshot = {};
|
|
13276
|
+
for (const filePath of filePaths) {
|
|
13277
|
+
if (await pathExists(filePath)) {
|
|
13278
|
+
snapshot[filePath] = await readFile(filePath, "utf8");
|
|
13279
|
+
} else {
|
|
13280
|
+
snapshot[filePath] = null;
|
|
13281
|
+
}
|
|
13282
|
+
}
|
|
13283
|
+
return snapshot;
|
|
13284
|
+
}
|
|
13285
|
+
|
|
13286
|
+
async function readArchitectureDriftStatus(workspaceRoot, snapshot) {
|
|
13287
|
+
const specRoots = await listSpecPackRoots(workspaceRoot);
|
|
13288
|
+
const productPath = path.join(workspaceRoot, "PRODUCT.md");
|
|
13289
|
+
const architecturePath = path.join(workspaceRoot, "ARCHITECTURE.md");
|
|
13290
|
+
const rulesPath = path.join(workspaceRoot, "ENGINEERING_RULES.md");
|
|
13291
|
+
const techPath = path.join(workspaceRoot, "TECH.md");
|
|
13292
|
+
const roadmapPath = path.join(workspaceRoot, "ROADMAP.md");
|
|
13293
|
+
const adrReadmePath = path.join(workspaceRoot, "docs", "adr", "README.md");
|
|
13294
|
+
const metadataPath = path.join(
|
|
13295
|
+
workspaceRoot,
|
|
13296
|
+
".cbx",
|
|
13297
|
+
ARCHITECTURE_BUILD_METADATA_FILENAME,
|
|
13298
|
+
);
|
|
13299
|
+
const productExists = await pathExists(productPath);
|
|
13300
|
+
const architectureExists = await pathExists(architecturePath);
|
|
13301
|
+
const rulesExists = await pathExists(rulesPath);
|
|
13302
|
+
const techExists = await pathExists(techPath);
|
|
13303
|
+
const roadmapExists = await pathExists(roadmapPath);
|
|
13304
|
+
const adrReadmeExists = await pathExists(adrReadmePath);
|
|
13305
|
+
|
|
13306
|
+
const expectedProductHash = hashStableObject(
|
|
13307
|
+
inferProductFoundationProfile(snapshot, specRoots),
|
|
13308
|
+
);
|
|
13309
|
+
const expectedArchitectureHash = hashStableObject(
|
|
13310
|
+
inferArchitectureDocProfile(snapshot, specRoots),
|
|
13311
|
+
);
|
|
13312
|
+
const expectedRulesHash = hashStableObject(
|
|
13313
|
+
inferArchitectureContractProfile(snapshot),
|
|
13314
|
+
);
|
|
13315
|
+
const expectedTechHash = hashStableObject({
|
|
13316
|
+
style: inferArchitectureContractProfile(snapshot).style,
|
|
13317
|
+
topDirs: snapshot.topDirs,
|
|
13318
|
+
frameworks: snapshot.frameworks,
|
|
13319
|
+
architectureByApp: snapshot.architectureByApp,
|
|
13320
|
+
});
|
|
13321
|
+
const expectedRoadmapHash = hashStableObject({
|
|
13322
|
+
topDirs: snapshot.topDirs,
|
|
13323
|
+
frameworks: snapshot.frameworks,
|
|
13324
|
+
specRoots,
|
|
13325
|
+
});
|
|
13326
|
+
|
|
13327
|
+
const findings = [];
|
|
13328
|
+
let actualProductHash = null;
|
|
13329
|
+
let actualArchitectureHash = null;
|
|
13330
|
+
let actualRulesHash = null;
|
|
13331
|
+
let actualTechHash = null;
|
|
13332
|
+
let actualRoadmapHash = null;
|
|
13333
|
+
|
|
13334
|
+
if (!productExists) {
|
|
13335
|
+
findings.push("PRODUCT.md is missing.");
|
|
13336
|
+
} else {
|
|
13337
|
+
const content = await readFile(productPath, "utf8");
|
|
13338
|
+
actualProductHash = extractTaggedMarkerAttribute(
|
|
13339
|
+
content,
|
|
13340
|
+
PRODUCT_FOUNDATION_BLOCK_START_RE,
|
|
13341
|
+
"profile",
|
|
13342
|
+
);
|
|
13343
|
+
if (!actualProductHash) {
|
|
13344
|
+
findings.push("PRODUCT.md is missing the managed product foundation block.");
|
|
13345
|
+
} else if (actualProductHash !== expectedProductHash) {
|
|
13346
|
+
findings.push(
|
|
13347
|
+
`PRODUCT.md foundation is stale (expected ${expectedProductHash}, found ${actualProductHash}).`,
|
|
13348
|
+
);
|
|
13349
|
+
}
|
|
13350
|
+
}
|
|
13351
|
+
|
|
13352
|
+
if (!architectureExists) {
|
|
13353
|
+
findings.push("ARCHITECTURE.md is missing.");
|
|
13354
|
+
} else {
|
|
13355
|
+
const content = await readFile(architecturePath, "utf8");
|
|
13356
|
+
actualArchitectureHash = extractTaggedMarkerAttribute(
|
|
13357
|
+
content,
|
|
13358
|
+
ARCHITECTURE_DOC_BLOCK_START_RE,
|
|
13359
|
+
"profile",
|
|
13360
|
+
);
|
|
13361
|
+
if (!actualArchitectureHash) {
|
|
13362
|
+
findings.push("ARCHITECTURE.md is missing the managed architecture backbone block.");
|
|
13363
|
+
} else if (actualArchitectureHash !== expectedArchitectureHash) {
|
|
13364
|
+
findings.push(
|
|
13365
|
+
`ARCHITECTURE.md backbone is stale (expected ${expectedArchitectureHash}, found ${actualArchitectureHash}).`,
|
|
13366
|
+
);
|
|
13367
|
+
}
|
|
13368
|
+
}
|
|
13369
|
+
|
|
13370
|
+
if (!rulesExists) {
|
|
13371
|
+
findings.push("ENGINEERING_RULES.md is missing.");
|
|
13372
|
+
} else {
|
|
13373
|
+
const content = await readFile(rulesPath, "utf8");
|
|
13374
|
+
actualRulesHash = extractTaggedMarkerAttribute(
|
|
13375
|
+
content,
|
|
13376
|
+
ENGINEERING_ARCHITECTURE_BLOCK_START_RE,
|
|
13377
|
+
"profile",
|
|
13378
|
+
);
|
|
13379
|
+
if (!actualRulesHash) {
|
|
13380
|
+
findings.push("ENGINEERING_RULES.md is missing the managed architecture contract block.");
|
|
13381
|
+
} else if (actualRulesHash !== expectedRulesHash) {
|
|
13382
|
+
findings.push(
|
|
13383
|
+
`ENGINEERING_RULES.md architecture profile is stale (expected ${expectedRulesHash}, found ${actualRulesHash}).`,
|
|
13384
|
+
);
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
13387
|
+
|
|
13388
|
+
if (!techExists) {
|
|
13389
|
+
findings.push("TECH.md is missing.");
|
|
13390
|
+
} else {
|
|
13391
|
+
const content = await readFile(techPath, "utf8");
|
|
13392
|
+
actualTechHash = extractTaggedMarkerAttribute(
|
|
13393
|
+
content,
|
|
13394
|
+
TECH_ARCHITECTURE_BLOCK_START_RE,
|
|
13395
|
+
"snapshot",
|
|
13396
|
+
);
|
|
13397
|
+
if (!actualTechHash) {
|
|
13398
|
+
findings.push("TECH.md is missing the managed architecture snapshot block.");
|
|
13399
|
+
} else if (actualTechHash !== expectedTechHash) {
|
|
13400
|
+
findings.push(
|
|
13401
|
+
`TECH.md architecture snapshot is stale (expected ${expectedTechHash}, found ${actualTechHash}).`,
|
|
13402
|
+
);
|
|
13403
|
+
}
|
|
13404
|
+
}
|
|
13405
|
+
|
|
13406
|
+
if (!roadmapExists) {
|
|
13407
|
+
findings.push("ROADMAP.md is missing.");
|
|
13408
|
+
} else {
|
|
13409
|
+
const content = await readFile(roadmapPath, "utf8");
|
|
13410
|
+
actualRoadmapHash = extractTaggedMarkerAttribute(
|
|
13411
|
+
content,
|
|
13412
|
+
ROADMAP_FOUNDATION_BLOCK_START_RE,
|
|
13413
|
+
"profile",
|
|
13414
|
+
);
|
|
13415
|
+
if (!actualRoadmapHash) {
|
|
13416
|
+
findings.push("ROADMAP.md is missing the managed roadmap foundation block.");
|
|
13417
|
+
} else if (actualRoadmapHash !== expectedRoadmapHash) {
|
|
13418
|
+
findings.push(
|
|
13419
|
+
`ROADMAP.md backbone is stale (expected ${expectedRoadmapHash}, found ${actualRoadmapHash}).`,
|
|
13420
|
+
);
|
|
13421
|
+
}
|
|
13422
|
+
}
|
|
13423
|
+
|
|
13424
|
+
if (!adrReadmeExists) {
|
|
13425
|
+
findings.push("docs/adr/README.md is missing.");
|
|
13426
|
+
}
|
|
13427
|
+
|
|
13428
|
+
const metadata = await readJsonFileIfExists(metadataPath);
|
|
13429
|
+
if (!metadata.exists) {
|
|
13430
|
+
findings.push("Architecture build metadata is missing.");
|
|
13431
|
+
}
|
|
13432
|
+
|
|
13433
|
+
return {
|
|
13434
|
+
stale: findings.length > 0,
|
|
13435
|
+
findings,
|
|
13436
|
+
productPath,
|
|
13437
|
+
architecturePath,
|
|
13438
|
+
rulesPath,
|
|
13439
|
+
techPath,
|
|
13440
|
+
roadmapPath,
|
|
13441
|
+
adrReadmePath,
|
|
13442
|
+
metadataPath,
|
|
13443
|
+
expectedProductHash,
|
|
13444
|
+
expectedArchitectureHash,
|
|
13445
|
+
expectedRulesHash,
|
|
13446
|
+
expectedTechHash,
|
|
13447
|
+
expectedRoadmapHash,
|
|
13448
|
+
actualProductHash,
|
|
13449
|
+
actualArchitectureHash,
|
|
13450
|
+
actualRulesHash,
|
|
13451
|
+
actualTechHash,
|
|
13452
|
+
actualRoadmapHash,
|
|
13453
|
+
};
|
|
13454
|
+
}
|
|
13455
|
+
|
|
13456
|
+
async function runBuildArchitecture(options) {
|
|
13457
|
+
try {
|
|
13458
|
+
const platform = normalizeArchitectureBuildPlatform(options.platform);
|
|
13459
|
+
const researchMode = normalizeArchitectureResearchMode(options.research);
|
|
13460
|
+
const overwrite = Boolean(options.overwrite);
|
|
13461
|
+
const dryRun = Boolean(options.dryRun);
|
|
13462
|
+
const emitJson = Boolean(options.json);
|
|
13463
|
+
const checkOnly = Boolean(options.check);
|
|
13464
|
+
const cwd = process.cwd();
|
|
13465
|
+
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
13466
|
+
const snapshot = await collectTechSnapshot(workspaceRoot);
|
|
13467
|
+
const specRoots = await listSpecPackRoots(workspaceRoot);
|
|
13468
|
+
|
|
13469
|
+
if (checkOnly) {
|
|
13470
|
+
const drift = await readArchitectureDriftStatus(workspaceRoot, snapshot);
|
|
13471
|
+
if (emitJson) {
|
|
13472
|
+
console.log(JSON.stringify(drift, null, 2));
|
|
13473
|
+
} else {
|
|
13474
|
+
console.log(`Platform: ${platform}`);
|
|
13475
|
+
console.log(`Workspace: ${toPosixPath(workspaceRoot)}`);
|
|
13476
|
+
console.log(`Status: ${drift.stale ? "stale" : "fresh"}`);
|
|
13477
|
+
console.log(
|
|
13478
|
+
"Backbone docs: PRODUCT.md, ARCHITECTURE.md, ENGINEERING_RULES.md, TECH.md, ROADMAP.md, docs/adr/README.md",
|
|
13479
|
+
);
|
|
13480
|
+
if (drift.findings.length > 0) {
|
|
13481
|
+
console.log("Findings:");
|
|
13482
|
+
for (const finding of drift.findings) {
|
|
13483
|
+
console.log(`- ${finding}`);
|
|
13484
|
+
}
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13487
|
+
if (drift.stale) process.exit(1);
|
|
13488
|
+
return;
|
|
13489
|
+
}
|
|
13490
|
+
|
|
13491
|
+
const managedFilePaths = [
|
|
13492
|
+
path.join(workspaceRoot, "PRODUCT.md"),
|
|
13493
|
+
path.join(workspaceRoot, "ARCHITECTURE.md"),
|
|
13494
|
+
path.join(workspaceRoot, "ENGINEERING_RULES.md"),
|
|
13495
|
+
path.join(workspaceRoot, "TECH.md"),
|
|
13496
|
+
path.join(workspaceRoot, "ROADMAP.md"),
|
|
13497
|
+
path.join(workspaceRoot, "docs", "adr", "README.md"),
|
|
13498
|
+
path.join(workspaceRoot, "docs", "adr", "0000-template.md"),
|
|
13499
|
+
];
|
|
13500
|
+
const filesBefore = dryRun
|
|
13501
|
+
? Object.fromEntries(managedFilePaths.map((filePath) => [filePath, null]))
|
|
13502
|
+
: await captureFileContents(managedFilePaths);
|
|
13503
|
+
|
|
13504
|
+
const scaffold = await ensureArchitectureDocScaffold({
|
|
13505
|
+
workspaceRoot,
|
|
13506
|
+
snapshot,
|
|
13507
|
+
specRoots,
|
|
13508
|
+
overwrite,
|
|
13509
|
+
dryRun,
|
|
13510
|
+
});
|
|
13511
|
+
const coreSkills = [
|
|
13512
|
+
"architecture-doc",
|
|
13513
|
+
"system-design",
|
|
13514
|
+
"tech-doc",
|
|
13515
|
+
"frontend-design",
|
|
13516
|
+
];
|
|
13517
|
+
const conditionalSkills = resolveArchitectureConditionalSkills(
|
|
13518
|
+
snapshot,
|
|
13519
|
+
specRoots,
|
|
13520
|
+
researchMode,
|
|
13521
|
+
);
|
|
13522
|
+
const skillBundle = [...coreSkills, ...conditionalSkills];
|
|
13523
|
+
const skillPathHints = await resolveArchitectureSkillPathHints(
|
|
13524
|
+
platform,
|
|
13525
|
+
cwd,
|
|
13526
|
+
skillBundle,
|
|
13527
|
+
);
|
|
13528
|
+
const prompt = buildArchitecturePrompt({
|
|
13529
|
+
platform,
|
|
13530
|
+
workspaceRoot,
|
|
13531
|
+
snapshot,
|
|
13532
|
+
specRoots,
|
|
13533
|
+
researchMode,
|
|
13534
|
+
coreSkills,
|
|
13535
|
+
conditionalSkills,
|
|
13536
|
+
skillPathHints,
|
|
13537
|
+
});
|
|
13538
|
+
const adapter = await probeArchitectureAdapter(platform, workspaceRoot);
|
|
13539
|
+
const args = adapter.buildInvocation(prompt);
|
|
13540
|
+
|
|
13541
|
+
if (dryRun) {
|
|
13542
|
+
const summary = {
|
|
13543
|
+
platform,
|
|
13544
|
+
workspaceRoot: toPosixPath(workspaceRoot),
|
|
13545
|
+
adapter: adapter.binary,
|
|
13546
|
+
invocation: [adapter.binary, ...args],
|
|
13547
|
+
researchMode,
|
|
13548
|
+
managedTargets: [
|
|
13549
|
+
toPosixPath(scaffold.productPath),
|
|
13550
|
+
toPosixPath(scaffold.architectureDocPath),
|
|
13551
|
+
toPosixPath(scaffold.engineeringRulesPath),
|
|
13552
|
+
toPosixPath(scaffold.techMdPath),
|
|
13553
|
+
toPosixPath(scaffold.roadmapPath),
|
|
13554
|
+
toPosixPath(scaffold.adrReadmePath),
|
|
13555
|
+
toPosixPath(scaffold.adrTemplatePath),
|
|
13556
|
+
],
|
|
13557
|
+
skillBundle,
|
|
13558
|
+
};
|
|
13559
|
+
if (emitJson) {
|
|
13560
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
13561
|
+
} else {
|
|
13562
|
+
console.log(`Platform: ${platform}`);
|
|
13563
|
+
console.log(`Workspace: ${toPosixPath(workspaceRoot)}`);
|
|
13564
|
+
console.log(`Adapter: ${adapter.binary}`);
|
|
13565
|
+
console.log(`Research mode: ${researchMode}`);
|
|
13566
|
+
console.log(
|
|
13567
|
+
`Managed targets: ${toPosixPath(scaffold.engineeringRulesPath)}, ${toPosixPath(scaffold.techMdPath)}`,
|
|
13568
|
+
);
|
|
13569
|
+
console.log(`Skill bundle: ${skillBundle.join(", ")}`);
|
|
13570
|
+
console.log(`Invocation: ${[adapter.binary, ...args].join(" ")}`);
|
|
13571
|
+
}
|
|
13572
|
+
return;
|
|
13573
|
+
}
|
|
13574
|
+
|
|
13575
|
+
if (!emitJson) {
|
|
13576
|
+
console.log(`Streaming ${adapter.binary} output...`);
|
|
13577
|
+
}
|
|
13578
|
+
|
|
13579
|
+
const execution = await spawnCapture(adapter.binary, args, {
|
|
13580
|
+
cwd: workspaceRoot,
|
|
13581
|
+
env: process.env,
|
|
13582
|
+
streamOutput: !emitJson,
|
|
13583
|
+
});
|
|
13584
|
+
if (!execution.ok) {
|
|
13585
|
+
throw new Error(explainArchitectureBuildFailure(platform, execution));
|
|
13586
|
+
}
|
|
13587
|
+
|
|
13588
|
+
const filesAfter = await captureFileContents(managedFilePaths);
|
|
13589
|
+
const changedFiles = managedFilePaths
|
|
13590
|
+
.filter((filePath) => filesBefore[filePath] !== filesAfter[filePath])
|
|
13591
|
+
.map((filePath) => toPosixPath(path.relative(workspaceRoot, filePath)));
|
|
13592
|
+
|
|
13593
|
+
const rulesContent =
|
|
13594
|
+
filesAfter[scaffold.engineeringRulesPath] ??
|
|
13595
|
+
(await readFile(scaffold.engineeringRulesPath, "utf8"));
|
|
13596
|
+
const techContent =
|
|
13597
|
+
filesAfter[scaffold.techMdPath] ?? (await readFile(scaffold.techMdPath, "utf8"));
|
|
13598
|
+
const productContent =
|
|
13599
|
+
filesAfter[scaffold.productPath] ?? (await readFile(scaffold.productPath, "utf8"));
|
|
13600
|
+
const architectureContent =
|
|
13601
|
+
filesAfter[scaffold.architectureDocPath] ??
|
|
13602
|
+
(await readFile(scaffold.architectureDocPath, "utf8"));
|
|
13603
|
+
const roadmapContent =
|
|
13604
|
+
filesAfter[scaffold.roadmapPath] ?? (await readFile(scaffold.roadmapPath, "utf8"));
|
|
13605
|
+
|
|
13606
|
+
const metadataPath = path.join(
|
|
13607
|
+
workspaceRoot,
|
|
13608
|
+
".cbx",
|
|
13609
|
+
ARCHITECTURE_BUILD_METADATA_FILENAME,
|
|
13610
|
+
);
|
|
13611
|
+
const metadata = buildArchitectureBuildMetadata({
|
|
13612
|
+
platform,
|
|
13613
|
+
researchMode,
|
|
13614
|
+
productProfileHash:
|
|
13615
|
+
extractTaggedMarkerAttribute(
|
|
13616
|
+
productContent,
|
|
13617
|
+
PRODUCT_FOUNDATION_BLOCK_START_RE,
|
|
13618
|
+
"profile",
|
|
13619
|
+
) || "unknown",
|
|
13620
|
+
architectureDocHash:
|
|
13621
|
+
extractTaggedMarkerAttribute(
|
|
13622
|
+
architectureContent,
|
|
13623
|
+
ARCHITECTURE_DOC_BLOCK_START_RE,
|
|
13624
|
+
"profile",
|
|
13625
|
+
) || "unknown",
|
|
13626
|
+
rulesProfileHash:
|
|
13627
|
+
extractTaggedMarkerAttribute(
|
|
13628
|
+
rulesContent,
|
|
13629
|
+
ENGINEERING_ARCHITECTURE_BLOCK_START_RE,
|
|
13630
|
+
"profile",
|
|
13631
|
+
) || "unknown",
|
|
13632
|
+
techSnapshotHash:
|
|
13633
|
+
extractTaggedMarkerAttribute(
|
|
13634
|
+
techContent,
|
|
13635
|
+
TECH_ARCHITECTURE_BLOCK_START_RE,
|
|
13636
|
+
"snapshot",
|
|
13637
|
+
) || "unknown",
|
|
13638
|
+
roadmapProfileHash:
|
|
13639
|
+
extractTaggedMarkerAttribute(
|
|
13640
|
+
roadmapContent,
|
|
13641
|
+
ROADMAP_FOUNDATION_BLOCK_START_RE,
|
|
13642
|
+
"profile",
|
|
13643
|
+
) || "unknown",
|
|
13644
|
+
});
|
|
13645
|
+
await mkdir(path.dirname(metadataPath), { recursive: true });
|
|
13646
|
+
await writeFile(
|
|
13647
|
+
metadataPath,
|
|
13648
|
+
`${JSON.stringify(metadata, null, 2)}\n`,
|
|
13649
|
+
"utf8",
|
|
13650
|
+
);
|
|
13651
|
+
|
|
13652
|
+
const result = normalizeArchitectureResult({
|
|
13653
|
+
stdout: execution.stdout,
|
|
13654
|
+
workspaceRoot: toPosixPath(workspaceRoot),
|
|
13655
|
+
researchMode,
|
|
13656
|
+
changedFiles: [...new Set(changedFiles)],
|
|
13657
|
+
});
|
|
13658
|
+
|
|
13659
|
+
if (emitJson) {
|
|
13660
|
+
console.log(
|
|
13661
|
+
JSON.stringify(
|
|
13662
|
+
{
|
|
13663
|
+
platform,
|
|
13664
|
+
adapter: adapter.binary,
|
|
13665
|
+
skillBundle,
|
|
13666
|
+
result,
|
|
13667
|
+
},
|
|
13668
|
+
null,
|
|
13669
|
+
2,
|
|
13670
|
+
),
|
|
13671
|
+
);
|
|
13672
|
+
return;
|
|
13673
|
+
}
|
|
13674
|
+
|
|
13675
|
+
console.log(`Platform: ${platform}`);
|
|
13676
|
+
console.log(`Adapter: ${adapter.binary}`);
|
|
13677
|
+
console.log(`Workspace: ${toPosixPath(workspaceRoot)}`);
|
|
13678
|
+
console.log(
|
|
13679
|
+
"Managed docs: PRODUCT.md, ARCHITECTURE.md, ENGINEERING_RULES.md, TECH.md, ROADMAP.md",
|
|
13680
|
+
);
|
|
13681
|
+
console.log(
|
|
13682
|
+
"ADR scaffold: docs/adr/README.md, docs/adr/0000-template.md",
|
|
13683
|
+
);
|
|
13684
|
+
console.log(`Skill bundle: ${skillBundle.join(", ")}`);
|
|
13685
|
+
console.log(
|
|
13686
|
+
`Files written: ${(result.filesWritten || []).join(", ") || "(none reported)"}`,
|
|
13687
|
+
);
|
|
13688
|
+
console.log(`Research used: ${result.researchUsed ? "yes" : "no"}`);
|
|
13689
|
+
if (result.gaps.length > 0) {
|
|
13690
|
+
console.log(`Gaps: ${result.gaps.join("; ")}`);
|
|
13691
|
+
}
|
|
13692
|
+
if (result.nextActions.length > 0) {
|
|
13693
|
+
console.log(`Next actions: ${result.nextActions.join("; ")}`);
|
|
13694
|
+
}
|
|
13695
|
+
} catch (error) {
|
|
13696
|
+
console.error(`\nError: ${error.message}`);
|
|
13697
|
+
process.exit(1);
|
|
13698
|
+
}
|
|
13699
|
+
}
|
|
13700
|
+
|
|
12084
13701
|
function parseCsvOption(value) {
|
|
12085
13702
|
return String(value || "")
|
|
12086
13703
|
.split(",")
|
|
@@ -12342,11 +13959,7 @@ async function runInitWizard(options) {
|
|
|
12342
13959
|
dryRun: installOutcome.dryRun,
|
|
12343
13960
|
});
|
|
12344
13961
|
printRuleSyncResult(installOutcome.syncResult);
|
|
12345
|
-
|
|
12346
|
-
engineeringResults:
|
|
12347
|
-
installOutcome.engineeringArtifactsResult.engineeringResults,
|
|
12348
|
-
techResult: installOutcome.engineeringArtifactsResult.techResult,
|
|
12349
|
-
});
|
|
13962
|
+
printInstallDocumentationNotice();
|
|
12350
13963
|
printPostmanSetupSummary({
|
|
12351
13964
|
postmanSetup: installOutcome.postmanSetupResult,
|
|
12352
13965
|
});
|
|
@@ -12467,6 +14080,7 @@ export function buildCliProgram() {
|
|
|
12467
14080
|
defaultMcpDockerContainerName: DEFAULT_MCP_DOCKER_CONTAINER_NAME,
|
|
12468
14081
|
runRulesInit,
|
|
12469
14082
|
runRulesTechMd,
|
|
14083
|
+
runBuildArchitecture,
|
|
12470
14084
|
});
|
|
12471
14085
|
}
|
|
12472
14086
|
|