@documentation-robotics/cli 0.1.7 → 0.1.9
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/README.md +18 -17
- package/dist/audit/nodes/ai/prompts.js +1 -0
- package/dist/audit/nodes/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/ai/prompts.js +20 -29
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/gap-analyzer.js.map +1 -1
- package/dist/audit/relationships/analysis/layer-templates.js +36 -0
- package/dist/audit/relationships/analysis/layer-templates.js.map +2 -2
- package/dist/cli.js +8 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/reports.js +3 -3
- package/dist/commands/reports.js.map +1 -1
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/upgrade.js +1 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +2 -8
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/layers.js +1 -0
- package/dist/core/layers.js.map +2 -2
- package/dist/core/migration-registry.js +59 -2
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model.js +3 -16
- package/dist/core/model.js.map +2 -2
- package/dist/core/spec-loader.js.map +1 -1
- package/dist/core/stats-collector.js +2 -0
- package/dist/core/stats-collector.js.map +2 -2
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/export/json-schema-exporter.js.map +1 -1
- package/dist/export/markdown-exporter.js.map +1 -1
- package/dist/export/markdown-utils.js +1 -0
- package/dist/export/markdown-utils.js.map +2 -2
- package/dist/export/mermaid-markdown-exporter.js.map +1 -1
- package/dist/export/openapi-exporter.js.map +1 -1
- package/dist/export/plantuml-exporter.js.map +1 -1
- package/dist/export/report-formatters.js +2 -2
- package/dist/export/report-formatters.js.map +1 -1
- package/dist/export/types.js +1 -0
- package/dist/export/types.js.map +2 -2
- package/dist/generated/layer-registry.js +44 -31
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/layer-types.js +1 -1
- package/dist/generated/layer-types.js.map +2 -2
- package/dist/generated/node-types.js +45 -0
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +201 -19
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/import/archimate-importer.js.map +1 -1
- package/dist/import/openapi-importer.js.map +1 -1
- package/dist/integrations/claude_code/agents/dr-advisor.md +14 -13
- package/dist/integrations/claude_code/agents/dr-extractor.md +2 -2
- package/dist/integrations/claude_code/commands/dr-info.md +1 -1
- package/dist/integrations/claude_code/commands/dr-init.md +3 -3
- package/dist/integrations/claude_code/commands/dr-map.md +2 -2
- package/dist/integrations/claude_code/commands/dr-model.md +1 -1
- package/dist/integrations/claude_code/commands/dr-relate.md +14 -4
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +3 -3
- package/dist/integrations/claude_code/docs/QUICK_START.md +1 -1
- package/dist/integrations/claude_code/docs/README.md +2 -2
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +2 -2
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +679 -0
- package/dist/integrations/claude_code/skills/{dr_08_dataset_layer → dr_09_dataset_layer}/SKILL.md +1 -1
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +2 -2
- package/dist/integrations/github_copilot/agents/dr-advisor.md +14 -13
- package/dist/integrations/github_copilot/agents/dr-architect.md +15 -14
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +2 -2
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +253 -0
- package/dist/reports/model-report-orchestrator.js +3 -3
- package/dist/reports/model-report-orchestrator.js.map +1 -1
- package/dist/schemas/bundled/analyzers/cbm.json +2 -2
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +3 -3
- package/dist/schemas/bundled/apm.json +3 -3
- package/dist/schemas/bundled/application.json +3 -3
- package/dist/schemas/bundled/base.json +29 -1
- package/dist/schemas/bundled/business.json +1 -1
- package/dist/schemas/bundled/data-model.json +3 -3
- package/dist/schemas/bundled/data-store.json +3 -3
- package/dist/schemas/bundled/manifest.json +20 -13
- package/dist/schemas/bundled/motivation.json +1 -1
- package/dist/schemas/bundled/navigation.json +3 -3
- package/dist/schemas/bundled/product.json +469 -0
- package/dist/schemas/bundled/security.json +3 -3
- package/dist/schemas/bundled/technology.json +3 -3
- package/dist/schemas/bundled/testing.json +3 -3
- package/dist/schemas/bundled/ux.json +3 -3
- package/dist/server/server.js +148 -101
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/spec-installer.js +2 -2
- package/dist/utils/spec-installer.js.map +1 -1
- package/dist/utils/spec-version.js +1 -1
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-D114QwJo.css +1 -0
- package/dist/viewer/assets/main-DyuokeQj.js +81 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +4 -1
- package/dist/viewer/assets/main-CY0Xufuj.css +0 -1
- package/dist/viewer/assets/main-DtgErnwF.js +0 -81
- /package/dist/integrations/claude_code/skills/{dr_03_security_layer → dr_04_security_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_04_application_layer → dr_05_application_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_05_technology_layer → dr_06_technology_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_06_api_layer → dr_07_api_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_07_data_model_layer → dr_08_data_model_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_09_ux_layer → dr_10_ux_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_10_navigation_layer → dr_11_navigation_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_11_apm_layer → dr_12_apm_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_12_testing_layer → dr_13_testing_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_03_security_layer → dr_04_security_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_04_application_layer → dr_05_application_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_05_technology_layer → dr_06_technology_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_06_api_layer → dr_07_api_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_07_data_model_layer → dr_08_data_model_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_08_dataset_layer → dr_09_dataset_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_09_ux_layer → dr_10_ux_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_10_navigation_layer → dr_11_navigation_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_11_apm_layer → dr_12_apm_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_12_testing_layer → dr_13_testing_layer}/SKILL.md +0 -0
package/dist/commands/version.js
CHANGED
|
@@ -2,7 +2,7 @@ import ansis from "ansis";
|
|
|
2
2
|
import { getCliBundledSpecVersion } from "../utils/spec-version.js";
|
|
3
3
|
import { isTelemetryBuiltIn, isTelemetryConfigured } from "../telemetry/detect.js";
|
|
4
4
|
import { getSpecReferencePath, getModelPath } from "../utils/project-paths.js";
|
|
5
|
-
const gitHash = "
|
|
5
|
+
const gitHash = "5345bf8", cliVersion = "0.1.9";
|
|
6
6
|
async function versionCommand() {
|
|
7
7
|
const specVersion = getCliBundledSpecVersion(), telemetryBuiltIn = isTelemetryBuiltIn(), telemetryConfigured = await isTelemetryConfigured();
|
|
8
8
|
let telemetryStatus;
|
|
@@ -56,7 +56,7 @@ async function visualizeCommand(options) {
|
|
|
56
56
|
DR_WITH_DANGER: String(withDanger)
|
|
57
57
|
}, { isDebug: getDebugState, isVerbose } = await import("../utils/globals.js");
|
|
58
58
|
getDebugState() && (env.DEBUG = "1", env.DR_TELEMETRY_DEBUG = "1", logDebug("Passing DEBUG=1 and DR_TELEMETRY_DEBUG=1 to subprocess")), isVerbose() && (env.VERBOSE = "1", logDebug("Passing VERBOSE=1 to subprocess")), authToken && (env.DR_AUTH_TOKEN = authToken), options.viewerPath && (env.DR_VIEWER_PATH = options.viewerPath, logDebug(`Using custom viewer from: ${options.viewerPath}`));
|
|
59
|
-
const serverProcess = spawn(
|
|
59
|
+
const serverProcess = spawn(process.execPath, [serverEntryPath], {
|
|
60
60
|
cwd: process.cwd(),
|
|
61
61
|
stdio: ["pipe", "pipe", "pipe"],
|
|
62
62
|
env
|
|
@@ -131,13 +131,7 @@ ${serverOutput}` : `Server exited with code ${code}`;
|
|
|
131
131
|
}), serverProcess.on("error", (error) => {
|
|
132
132
|
const errorCode = error.code;
|
|
133
133
|
let message, suggestions;
|
|
134
|
-
errorCode === "
|
|
135
|
-
"Install Bun: https://bun.sh/docs/installation",
|
|
136
|
-
" curl -fsSL https://bun.sh/install | bash (macOS/Linux)",
|
|
137
|
-
' powershell -c "irm bun.sh/install.ps1 | iex" (Windows)',
|
|
138
|
-
"After installing, make sure `bun` is on your PATH, then re-run `dr visualize`.",
|
|
139
|
-
"Tracking issue: https://github.com/tinkermonkey/documentation_robotics/issues/800"
|
|
140
|
-
]) : errorCode === "EACCES" ? (message = "dr visualize could not run the Bun runtime: permission denied.", suggestions = ["Check that the `bun` executable on your PATH is executable (chmod +x)."]) : message = `Failed to start visualization server: ${error.message}`, isTelemetryEnabled && serverStartupSpan && (async () => {
|
|
134
|
+
errorCode === "EACCES" ? (message = `dr visualize could not run the visualization server: permission denied executing ${process.execPath}.`, suggestions = [`Check that ${process.execPath} is executable (chmod +x).`]) : message = `Failed to start visualization server: ${error.message}`, isTelemetryEnabled && serverStartupSpan && (async () => {
|
|
141
135
|
const { endSpan } = await import("../telemetry/index.js");
|
|
142
136
|
"recordException" in serverStartupSpan && (serverStartupSpan.recordException(error), serverStartupSpan.setStatus({ code: 2, message })), endSpan(serverStartupSpan), serverStartupSpan = null;
|
|
143
137
|
})();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/commands/visualize.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Visualize command - Launch visualization server\n */\n\nimport ansis from \"ansis\"\nimport { spawn } from \"child_process\"\nimport { fileURLToPath } from \"node:url\"\nimport { dirname } from \"node:path\"\nimport { Model } from \"../core/model.js\"\nimport { logVerbose, logDebug } from \"../utils/globals.js\"\nimport { CLIError } from \"../utils/errors.js\"\nimport { getSpecReferencePath, getModelPath } from \"../utils/project-paths.js\"\nimport { getErrorMessage } from \"../utils/errors.js\"\n\n// Conditional telemetry import based on compile-time flag\ndeclare const TELEMETRY_ENABLED: boolean | undefined\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false\n\nexport interface VisualizeOptions {\n port?: number\n noBrowser?: boolean\n noAuth?: boolean\n token?: string\n verbose?: boolean\n debug?: boolean\n withDanger?: boolean\n viewerPath?: string\n}\n\nasync function findAvailablePort(startPort: number): Promise<number> {\n const { createServer } = await import(\"net\")\n for (let port = startPort; port <= 65535; port++) {\n const available = await new Promise<boolean>((resolve) => {\n const server = createServer()\n server.once(\"error\", () => resolve(false))\n server.once(\"listening\", () => { server.close(); resolve(true) })\n server.listen(port, \"127.0.0.1\")\n })\n if (available) return port\n }\n throw new Error(\"No available ports found\")\n}\n\nexport async function visualizeCommand(\n options: VisualizeOptions & { port?: string }\n): Promise<void> {\n try {\n logDebug(\"Loading model for validation...\")\n\n // Load model to validate it exists (without full content to be quick)\n const model = await Model.load(process.cwd(), { lazyLoad: true })\n\n logVerbose(`Model validated: ${model.manifest.name}`)\n\n // Parse and validate port\n let port = 8080\n if (options.port) {\n const portNum = parseInt(String(options.port), 10)\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${options.port}. Port must be between 1 and 65535.`)\n }\n port = portNum\n }\n\n // Find an available port starting from the requested port\n port = await findAvailablePort(port)\n if (port !== (options.port ? parseInt(String(options.port), 10) : 8080)) {\n console.log(ansis.yellow(` Port ${options.port ?? 8080} is in use, using port ${port} instead`))\n }\n\n // Determine auth settings\n const authEnabled = !options.noAuth // Auth enabled by default unless --no-auth flag\n const authToken = options.token // Optional token for testing\n const withDanger = options.withDanger || false // Danger mode disabled by default\n\n logDebug(`Starting visualization server on port ${port}`)\n logDebug(`Authentication: ${authEnabled ? \"enabled\" : \"disabled\"}`)\n if (authEnabled && authToken) {\n logDebug(`Using provided token for authentication`)\n } else if (authEnabled) {\n logDebug(`Token will be auto-generated`)\n }\n if (withDanger) {\n logDebug(`Danger mode enabled - chat permissions will be skipped`)\n }\n\n // Get the directory where this command file is located (dist/commands/)\n const commandDir = dirname(fileURLToPath(import.meta.url))\n const distDir = dirname(commandDir) // Go up to dist/\n const serverEntryPath = `${distDir}/server-entry.js`\n\n logDebug(`Server entry path: ${serverEntryPath}`)\n\n // Create telemetry span for server startup\n let serverStartupSpan: any = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Creating visualize.server.startup span\")\n const { startSpan } = await import(\"../telemetry/index.js\")\n serverStartupSpan = startSpan(\"visualize.server.startup\", {\n \"server.port\": port,\n \"server.auth_enabled\": authEnabled,\n \"server.with_danger\": withDanger,\n })\n logDebug(`[Telemetry] Span created: ${serverStartupSpan ? \"success\" : \"failed\"}`)\n } else {\n logDebug(\"[Telemetry] TELEMETRY_ENABLED is false - no spans will be created\")\n }\n\n // Spawn server in Bun subprocess with environment variables\n const env: Record<string, string> = {\n ...process.env,\n DR_VISUALIZE_PORT: String(port),\n DR_PROJECT_PATH: process.cwd(),\n DR_AUTH_ENABLED: String(authEnabled),\n DR_WITH_DANGER: String(withDanger),\n }\n\n // Pass debug/verbose flags to subprocess\n // The CLI --debug flag sets globalOptions.debug, but subprocess checks process.env.DEBUG\n const { isDebug: getDebugState, isVerbose } = await import(\"../utils/globals.js\")\n if (getDebugState()) {\n env.DEBUG = \"1\"\n env.DR_TELEMETRY_DEBUG = \"1\" // Enable telemetry exporter debug logging\n logDebug(\"Passing DEBUG=1 and DR_TELEMETRY_DEBUG=1 to subprocess\")\n }\n if (isVerbose()) {\n env.VERBOSE = \"1\"\n logDebug(\"Passing VERBOSE=1 to subprocess\")\n }\n\n // Only set auth token if provided\n if (authToken) {\n env.DR_AUTH_TOKEN = authToken\n }\n\n // Set custom viewer path if provided\n if (options.viewerPath) {\n env.DR_VIEWER_PATH = options.viewerPath\n logDebug(`Using custom viewer from: ${options.viewerPath}`)\n }\n\n const serverProcess = spawn(\"bun\", [\"run\", serverEntryPath], {\n cwd: process.cwd(),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env,\n })\n\n let authTokenFromServer = \"\"\n let serverOutput = \"\"\n let serverStartupShown = false\n\n // Capture output from server process\n if (serverProcess.stdout) {\n serverProcess.stdout.on(\"data\", async (data) => {\n const chunk = data.toString()\n serverOutput += chunk\n\n // Process each line individually: Bun's pipe buffering often batches multiple\n // console.log calls (e.g. \"running at\" + \"TOKEN:\") into a single data event.\n // Using startsWith/substring on the whole chunk would either miss the TOKEN line\n // (when \"running at\" is first) or extract a malformed token (when trailing lines\n // are appended), causing the browser to open with the wrong token and get a 403.\n const lines = chunk.split(\"\\n\")\n for (const rawLine of lines) {\n const line = rawLine.trim()\n if (!line) continue\n\n // Extract token from output\n if (line.startsWith(\"TOKEN:\")) {\n authTokenFromServer = line.substring(6)\n logDebug(`Received auth token from server: ${authTokenFromServer.substring(0, 8)}...`)\n\n // If we haven't shown startup info yet and now have the token, show it\n if (serverStartupShown && authEnabled && authTokenFromServer) {\n // Update the URL display with actual token\n console.log(\n ansis.cyan(` Access URL: http://localhost:${port}?token=${authTokenFromServer}`)\n )\n console.log(ansis.dim(` Auth token: ${authTokenFromServer}`))\n\n // Now that we have the token, open browser if needed\n if (!options.noBrowser) {\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}?token=${authTokenFromServer}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n }\n }\n }\n\n // Server started message - defer detailed output if auth enabled\n if (line.includes(\"running at http://localhost\")) {\n serverStartupShown = true\n\n // End telemetry span for server startup\n if (isTelemetryEnabled && serverStartupSpan) {\n logDebug(\n \"[Telemetry] Ending visualize.server.startup span (server started successfully)\"\n )\n const { endSpan } = await import(\"../telemetry/index.js\")\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n logDebug(\"[Telemetry] Span ended\")\n }\n\n console.log(ansis.green(`\u2713 Visualization server started`))\n\n // Display access information (but may be incomplete if auth enabled and token not yet received)\n if (authEnabled) {\n const token = authTokenFromServer || authToken || \"(pending)\"\n if (authTokenFromServer) {\n // Token already received (same chunk), show full info immediately\n console.log(ansis.cyan(` Access URL: http://localhost:${port}?token=${token}`))\n console.log(ansis.dim(` Auth token: ${token}`))\n } else {\n // Token not yet received, just show port for now\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n logDebug(` (waiting for auth token...)`)\n }\n } else {\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n console.log(ansis.yellow(` \u26A0 Authentication disabled`))\n }\n\n if (withDanger) {\n console.log(\n ansis.yellow(` \u26A0 Danger mode enabled - chat permissions will be skipped`)\n )\n }\n\n console.log(ansis.blueBright(`Model name: ${model.manifest.name}`))\n const drFolder = await getSpecReferencePath()\n console.log(\n `DR folder: ${ansis.cyan(drFolder || \"Not found\")}`\n )\n console.log(`Model path: ${ansis.cyan((await getModelPath()) || \"Not found\")}`)\n\n // Optionally open browser\n // If auth is enabled, delay opening until we have the token\n const shouldOpenBrowser = !options.noBrowser\n logDebug(\n `Browser auto-open: ${shouldOpenBrowser ? \"enabled\" : \"disabled\"} (noBrowser=${options.noBrowser})`\n )\n\n if (shouldOpenBrowser && !authEnabled) {\n // Auth disabled: open immediately\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n } else if (shouldOpenBrowser && authEnabled && !authTokenFromServer) {\n // Auth enabled but token not yet in this chunk: will open when TOKEN line is processed\n logDebug(\"Browser auto-open deferred until token is received\")\n } else {\n logDebug(\"Browser auto-open disabled\")\n }\n }\n\n // Log other output\n if (!line.startsWith(\"TOKEN:\")) {\n logDebug(`[server] ${line}`)\n }\n }\n })\n }\n\n // Capture stderr\n if (serverProcess.stderr) {\n serverProcess.stderr.on(\"data\", (data) => {\n const output = data.toString().trim()\n if (output) {\n serverOutput += output + \"\\n\"\n // Log stderr at debug level so --debug flag shows telemetry exporter logs\n logDebug(`[server stderr] ${output}`)\n }\n })\n }\n\n // Handle process close (fires after all stdio streams have closed)\n let rejectKeepAlive: ((reason: Error) => void) | null = null\n\n serverProcess.on(\"close\", (code) => {\n if (code !== 0) {\n // Include server output in error message if available (avoid separate logDebug to prevent minification issues)\n const message = serverOutput\n ? `Server exited with code ${code}\\nServer output:\\n${serverOutput}`\n : `Server exited with code ${code}`\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(new Error(message));\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Print error before rejecting (CLI wrapper expects CLIError messages to be pre-printed)\n console.error(message)\n const error = new CLIError(message, code || 1)\n if (rejectKeepAlive) {\n rejectKeepAlive(error)\n }\n }\n })\n\n // Handle process errors\n serverProcess.on(\"error\", (error) => {\n const errorCode = (error as NodeJS.ErrnoException).code\n let message: string\n let suggestions: string[] | undefined\n\n if (errorCode === \"ENOENT\") {\n // `dr visualize` currently spawns its server as a Bun subprocess (see #800 for\n // the plan to remove this dependency). On a fresh `npm install -g` there's no\n // reason Bun would be present, so this is an expected, actionable failure mode\n // rather than an unexpected crash - explain it instead of surfacing the raw\n // \"spawn bun ENOENT\".\n message = \"dr visualize requires the Bun runtime, which was not found on this system.\"\n suggestions = [\n \"Install Bun: https://bun.sh/docs/installation\",\n \" curl -fsSL https://bun.sh/install | bash (macOS/Linux)\",\n \" powershell -c \\\"irm bun.sh/install.ps1 | iex\\\" (Windows)\",\n \"After installing, make sure `bun` is on your PATH, then re-run `dr visualize`.\",\n \"Tracking issue: https://github.com/tinkermonkey/documentation_robotics/issues/800\",\n ]\n } else if (errorCode === \"EACCES\") {\n message = \"dr visualize could not run the Bun runtime: permission denied.\"\n suggestions = [\"Check that the `bun` executable on your PATH is executable (chmod +x).\"]\n } else {\n message = `Failed to start visualization server: ${error.message}`\n }\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(error);\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Don't print here: rejecting propagates this CLIError up through the catch block\n // below to the top-level CLI handler, which prints `.format()` (including\n // `suggestions`) exactly once. Printing here too would duplicate the whole block.\n const cliError = new CLIError(message, 1, suggestions)\n if (rejectKeepAlive) {\n rejectKeepAlive(cliError)\n }\n })\n\n // Setup periodic telemetry flushing for long-running command\n // Since the visualize command never completes (keep-alive pattern),\n // the cli.execute span would never end and export. We flush periodically\n // to ensure active spans are exported to the telemetry backend.\n let flushInterval: NodeJS.Timeout | null = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Setting up periodic flush (every 15 seconds)\")\n const { flushTelemetry } = await import(\"../telemetry/index.js\")\n\n flushInterval = setInterval(async () => {\n logDebug(\"[Telemetry] Periodic flush: flushing pending spans...\")\n try {\n await flushTelemetry()\n logDebug(\"[Telemetry] Periodic flush complete\")\n } catch (error) {\n // Silently ignore flush errors - telemetry should never break the app\n logDebug(`[Telemetry] Periodic flush failed: ${error}`)\n }\n }, 15000) // Flush every 15 seconds\n }\n\n // Handle graceful shutdown on SIGINT (Ctrl-C) and SIGTERM\n const handleShutdown = async (signal: string) => {\n logDebug(`\\n[${signal}] Shutting down visualization server gracefully...`)\n\n // Clear periodic flush interval\n if (flushInterval) {\n logDebug(\"[Telemetry] Stopping periodic flush\")\n clearInterval(flushInterval)\n flushInterval = null\n }\n\n // Forward signal to server process for graceful shutdown\n if (serverProcess && !serverProcess.killed) {\n logDebug(\"Sending termination signal to server process...\")\n serverProcess.kill(signal as NodeJS.Signals)\n\n // Wait for server to exit (with timeout)\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n logDebug(\"Server shutdown timeout, forcing exit...\")\n if (!serverProcess.killed) {\n serverProcess.kill(\"SIGKILL\")\n }\n resolve()\n }, 3000) // 3 second timeout\n\n serverProcess.on(\"exit\", () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n logDebug(\"Visualization server stopped\")\n process.exit(0)\n }\n\n // Register signal handlers\n process.on(\"SIGINT\", () => handleShutdown(\"SIGINT\"))\n process.on(\"SIGTERM\", () => handleShutdown(\"SIGTERM\"))\n\n // Keep parent process alive - reject promise if server fails\n await new Promise((_, reject) => {\n rejectKeepAlive = reject\n })\n } catch (error) {\n // Throw CLIError instead of process.exit to allow telemetry shutdown\n // Note: Avoid side effects (like logDebug) before throw to prevent minification issues\n if (error instanceof CLIError) {\n // CLIError should have already printed its message\n throw error\n }\n // Non-CLIError needs to be printed and wrapped\n const message = getErrorMessage(error)\n console.error(message)\n throw new CLIError(message, 1)\n }\n}\n\n/**\n * Get the appropriate command to open a URL\n */\nfunction getOpenCommand(): string {\n const platform = process.platform\n\n if (platform === \"darwin\") {\n return \"open\"\n }\n if (platform === \"win32\") {\n return \"start\"\n }\n // Linux and others\n return \"xdg-open\"\n}\n"],
|
|
5
|
-
"mappings": "AAIA,OAAO,WAAW;AAClB,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,YAAY,gBAAgB;AACrC,SAAS,gBAAgB;AACzB,SAAS,sBAAsB,oBAAoB;AACnD,SAAS,uBAAuB;AAIhC,MAAM,qBAAgE;AAatE,eAAe,kBAAkB,WAAiB;AAChD,QAAM,EAAE,aAAY,IAAK,MAAM,OAAO,KAAK;AAC3C,WAAS,OAAO,WAAW,QAAQ,OAAO;AAOxC,QANkB,MAAM,IAAI,QAAiB,CAAC,YAAW;AACvD,YAAM,SAAS,aAAY;AAC3B,aAAO,KAAK,SAAS,MAAM,QAAQ,EAAK,CAAC,GACzC,OAAO,KAAK,aAAa,MAAK;AAAG,eAAO,MAAK,GAAI,QAAQ,EAAI;MAAE,CAAC,GAChE,OAAO,OAAO,MAAM,WAAW;IACjC,CAAC;AACc,aAAO;AAExB,QAAM,IAAI,MAAM,0BAA0B;AAC5C;AAEA,eAAsB,iBACpB,SAA6C;AAE7C,MAAI;AACF,aAAS,iCAAiC;AAG1C,UAAM,QAAQ,MAAM,MAAM,KAAK,QAAQ,IAAG,GAAI,EAAE,UAAU,GAAI,CAAE;AAEhE,eAAW,oBAAoB,MAAM,SAAS,IAAI,EAAE;AAGpD,QAAI,OAAO;AACX,QAAI,QAAQ,MAAM;AAChB,YAAM,UAAU,SAAS,OAAO,QAAQ,IAAI,GAAG,EAAE;AACjD,UAAI,MAAM,OAAO,KAAK,UAAU,KAAK,UAAU;AAC7C,cAAM,IAAI,MAAM,wBAAwB,QAAQ,IAAI,qCAAqC;AAE3F,aAAO;IACT;AAGA,WAAO,MAAM,kBAAkB,IAAI,GAC/B,UAAU,QAAQ,OAAO,SAAS,OAAO,QAAQ,IAAI,GAAG,EAAE,IAAI,SAChE,QAAQ,IAAI,MAAM,OAAO,WAAW,QAAQ,QAAQ,IAAI,0BAA0B,IAAI,UAAU,CAAC;AAInG,UAAM,cAAc,CAAC,QAAQ,QACvB,YAAY,QAAQ,OACpB,aAAa,QAAQ,cAAc;AAEzC,aAAS,yCAAyC,IAAI,EAAE,GACxD,SAAS,mBAAmB,cAAc,YAAY,UAAU,EAAE,GAC9D,eAAe,YACjB,SAAS,yCAAyC,IACzC,eACT,SAAS,8BAA8B,GAErC,cACF,SAAS,wDAAwD;AAInE,UAAM,aAAa,QAAQ,cAAc,YAAY,GAAG,CAAC,GAEnD,kBAAkB,GADR,QAAQ,UAAU,CACA;AAElC,aAAS,sBAAsB,eAAe,EAAE;AAGhD,QAAI,oBAAyB;AAC7B,QAAI,oBAAoB;AACtB,eAAS,oDAAoD;AAC7D,YAAM,EAAE,UAAS,IAAK,MAAM,OAAO,uBAAuB;AAC1D,0BAAoB,UAAU,4BAA4B;QACxD,eAAe;QACf,uBAAuB;QACvB,sBAAsB;OACvB,GACD,SAAS,6BAA6B,oBAAoB,YAAY,QAAQ,EAAE;IAClF;AACE,eAAS,mEAAmE;AAI9E,UAAM,MAA8B;MAClC,GAAG,QAAQ;MACX,mBAAmB,OAAO,IAAI;MAC9B,iBAAiB,QAAQ,IAAG;MAC5B,iBAAiB,OAAO,WAAW;MACnC,gBAAgB,OAAO,UAAU;OAK7B,EAAE,SAAS,eAAe,UAAS,IAAK,MAAM,OAAO,qBAAqB;AAChF,IAAI,cAAa,MACf,IAAI,QAAQ,KACZ,IAAI,qBAAqB,KACzB,SAAS,wDAAwD,IAE/D,UAAS,MACX,IAAI,UAAU,KACd,SAAS,iCAAiC,IAIxC,cACF,IAAI,gBAAgB,YAIlB,QAAQ,eACV,IAAI,iBAAiB,QAAQ,YAC7B,SAAS,6BAA6B,QAAQ,UAAU,EAAE;
|
|
4
|
+
"sourcesContent": ["/**\n * Visualize command - Launch visualization server\n */\n\nimport ansis from \"ansis\"\nimport { spawn } from \"child_process\"\nimport { fileURLToPath } from \"node:url\"\nimport { dirname } from \"node:path\"\nimport { Model } from \"../core/model.js\"\nimport { logVerbose, logDebug } from \"../utils/globals.js\"\nimport { CLIError } from \"../utils/errors.js\"\nimport { getSpecReferencePath, getModelPath } from \"../utils/project-paths.js\"\nimport { getErrorMessage } from \"../utils/errors.js\"\n\n// Conditional telemetry import based on compile-time flag\ndeclare const TELEMETRY_ENABLED: boolean | undefined\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false\n\nexport interface VisualizeOptions {\n port?: number\n noBrowser?: boolean\n noAuth?: boolean\n token?: string\n verbose?: boolean\n debug?: boolean\n withDanger?: boolean\n viewerPath?: string\n}\n\nasync function findAvailablePort(startPort: number): Promise<number> {\n const { createServer } = await import(\"net\")\n for (let port = startPort; port <= 65535; port++) {\n const available = await new Promise<boolean>((resolve) => {\n const server = createServer()\n server.once(\"error\", () => resolve(false))\n server.once(\"listening\", () => { server.close(); resolve(true) })\n server.listen(port, \"127.0.0.1\")\n })\n if (available) return port\n }\n throw new Error(\"No available ports found\")\n}\n\nexport async function visualizeCommand(\n options: VisualizeOptions & { port?: string }\n): Promise<void> {\n try {\n logDebug(\"Loading model for validation...\")\n\n // Load model to validate it exists (without full content to be quick)\n const model = await Model.load(process.cwd(), { lazyLoad: true })\n\n logVerbose(`Model validated: ${model.manifest.name}`)\n\n // Parse and validate port\n let port = 8080\n if (options.port) {\n const portNum = parseInt(String(options.port), 10)\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${options.port}. Port must be between 1 and 65535.`)\n }\n port = portNum\n }\n\n // Find an available port starting from the requested port\n port = await findAvailablePort(port)\n if (port !== (options.port ? parseInt(String(options.port), 10) : 8080)) {\n console.log(ansis.yellow(` Port ${options.port ?? 8080} is in use, using port ${port} instead`))\n }\n\n // Determine auth settings\n const authEnabled = !options.noAuth // Auth enabled by default unless --no-auth flag\n const authToken = options.token // Optional token for testing\n const withDanger = options.withDanger || false // Danger mode disabled by default\n\n logDebug(`Starting visualization server on port ${port}`)\n logDebug(`Authentication: ${authEnabled ? \"enabled\" : \"disabled\"}`)\n if (authEnabled && authToken) {\n logDebug(`Using provided token for authentication`)\n } else if (authEnabled) {\n logDebug(`Token will be auto-generated`)\n }\n if (withDanger) {\n logDebug(`Danger mode enabled - chat permissions will be skipped`)\n }\n\n // Get the directory where this command file is located (dist/commands/)\n const commandDir = dirname(fileURLToPath(import.meta.url))\n const distDir = dirname(commandDir) // Go up to dist/\n const serverEntryPath = `${distDir}/server-entry.js`\n\n logDebug(`Server entry path: ${serverEntryPath}`)\n\n // Create telemetry span for server startup\n let serverStartupSpan: any = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Creating visualize.server.startup span\")\n const { startSpan } = await import(\"../telemetry/index.js\")\n serverStartupSpan = startSpan(\"visualize.server.startup\", {\n \"server.port\": port,\n \"server.auth_enabled\": authEnabled,\n \"server.with_danger\": withDanger,\n })\n logDebug(`[Telemetry] Span created: ${serverStartupSpan ? \"success\" : \"failed\"}`)\n } else {\n logDebug(\"[Telemetry] TELEMETRY_ENABLED is false - no spans will be created\")\n }\n\n // Build environment variables for the server subprocess\n const env: Record<string, string> = {\n ...process.env,\n DR_VISUALIZE_PORT: String(port),\n DR_PROJECT_PATH: process.cwd(),\n DR_AUTH_ENABLED: String(authEnabled),\n DR_WITH_DANGER: String(withDanger),\n }\n\n // Pass debug/verbose flags to subprocess\n // The CLI --debug flag sets globalOptions.debug, but subprocess checks process.env.DEBUG\n const { isDebug: getDebugState, isVerbose } = await import(\"../utils/globals.js\")\n if (getDebugState()) {\n env.DEBUG = \"1\"\n env.DR_TELEMETRY_DEBUG = \"1\" // Enable telemetry exporter debug logging\n logDebug(\"Passing DEBUG=1 and DR_TELEMETRY_DEBUG=1 to subprocess\")\n }\n if (isVerbose()) {\n env.VERBOSE = \"1\"\n logDebug(\"Passing VERBOSE=1 to subprocess\")\n }\n\n // Only set auth token if provided\n if (authToken) {\n env.DR_AUTH_TOKEN = authToken\n }\n\n // Set custom viewer path if provided\n if (options.viewerPath) {\n env.DR_VIEWER_PATH = options.viewerPath\n logDebug(`Using custom viewer from: ${options.viewerPath}`)\n }\n\n // Reuse whichever runtime is currently executing the CLI (node or bun) to launch\n // the server subprocess. server-entry.ts has no Bun-specific dependencies (see #800),\n // so this works identically under Node.js or Bun \u2014 and unlike spawning a literal\n // \"bun\" binary, process.execPath is guaranteed to exist since it's already running.\n const serverProcess = spawn(process.execPath, [serverEntryPath], {\n cwd: process.cwd(),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env,\n })\n\n let authTokenFromServer = \"\"\n let serverOutput = \"\"\n let serverStartupShown = false\n\n // Capture output from server process\n if (serverProcess.stdout) {\n serverProcess.stdout.on(\"data\", async (data) => {\n const chunk = data.toString()\n serverOutput += chunk\n\n // Process each line individually: stdio pipe buffering can batch multiple\n // console.log calls (e.g. \"running at\" + \"TOKEN:\") into a single data event,\n // regardless of runtime.\n // Using startsWith/substring on the whole chunk would either miss the TOKEN line\n // (when \"running at\" is first) or extract a malformed token (when trailing lines\n // are appended), causing the browser to open with the wrong token and get a 403.\n const lines = chunk.split(\"\\n\")\n for (const rawLine of lines) {\n const line = rawLine.trim()\n if (!line) continue\n\n // Extract token from output\n if (line.startsWith(\"TOKEN:\")) {\n authTokenFromServer = line.substring(6)\n logDebug(`Received auth token from server: ${authTokenFromServer.substring(0, 8)}...`)\n\n // If we haven't shown startup info yet and now have the token, show it\n if (serverStartupShown && authEnabled && authTokenFromServer) {\n // Update the URL display with actual token\n console.log(\n ansis.cyan(` Access URL: http://localhost:${port}?token=${authTokenFromServer}`)\n )\n console.log(ansis.dim(` Auth token: ${authTokenFromServer}`))\n\n // Now that we have the token, open browser if needed\n if (!options.noBrowser) {\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}?token=${authTokenFromServer}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n }\n }\n }\n\n // Server started message - defer detailed output if auth enabled\n if (line.includes(\"running at http://localhost\")) {\n serverStartupShown = true\n\n // End telemetry span for server startup\n if (isTelemetryEnabled && serverStartupSpan) {\n logDebug(\n \"[Telemetry] Ending visualize.server.startup span (server started successfully)\"\n )\n const { endSpan } = await import(\"../telemetry/index.js\")\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n logDebug(\"[Telemetry] Span ended\")\n }\n\n console.log(ansis.green(`\u2713 Visualization server started`))\n\n // Display access information (but may be incomplete if auth enabled and token not yet received)\n if (authEnabled) {\n const token = authTokenFromServer || authToken || \"(pending)\"\n if (authTokenFromServer) {\n // Token already received (same chunk), show full info immediately\n console.log(ansis.cyan(` Access URL: http://localhost:${port}?token=${token}`))\n console.log(ansis.dim(` Auth token: ${token}`))\n } else {\n // Token not yet received, just show port for now\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n logDebug(` (waiting for auth token...)`)\n }\n } else {\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n console.log(ansis.yellow(` \u26A0 Authentication disabled`))\n }\n\n if (withDanger) {\n console.log(\n ansis.yellow(` \u26A0 Danger mode enabled - chat permissions will be skipped`)\n )\n }\n\n console.log(ansis.blueBright(`Model name: ${model.manifest.name}`))\n const drFolder = await getSpecReferencePath()\n console.log(\n `DR folder: ${ansis.cyan(drFolder || \"Not found\")}`\n )\n console.log(`Model path: ${ansis.cyan((await getModelPath()) || \"Not found\")}`)\n\n // Optionally open browser\n // If auth is enabled, delay opening until we have the token\n const shouldOpenBrowser = !options.noBrowser\n logDebug(\n `Browser auto-open: ${shouldOpenBrowser ? \"enabled\" : \"disabled\"} (noBrowser=${options.noBrowser})`\n )\n\n if (shouldOpenBrowser && !authEnabled) {\n // Auth disabled: open immediately\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n } else if (shouldOpenBrowser && authEnabled && !authTokenFromServer) {\n // Auth enabled but token not yet in this chunk: will open when TOKEN line is processed\n logDebug(\"Browser auto-open deferred until token is received\")\n } else {\n logDebug(\"Browser auto-open disabled\")\n }\n }\n\n // Log other output\n if (!line.startsWith(\"TOKEN:\")) {\n logDebug(`[server] ${line}`)\n }\n }\n })\n }\n\n // Capture stderr\n if (serverProcess.stderr) {\n serverProcess.stderr.on(\"data\", (data) => {\n const output = data.toString().trim()\n if (output) {\n serverOutput += output + \"\\n\"\n // Log stderr at debug level so --debug flag shows telemetry exporter logs\n logDebug(`[server stderr] ${output}`)\n }\n })\n }\n\n // Handle process close (fires after all stdio streams have closed)\n let rejectKeepAlive: ((reason: Error) => void) | null = null\n\n serverProcess.on(\"close\", (code) => {\n if (code !== 0) {\n // Include server output in error message if available (avoid separate logDebug to prevent minification issues)\n const message = serverOutput\n ? `Server exited with code ${code}\\nServer output:\\n${serverOutput}`\n : `Server exited with code ${code}`\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(new Error(message));\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Print error before rejecting (CLI wrapper expects CLIError messages to be pre-printed)\n console.error(message)\n const error = new CLIError(message, code || 1)\n if (rejectKeepAlive) {\n rejectKeepAlive(error)\n }\n }\n })\n\n // Handle process errors\n serverProcess.on(\"error\", (error) => {\n const errorCode = (error as NodeJS.ErrnoException).code\n let message: string\n let suggestions: string[] | undefined\n\n // The server subprocess is launched via process.execPath (the same node/bun\n // binary already running this CLI), so it's always present - ENOENT here would\n // indicate something unusual about the runtime environment rather than a missing\n // dependency (see #800: this used to hard-depend on a separately installed `bun`).\n if (errorCode === \"EACCES\") {\n message = `dr visualize could not run the visualization server: permission denied executing ${process.execPath}.`\n suggestions = [`Check that ${process.execPath} is executable (chmod +x).`]\n } else {\n message = `Failed to start visualization server: ${error.message}`\n }\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(error);\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Don't print here: rejecting propagates this CLIError up through the catch block\n // below to the top-level CLI handler, which prints `.format()` (including\n // `suggestions`) exactly once. Printing here too would duplicate the whole block.\n const cliError = new CLIError(message, 1, suggestions)\n if (rejectKeepAlive) {\n rejectKeepAlive(cliError)\n }\n })\n\n // Setup periodic telemetry flushing for long-running command\n // Since the visualize command never completes (keep-alive pattern),\n // the cli.execute span would never end and export. We flush periodically\n // to ensure active spans are exported to the telemetry backend.\n let flushInterval: NodeJS.Timeout | null = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Setting up periodic flush (every 15 seconds)\")\n const { flushTelemetry } = await import(\"../telemetry/index.js\")\n\n flushInterval = setInterval(async () => {\n logDebug(\"[Telemetry] Periodic flush: flushing pending spans...\")\n try {\n await flushTelemetry()\n logDebug(\"[Telemetry] Periodic flush complete\")\n } catch (error) {\n // Silently ignore flush errors - telemetry should never break the app\n logDebug(`[Telemetry] Periodic flush failed: ${error}`)\n }\n }, 15000) // Flush every 15 seconds\n }\n\n // Handle graceful shutdown on SIGINT (Ctrl-C) and SIGTERM\n const handleShutdown = async (signal: string) => {\n logDebug(`\\n[${signal}] Shutting down visualization server gracefully...`)\n\n // Clear periodic flush interval\n if (flushInterval) {\n logDebug(\"[Telemetry] Stopping periodic flush\")\n clearInterval(flushInterval)\n flushInterval = null\n }\n\n // Forward signal to server process for graceful shutdown\n if (serverProcess && !serverProcess.killed) {\n logDebug(\"Sending termination signal to server process...\")\n serverProcess.kill(signal as NodeJS.Signals)\n\n // Wait for server to exit (with timeout)\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n logDebug(\"Server shutdown timeout, forcing exit...\")\n if (!serverProcess.killed) {\n serverProcess.kill(\"SIGKILL\")\n }\n resolve()\n }, 3000) // 3 second timeout\n\n serverProcess.on(\"exit\", () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n logDebug(\"Visualization server stopped\")\n process.exit(0)\n }\n\n // Register signal handlers\n process.on(\"SIGINT\", () => handleShutdown(\"SIGINT\"))\n process.on(\"SIGTERM\", () => handleShutdown(\"SIGTERM\"))\n\n // Keep parent process alive - reject promise if server fails\n await new Promise((_, reject) => {\n rejectKeepAlive = reject\n })\n } catch (error) {\n // Throw CLIError instead of process.exit to allow telemetry shutdown\n // Note: Avoid side effects (like logDebug) before throw to prevent minification issues\n if (error instanceof CLIError) {\n // CLIError should have already printed its message\n throw error\n }\n // Non-CLIError needs to be printed and wrapped\n const message = getErrorMessage(error)\n console.error(message)\n throw new CLIError(message, 1)\n }\n}\n\n/**\n * Get the appropriate command to open a URL\n */\nfunction getOpenCommand(): string {\n const platform = process.platform\n\n if (platform === \"darwin\") {\n return \"open\"\n }\n if (platform === \"win32\") {\n return \"start\"\n }\n // Linux and others\n return \"xdg-open\"\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/core/layers.js
CHANGED
package/dist/core/layers.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/core/layers.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Canonical layer names and ordering for the
|
|
5
|
-
"mappings": "AAKO,MAAM,wBAAwB;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAQI,SAAU,cAAc,WAAiB;AAC7C,QAAM,QAAQ,sBAAsB,QAAQ,SAA+B;AAC3E,SAAO,SAAS,IAAI,QAAQ,IAAI;AAClC;AAKM,SAAU,gBAAgB,OAAa;AAC3C,MAAI,UAAQ,KAAK,QAAQ,sBAAsB;AAG/C,WAAO,sBAAsB,QAAQ,CAAC;AACxC;AAKM,SAAU,iBAAiB,MAAY;AAC3C,SAAO,sBAAsB,SAAS,IAA0B;AAClE;",
|
|
4
|
+
"sourcesContent": ["/**\n * Canonical layer names and ordering for the 13-layer architecture model.\n * This is the source of truth for layer naming and ordering.\n */\n\nexport const CANONICAL_LAYER_NAMES = [\n 'motivation',\n 'business',\n 'product',\n 'security',\n 'application',\n 'technology',\n 'api',\n 'data-model',\n 'data-store',\n 'ux',\n 'navigation',\n 'apm',\n 'testing'\n] as const;\n\nexport type CanonicalLayerName = (typeof CANONICAL_LAYER_NAMES)[number];\n\n/**\n * Get the numeric order of a layer (1-13)\n */\nexport function getLayerOrder(layerName: string): number {\n const index = CANONICAL_LAYER_NAMES.indexOf(layerName as CanonicalLayerName);\n return index >= 0 ? index + 1 : -1;\n}\n\n/**\n * Get layer name by numeric order (1-13)\n */\nexport function getLayerByOrder(order: number): string | undefined {\n if (order < 1 || order > CANONICAL_LAYER_NAMES.length) {\n return undefined;\n }\n return CANONICAL_LAYER_NAMES[order - 1];\n}\n\n/**\n * Check if a layer name is valid (type predicate for compile-time narrowing)\n */\nexport function isValidLayerName(name: string): name is CanonicalLayerName {\n return CANONICAL_LAYER_NAMES.includes(name as CanonicalLayerName);\n}\n"],
|
|
5
|
+
"mappings": "AAKO,MAAM,wBAAwB;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAQI,SAAU,cAAc,WAAiB;AAC7C,QAAM,QAAQ,sBAAsB,QAAQ,SAA+B;AAC3E,SAAO,SAAS,IAAI,QAAQ,IAAI;AAClC;AAKM,SAAU,gBAAgB,OAAa;AAC3C,MAAI,UAAQ,KAAK,QAAQ,sBAAsB;AAG/C,WAAO,sBAAsB,QAAQ,CAAC;AACxC;AAKM,SAAU,iBAAiB,MAAY;AAC3C,SAAO,sBAAsB,SAAS,IAA0B;AAClE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -80,6 +80,60 @@ class MigrationRegistry {
|
|
|
80
80
|
filesModified: 0,
|
|
81
81
|
description: "Spec version updated to 0.8.4 (Analyzer Infrastructure)"
|
|
82
82
|
})
|
|
83
|
+
}), this.migrations.push({
|
|
84
|
+
fromVersion: "0.8.4",
|
|
85
|
+
toVersion: "0.9.0",
|
|
86
|
+
description: "Product Layer Introduction (Spec v0.9.0)",
|
|
87
|
+
apply: async (model) => {
|
|
88
|
+
const fs = await import("fs/promises"), pathModule = await import("path"), modelDir = `${model.rootPath}/documentation-robotics/model`;
|
|
89
|
+
let filesModified = 0;
|
|
90
|
+
try {
|
|
91
|
+
await fs.access(modelDir);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error.code === "ENOENT")
|
|
94
|
+
return {
|
|
95
|
+
migrationsApplied: 1,
|
|
96
|
+
filesModified: 0,
|
|
97
|
+
description: "Spec version updated to 0.9.0 (Product layer introduced; no model directories to migrate)"
|
|
98
|
+
};
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
const renameMap = [
|
|
102
|
+
["12_testing", "13_testing"],
|
|
103
|
+
["11_apm", "12_apm"],
|
|
104
|
+
["10_navigation", "11_navigation"],
|
|
105
|
+
["09_ux", "10_ux"],
|
|
106
|
+
["08_data-store", "09_data-store"],
|
|
107
|
+
["07_data-model", "08_data-model"],
|
|
108
|
+
["06_api", "07_api"],
|
|
109
|
+
["05_technology", "06_technology"],
|
|
110
|
+
["04_application", "05_application"],
|
|
111
|
+
["03_security", "04_security"]
|
|
112
|
+
], renameErrors = [];
|
|
113
|
+
for (const [oldName, newName] of renameMap) {
|
|
114
|
+
const oldPath = pathModule.join(modelDir, oldName), newPath = pathModule.join(modelDir, newName);
|
|
115
|
+
try {
|
|
116
|
+
await fs.rename(oldPath, newPath), filesModified++;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error.code === "ENOENT")
|
|
119
|
+
continue;
|
|
120
|
+
renameErrors.push(`Failed to rename ${oldName} to ${newName}: ${getErrorMessage(error)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (renameErrors.length > 0)
|
|
124
|
+
return {
|
|
125
|
+
migrationsApplied: 1,
|
|
126
|
+
filesModified,
|
|
127
|
+
description: "Spec version updated to 0.9.0 (Product layer introduced at layer 3; Security through Testing shifted from layers 3\u201312 to 4\u201313)",
|
|
128
|
+
error: renameErrors.join("; ")
|
|
129
|
+
};
|
|
130
|
+
const productPath = pathModule.join(modelDir, "03_product");
|
|
131
|
+
return await fs.mkdir(productPath, { recursive: !0 }), {
|
|
132
|
+
migrationsApplied: 1,
|
|
133
|
+
filesModified,
|
|
134
|
+
description: "Spec version updated to 0.9.0 (Product layer introduced at layer 3; Security through Testing shifted from layers 3\u201312 to 4\u201313)"
|
|
135
|
+
};
|
|
136
|
+
}
|
|
83
137
|
});
|
|
84
138
|
}
|
|
85
139
|
/**
|
|
@@ -137,12 +191,15 @@ class MigrationRegistry {
|
|
|
137
191
|
else
|
|
138
192
|
try {
|
|
139
193
|
const result = await migration.apply(model);
|
|
140
|
-
results.applied.push({
|
|
194
|
+
if (results.applied.push({
|
|
141
195
|
from: migration.fromVersion,
|
|
142
196
|
to: migration.toVersion,
|
|
143
197
|
description: migration.description,
|
|
144
198
|
changes: result
|
|
145
|
-
}), results.totalChanges += result.migrationsApplied,
|
|
199
|
+
}), results.totalChanges += result.migrationsApplied, !result.error)
|
|
200
|
+
model.manifest.specVersion = migration.toVersion;
|
|
201
|
+
else
|
|
202
|
+
throw new Error(result.error);
|
|
146
203
|
} catch (error) {
|
|
147
204
|
throw new Error(`Migration ${migration.fromVersion} \u2192 ${migration.toVersion} failed: ${getErrorMessage(error)}`);
|
|
148
205
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/core/migration-registry.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Migration Registry - Manages version-based model migrations\n *\n * Handles sequential migrations from one spec version to another,\n * supporting the evolution of the architecture model specification.\n */\n\nimport type { Model } from \"./model.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * Represents a single version migration\n */\nexport interface Migration {\n fromVersion: string;\n toVersion: string;\n description: string;\n apply: (model: Model) => Promise<MigrationResult>;\n}\n\n/**\n * Result of applying a migration\n */\nexport interface MigrationResult {\n migrationsApplied: number;\n filesModified: number;\n description: string;\n error?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * Migration Registry managing all available migrations\n */\nexport class MigrationRegistry {\n private migrations: Migration[] = [];\n\n constructor() {\n this.registerMigrations();\n }\n\n /**\n * Register all available migrations\n */\n private registerMigrations(): void {\n // Migration from v0.5.0 to v0.6.0: Relationship Taxonomy\n this.migrations.push({\n fromVersion: \"0.5.0\",\n toVersion: \"0.6.0\",\n description: \"Relationship Taxonomy (Spec v0.6.0)\",\n apply: async () => {\n // This migration is opt-in - the new relationship taxonomy\n // is fully backward compatible with existing models\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.6.0 (Relationship Taxonomy now available)\",\n };\n },\n });\n\n // Migration from v0.6.0 to v0.7.0: Layer Schema Relationship Metadata\n this.migrations.push({\n fromVersion: \"0.6.0\",\n toVersion: \"0.7.0\",\n description: \"Layer Schema Relationship Metadata (Spec v0.7.0)\",\n apply: async () => {\n // This migration updates layer schemas with relationship metadata sections\n // - All 12 layer schemas now include layerMetadata, intraLayerRelationships, crossLayerRelationships\n // - Deprecated link-registry.json (removed in v0.8.0)\n // - Terminology change: \"cross-layer links\" \u2192 \"cross-layer relationships\"\n // Fully backward compatible - existing models continue to work\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description:\n \"Spec version updated to 0.7.0 (Layer schemas now include relationship metadata sections)\",\n };\n },\n });\n\n // Migration from v0.7.0 to v0.7.1: Source Code Reference Infrastructure\n this.migrations.push({\n fromVersion: \"0.7.0\",\n toVersion: \"0.7.1\",\n description: \"Source Code Reference Infrastructure (Spec v0.7.1)\",\n apply: async () => {\n // This migration adds source code reference infrastructure\n // - Common schema definitions in spec/schemas/common/\n // - Source references integrated across 10 layers\n // - Backward compatible - existing models continue to work\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.7.1 (Source Code Reference Infrastructure)\",\n };\n },\n });\n\n // Migration from v0.7.1 to v0.8.0: JSON Schema-Only Spec Architecture\n this.migrations.push({\n fromVersion: \"0.7.1\",\n toVersion: \"0.8.0\",\n description: \"JSON Schema-Only Spec Architecture (Spec v0.8.0)\",\n apply: async () => {\n // Spec architecture refactored to JSON Schema-only model:\n // - Node type definitions moved to spec/schemas/nodes/{layer}/*.node.schema.json\n // - Relationship definitions moved to spec/schemas/relationships/{layer}/*.relationship.schema.json\n // - Predicates relocated to spec/schemas/base/predicates.json\n // This is a spec architecture change only - model data files are fully backward compatible\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.0 (JSON Schema-Only Spec Architecture)\",\n };\n },\n });\n\n // Migration from v0.8.0 to v0.8.1: Expanded Relationship Coverage and Node Improvements\n this.migrations.push({\n fromVersion: \"0.8.0\",\n toVersion: \"0.8.1\",\n description: \"Expanded Relationship Coverage and Node Improvements (Spec v0.8.1)\",\n apply: async () => {\n // Spec enhancements only - no model data format changes:\n // - 733 new relationship schemas added across all 12 layers\n // - Node type descriptions improved for all 12 layers\n // - Enum-masquerader node types consolidated into parent attributes\n // - Existing models continue to work without modification\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.1 (Expanded Relationship Coverage and Node Improvements)\",\n };\n },\n });\n\n // Migration from v0.8.1 to v0.8.2: UUID/Path identifier separation\n this.migrations.push({\n fromVersion: \"0.8.1\",\n toVersion: \"0.8.2\",\n description: \"UUID/Path identifier separation (Spec v0.8.2)\",\n apply: async () => {\n // The path field is now required on all spec nodes (spec-node.schema.json).\n // Existing elements are migrated automatically on load in model.ts (loadLayer):\n // - Elements with slug-format id: slug moved to path, deterministic UUID assigned to id\n // - Elements with UUID id but no path: path derived from layer_id.type.kebab(name)\n // No manual data file changes needed; migration runs transparently on next load/save.\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.2 (UUID/Path identifier separation)\",\n };\n },\n });\n\n // Migration from v0.8.2 to v0.8.3: Inter-layer Relationship Schemas and APM Node Types\n this.migrations.push({\n fromVersion: \"0.8.2\",\n toVersion: \"0.8.3\",\n description: \"Inter-layer Relationship Schemas and APM Node Types (Spec v0.8.3)\",\n apply: async () => {\n // Major expansion of inter-layer relationship coverage and APM layer:\n // - 495 new inter-layer relationship schemas across all 12 layers\n // - Total relationship count increased from 969 to 1,447\n // - 2 new APM node types: apm.alert and apm.dashboard\n // - Backward compatible - existing models continue to work without modification\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.3 (Inter-layer Relationship Schemas and APM Node Types)\",\n };\n },\n });\n\n // Migration from v0.8.3 to v0.8.4: Analyzer Infrastructure (CBM Mapping)\n this.migrations.push({\n fromVersion: \"0.8.3\",\n toVersion: \"0.8.4\",\n description: \"Analyzer Infrastructure (Spec v0.8.4)\",\n apply: async () => {\n // Introduces spec/analyzers/ with codebase-memory (CBM) mapping:\n // - New analyzer adapter foundation and full analyzer subcommands\n // - dr analyzer verify with changeset awareness\n // - Claude Code integration commands (/dr-verify, /dr-map, dr-extractor)\n // - No model data format changes; existing models are fully compatible\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.4 (Analyzer Infrastructure)\",\n };\n },\n });\n }\n\n /**\n * Get the latest available specification version\n */\n getLatestVersion(): string {\n if (this.migrations.length === 0) {\n return \"0.7.0\";\n }\n\n // Return the highest toVersion from all migrations\n const versions = this.migrations.map((m) => m.toVersion);\n return versions.sort().reverse()[0];\n }\n\n /**\n * Get the sequence of migrations needed from one version to another\n */\n getMigrationPath(fromVersion: string, toVersion?: string): Migration[] {\n const targetVersion = toVersion || this.getLatestVersion();\n\n if (this.compareVersions(fromVersion, targetVersion) >= 0) {\n return [];\n }\n\n const path: Migration[] = [];\n let current = fromVersion;\n\n // Sort migrations by fromVersion\n const sortedMigrations = [...this.migrations].sort((a, b) =>\n this.compareVersions(a.fromVersion, b.fromVersion)\n );\n\n for (const migration of sortedMigrations) {\n // Check if this migration is in our path\n if (\n this.compareVersions(current, migration.fromVersion) >= 0 &&\n this.compareVersions(current, migration.toVersion) < 0 &&\n this.compareVersions(migration.toVersion, targetVersion) <= 0\n ) {\n path.push(migration);\n current = migration.toVersion;\n }\n }\n\n return path;\n }\n\n /**\n * Check if a model requires migration\n */\n requiresMigration(currentVersion: string): boolean {\n const path = this.getMigrationPath(currentVersion);\n return path.length > 0;\n }\n\n /**\n * Apply all migrations in sequence\n */\n async applyMigrations(\n model: Model,\n options: {\n fromVersion: string;\n toVersion?: string;\n dryRun?: boolean;\n validate?: boolean;\n }\n ): Promise<ApplyMigrationsResult> {\n const path = this.getMigrationPath(options.fromVersion, options.toVersion);\n const targetVersion = options.toVersion || this.getLatestVersion();\n\n if (path.length === 0) {\n return {\n applied: [],\n currentVersion: options.fromVersion,\n targetVersion,\n totalChanges: 0,\n };\n }\n\n const results: ApplyMigrationsResult = {\n applied: [],\n currentVersion: options.fromVersion,\n targetVersion,\n totalChanges: 0,\n };\n\n for (const migration of path) {\n if (!options.dryRun) {\n try {\n const result = await migration.apply(model);\n\n results.applied.push({\n from: migration.fromVersion,\n to: migration.toVersion,\n description: migration.description,\n changes: result,\n });\n\n results.totalChanges += result.migrationsApplied;\n\n // Update model's spec version\n model.manifest.specVersion = migration.toVersion;\n } catch (error) {\n throw new Error(\n `Migration ${migration.fromVersion} \u2192 ${migration.toVersion} failed: ${\n getErrorMessage(error)\n }`\n );\n }\n } else {\n // Dry run - just record what would happen\n results.applied.push({\n from: migration.fromVersion,\n to: migration.toVersion,\n description: migration.description,\n dryRun: true,\n });\n }\n }\n\n return results;\n }\n\n /**\n * Get a summary of migrations that would be applied\n */\n getMigrationSummary(\n fromVersion: string,\n toVersion?: string\n ): {\n currentVersion: string;\n targetVersion: string;\n migrationsNeeded: number;\n migrations: Array<{\n from: string;\n to: string;\n description: string;\n }>;\n } {\n const path = this.getMigrationPath(fromVersion, toVersion);\n const targetVersion = toVersion || this.getLatestVersion();\n\n return {\n currentVersion: fromVersion,\n targetVersion,\n migrationsNeeded: path.length,\n migrations: path.map((m) => ({\n from: m.fromVersion,\n to: m.toVersion,\n description: m.description,\n })),\n };\n }\n\n /**\n * Compare two version strings\n * Returns: -1 if a < b, 0 if a == b, 1 if a > b\n */\n private compareVersions(a: string, b: string): number {\n const aParts = a.split(\".\").map(Number);\n const bParts = b.split(\".\").map(Number);\n\n const maxLength = Math.max(aParts.length, bParts.length);\n\n for (let i = 0; i < maxLength; i++) {\n const aPart = aParts[i] || 0;\n const bPart = bParts[i] || 0;\n\n if (aPart < bPart) return -1;\n if (aPart > bPart) return 1;\n }\n\n return 0;\n }\n}\n\n/**\n * Result of applying multiple migrations\n */\nexport interface ApplyMigrationsResult {\n applied: Array<{\n from: string;\n to: string;\n description: string;\n changes?: MigrationResult;\n dryRun?: boolean;\n }>;\n currentVersion: string;\n targetVersion: string;\n totalChanges: number;\n}\n"],
|
|
5
|
-
"mappings": "AAQA,SAAS,uBAAuB;AA0B1B,MAAO,kBAAiB;EACpB,aAA0B,CAAA;EAElC,cAAA;AACE,SAAK,mBAAkB;EACzB;;;;EAKQ,qBAAkB;AAExB,SAAK,WAAW,KAAK;MACnB,aAAa;MACb,WAAW;MACX,aAAa;MACb,OAAO,aAGE;QACL,mBAAmB;QACnB,eAAe;QACf,aAAa;;KAGlB,GAGD,KAAK,WAAW,KAAK;MACnB,aAAa;MACb,WAAW;MACX,aAAa;MACb,OAAO,aAME;QACL,mBAAmB;QACnB,eAAe;QACf,aACE;;KAGP,GAGD,KAAK,WAAW,KAAK;MACnB,aAAa;MACb,WAAW;MACX,aAAa;MACb,OAAO,aAKE;QACL,mBAAmB;QACnB,eAAe;QACf,aAAa;;KAGlB,GAGD,KAAK,WAAW,KAAK;MACnB,aAAa;MACb,WAAW;MACX,aAAa;MACb,OAAO,aAME;QACL,mBAAmB;QACnB,eAAe;QACf,aAAa;;KAGlB,GAGD,KAAK,WAAW,KAAK;MACnB,aAAa;MACb,WAAW;MACX,aAAa;MACb,OAAO,aAME;QACL,mBAAmB;QACnB,eAAe;QACf,aAAa;;KAGlB,GAGD,KAAK,WAAW,KAAK;MACnB,aAAa;MACb,WAAW;MACX,aAAa;MACb,OAAO,aAME;QACL,mBAAmB;QACnB,eAAe;QACf,aAAa;;KAGlB,GAGD,KAAK,WAAW,KAAK;MACnB,aAAa;MACb,WAAW;MACX,aAAa;MACb,OAAO,aAME;QACL,mBAAmB;QACnB,eAAe;QACf,aAAa;;KAGlB,GAGD,KAAK,WAAW,KAAK;MACnB,aAAa;MACb,WAAW;MACX,aAAa;MACb,OAAO,aAME;QACL,mBAAmB;QACnB,eAAe;QACf,aAAa;;KAGlB;EACH;;;;EAKA,mBAAgB;AACd,WAAI,KAAK,WAAW,WAAW,IACtB,UAIQ,KAAK,WAAW,IAAI,CAAC,MAAM,EAAE,SAAS,EACvC,KAAI,EAAG,QAAO,EAAG,CAAC;EACpC;;;;EAKA,iBAAiB,aAAqB,WAAkB;AACtD,UAAM,gBAAgB,aAAa,KAAK,iBAAgB;AAExD,QAAI,KAAK,gBAAgB,aAAa,aAAa,KAAK;AACtD,aAAO,CAAA;AAGT,UAAM,OAAoB,CAAA;AAC1B,QAAI,UAAU;AAGd,UAAM,mBAAmB,CAAC,GAAG,KAAK,UAAU,EAAE,KAAK,CAAC,GAAG,MACrD,KAAK,gBAAgB,EAAE,aAAa,EAAE,WAAW,CAAC;AAGpD,eAAW,aAAa;AAEtB,MACE,KAAK,gBAAgB,SAAS,UAAU,WAAW,KAAK,KACxD,KAAK,gBAAgB,SAAS,UAAU,SAAS,IAAI,KACrD,KAAK,gBAAgB,UAAU,WAAW,aAAa,KAAK,MAE5D,KAAK,KAAK,SAAS,GACnB,UAAU,UAAU;AAIxB,WAAO;EACT;;;;EAKA,kBAAkB,gBAAsB;AAEtC,WADa,KAAK,iBAAiB,cAAc,EACrC,SAAS;EACvB;;;;EAKA,MAAM,gBACJ,OACA,SAKC;AAED,UAAM,OAAO,KAAK,iBAAiB,QAAQ,aAAa,QAAQ,SAAS,GACnE,gBAAgB,QAAQ,aAAa,KAAK,iBAAgB;AAEhE,QAAI,KAAK,WAAW;AAClB,aAAO;QACL,SAAS,CAAA;QACT,gBAAgB,QAAQ;QACxB;QACA,cAAc;;AAIlB,UAAM,UAAiC;MACrC,SAAS,CAAA;MACT,gBAAgB,QAAQ;MACxB;MACA,cAAc;;AAGhB,eAAW,aAAa;AACtB,UAAK,QAAQ;
|
|
4
|
+
"sourcesContent": ["/**\n * Migration Registry - Manages version-based model migrations\n *\n * Handles sequential migrations from one spec version to another,\n * supporting the evolution of the architecture model specification.\n */\n\nimport type { Model } from \"./model.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * Represents a single version migration\n */\nexport interface Migration {\n fromVersion: string;\n toVersion: string;\n description: string;\n apply: (model: Model) => Promise<MigrationResult>;\n}\n\n/**\n * Result of applying a migration\n */\nexport interface MigrationResult {\n migrationsApplied: number;\n filesModified: number;\n description: string;\n error?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * Migration Registry managing all available migrations\n */\nexport class MigrationRegistry {\n private migrations: Migration[] = [];\n\n constructor() {\n this.registerMigrations();\n }\n\n /**\n * Register all available migrations\n */\n private registerMigrations(): void {\n // Migration from v0.5.0 to v0.6.0: Relationship Taxonomy\n this.migrations.push({\n fromVersion: \"0.5.0\",\n toVersion: \"0.6.0\",\n description: \"Relationship Taxonomy (Spec v0.6.0)\",\n apply: async () => {\n // This migration is opt-in - the new relationship taxonomy\n // is fully backward compatible with existing models\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.6.0 (Relationship Taxonomy now available)\",\n };\n },\n });\n\n // Migration from v0.6.0 to v0.7.0: Layer Schema Relationship Metadata\n this.migrations.push({\n fromVersion: \"0.6.0\",\n toVersion: \"0.7.0\",\n description: \"Layer Schema Relationship Metadata (Spec v0.7.0)\",\n apply: async () => {\n // This migration updates layer schemas with relationship metadata sections\n // - All 12 layer schemas now include layerMetadata, intraLayerRelationships, crossLayerRelationships\n // - Deprecated link-registry.json (removed in v0.8.0)\n // - Terminology change: \"cross-layer links\" \u2192 \"cross-layer relationships\"\n // Fully backward compatible - existing models continue to work\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description:\n \"Spec version updated to 0.7.0 (Layer schemas now include relationship metadata sections)\",\n };\n },\n });\n\n // Migration from v0.7.0 to v0.7.1: Source Code Reference Infrastructure\n this.migrations.push({\n fromVersion: \"0.7.0\",\n toVersion: \"0.7.1\",\n description: \"Source Code Reference Infrastructure (Spec v0.7.1)\",\n apply: async () => {\n // This migration adds source code reference infrastructure\n // - Common schema definitions in spec/schemas/common/\n // - Source references integrated across 10 layers\n // - Backward compatible - existing models continue to work\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.7.1 (Source Code Reference Infrastructure)\",\n };\n },\n });\n\n // Migration from v0.7.1 to v0.8.0: JSON Schema-Only Spec Architecture\n this.migrations.push({\n fromVersion: \"0.7.1\",\n toVersion: \"0.8.0\",\n description: \"JSON Schema-Only Spec Architecture (Spec v0.8.0)\",\n apply: async () => {\n // Spec architecture refactored to JSON Schema-only model:\n // - Node type definitions moved to spec/schemas/nodes/{layer}/*.node.schema.json\n // - Relationship definitions moved to spec/schemas/relationships/{layer}/*.relationship.schema.json\n // - Predicates relocated to spec/schemas/base/predicates.json\n // This is a spec architecture change only - model data files are fully backward compatible\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.0 (JSON Schema-Only Spec Architecture)\",\n };\n },\n });\n\n // Migration from v0.8.0 to v0.8.1: Expanded Relationship Coverage and Node Improvements\n this.migrations.push({\n fromVersion: \"0.8.0\",\n toVersion: \"0.8.1\",\n description: \"Expanded Relationship Coverage and Node Improvements (Spec v0.8.1)\",\n apply: async () => {\n // Spec enhancements only - no model data format changes:\n // - 733 new relationship schemas added across all 12 layers\n // - Node type descriptions improved for all 12 layers\n // - Enum-masquerader node types consolidated into parent attributes\n // - Existing models continue to work without modification\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.1 (Expanded Relationship Coverage and Node Improvements)\",\n };\n },\n });\n\n // Migration from v0.8.1 to v0.8.2: UUID/Path identifier separation\n this.migrations.push({\n fromVersion: \"0.8.1\",\n toVersion: \"0.8.2\",\n description: \"UUID/Path identifier separation (Spec v0.8.2)\",\n apply: async () => {\n // The path field is now required on all spec nodes (spec-node.schema.json).\n // Existing elements are migrated automatically on load in model.ts (loadLayer):\n // - Elements with slug-format id: slug moved to path, deterministic UUID assigned to id\n // - Elements with UUID id but no path: path derived from layer_id.type.kebab(name)\n // No manual data file changes needed; migration runs transparently on next load/save.\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.2 (UUID/Path identifier separation)\",\n };\n },\n });\n\n // Migration from v0.8.2 to v0.8.3: Inter-layer Relationship Schemas and APM Node Types\n this.migrations.push({\n fromVersion: \"0.8.2\",\n toVersion: \"0.8.3\",\n description: \"Inter-layer Relationship Schemas and APM Node Types (Spec v0.8.3)\",\n apply: async () => {\n // Major expansion of inter-layer relationship coverage and APM layer:\n // - 495 new inter-layer relationship schemas across all 12 layers\n // - Total relationship count increased from 969 to 1,447\n // - 2 new APM node types: apm.alert and apm.dashboard\n // - Backward compatible - existing models continue to work without modification\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.3 (Inter-layer Relationship Schemas and APM Node Types)\",\n };\n },\n });\n\n // Migration from v0.8.3 to v0.8.4: Analyzer Infrastructure (CBM Mapping)\n this.migrations.push({\n fromVersion: \"0.8.3\",\n toVersion: \"0.8.4\",\n description: \"Analyzer Infrastructure (Spec v0.8.4)\",\n apply: async () => {\n // Introduces spec/analyzers/ with codebase-memory (CBM) mapping:\n // - New analyzer adapter foundation and full analyzer subcommands\n // - dr analyzer verify with changeset awareness\n // - Claude Code integration commands (/dr-verify, /dr-map, dr-extractor)\n // - No model data format changes; existing models are fully compatible\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description: \"Spec version updated to 0.8.4 (Analyzer Infrastructure)\",\n };\n },\n });\n\n // Migration from v0.8.4 to v0.9.0: Product Layer Introduction\n this.migrations.push({\n fromVersion: \"0.8.4\",\n toVersion: \"0.9.0\",\n description: \"Product Layer Introduction (Spec v0.9.0)\",\n apply: async (model: Model) => {\n const fs = await import(\"fs/promises\");\n const pathModule = await import(\"path\");\n\n const modelDir = `${model.rootPath}/documentation-robotics/model`;\n let filesModified = 0;\n\n try {\n // Verify model directory exists\n await fs.access(modelDir);\n } catch (error) {\n const err = error as { code?: string };\n // ENOENT means the directory doesn't exist - nothing to migrate\n if (err.code === \"ENOENT\") {\n return {\n migrationsApplied: 1,\n filesModified: 0,\n description:\n \"Spec version updated to 0.9.0 (Product layer introduced; no model directories to migrate)\",\n };\n }\n // Re-throw other errors (e.g., EACCES for permission denied)\n throw error;\n }\n\n // Rename existing layer directories: shift from 03-12 to 04-13\n // Must do this in reverse order (12\u219213, 11\u219212, ..., 03\u219204) to avoid conflicts\n const renameMap = [\n [\"12_testing\", \"13_testing\"],\n [\"11_apm\", \"12_apm\"],\n [\"10_navigation\", \"11_navigation\"],\n [\"09_ux\", \"10_ux\"],\n [\"08_data-store\", \"09_data-store\"],\n [\"07_data-model\", \"08_data-model\"],\n [\"06_api\", \"07_api\"],\n [\"05_technology\", \"06_technology\"],\n [\"04_application\", \"05_application\"],\n [\"03_security\", \"04_security\"],\n ];\n\n const renameErrors: string[] = [];\n\n for (const [oldName, newName] of renameMap) {\n const oldPath = pathModule.join(modelDir, oldName);\n const newPath = pathModule.join(modelDir, newName);\n\n try {\n await fs.rename(oldPath, newPath);\n filesModified++;\n } catch (error) {\n const err = error as { code?: string };\n // ENOENT is expected when a directory hasn't been created yet\n if (err.code === \"ENOENT\") {\n continue;\n }\n // Track non-ENOENT errors to report them in the result\n renameErrors.push(\n `Failed to rename ${oldName} to ${newName}: ${getErrorMessage(error)}`\n );\n }\n }\n\n // If there were any rename failures, include them in the error field\n if (renameErrors.length > 0) {\n return {\n migrationsApplied: 1,\n filesModified,\n description:\n \"Spec version updated to 0.9.0 (Product layer introduced at layer 3; Security through Testing shifted from layers 3\u201312 to 4\u201313)\",\n error: renameErrors.join(\"; \"),\n };\n }\n\n // Create new product layer directory (it will be empty for now)\n // recursive: true handles EEXIST, so no catch needed\n const productPath = pathModule.join(modelDir, \"03_product\");\n await fs.mkdir(productPath, { recursive: true });\n\n return {\n migrationsApplied: 1,\n filesModified,\n description:\n \"Spec version updated to 0.9.0 (Product layer introduced at layer 3; Security through Testing shifted from layers 3\u201312 to 4\u201313)\",\n };\n },\n });\n }\n\n /**\n * Get the latest available specification version\n */\n getLatestVersion(): string {\n if (this.migrations.length === 0) {\n return \"0.7.0\";\n }\n\n // Return the highest toVersion from all migrations\n const versions = this.migrations.map((m) => m.toVersion);\n return versions.sort().reverse()[0];\n }\n\n /**\n * Get the sequence of migrations needed from one version to another\n */\n getMigrationPath(fromVersion: string, toVersion?: string): Migration[] {\n const targetVersion = toVersion || this.getLatestVersion();\n\n if (this.compareVersions(fromVersion, targetVersion) >= 0) {\n return [];\n }\n\n const path: Migration[] = [];\n let current = fromVersion;\n\n // Sort migrations by fromVersion\n const sortedMigrations = [...this.migrations].sort((a, b) =>\n this.compareVersions(a.fromVersion, b.fromVersion)\n );\n\n for (const migration of sortedMigrations) {\n // Check if this migration is in our path\n if (\n this.compareVersions(current, migration.fromVersion) >= 0 &&\n this.compareVersions(current, migration.toVersion) < 0 &&\n this.compareVersions(migration.toVersion, targetVersion) <= 0\n ) {\n path.push(migration);\n current = migration.toVersion;\n }\n }\n\n return path;\n }\n\n /**\n * Check if a model requires migration\n */\n requiresMigration(currentVersion: string): boolean {\n const path = this.getMigrationPath(currentVersion);\n return path.length > 0;\n }\n\n /**\n * Apply all migrations in sequence\n */\n async applyMigrations(\n model: Model,\n options: {\n fromVersion: string;\n toVersion?: string;\n dryRun?: boolean;\n validate?: boolean;\n }\n ): Promise<ApplyMigrationsResult> {\n const path = this.getMigrationPath(options.fromVersion, options.toVersion);\n const targetVersion = options.toVersion || this.getLatestVersion();\n\n if (path.length === 0) {\n return {\n applied: [],\n currentVersion: options.fromVersion,\n targetVersion,\n totalChanges: 0,\n };\n }\n\n const results: ApplyMigrationsResult = {\n applied: [],\n currentVersion: options.fromVersion,\n targetVersion,\n totalChanges: 0,\n };\n\n for (const migration of path) {\n if (!options.dryRun) {\n try {\n const result = await migration.apply(model);\n\n results.applied.push({\n from: migration.fromVersion,\n to: migration.toVersion,\n description: migration.description,\n changes: result,\n });\n\n results.totalChanges += result.migrationsApplied;\n\n // Only update model's spec version if migration succeeded (no error field)\n if (!result.error) {\n model.manifest.specVersion = migration.toVersion;\n } else {\n // If migration has errors, throw to prevent partial success state\n throw new Error(result.error);\n }\n } catch (error) {\n throw new Error(\n `Migration ${migration.fromVersion} \u2192 ${migration.toVersion} failed: ${\n getErrorMessage(error)\n }`\n );\n }\n } else {\n // Dry run - just record what would happen\n results.applied.push({\n from: migration.fromVersion,\n to: migration.toVersion,\n description: migration.description,\n dryRun: true,\n });\n }\n }\n\n return results;\n }\n\n /**\n * Get a summary of migrations that would be applied\n */\n getMigrationSummary(\n fromVersion: string,\n toVersion?: string\n ): {\n currentVersion: string;\n targetVersion: string;\n migrationsNeeded: number;\n migrations: Array<{\n from: string;\n to: string;\n description: string;\n }>;\n } {\n const path = this.getMigrationPath(fromVersion, toVersion);\n const targetVersion = toVersion || this.getLatestVersion();\n\n return {\n currentVersion: fromVersion,\n targetVersion,\n migrationsNeeded: path.length,\n migrations: path.map((m) => ({\n from: m.fromVersion,\n to: m.toVersion,\n description: m.description,\n })),\n };\n }\n\n /**\n * Compare two version strings\n * Returns: -1 if a < b, 0 if a == b, 1 if a > b\n */\n private compareVersions(a: string, b: string): number {\n const aParts = a.split(\".\").map(Number);\n const bParts = b.split(\".\").map(Number);\n\n const maxLength = Math.max(aParts.length, bParts.length);\n\n for (let i = 0; i < maxLength; i++) {\n const aPart = aParts[i] || 0;\n const bPart = bParts[i] || 0;\n\n if (aPart < bPart) return -1;\n if (aPart > bPart) return 1;\n }\n\n return 0;\n }\n}\n\n/**\n * Result of applying multiple migrations\n */\nexport interface ApplyMigrationsResult {\n applied: Array<{\n from: string;\n to: string;\n description: string;\n changes?: MigrationResult;\n dryRun?: boolean;\n }>;\n currentVersion: string;\n targetVersion: string;\n totalChanges: number;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/core/model.js
CHANGED
|
@@ -314,22 +314,9 @@ class Model {
|
|
|
314
314
|
},
|
|
315
315
|
documentation: ".dr/README.md",
|
|
316
316
|
layers: {}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
"
|
|
320
|
-
"security",
|
|
321
|
-
"application",
|
|
322
|
-
"technology",
|
|
323
|
-
"api",
|
|
324
|
-
"data-model",
|
|
325
|
-
"data-store",
|
|
326
|
-
"ux",
|
|
327
|
-
"navigation",
|
|
328
|
-
"apm",
|
|
329
|
-
"testing"
|
|
330
|
-
];
|
|
331
|
-
for (let i = 0; i < layerOrder.length; i++) {
|
|
332
|
-
const layerName = layerOrder[i], layer = this.layers.get(layerName), orderNum = String(i + 1).padStart(2, "0");
|
|
317
|
+
};
|
|
318
|
+
for (let i = 0; i < CANONICAL_LAYER_NAMES.length; i++) {
|
|
319
|
+
const layerName = CANONICAL_LAYER_NAMES[i], layer = this.layers.get(layerName), orderNum = String(i + 1).padStart(2, "0");
|
|
333
320
|
yamlData.layers[layerName] = {
|
|
334
321
|
order: i + 1,
|
|
335
322
|
name: layerName.charAt(0).toUpperCase() + layerName.slice(1).replace("-", " "),
|