@documentation-robotics/cli 0.3.0 → 0.4.0
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 +10 -14
- package/dist/analyzers/active-analyzer.js +62 -0
- package/dist/analyzers/active-analyzer.js.map +7 -0
- package/dist/analyzers/cards/build.js +21 -1
- package/dist/analyzers/cards/build.js.map +2 -2
- package/dist/analyzers/cards/extract.js +138 -0
- package/dist/analyzers/cards/extract.js.map +7 -0
- package/dist/analyzers/cards/routes.js +96 -0
- package/dist/analyzers/cards/routes.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +76 -4
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +12 -6
- package/dist/analyzers/cbm-responses.js.map +2 -2
- package/dist/analyzers/code-changes.js +159 -0
- package/dist/analyzers/code-changes.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +6 -2
- package/dist/analyzers/edge-relationships.js.map +2 -2
- package/dist/analyzers/extraction-plan.js +541 -0
- package/dist/analyzers/extraction-plan.js.map +7 -0
- package/dist/analyzers/index-job.js +229 -0
- package/dist/analyzers/index-job.js.map +7 -0
- package/dist/cli.js +12 -10
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +120 -18
- package/dist/coding-agents/drbot-mcp.js.map +2 -2
- package/dist/commands/add.js +4 -2
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +35 -35
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +16 -8
- package/dist/commands/changeset-verify.js.map +2 -2
- package/dist/commands/changeset.js +41 -11
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/init.js +9 -26
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +35 -31
- package/dist/commands/mcp.js.map +3 -3
- package/dist/commands/relationship.js +2 -12
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +3 -3
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +14 -13
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +28 -120
- package/dist/commands/upgrade.js.map +2 -2
- package/dist/commands/validate.js +19 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +30 -12
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/annotation-store.js +15 -5
- package/dist/core/annotation-store.js.map +2 -2
- package/dist/core/catalog-migration.js +2 -1
- package/dist/core/catalog-migration.js.map +2 -2
- package/dist/core/changeset-base-index.js +7 -0
- package/dist/core/changeset-base-index.js.map +2 -2
- package/dist/core/changeset-brief-compact.js +256 -0
- package/dist/core/changeset-brief-compact.js.map +7 -0
- package/dist/core/changeset-verify.js +356 -26
- package/dist/core/changeset-verify.js.map +3 -3
- package/dist/core/extraction-observability.js +161 -0
- package/dist/core/extraction-observability.js.map +7 -0
- package/dist/core/migration-registry.js +13 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/project-init.js +53 -0
- package/dist/core/project-init.js.map +7 -0
- package/dist/core/relationship-staging.js +94 -1
- package/dist/core/relationship-staging.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +83 -3
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staging-area.js +85 -8
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/upgrade-plan.js +117 -0
- package/dist/core/upgrade-plan.js.map +7 -0
- package/dist/generated/relationship-index.js +124 -15
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +19 -5
- package/dist/integrations/claude_code/agents/dr-extractor.md +7 -1
- package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
- package/dist/integrations/claude_code/commands/dr-map.md +7 -1
- package/dist/integrations/claude_code/commands/dr-plan.md +42 -16
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +28 -31
- package/dist/integrations/claude_code/docs/README.md +20 -1
- package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
- package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +90 -31
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +5 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +5 -0
- package/dist/integrations/github_copilot/agents/dr-architect.md +2 -0
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +3 -0
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +88 -30
- package/dist/mcp/changeset-only.js +14 -3
- package/dist/mcp/changeset-only.js.map +2 -2
- package/dist/mcp/project-root.js +85 -0
- package/dist/mcp/project-root.js.map +7 -0
- package/dist/mcp/server-instructions.js +27 -0
- package/dist/mcp/server-instructions.js.map +7 -0
- package/dist/mcp/strict-schema.js +52 -0
- package/dist/mcp/strict-schema.js.map +7 -0
- package/dist/mcp/tool-registry.js +39 -5
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +25 -5
- package/dist/mcp/tools/changeset-analysis-tools.js.map +3 -3
- package/dist/mcp/tools/changeset-commit.js +224 -0
- package/dist/mcp/tools/changeset-commit.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +133 -57
- package/dist/mcp/tools/changeset-editing.js.map +3 -3
- package/dist/mcp/tools/changeset-lifecycle.js +1 -1
- package/dist/mcp/tools/changeset-lifecycle.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +12 -6
- package/dist/mcp/tools/changeset-verify.js.map +2 -2
- package/dist/mcp/tools/codebase-extract.js +836 -0
- package/dist/mcp/tools/codebase-extract.js.map +7 -0
- package/dist/mcp/tools/codebase-index.js +125 -0
- package/dist/mcp/tools/codebase-index.js.map +7 -0
- package/dist/mcp/tools/codebase-shared.js +265 -0
- package/dist/mcp/tools/codebase-shared.js.map +7 -0
- package/dist/mcp/tools/codebase-verify.js +48 -0
- package/dist/mcp/tools/codebase-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +15 -0
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +189 -73
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-update.js +16 -12
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/model-validate.js +46 -4
- package/dist/mcp/tools/model-validate.js.map +2 -2
- package/dist/mcp/tools/project-status-collectors.js +381 -0
- package/dist/mcp/tools/project-status-collectors.js.map +7 -0
- package/dist/mcp/tools/project-status.js +237 -0
- package/dist/mcp/tools/project-status.js.map +7 -0
- package/dist/mcp/tools/project-tools.js +505 -0
- package/dist/mcp/tools/project-tools.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +89 -20
- package/dist/mcp/tools/relationship-tools.js.map +2 -2
- package/dist/mcp/tools/review-open.js +210 -0
- package/dist/mcp/tools/review-open.js.map +7 -0
- package/dist/mcp/tools/shared.js +10 -2
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/reports/model-report-orchestrator.js +31 -2
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/schemas/bundled/analyzers/cbm.json +22 -10
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1 -1
- package/dist/schemas/bundled/apm.json +1 -1
- package/dist/schemas/bundled/application.json +1 -1
- package/dist/schemas/bundled/base.json +1 -1
- package/dist/schemas/bundled/business.json +1 -1
- package/dist/schemas/bundled/data-model.json +1 -1
- package/dist/schemas/bundled/data-store.json +1 -1
- package/dist/schemas/bundled/manifest.json +3 -3
- package/dist/schemas/bundled/motivation.json +1 -1
- package/dist/schemas/bundled/navigation.json +1 -1
- package/dist/schemas/bundled/product.json +1 -1
- package/dist/schemas/bundled/security.json +1 -1
- package/dist/schemas/bundled/technology.json +1 -1
- package/dist/schemas/bundled/testing.json +145 -1
- package/dist/schemas/bundled/ux.json +1 -1
- package/dist/server/server.js +508 -237
- package/dist/server/server.js.map +3 -3
- package/dist/server/viewer-registry.js +264 -0
- package/dist/server/viewer-registry.js.map +7 -0
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/id-generator.js +5 -2
- package/dist/utils/id-generator.js.map +2 -2
- package/dist/utils/source-reference.js +58 -27
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +2 -2
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/validators/model-warnings.js +28 -0
- package/dist/validators/model-warnings.js.map +7 -0
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
- package/dist/viewer/assets/main-DvH3gYrI.js +84 -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 +1 -1
- package/dist/viewer/assets/main-CxzcuU_o.js +0 -84
- package/dist/viewer/assets/main-j-7ULYpm.css +0 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { intro, text, outro } from "@clack/prompts";
|
|
2
2
|
import ansis from "ansis";
|
|
3
|
-
import {
|
|
4
|
-
import { fileExists } from "../utils/file-io.js";
|
|
5
|
-
import { logVerbose, logDebug } from "../utils/globals.js";
|
|
6
|
-
import { installSpecReference } from "../utils/spec-installer.js";
|
|
3
|
+
import { hasModel, initProject } from "../core/project-init.js";
|
|
7
4
|
import { getCliBundledSpecVersion } from "../utils/spec-version.js";
|
|
8
5
|
import { isTelemetryEnabled, startSpan, endSpan } from "../telemetry/index.js";
|
|
9
6
|
import { getErrorMessage } from "../utils/errors.js";
|
|
10
|
-
import { regenerateLayerReports } from "./reports.js";
|
|
11
7
|
async function initCommand(options) {
|
|
12
8
|
intro(ansis.bold(ansis.blue("\u2699\uFE0F Initialize Documentation Robotics Model")));
|
|
13
9
|
const span = isTelemetryEnabled ? startSpan("model.init", {
|
|
@@ -16,8 +12,8 @@ async function initCommand(options) {
|
|
|
16
12
|
"init.hasAuthor": !!options.author
|
|
17
13
|
}) : null;
|
|
18
14
|
try {
|
|
19
|
-
const rootPath = process.cwd()
|
|
20
|
-
await
|
|
15
|
+
const rootPath = process.cwd();
|
|
16
|
+
await hasModel(rootPath) && (console.error(ansis.red("Error: Model already initialized in this directory")), isTelemetryEnabled && span && span.setStatus({ code: 2, message: "Model already exists" }), endSpan(span), process.exit(1));
|
|
21
17
|
const isInteractive = process.stdin.isTTY;
|
|
22
18
|
isTelemetryEnabled && span && span.setAttribute("cli.interactive", isInteractive);
|
|
23
19
|
const skipPrompts = !!options.name, name = options.name || (isInteractive ? await text({
|
|
@@ -32,28 +28,15 @@ async function initCommand(options) {
|
|
|
32
28
|
message: "Author (optional):",
|
|
33
29
|
defaultValue: ""
|
|
34
30
|
}) : "");
|
|
35
|
-
isTelemetryEnabled && span && (span.setAttribute("model.name", name), span.setAttribute("model.specVersion", getCliBundledSpecVersion()))
|
|
36
|
-
const
|
|
31
|
+
isTelemetryEnabled && span && (span.setAttribute("model.name", name), span.setAttribute("model.specVersion", getCliBundledSpecVersion()));
|
|
32
|
+
const result = await initProject(rootPath, {
|
|
37
33
|
name,
|
|
38
|
-
version: "0.1.0",
|
|
39
34
|
description: description || void 0,
|
|
40
|
-
author: author || void 0
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}, { lazyLoad: !1 });
|
|
44
|
-
logVerbose(`Model saved with:
|
|
45
|
-
- Version: 0.1.0
|
|
46
|
-
- Spec Version: ${getCliBundledSpecVersion()}
|
|
47
|
-
- Location: ${rootPath}/documentation-robotics/model`), logDebug("Installing spec reference (.dr/ folder)..."), await installSpecReference(rootPath, !1), logVerbose("Spec reference installed"), logDebug("Generating reports and README...");
|
|
48
|
-
try {
|
|
49
|
-
await regenerateLayerReports(rootPath), logVerbose("Reports and README generated");
|
|
50
|
-
} catch (reportError) {
|
|
51
|
-
const reportMessage = getErrorMessage(reportError);
|
|
52
|
-
console.warn(ansis.yellow(`\u26A0 Warning: Failed to generate reports and README: ${reportMessage}
|
|
35
|
+
author: author || void 0
|
|
36
|
+
});
|
|
37
|
+
result.reportError !== void 0 && console.warn(ansis.yellow(`\u26A0 Warning: Failed to generate reports and README: ${result.reportError}
|
|
53
38
|
Model initialized successfully. To regenerate reports later, run:
|
|
54
|
-
dr reports regenerate`)),
|
|
55
|
-
}
|
|
56
|
-
isTelemetryEnabled && span && (span.setAttribute("model.path", rootPath), span.setStatus({ code: 0 })), outro(ansis.green(`\u2713 Model initialized: ${ansis.bold(model.manifest.name)}`));
|
|
39
|
+
dr reports regenerate`)), isTelemetryEnabled && span && (span.setAttribute("model.path", rootPath), span.setStatus({ code: 0 })), outro(ansis.green(`\u2713 Model initialized: ${ansis.bold(result.name)}`));
|
|
57
40
|
} catch (error) {
|
|
58
41
|
isTelemetryEnabled && span && (span.recordException(error), span.setStatus({
|
|
59
42
|
code: 2,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/commands/init.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Initialize a new Documentation Robotics model\n */\n\nimport { intro, text, outro } from \"@clack/prompts\";\nimport ansis from \"ansis\";\nimport {
|
|
5
|
-
"mappings": "AAIA,SAAS,OAAO,MAAM,aAAa;AACnC,OAAO,WAAW;AAClB,SAAS,
|
|
4
|
+
"sourcesContent": ["/**\n * Initialize a new Documentation Robotics model\n */\n\nimport { intro, text, outro } from \"@clack/prompts\";\nimport ansis from \"ansis\";\nimport { hasModel, initProject } from \"../core/project-init.js\";\nimport { getCliBundledSpecVersion } from \"../utils/spec-version.js\";\nimport { isTelemetryEnabled, startSpan, endSpan } from \"../telemetry/index.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\nexport interface InitOptions {\n name?: string;\n author?: string;\n description?: string;\n verbose?: boolean;\n debug?: boolean;\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n intro(ansis.bold(ansis.blue(\"\u2699\uFE0F Initialize Documentation Robotics Model\")));\n\n const span = isTelemetryEnabled\n ? startSpan(\"model.init\", {\n \"init.hasName\": !!options.name,\n \"init.hasDescription\": !!options.description,\n \"init.hasAuthor\": !!options.author,\n })\n : null;\n\n try {\n const rootPath = process.cwd();\n // Check if model already exists\n if (await hasModel(rootPath)) {\n console.error(ansis.red(\"Error: Model already initialized in this directory\"));\n if (isTelemetryEnabled && span) {\n (span as any).setStatus({ code: 2, message: \"Model already exists\" });\n }\n endSpan(span);\n process.exit(1);\n }\n\n // Gather model metadata\n // Check if stdin is a TTY to determine if we should prompt\n const isInteractive = process.stdin.isTTY;\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"cli.interactive\", isInteractive);\n }\n\n // If name is provided, skip all prompts\n const skipPrompts = !!options.name;\n\n const name =\n options.name ||\n (isInteractive\n ? await text({\n message: \"Model name:\",\n validate: (value) => (value.length === 0 ? \"Name is required\" : undefined),\n })\n : (() => {\n console.error(ansis.red(\"Error: Model name is required (use --name option)\"));\n if (isTelemetryEnabled && span) {\n (span as any).setStatus({ code: 2, message: \"Model name required\" });\n }\n endSpan(span);\n process.exit(1);\n })());\n\n const description =\n options.description ||\n (skipPrompts\n ? \"\"\n : isInteractive\n ? await text({\n message: \"Description (optional):\",\n defaultValue: \"\",\n })\n : \"\");\n\n const author =\n options.author ||\n (skipPrompts\n ? \"\"\n : isInteractive\n ? await text({\n message: \"Author (optional):\",\n defaultValue: \"\",\n })\n : \"\");\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"model.name\", name as string);\n (span as any).setAttribute(\"model.specVersion\", getCliBundledSpecVersion());\n }\n\n // Create the model, spec reference and reports (shared with the project_init MCP tool)\n const result = await initProject(rootPath, {\n name: name as string,\n description: (description as string) || undefined,\n author: (author as string) || undefined,\n });\n\n if (result.reportError !== undefined) {\n console.warn(\n ansis.yellow(\n `\u26A0 Warning: Failed to generate reports and README: ${result.reportError}\\n` +\n ` Model initialized successfully. To regenerate reports later, run:\\n` +\n ` dr reports regenerate`\n )\n );\n }\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"model.path\", rootPath);\n (span as any).setStatus({ code: 0 });\n }\n\n outro(ansis.green(`\u2713 Model initialized: ${ansis.bold(result.name)}`));\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setStatus({\n code: 2,\n message: getErrorMessage(error),\n });\n }\n const message = getErrorMessage(error);\n console.error(ansis.red(`Error: ${message}`));\n endSpan(span);\n process.exit(1);\n } finally {\n endSpan(span);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAIA,SAAS,OAAO,MAAM,aAAa;AACnC,OAAO,WAAW;AAClB,SAAS,UAAU,mBAAmB;AACtC,SAAS,gCAAgC;AACzC,SAAS,oBAAoB,WAAW,eAAe;AACvD,SAAS,uBAAuB;AAUhC,eAAsB,YAAY,SAAoB;AACpD,QAAM,MAAM,KAAK,MAAM,KAAK,uDAA6C,CAAC,CAAC;AAE3E,QAAM,OAAO,qBACT,UAAU,cAAc;IACtB,gBAAgB,CAAC,CAAC,QAAQ;IAC1B,uBAAuB,CAAC,CAAC,QAAQ;IACjC,kBAAkB,CAAC,CAAC,QAAQ;GAC7B,IACD;AAEJ,MAAI;AACF,UAAM,WAAW,QAAQ,IAAG;AAE5B,IAAI,MAAM,SAAS,QAAQ,MACzB,QAAQ,MAAM,MAAM,IAAI,oDAAoD,CAAC,GACzE,sBAAsB,QACvB,KAAa,UAAU,EAAE,MAAM,GAAG,SAAS,uBAAsB,CAAE,GAEtE,QAAQ,IAAI,GACZ,QAAQ,KAAK,CAAC;AAKhB,UAAM,gBAAgB,QAAQ,MAAM;AAEpC,IAAI,sBAAsB,QACvB,KAAa,aAAa,mBAAmB,aAAa;AAI7D,UAAM,cAAc,CAAC,CAAC,QAAQ,MAExB,OACJ,QAAQ,SACP,gBACG,MAAM,KAAK;MACT,SAAS;MACT,UAAU,CAAC,UAAW,MAAM,WAAW,IAAI,qBAAqB;KACjE,KACA,MAAK;AACJ,cAAQ,MAAM,MAAM,IAAI,mDAAmD,CAAC,GACxE,sBAAsB,QACvB,KAAa,UAAU,EAAE,MAAM,GAAG,SAAS,sBAAqB,CAAE,GAErE,QAAQ,IAAI,GACZ,QAAQ,KAAK,CAAC;IAChB,GAAE,IAEF,cACJ,QAAQ,gBACP,cACG,KACA,gBACE,MAAM,KAAK;MACT,SAAS;MACT,cAAc;KACf,IACD,KAEF,SACJ,QAAQ,WACP,cACG,KACA,gBACE,MAAM,KAAK;MACT,SAAS;MACT,cAAc;KACf,IACD;AAER,IAAI,sBAAsB,SACvB,KAAa,aAAa,cAAc,IAAc,GACtD,KAAa,aAAa,qBAAqB,yBAAwB,CAAE;AAI5E,UAAM,SAAS,MAAM,YAAY,UAAU;MACzC;MACA,aAAc,eAA0B;MACxC,QAAS,UAAqB;KAC/B;AAED,IAAI,OAAO,gBAAgB,UACzB,QAAQ,KACN,MAAM,OACJ,0DAAqD,OAAO,WAAW;;wBAE9C,CAC1B,GAID,sBAAsB,SACvB,KAAa,aAAa,cAAc,QAAQ,GAChD,KAAa,UAAU,EAAE,MAAM,EAAC,CAAE,IAGrC,MAAM,MAAM,MAAM,6BAAwB,MAAM,KAAK,OAAO,IAAI,CAAC,EAAE,CAAC;EACtE,SAAS,OAAO;AACd,IAAI,sBAAsB,SACvB,KAAa,gBAAgB,KAAc,GAC3C,KAAa,UAAU;MACtB,MAAM;MACN,SAAS,gBAAgB,KAAK;KAC/B;AAEH,UAAM,UAAU,gBAAgB,KAAK;AACrC,YAAQ,MAAM,MAAM,IAAI,UAAU,OAAO,EAAE,CAAC,GAC5C,QAAQ,IAAI,GACZ,QAAQ,KAAK,CAAC;EAChB;AACE,YAAQ,IAAI;EACd;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { isCancel, text } from "@clack/prompts";
|
|
2
|
+
import { RootsListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2
3
|
import { ApiKeyManager } from "../mcp/api-key-manager.js";
|
|
4
|
+
import { followClientRoots, markEnvProjectRoot } from "../mcp/project-root.js";
|
|
3
5
|
import { McpResourceRegistry } from "../mcp/resource-registry.js";
|
|
6
|
+
import { SERVER_INSTRUCTIONS } from "../mcp/server-instructions.js";
|
|
4
7
|
import { McpToolRegistry } from "../mcp/tool-registry.js";
|
|
5
|
-
import { loadModel } from "../mcp/tools/shared.js";
|
|
8
|
+
import { clearModelCache, loadModel } from "../mcp/tools/shared.js";
|
|
6
9
|
import { startActiveSpan } from "../telemetry/index.js";
|
|
7
10
|
import { CLIError, getErrorMessage } from "../utils/errors.js";
|
|
8
11
|
import { getCliVersion } from "../utils/spec-version.js";
|
|
@@ -15,48 +18,55 @@ const cliVersion = getCliVersion(), promptForKeyPath = async (defaultPath) => {
|
|
|
15
18
|
return isCancel(result) || typeof result != "string" || result.trim().length === 0 ? defaultPath : result.trim();
|
|
16
19
|
};
|
|
17
20
|
async function createConfiguredServer() {
|
|
18
|
-
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js"), server = new McpServer({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
new McpToolRegistry().registerAll(server), await new McpResourceRegistry().registerAll(server);
|
|
21
|
+
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js"), server = new McpServer({ name: "documentation-robotics", version: cliVersion }, { instructions: SERVER_INSTRUCTIONS });
|
|
22
|
+
return new McpToolRegistry().registerAll(server), await new McpResourceRegistry().registerAll(server), server;
|
|
23
|
+
}
|
|
24
|
+
async function warmModel() {
|
|
23
25
|
try {
|
|
24
26
|
await loadModel();
|
|
25
27
|
} catch (error) {
|
|
26
28
|
process.stderr.write(`[mcp] model warmup skipped: ${getErrorMessage(error)}
|
|
27
29
|
`);
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
}
|
|
32
|
+
function serveClientRoots(server) {
|
|
33
|
+
const inner = server.server, follow = () => followClientRoots(() => inner.listRoots(), { onChange: () => clearModelCache() }).then(warmModel);
|
|
34
|
+
inner.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
|
|
35
|
+
await follow();
|
|
36
|
+
}), inner.oninitialized = () => {
|
|
37
|
+
inner.getClientCapabilities()?.roots ? follow() : warmModel();
|
|
38
|
+
};
|
|
30
39
|
}
|
|
31
40
|
function serveFromRootPathEnv(env = process.env) {
|
|
32
41
|
const rootPath = env.DR_MCP_ROOT_PATH;
|
|
33
|
-
if (rootPath)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`);
|
|
39
|
-
|
|
42
|
+
if (!rootPath)
|
|
43
|
+
return !1;
|
|
44
|
+
try {
|
|
45
|
+
process.chdir(rootPath);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return process.stderr.write(`[mcp] DR_MCP_ROOT_PATH=${rootPath} is not usable (${getErrorMessage(error)}); serving from ${process.cwd()}
|
|
48
|
+
`), !1;
|
|
49
|
+
}
|
|
50
|
+
return markEnvProjectRoot(process.cwd()), !0;
|
|
40
51
|
}
|
|
41
52
|
async function mcpCommand(options = {}) {
|
|
42
53
|
const keyManager = new ApiKeyManager();
|
|
43
54
|
try {
|
|
44
55
|
if (options.regenerateKey) {
|
|
45
56
|
await startActiveSpan("mcp.key.rotate", async (span) => {
|
|
46
|
-
const
|
|
47
|
-
span.setAttribute("mcp.key.path",
|
|
48
|
-
`), process.stderr.write(`MCP API key: ${
|
|
57
|
+
const isInteractive = process.stdin.isTTY === !0, { key, path } = await keyManager.rotate(isInteractive ? promptForKeyPath : void 0);
|
|
58
|
+
span.setAttribute("mcp.key.path", path), process.stderr.write(`Generated new MCP API key, stored at ${path}
|
|
59
|
+
`), process.stderr.write(`MCP API key: ${key}
|
|
49
60
|
`);
|
|
50
61
|
});
|
|
51
62
|
return;
|
|
52
63
|
}
|
|
53
|
-
serveFromRootPathEnv();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`), options.transport === "http") {
|
|
64
|
+
const servesEnvRoot = serveFromRootPathEnv();
|
|
65
|
+
if (options.transport === "http") {
|
|
66
|
+
const isInteractive = process.stdin.isTTY === !0, { key, path, isNew } = await keyManager.ensureKey(isInteractive ? promptForKeyPath : void 0);
|
|
67
|
+
isNew && process.stderr.write(`Generated new MCP API key, stored at ${path}
|
|
68
|
+
`), /^(1|true|yes|on)$/i.test(process.env.DR_MCP_QUIET_KEY ?? "") || process.stderr.write(`MCP API key: ${key}
|
|
69
|
+
`), await warmModel();
|
|
60
70
|
const { createMcpHttpApp, startHttpServer, closeHttpServer } = await import("../mcp/http-transport.js"), host = options.host || "127.0.0.1", port = options.port || 3100;
|
|
61
71
|
let httpApp;
|
|
62
72
|
const httpServer = await startActiveSpan("mcp.server.start", async (span) => {
|
|
@@ -90,15 +100,9 @@ Shutting down HTTP server...
|
|
|
90
100
|
};
|
|
91
101
|
process.on("SIGINT", shutdown), process.on("SIGTERM", shutdown), await keepAlive;
|
|
92
102
|
} else {
|
|
93
|
-
if (!keyManager.validate(process.env.DR_MCP_API_KEY, key))
|
|
94
|
-
throw quietKey && process.stderr.write(`MCP API key: ${key}
|
|
95
|
-
`), process.stderr.write(`Error: DR_MCP_API_KEY environment variable is missing or does not match the stored API key.
|
|
96
|
-
`), process.stderr.write(`Set DR_MCP_API_KEY to the key above when configuring the MCP client, e.g.:
|
|
97
|
-
`), process.stderr.write(` { "command": "dr", "args": ["mcp"], "env": { "DR_MCP_API_KEY": "<key>" } }
|
|
98
|
-
`), new CLIError("MCP authentication failed: invalid or missing DR_MCP_API_KEY", 1);
|
|
99
103
|
const transport = await startActiveSpan("mcp.server.start", async (span) => {
|
|
100
104
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js"), server = await createConfiguredServer();
|
|
101
|
-
span.setAttribute("mcp.server.name", "documentation-robotics"), span.setAttribute("mcp.server.version", cliVersion), span.setAttribute("mcp.server.transport", "stdio");
|
|
105
|
+
servesEnvRoot ? await warmModel() : serveClientRoots(server), span.setAttribute("mcp.server.name", "documentation-robotics"), span.setAttribute("mcp.server.version", cliVersion), span.setAttribute("mcp.server.transport", "stdio");
|
|
102
106
|
const serverTransport = new StdioServerTransport();
|
|
103
107
|
return await server.connect(serverTransport), serverTransport;
|
|
104
108
|
});
|
package/dist/commands/mcp.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/commands/mcp.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * MCP server command - Starts an MCP server for AI assistant integration\n *\n * Supports two transport modes:\n * - stdio (default): In-process over stdio; stdout carries the MCP JSON-RPC protocol\n * - http: HTTP server with Express, exposing MCP at /mcp endpoint with bearer token auth\n *\n * All non-protocol output goes to stderr.\n */\n\nimport { isCancel, text } from \"@clack/prompts\";\nimport { ApiKeyManager, type ApiKeyStoragePrompt } from \"../mcp/api-key-manager.js\";\nimport { type HttpTransportApp } from \"../mcp/http-transport.js\";\nimport { McpResourceRegistry } from \"../mcp/resource-registry.js\";\nimport { McpToolRegistry } from \"../mcp/tool-registry.js\";\nimport { loadModel } from \"../mcp/tools/shared.js\";\nimport { startActiveSpan } from \"../telemetry/index.js\";\nimport { CLIError, getErrorMessage } from \"../utils/errors.js\";\nimport { getCliVersion } from \"../utils/spec-version.js\";\n\nconst cliVersion = getCliVersion();\n\n/**\n * Prompt the user (via stdout/stdin) for where to store a newly generated API key.\n * Only invoked when stdin is a TTY \u2014 non-interactive launches (e.g. an MCP client\n * spawning `dr mcp`) fall back to the default path without prompting.\n */\nconst promptForKeyPath: ApiKeyStoragePrompt = async (defaultPath) => {\n const result = await text({\n message: \"Where should the MCP API key be stored?\",\n placeholder: defaultPath,\n defaultValue: defaultPath,\n });\n\n if (isCancel(result) || typeof result !== \"string\" || result.trim().length === 0) {\n return defaultPath;\n }\n return result.trim();\n};\n\nasync function createConfiguredServer() {\n const { McpServer } = await import(\"@modelcontextprotocol/sdk/server/mcp.js\");\n\n const server = new McpServer({\n name: \"documentation-robotics\",\n version: cliVersion,\n });\n\n // Register the model tool surface and spec/model manifest resources.\n new McpToolRegistry().registerAll(server);\n await new McpResourceRegistry().registerAll(server);\n\n // Warm the model cache at startup so it's loaded once and held in memory\n // for the session, rather than waiting for the first tool call. Best-effort:\n // a server started outside a DR project still starts up cleanly \u2014 the first\n // tool call that supplies a valid rootPath will load and cache it.\n try {\n await loadModel();\n } catch (error) {\n process.stderr.write(`[mcp] model warmup skipped: ${getErrorMessage(error)}\\n`);\n }\n\n return server;\n}\n\n/**\n * DR_MCP_ROOT_PATH lets a launcher (DrBot under `dr visualize`) point the server at a model\n * without relying on the cwd its MCP client spawns it in: tools default to this directory. A bad\n * path is reported and the server keeps its own cwd.\n */\nexport function serveFromRootPathEnv(env: NodeJS.ProcessEnv = process.env): void {\n const rootPath = env.DR_MCP_ROOT_PATH;\n if (!rootPath) return;\n try {\n process.chdir(rootPath);\n } catch (error) {\n process.stderr.write(\n `[mcp] DR_MCP_ROOT_PATH=${rootPath} is not usable (${getErrorMessage(error)}); serving from ${process.cwd()}\\n`\n );\n }\n}\n\nexport interface McpCommandOptions {\n /** Force-generate a new API key, overwrite it at the configured storage path, and exit. */\n regenerateKey?: boolean;\n /** Transport type: \"stdio\" or \"http\" (default: \"stdio\") */\n transport?: \"stdio\" | \"http\";\n /** Port for HTTP transport (default: 3100, only meaningful with --transport http) */\n port?: number;\n /** Host address for HTTP transport (default: \"127.0.0.1\", only meaningful with --transport http) */\n host?: string;\n}\n\nexport async function mcpCommand(options: McpCommandOptions = {}): Promise<void> {\n const keyManager = new ApiKeyManager();\n\n try {\n if (options.regenerateKey) {\n await startActiveSpan(\"mcp.key.rotate\", async (span) => {\n const isInteractive = process.stdin.isTTY === true;\n const { key, path } = await keyManager.rotate(\n isInteractive ? promptForKeyPath : undefined\n );\n span.setAttribute(\"mcp.key.path\", path);\n process.stderr.write(`Generated new MCP API key, stored at ${path}\\n`);\n process.stderr.write(`MCP API key: ${key}\\n`);\n });\n return;\n }\n\n serveFromRootPathEnv();\n\n const isInteractive = process.stdin.isTTY === true;\n const { key, path, isNew } = await keyManager.ensureKey(\n isInteractive ? promptForKeyPath : undefined\n );\n\n if (isNew) {\n process.stderr.write(`Generated new MCP API key, stored at ${path}\\n`);\n }\n // Print the API key to stderr (never stdout, which carries the MCP protocol) so a user can\n // configure a client. A launcher that already passes the key (DrBot) sets DR_MCP_QUIET_KEY so\n // the key does not end up in the client's MCP server logs on every start.\n const quietKey = /^(1|true|yes|on)$/i.test(process.env.DR_MCP_QUIET_KEY ?? \"\");\n if (!quietKey) {\n process.stderr.write(`MCP API key: ${key}\\n`);\n }\n\n if (options.transport === \"http\") {\n // HTTP transport implementation\n const { createMcpHttpApp, startHttpServer, closeHttpServer } = await import(\n \"../mcp/http-transport.js\"\n );\n\n const host = options.host || \"127.0.0.1\";\n const port = options.port || 3100;\n\n let httpApp: HttpTransportApp;\n const httpServer = await startActiveSpan(\n \"mcp.server.start\",\n async (span) => {\n httpApp = await createMcpHttpApp(keyManager, key, createConfiguredServer, host);\n const server = await startHttpServer(httpApp, host, port);\n\n span.setAttribute(\"mcp.server.name\", \"documentation-robotics\");\n span.setAttribute(\"mcp.server.version\", cliVersion);\n span.setAttribute(\"mcp.server.transport\", \"http\");\n span.setAttribute(\"mcp.server.host\", host);\n span.setAttribute(\"mcp.server.port\", port);\n\n return server;\n }\n );\n\n // Warn if bound to 0.0.0.0\n if (host === \"0.0.0.0\") {\n process.stderr.write(\n \"Warning: Server bound to 0.0.0.0. DNS rebinding protection is not automatic; ensure your MCP client validates the Host header.\\n\"\n );\n }\n\n process.stderr.write(`Documentation Robotics MCP server ready (http://${host}:${port}/mcp)\\n`);\n\n // Set up graceful shutdown on SIGINT/SIGTERM\n let resolveKeepAlive: () => void;\n const keepAlive = new Promise<void>((resolve) => {\n resolveKeepAlive = resolve;\n });\n\n let isShuttingDown = false;\n const shutdown = async () => {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n try {\n process.stderr.write(\"\\nShutting down HTTP server...\\n\");\n await closeHttpServer(httpServer, httpApp);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : \"unknown error\";\n process.stderr.write(`[mcp] Error closing server: ${errorMsg}\\n`);\n } finally {\n resolveKeepAlive!();\n }\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Keep the process alive until explicitly terminated\n await keepAlive;\n } else {\n // Stdio transport (default)\n // Validate that the environment has the correct API key configured\n if (!keyManager.validate(process.env.DR_MCP_API_KEY, key)) {\n // A quiet launch passed a key that doesn't match: show the right one so it can be fixed.\n if (quietKey) process.stderr.write(`MCP API key: ${key}\\n`);\n process.stderr.write(\n \"Error: DR_MCP_API_KEY environment variable is missing or does not match the stored API key.\\n\"\n );\n process.stderr.write(\n \"Set DR_MCP_API_KEY to the key above when configuring the MCP client, e.g.:\\n\"\n );\n process.stderr.write(\n ' { \"command\": \"dr\", \"args\": [\"mcp\"], \"env\": { \"DR_MCP_API_KEY\": \"<key>\" } }\\n'\n );\n throw new CLIError(\"MCP authentication failed: invalid or missing DR_MCP_API_KEY\", 1);\n }\n\n const transport = await startActiveSpan(\n \"mcp.server.start\",\n async (span) => {\n const { StdioServerTransport } = await import(\"@modelcontextprotocol/sdk/server/stdio.js\");\n\n const server = await createConfiguredServer();\n\n span.setAttribute(\"mcp.server.name\", \"documentation-robotics\");\n span.setAttribute(\"mcp.server.version\", cliVersion);\n span.setAttribute(\"mcp.server.transport\", \"stdio\");\n\n const serverTransport = new StdioServerTransport();\n await server.connect(serverTransport);\n return serverTransport;\n }\n );\n\n process.stderr.write(\"Documentation Robotics MCP server ready (stdio)\\n\");\n\n // Keep the process alive for the lifetime of the stdio session; the\n // transport closes (and the process exits) when stdin closes. Shutdown\n // is implicit in the `mcp.server.start` span's end, so no separate\n // zero-duration \"stop\" span is recorded here.\n await new Promise<void>((resolve) => {\n transport.onclose = () => resolve();\n });\n }\n } catch (error) {\n if (error instanceof CLIError) {\n throw error;\n }\n\n let message = getErrorMessage(error);\n\n // Handle port-in-use errors with actionable message\n if (error instanceof Error && (error as NodeJS.ErrnoException).code === \"EADDRINUSE\") {\n const port = options.port || 3100;\n message = `Port ${port} is already in use. Try a different port with --port or kill the process using that port.`;\n }\n\n process.stderr.write(`Error: ${message}\\n`);\n throw new CLIError(message, 1);\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["/**\n * MCP server command - Starts an MCP server for AI assistant integration\n *\n * Supports two transport modes:\n * - stdio (default): In-process over stdio; stdout carries the MCP JSON-RPC protocol. The client\n * spawns the server as the same user, so there is no API key: anything that can start\n * `dr mcp` can already read the model files.\n * - http: HTTP server with Express, exposing MCP at /mcp endpoint with bearer token auth\n *\n * All non-protocol output goes to stderr.\n */\n\nimport { isCancel, text } from \"@clack/prompts\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { RootsListChangedNotificationSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { ApiKeyManager, type ApiKeyStoragePrompt } from \"../mcp/api-key-manager.js\";\nimport { type HttpTransportApp } from \"../mcp/http-transport.js\";\nimport { followClientRoots, markEnvProjectRoot } from \"../mcp/project-root.js\";\nimport { McpResourceRegistry } from \"../mcp/resource-registry.js\";\nimport { SERVER_INSTRUCTIONS } from \"../mcp/server-instructions.js\";\nimport { McpToolRegistry } from \"../mcp/tool-registry.js\";\nimport { clearModelCache, loadModel } from \"../mcp/tools/shared.js\";\nimport { startActiveSpan } from \"../telemetry/index.js\";\nimport { CLIError, getErrorMessage } from \"../utils/errors.js\";\nimport { getCliVersion } from \"../utils/spec-version.js\";\n\nconst cliVersion = getCliVersion();\n\n/**\n * Prompt the user (via stdout/stdin) for where to store a newly generated API key.\n * Only invoked when stdin is a TTY \u2014 non-interactive launches (e.g. an MCP client\n * spawning `dr mcp`) fall back to the default path without prompting.\n */\nconst promptForKeyPath: ApiKeyStoragePrompt = async (defaultPath) => {\n const result = await text({\n message: \"Where should the MCP API key be stored?\",\n placeholder: defaultPath,\n defaultValue: defaultPath,\n });\n\n if (isCancel(result) || typeof result !== \"string\" || result.trim().length === 0) {\n return defaultPath;\n }\n return result.trim();\n};\n\nasync function createConfiguredServer(): Promise<McpServer> {\n const { McpServer } = await import(\"@modelcontextprotocol/sdk/server/mcp.js\");\n\n const server = new McpServer(\n { name: \"documentation-robotics\", version: cliVersion },\n { instructions: SERVER_INSTRUCTIONS }\n );\n\n // Register the model tool surface and spec/model manifest resources.\n new McpToolRegistry().registerAll(server);\n await new McpResourceRegistry().registerAll(server);\n return server;\n}\n\n/**\n * Loads the default project's model into the cache so the first tool call doesn't wait for it.\n * Best-effort: a server started outside a DR project still starts cleanly, and the first tool\n * call that supplies a valid rootPath loads and caches that model.\n */\nasync function warmModel(): Promise<void> {\n try {\n await loadModel();\n } catch (error) {\n process.stderr.write(`[mcp] model warmup skipped: ${getErrorMessage(error)}\\n`);\n }\n}\n\n/**\n * Serve the project the client has open: once the client has initialized, ask for its roots\n * (mcp/project-root.ts), and ask again whenever it says they changed. A client without roots\n * leaves the server in its working directory.\n */\nfunction serveClientRoots(server: McpServer): void {\n const inner = server.server;\n const follow = () =>\n followClientRoots(() => inner.listRoots(), { onChange: () => clearModelCache() }).then(warmModel);\n\n inner.setNotificationHandler(RootsListChangedNotificationSchema, async () => {\n await follow();\n });\n inner.oninitialized = () => {\n if (inner.getClientCapabilities()?.roots) {\n void follow();\n } else {\n void warmModel();\n }\n };\n}\n\n/**\n * DR_MCP_ROOT_PATH lets a launcher (DrBot under `dr visualize`) point the server at a model\n * without relying on the cwd its MCP client spawns it in: tools default to this directory, and\n * the client's roots are not consulted. A bad path is reported and the server keeps its own cwd.\n * Returns whether the server now serves from DR_MCP_ROOT_PATH.\n */\nexport function serveFromRootPathEnv(env: NodeJS.ProcessEnv = process.env): boolean {\n const rootPath = env.DR_MCP_ROOT_PATH;\n if (!rootPath) return false;\n try {\n process.chdir(rootPath);\n } catch (error) {\n process.stderr.write(\n `[mcp] DR_MCP_ROOT_PATH=${rootPath} is not usable (${getErrorMessage(error)}); serving from ${process.cwd()}\\n`\n );\n return false;\n }\n markEnvProjectRoot(process.cwd());\n return true;\n}\n\nexport interface McpCommandOptions {\n /** Force-generate a new API key, overwrite it at the configured storage path, and exit. */\n regenerateKey?: boolean;\n /** Transport type: \"stdio\" or \"http\" (default: \"stdio\") */\n transport?: \"stdio\" | \"http\";\n /** Port for HTTP transport (default: 3100, only meaningful with --transport http) */\n port?: number;\n /** Host address for HTTP transport (default: \"127.0.0.1\", only meaningful with --transport http) */\n host?: string;\n}\n\nexport async function mcpCommand(options: McpCommandOptions = {}): Promise<void> {\n const keyManager = new ApiKeyManager();\n\n try {\n if (options.regenerateKey) {\n await startActiveSpan(\"mcp.key.rotate\", async (span) => {\n const isInteractive = process.stdin.isTTY === true;\n const { key, path } = await keyManager.rotate(\n isInteractive ? promptForKeyPath : undefined\n );\n span.setAttribute(\"mcp.key.path\", path);\n process.stderr.write(`Generated new MCP API key, stored at ${path}\\n`);\n process.stderr.write(`MCP API key: ${key}\\n`);\n });\n return;\n }\n\n const servesEnvRoot = serveFromRootPathEnv();\n\n if (options.transport === \"http\") {\n // HTTP clients authenticate with the API key as a bearer token.\n const isInteractive = process.stdin.isTTY === true;\n const { key, path, isNew } = await keyManager.ensureKey(\n isInteractive ? promptForKeyPath : undefined\n );\n if (isNew) {\n process.stderr.write(`Generated new MCP API key, stored at ${path}\\n`);\n }\n // Print the key to stderr so a user can configure a client. DR_MCP_QUIET_KEY keeps it out\n // of logs for a launcher that already has it.\n if (!/^(1|true|yes|on)$/i.test(process.env.DR_MCP_QUIET_KEY ?? \"\")) {\n process.stderr.write(`MCP API key: ${key}\\n`);\n }\n\n // One server for every HTTP session, so the project is the env root or the cwd (client\n // roots are per session and can't move a shared working directory).\n await warmModel();\n const { createMcpHttpApp, startHttpServer, closeHttpServer } = await import(\n \"../mcp/http-transport.js\"\n );\n\n const host = options.host || \"127.0.0.1\";\n const port = options.port || 3100;\n\n let httpApp: HttpTransportApp;\n const httpServer = await startActiveSpan(\n \"mcp.server.start\",\n async (span) => {\n httpApp = await createMcpHttpApp(keyManager, key, createConfiguredServer, host);\n const server = await startHttpServer(httpApp, host, port);\n\n span.setAttribute(\"mcp.server.name\", \"documentation-robotics\");\n span.setAttribute(\"mcp.server.version\", cliVersion);\n span.setAttribute(\"mcp.server.transport\", \"http\");\n span.setAttribute(\"mcp.server.host\", host);\n span.setAttribute(\"mcp.server.port\", port);\n\n return server;\n }\n );\n\n // Warn if bound to 0.0.0.0\n if (host === \"0.0.0.0\") {\n process.stderr.write(\n \"Warning: Server bound to 0.0.0.0. DNS rebinding protection is not automatic; ensure your MCP client validates the Host header.\\n\"\n );\n }\n\n process.stderr.write(`Documentation Robotics MCP server ready (http://${host}:${port}/mcp)\\n`);\n\n // Set up graceful shutdown on SIGINT/SIGTERM\n let resolveKeepAlive: () => void;\n const keepAlive = new Promise<void>((resolve) => {\n resolveKeepAlive = resolve;\n });\n\n let isShuttingDown = false;\n const shutdown = async () => {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n try {\n process.stderr.write(\"\\nShutting down HTTP server...\\n\");\n await closeHttpServer(httpServer, httpApp);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : \"unknown error\";\n process.stderr.write(`[mcp] Error closing server: ${errorMsg}\\n`);\n } finally {\n resolveKeepAlive!();\n }\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Keep the process alive until explicitly terminated\n await keepAlive;\n } else {\n // Stdio transport (default). No API key: the client spawns this process as its own user.\n const transport = await startActiveSpan(\n \"mcp.server.start\",\n async (span) => {\n const { StdioServerTransport } = await import(\"@modelcontextprotocol/sdk/server/stdio.js\");\n\n const server = await createConfiguredServer();\n if (servesEnvRoot) {\n await warmModel();\n } else {\n serveClientRoots(server);\n }\n\n span.setAttribute(\"mcp.server.name\", \"documentation-robotics\");\n span.setAttribute(\"mcp.server.version\", cliVersion);\n span.setAttribute(\"mcp.server.transport\", \"stdio\");\n\n const serverTransport = new StdioServerTransport();\n await server.connect(serverTransport);\n return serverTransport;\n }\n );\n\n process.stderr.write(\"Documentation Robotics MCP server ready (stdio)\\n\");\n\n // Keep the process alive for the lifetime of the stdio session; the\n // transport closes (and the process exits) when stdin closes. Shutdown\n // is implicit in the `mcp.server.start` span's end, so no separate\n // zero-duration \"stop\" span is recorded here.\n await new Promise<void>((resolve) => {\n transport.onclose = () => resolve();\n });\n }\n } catch (error) {\n if (error instanceof CLIError) {\n throw error;\n }\n\n let message = getErrorMessage(error);\n\n // Handle port-in-use errors with actionable message\n if (error instanceof Error && (error as NodeJS.ErrnoException).code === \"EADDRINUSE\") {\n const port = options.port || 3100;\n message = `Port ${port} is already in use. Try a different port with --port or kill the process using that port.`;\n }\n\n process.stderr.write(`Error: ${message}\\n`);\n throw new CLIError(message, 1);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ import { StagingAreaManager } from "../core/staging-area.js";
|
|
|
4
4
|
import { findElementLayer } from "../utils/element-utils.js";
|
|
5
5
|
import { isValidLayerName } from "../core/layers.js";
|
|
6
6
|
import { CLIError, ErrorCategory, handleError, handleSuccess, handleInfo, getErrorMessage } from "../utils/errors.js";
|
|
7
|
-
import { getValidRelationships
|
|
7
|
+
import { getValidRelationships } from "../generated/relationship-index.js";
|
|
8
8
|
import { normalizeNodeType } from "../generated/node-types.js";
|
|
9
9
|
import { ModelReportOrchestrator } from "../reports/model-report-orchestrator.js";
|
|
10
10
|
import { emitLog, SeverityNumber } from "../telemetry/index.js";
|
|
@@ -13,7 +13,7 @@ import { RelationshipCatalog } from "../core/relationship-catalog.js";
|
|
|
13
13
|
import { collectKnownIdentifiers, findDanglingRelationships } from "../core/relationship-prune.js";
|
|
14
14
|
import { isJson } from "../utils/globals.js";
|
|
15
15
|
import { ChangesetGraph } from "../core/changeset-graph.js";
|
|
16
|
-
import { liveRelationshipsBetween, planRelationshipAdd, stageRelationshipDeletes } from "../core/relationship-staging.js";
|
|
16
|
+
import { liveRelationshipsBetween, planRelationshipAdd, stageRelationshipDeletes, validateRelationshipCombination } from "../core/relationship-staging.js";
|
|
17
17
|
function constructSpecNodeId(element, elementId) {
|
|
18
18
|
const layerId = element.layer_id, type = element.type;
|
|
19
19
|
if (typeof layerId != "string" || !layerId)
|
|
@@ -23,16 +23,6 @@ function constructSpecNodeId(element, elementId) {
|
|
|
23
23
|
const normalizedType = normalizeNodeType(layerId, type);
|
|
24
24
|
return `${layerId}.${normalizedType}`;
|
|
25
25
|
}
|
|
26
|
-
function validateRelationshipCombination(sourceSpecNodeId, predicate, destSpecNodeId) {
|
|
27
|
-
if (getValidRelationships(sourceSpecNodeId, predicate, destSpecNodeId).length > 0)
|
|
28
|
-
return { valid: !0 };
|
|
29
|
-
const suggestions = [], validPredicates = getValidPredicatesForSource(sourceSpecNodeId);
|
|
30
|
-
if (validPredicates.length > 0 ? suggestions.push(`Valid predicates for ${sourceSpecNodeId}: ${validPredicates.join(", ")}`) : suggestions.push(`No valid relationships defined for ${sourceSpecNodeId}`), predicate) {
|
|
31
|
-
const validDests = getValidDestinationsForSourceAndPredicate(sourceSpecNodeId, predicate);
|
|
32
|
-
validDests.length > 0 ? suggestions.push(`Valid destinations for --[${predicate}]-->: ${validDests.join(", ")}`) : suggestions.push(`No valid destinations for ${sourceSpecNodeId} --[${predicate}]-->`);
|
|
33
|
-
}
|
|
34
|
-
return { valid: !1, suggestions };
|
|
35
|
-
}
|
|
36
26
|
function getRelationshipConstraints(sourceSpecNodeId, predicate, destSpecNodeId) {
|
|
37
27
|
const rels = getValidRelationships(sourceSpecNodeId, predicate, destSpecNodeId);
|
|
38
28
|
if (rels.length === 0)
|