@documentation-robotics/cli 0.1.12 → 0.2.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 +39 -1
- package/dist/analyzers/verify-engine.js +22 -19
- package/dist/analyzers/verify-engine.js.map +2 -2
- package/dist/cli.js +101 -27
- package/dist/cli.js.map +3 -3
- package/dist/coding-agents/agents/claude-code.js +3 -1
- package/dist/coding-agents/agents/claude-code.js.map +2 -2
- package/dist/coding-agents/claude-code-client.js +2 -1
- package/dist/coding-agents/claude-code-client.js.map +2 -2
- package/dist/coding-agents/copilot-client.js +17 -12
- package/dist/coding-agents/copilot-client.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +122 -0
- package/dist/coding-agents/default-permissions.js.map +7 -0
- package/dist/commands/add.js +10 -6
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +18 -10
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/audit.js +6 -6
- package/dist/commands/audit.js.map +2 -2
- package/dist/commands/changeset.js +238 -145
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +45 -18
- package/dist/commands/delete.js.map +3 -3
- package/dist/commands/element.js +3 -2
- package/dist/commands/element.js.map +2 -2
- package/dist/commands/farm.js +657 -0
- package/dist/commands/farm.js.map +7 -0
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +107 -0
- package/dist/commands/mcp.js.map +7 -0
- package/dist/commands/relationship.js +53 -30
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +19 -6
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/validate.js +41 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +2 -2
- package/dist/commands/version.js.map +2 -2
- package/dist/config.js +83 -0
- package/dist/config.js.map +7 -0
- package/dist/core/annotation-store.js +138 -0
- package/dist/core/annotation-store.js.map +7 -0
- package/dist/core/farm-manifest.js +134 -0
- package/dist/core/farm-manifest.js.map +7 -0
- package/dist/core/farm-sync-engine.js +346 -0
- package/dist/core/farm-sync-engine.js.map +7 -0
- package/dist/core/farm-sync-state.js +159 -0
- package/dist/core/farm-sync-state.js.map +7 -0
- package/dist/core/manifest.js +6 -3
- package/dist/core/manifest.js.map +2 -2
- package/dist/core/migration-registry.js +55 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model.js +177 -16
- package/dist/core/model.js.map +3 -3
- package/dist/core/virtual-projection.js +1 -0
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/generated/node-types.js +1 -1
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +45 -9
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +55 -0
- package/dist/integrations/claude_code/docs/README.md +4 -0
- package/dist/mcp/api-key-manager.js +98 -0
- package/dist/mcp/api-key-manager.js.map +7 -0
- package/dist/mcp/http-transport.js +176 -0
- package/dist/mcp/http-transport.js.map +7 -0
- package/dist/mcp/resource-registry.js +97 -0
- package/dist/mcp/resource-registry.js.map +7 -0
- package/dist/mcp/tool-registry.js +44 -0
- package/dist/mcp/tool-registry.js.map +7 -0
- package/dist/mcp/tools/annotation-create.js +39 -0
- package/dist/mcp/tools/annotation-create.js.map +7 -0
- package/dist/mcp/tools/annotation-delete.js +29 -0
- package/dist/mcp/tools/annotation-delete.js.map +7 -0
- package/dist/mcp/tools/annotation-get.js +29 -0
- package/dist/mcp/tools/annotation-get.js.map +7 -0
- package/dist/mcp/tools/annotation-list.js +24 -0
- package/dist/mcp/tools/annotation-list.js.map +7 -0
- package/dist/mcp/tools/annotation-reply.js +34 -0
- package/dist/mcp/tools/annotation-reply.js.map +7 -0
- package/dist/mcp/tools/annotation-shared.js +10 -0
- package/dist/mcp/tools/annotation-shared.js.map +7 -0
- package/dist/mcp/tools/annotation-update.js +39 -0
- package/dist/mcp/tools/annotation-update.js.map +7 -0
- package/dist/mcp/tools/changeset-list.js +33 -0
- package/dist/mcp/tools/changeset-list.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +39 -0
- package/dist/mcp/tools/changeset-show.js.map +7 -0
- package/dist/mcp/tools/chat-launch.js +50 -0
- package/dist/mcp/tools/chat-launch.js.map +7 -0
- package/dist/mcp/tools/chat-status.js +26 -0
- package/dist/mcp/tools/chat-status.js.map +7 -0
- package/dist/mcp/tools/index.js +47 -0
- package/dist/mcp/tools/index.js.map +7 -0
- package/dist/mcp/tools/model-add.js +106 -0
- package/dist/mcp/tools/model-add.js.map +7 -0
- package/dist/mcp/tools/model-delete.js +99 -0
- package/dist/mcp/tools/model-delete.js.map +7 -0
- package/dist/mcp/tools/model-export.js +72 -0
- package/dist/mcp/tools/model-export.js.map +7 -0
- package/dist/mcp/tools/model-info.js +51 -0
- package/dist/mcp/tools/model-info.js.map +7 -0
- package/dist/mcp/tools/model-list.js +53 -0
- package/dist/mcp/tools/model-list.js.map +7 -0
- package/dist/mcp/tools/model-reload.js +34 -0
- package/dist/mcp/tools/model-reload.js.map +7 -0
- package/dist/mcp/tools/model-search.js +88 -0
- package/dist/mcp/tools/model-search.js.map +7 -0
- package/dist/mcp/tools/model-show.js +41 -0
- package/dist/mcp/tools/model-show.js.map +7 -0
- package/dist/mcp/tools/model-stats.js +22 -0
- package/dist/mcp/tools/model-stats.js.map +7 -0
- package/dist/mcp/tools/model-trace.js +55 -0
- package/dist/mcp/tools/model-trace.js.map +7 -0
- package/dist/mcp/tools/model-update.js +105 -0
- package/dist/mcp/tools/model-update.js.map +7 -0
- package/dist/mcp/tools/model-validate.js +78 -0
- package/dist/mcp/tools/model-validate.js.map +7 -0
- package/dist/mcp/tools/shared.js +103 -0
- package/dist/mcp/tools/shared.js.map +7 -0
- package/dist/reports/model-layer-report-generator.js +5 -3
- package/dist/reports/model-layer-report-generator.js.map +2 -2
- package/dist/reports/model-readme-generator.js +116 -0
- package/dist/reports/model-readme-generator.js.map +7 -0
- package/dist/reports/model-report-data.js +37 -1
- package/dist/reports/model-report-data.js.map +2 -2
- package/dist/reports/model-report-orchestrator.js +29 -6
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/reports/model-report-utils.js +9 -0
- package/dist/reports/model-report-utils.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +2 -2
- 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 +12 -1
- package/dist/schemas/bundled/base.json +1 -1
- package/dist/schemas/bundled/business.json +1 -1
- package/dist/schemas/bundled/data-model.json +38 -2
- package/dist/schemas/bundled/data-store.json +1 -1
- package/dist/schemas/bundled/manifest.json +4 -4
- 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 +1 -1
- package/dist/schemas/bundled/ux.json +1 -1
- package/dist/server/server.js +45 -7
- package/dist/server/server.js.map +2 -2
- package/dist/telemetry/config.js +2 -40
- package/dist/telemetry/config.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/types/index.js.map +2 -2
- package/dist/utils/ansi-suppressor.js +25 -0
- package/dist/utils/ansi-suppressor.js.map +7 -0
- package/dist/utils/color-utils.js +34 -0
- package/dist/utils/color-utils.js.map +7 -0
- package/dist/utils/element-reference-shape.js +15 -1
- package/dist/utils/element-reference-shape.js.map +3 -3
- package/dist/utils/errors.js +69 -10
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/globals.js +17 -1
- package/dist/utils/globals.js.map +2 -2
- package/dist/utils/project-paths.js +24 -2
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/reference-path-parser.js +49 -0
- package/dist/utils/reference-path-parser.js.map +7 -0
- package/dist/utils/source-reference.js +5 -0
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +10 -2
- package/dist/utils/spec-version.js.map +2 -2
- package/dist/validators/composed-reference-validator.js +262 -0
- package/dist/validators/composed-reference-validator.js.map +7 -0
- package/dist/validators/reference-validator.js +59 -8
- package/dist/validators/reference-validator.js.map +3 -3
- package/dist/validators/relationship-schema-validator.js +30 -2
- package/dist/validators/relationship-schema-validator.js.map +3 -3
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-B7AOQXXX.js +81 -0
- package/dist/viewer/assets/main-vdrFrG7q.css +1 -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 +6 -3
- package/dist/viewer/assets/main-BRPZyc36.css +0 -1
- package/dist/viewer/assets/main-BrpLargo.js +0 -81
package/README.md
CHANGED
|
@@ -264,6 +264,44 @@ dr chat github-copilot # Use GitHub Copilot, save as preferenc
|
|
|
264
264
|
|
|
265
265
|
**Note**: Chat requires either [Claude Code CLI](https://claude.ai/download) or [GitHub Copilot CLI](https://github.com/github/gh-copilot) to be installed and authenticated. The CLI will auto-detect available clients and prompt you to choose if both are installed. You can also explicitly specify which client to use.
|
|
266
266
|
|
|
267
|
+
### MCP Server
|
|
268
|
+
|
|
269
|
+
`dr mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io/) server over stdio, exposing the architecture model (list/show/search/add/update/delete/validate/export/trace elements, changesets, chat) as tools an MCP-aware AI client can call directly.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Start the server (run by your MCP client, not typically by hand)
|
|
273
|
+
dr mcp
|
|
274
|
+
|
|
275
|
+
# Rotate the API key: generates a new key, overwrites it at the configured
|
|
276
|
+
# storage path, and prints it — without touching the server process
|
|
277
|
+
dr mcp --regenerate-key
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**API key**:
|
|
281
|
+
|
|
282
|
+
- On first launch, `dr mcp` generates a random API key and stores it on disk with `0600` permissions. Interactive sessions are prompted for a storage path; non-interactive launches (the normal case — an MCP client spawning `dr mcp`) default to `~/.dr-mcp-key`.
|
|
283
|
+
- The chosen path is recorded under `mcp.api_key_path` in `~/.dr-config.yaml` (or the path from `DR_CONFIG_PATH`, if set), so subsequent launches reuse the same key without re-prompting.
|
|
284
|
+
- Every launch prints the current key to stderr (never stdout, which carries the MCP protocol) and requires the client to supply it back via the `DR_MCP_API_KEY` environment variable. The connection is rejected if `DR_MCP_API_KEY` is missing or doesn't match.
|
|
285
|
+
- Run `dr mcp --regenerate-key` to rotate the key (e.g. after a suspected leak). This overwrites the key at its existing configured path (or the default path, prompting once if none is configured yet), updates `~/.dr-config.yaml`, prints the new key, and exits without starting the server — no manual deletion of config or key files required.
|
|
286
|
+
|
|
287
|
+
**Configuring an MCP client**:
|
|
288
|
+
|
|
289
|
+
Point your MCP client at the `dr` binary and pass the key via `env`:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"mcpServers": {
|
|
294
|
+
"documentation-robotics": {
|
|
295
|
+
"command": "dr",
|
|
296
|
+
"args": ["mcp"],
|
|
297
|
+
"env": {
|
|
298
|
+
"DR_MCP_API_KEY": "<key printed by dr mcp>"
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
267
305
|
## Configuration
|
|
268
306
|
|
|
269
307
|
### Model Structure
|
|
@@ -289,7 +327,7 @@ your-project/
|
|
|
289
327
|
│ └── ...
|
|
290
328
|
```
|
|
291
329
|
|
|
292
|
-
**
|
|
330
|
+
**Spec-version migrations**: When the model's spec version is older than the CLI's bundled spec (e.g. after a layer renumbering), run `dr upgrade` to migrate the on-disk model in place — see `MigrationRegistry` in `cli/src/core/migration-registry.ts` for the supported version paths.
|
|
293
331
|
|
|
294
332
|
### Specification Reference
|
|
295
333
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import { access } from "fs/promises";
|
|
3
3
|
import { Model } from "../core/model.js";
|
|
4
|
+
import { getErrorMessage } from "../utils/errors.js";
|
|
4
5
|
import { IgnoreFileLoader } from "./verify-ignore.js";
|
|
5
6
|
import { SOURCE_CODE_EXT_RE } from "./cbm-analyzer.js";
|
|
6
7
|
class VerifyEngine {
|
|
@@ -25,8 +26,8 @@ class VerifyEngine {
|
|
|
25
26
|
try {
|
|
26
27
|
model = await Model.load(projectRoot);
|
|
27
28
|
} catch (error) {
|
|
28
|
-
const
|
|
29
|
-
throw
|
|
29
|
+
const message = getErrorMessage(error);
|
|
30
|
+
throw message.includes("No DR project") || message.includes("Model not found") || message.includes("no model found") || message.includes("Could not find documentation_robotics") ? new Error(`Failed to load DR model at ${projectRoot}: Model not found. Please run 'dr init' to initialize the documentation robotics model.`) : error;
|
|
30
31
|
}
|
|
31
32
|
const activeChangesetId = model.getActiveChangesetId(), apiLayer = options.changesetAware === !1 ? await model.getBaseLayer("api") : await model.getLayer("api");
|
|
32
33
|
if (!apiLayer)
|
|
@@ -129,7 +130,7 @@ Warning: ${emptySymbolCount}/${routes.length} graph route(s) have empty source_s
|
|
|
129
130
|
file: firstLocation.file,
|
|
130
131
|
symbol: firstLocation.symbol || ""
|
|
131
132
|
});
|
|
132
|
-
const filePath = path.join(
|
|
133
|
+
const filePath = path.join(model.codebaseRoot, firstLocation.file);
|
|
133
134
|
fileChecks.push(access(filePath).then(() => !0).catch((err) => err.code !== "ENOENT"));
|
|
134
135
|
}
|
|
135
136
|
elementsWithoutSourceRef > 0 && console.warn(`
|
|
@@ -143,22 +144,24 @@ Warning: ${elementsWithNonIndexableSource} API model element(s) excluded from dr
|
|
|
143
144
|
or use dr analyzer endpoints to discover routes automatically.
|
|
144
145
|
`);
|
|
145
146
|
const fileExistsResults = await Promise.all(fileChecks);
|
|
146
|
-
for (let i = 0; i < elementsToCheck.length; i++)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
147
|
+
for (let i = 0; i < elementsToCheck.length; i++) {
|
|
148
|
+
const fileExists = fileExistsResults[i], elem = elementsToCheck[i];
|
|
149
|
+
if (fileExists === !1)
|
|
150
|
+
continue;
|
|
151
|
+
const ignoreReason = IgnoreFileLoader.matches({
|
|
152
|
+
element_id: elem.path
|
|
153
|
+
}, "element", ignoreRules);
|
|
154
|
+
ignoreReason ? ignored.push({
|
|
155
|
+
id: elem.id,
|
|
156
|
+
entry_type: "element",
|
|
157
|
+
reason: ignoreReason
|
|
158
|
+
}) : inModelOnly.push({
|
|
159
|
+
id: elem.id,
|
|
160
|
+
type: "operation",
|
|
161
|
+
source_file: elem.file,
|
|
162
|
+
source_symbol: elem.symbol
|
|
163
|
+
});
|
|
164
|
+
}
|
|
162
165
|
const buckets = {
|
|
163
166
|
matched,
|
|
164
167
|
in_graph_only: inGraphOnly,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/analyzers/verify-engine.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Verify Engine - computes verification reports for API endpoints\n *\n * Implements the core algorithm for comparing graph-discovered routes against\n * model-defined operations, supporting dual-index matching and changeset awareness.\n */\n\nimport * as path from \"path\";\nimport { access } from \"fs/promises\";\nimport { Model } from \"../core/model.js\";\nimport type {\n VerifyReport,\n VerifyOptions,\n MatchedEntry,\n GraphOnlyEntry,\n ModelOnlyEntry,\n IgnoredEntry,\n VerifyBuckets,\n VerifySummary,\n IndexMeta,\n VerifyChangesetContext,\n} from \"./types.js\";\nimport { IgnoreFileLoader } from \"./verify-ignore.js\";\nimport { SOURCE_CODE_EXT_RE } from \"./cbm-analyzer.js\";\n\n/**\n * A discovered route from the graph\n */\nexport interface DiscoveredRoute {\n id: string;\n http_method?: string;\n http_path?: string;\n handler?: string;\n source_file?: string;\n source_symbol?: string;\n}\n\n/**\n * Model element reference extracted from the API layer\n * Contains the minimal set of fields needed for dual-index matching\n */\ninterface ModelElementRef {\n /** Element ID from the model */\n id: string;\n /** Source file path for primary index matching */\n source_file?: string;\n /** Source symbol for primary index matching */\n source_symbol?: string;\n /** HTTP method for secondary index matching */\n http_method?: string;\n /** HTTP path for secondary index matching */\n http_path?: string;\n}\n\n/**\n * Primary or secondary index entry (holds model element data, not discovered route data)\n */\ninterface IndexEntry {\n modelEntry: ModelElementRef;\n key: string;\n}\n\n/**\n * VerifyEngine - computes verification reports for API endpoints\n */\nexport class VerifyEngine {\n /**\n * Compute a comprehensive verification report\n *\n * Implements the five-step algorithm:\n * 1. Resolve model view via Model.load() + getActiveChangesetId() + getLayer(\"api\")\n * 2. Build primary index on source_reference.locations[0].file + \":\" + symbol\n * and optional secondary index on http_method + \":\" + http_path\n * 3. Load ignore rules\n * 4. Compute four buckets (matched, in_graph_only, in_model_only, ignored)\n * 5. Assemble VerifyReport\n *\n * @param projectRoot Absolute path to the project root\n * @param routes Array of discovered routes from the graph\n * @param options Verification options\n * @returns Comprehensive verification report\n */\n async computeReport(\n projectRoot: string,\n routes: DiscoveredRoute[],\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta\n ): Promise<VerifyReport> {\n // Step 1: Resolve model view\n let model: Model;\n try {\n model = await Model.load(projectRoot);\n } catch (error) {\n const err = error as NodeJS.ErrnoException;\n // Provide helpful error message if model directory is missing or corrupted\n if (err.code === \"ENOENT\" || err.code === \"ENOTDIR\") {\n throw new Error(\n `Failed to load DR model at ${projectRoot}: Model directory not found or inaccessible. ` +\n `Please run 'dr init' to initialize the documentation robotics model.`\n );\n }\n throw new Error(\n `Failed to load DR model at ${projectRoot}: ${err.message}. ` +\n `Please check that the model directory exists and is accessible.`\n );\n }\n\n const activeChangesetId = model.getActiveChangesetId();\n\n // Use base layer if changesetAware is explicitly false\n const apiLayer = options.changesetAware === false\n ? await model.getBaseLayer(\"api\")\n : await model.getLayer(\"api\");\n\n if (!apiLayer) {\n // No API layer found - all routes are in_graph_only\n return this.assembleReport(\n projectRoot,\n model,\n {\n matched: [],\n in_graph_only: routes.map((r) => ({\n id: r.id,\n http_method: r.http_method,\n http_path: r.http_path,\n source_file: r.source_file || \"\",\n source_symbol: r.source_symbol || \"\",\n })),\n in_model_only: [],\n ignored: [],\n },\n activeChangesetId,\n options,\n analyzerName,\n indexMeta\n );\n }\n\n // Step 2: Build dual-index\n const primaryIndex = new Map<string, IndexEntry>();\n const secondaryIndex = new Map<string, IndexEntry>();\n\n for (const element of apiLayer.elements.values()) {\n const firstLocation = element.source_reference?.locations?.[0];\n\n // Primary index: file:symbol\n if (firstLocation && firstLocation.file) {\n const symbol = firstLocation.symbol || \"\";\n const key = `${firstLocation.file}:${symbol}`;\n primaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n source_file: firstLocation.file,\n source_symbol: symbol,\n },\n key,\n });\n }\n\n // Secondary index: http_method:http_path\n const attrs = element.attributes as any;\n if (attrs?.http_method && attrs?.http_path) {\n const key = `${attrs.http_method}:${attrs.http_path}`;\n secondaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n http_method: attrs.http_method,\n http_path: attrs.http_path,\n },\n key,\n });\n }\n }\n\n // Warn if any graph routes have no source_symbol \u2014 they will be skipped by primary-index matching\n const emptySymbolCount = routes.filter((r) => !r.source_symbol).length;\n if (emptySymbolCount > 0) {\n console.warn(\n `\\nWarning: ${emptySymbolCount}/${routes.length} graph route(s) have empty source_symbol.` +\n ` Primary-index matching by (source_file, source_symbol) will skip them.\\n` +\n ` Likely cause: the code graph analyzer did not extract handler function names for this language.\\n` +\n ` Secondary-index matching (http_method + http_path) requires those attributes on model operations.\\n` +\n ` To fix: re-index after the analyzer is updated, or manually set http_method/http_path on operations.\\n`\n );\n }\n\n // Step 3: Load ignore rules\n const ignoreFilePath =\n options.ignoreFilePath || path.join(projectRoot, \".dr-verify-ignore.yaml\");\n const ignoreRules = await IgnoreFileLoader.load(ignoreFilePath);\n\n // Step 4: Compute buckets\n const matched: MatchedEntry[] = [];\n const inGraphOnly: GraphOnlyEntry[] = [];\n const ignored: IgnoredEntry[] = [];\n const matchedRouteIds = new Set<string>();\n\n for (const route of routes) {\n let matchedElement: { id: string } | undefined;\n\n // Try primary index match\n if (route.source_file && route.source_symbol) {\n const key = `${route.source_file}:${route.source_symbol}`;\n const primaryMatch = primaryIndex.get(key);\n if (primaryMatch) {\n matchedElement = primaryMatch.modelEntry;\n }\n }\n\n // Fallback to secondary index match\n if (!matchedElement && route.http_method && route.http_path) {\n const key = `${route.http_method}:${route.http_path}`;\n const secondaryMatch = secondaryIndex.get(key);\n if (secondaryMatch) {\n matchedElement = secondaryMatch.modelEntry;\n }\n }\n\n // Check if ignored\n const ignoreReason = IgnoreFileLoader.matches(\n {\n handler: route.handler,\n path: route.http_path,\n element_id: route.id,\n },\n \"route\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: route.id,\n entry_type: \"route\",\n reason: ignoreReason,\n });\n // Update matchedRouteIds even when ignored to prevent false drift\n if (matchedElement) {\n matchedRouteIds.add(matchedElement.id);\n }\n } else if (matchedElement) {\n matched.push({\n id: matchedElement.id,\n type: \"operation\",\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n matchedRouteIds.add(matchedElement.id);\n } else {\n inGraphOnly.push({\n id: route.id,\n http_method: route.http_method,\n http_path: route.http_path,\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n }\n }\n\n // Compute in_model_only: model elements not matched in graph\n const inModelOnly: ModelOnlyEntry[] = [];\n\n // Batch file-existence checks\n const fileChecks: Promise<boolean>[] = [];\n const elementsToCheck: Array<{ id: string; path: string; file: string; symbol: string }> = [];\n let elementsWithoutSourceRef = 0;\n let elementsWithNonIndexableSource = 0;\n\n for (const element of apiLayer.elements.values()) {\n if (matchedRouteIds.has(element.id)) {\n continue; // Already matched\n }\n\n const firstLocation = element.source_reference?.locations?.[0];\n\n if (!firstLocation || !firstLocation.file) {\n // Skip elements with no source_reference file - they cannot drift since they have no code linkage.\n // This includes manually-created elements that are documentation-only.\n elementsWithoutSourceRef++;\n continue;\n }\n\n // CBM only indexes source-code files \u2014 non-code source files (e.g. openapi.yaml)\n // will never appear in the graph, so including them in drift detection produces false positives.\n if (!SOURCE_CODE_EXT_RE.test(firstLocation.file)) {\n elementsWithNonIndexableSource++;\n continue;\n }\n\n elementsToCheck.push({\n id: element.id,\n path: element.path,\n file: firstLocation.file,\n symbol: firstLocation.symbol || \"\",\n });\n\n const filePath = path.join(projectRoot, firstLocation.file);\n fileChecks.push(\n access(filePath)\n .then(() => true)\n .catch((err: NodeJS.ErrnoException) => {\n // Only treat ENOENT (file not found) as \"file doesn't exist\"\n // Other errors (EACCES, EMFILE, etc.) are treated as \"file exists\" to avoid false negatives\n if (err.code === \"ENOENT\") {\n return false;\n }\n // For permission errors and other issues, assume file exists to be conservative\n return true;\n })\n );\n }\n\n if (elementsWithoutSourceRef > 0) {\n console.warn(\n `\\nWarning: ${elementsWithoutSourceRef} API model element(s) excluded from drift detection` +\n ` \u2014 they have no source_reference with a file location.\\n` +\n ` Drift detection requires a linked source file to compare against the code graph.\\n` +\n ` Fix: dr update <element-id> --source-file <relative-path> --source-symbol <symbol>\\n` +\n ` Elements ingested via /dr-map or dr analyzer endpoints are automatically attributed.\\n`\n );\n }\n\n if (elementsWithNonIndexableSource > 0) {\n console.warn(\n `\\nWarning: ${elementsWithNonIndexableSource} API model element(s) excluded from drift detection` +\n ` \u2014 their source files are not indexable source-code files.\\n` +\n ` Point source_reference to a source-code file (.ts, .js, .py, etc.) for drift detection,\\n` +\n ` or use dr analyzer endpoints to discover routes automatically.\\n`\n );\n }\n\n // Wait for all file checks. All promises have .catch() handlers that return booleans,\n // so they will never reject - Promise.all() is safe and simpler than allSettled.\n const fileExistsResults = await Promise.all(fileChecks);\n\n // Add to in_model_only only if file exists, then check against ignore rules\n for (let i = 0; i < elementsToCheck.length; i++) {\n const fileExists = fileExistsResults[i];\n\n if (fileExists) {\n const elem = elementsToCheck[i];\n\n // Check if element matches any ignore rule\n // Use element.path (semantic path) instead of elem.id (UUID)\n const ignoreReason = IgnoreFileLoader.matches(\n {\n element_id: elem.path,\n },\n \"element\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: elem.id,\n entry_type: \"element\",\n reason: ignoreReason,\n });\n } else {\n inModelOnly.push({\n id: elem.id,\n type: \"operation\",\n source_file: elem.file,\n source_symbol: elem.symbol,\n });\n }\n }\n }\n\n // Step 5: Assemble report\n const buckets: VerifyBuckets = {\n matched,\n in_graph_only: inGraphOnly,\n in_model_only: inModelOnly,\n ignored,\n };\n\n return this.assembleReport(\n projectRoot,\n model,\n buckets,\n activeChangesetId,\n options,\n analyzerName,\n indexMeta,\n elementsWithoutSourceRef + elementsWithNonIndexableSource\n );\n }\n\n /**\n * Assemble the final verification report\n *\n * @private\n */\n private assembleReport(\n projectRoot: string,\n model: Model,\n buckets: VerifyBuckets,\n activeChangesetId: string | null,\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta,\n elementsWithoutSourceRef: number = 0\n ): VerifyReport {\n const matchedElements = buckets.matched.length;\n const inModelOnly = buckets.in_model_only.length;\n const ignoredElements = buckets.ignored.filter((e) => e.entry_type === \"element\").length;\n const summary: VerifySummary = {\n matched_count: matchedElements,\n gap_count: buckets.in_graph_only.length,\n drift_count: inModelOnly,\n ignored_count: buckets.ignored.length,\n total_graph_entries: matchedElements + buckets.in_graph_only.length + buckets.ignored.filter((e) => e.entry_type === \"route\").length,\n // Count all API layer elements: those that participated in matching plus those\n // excluded because they lack source_reference (documentation-only elements).\n total_model_entries: matchedElements + inModelOnly + ignoredElements + elementsWithoutSourceRef,\n elements_without_source_ref: elementsWithoutSourceRef,\n };\n\n // Determine changeset context\n const changesetAware = options.changesetAware ?? true;\n const hasActiveChangeset = changesetAware && activeChangesetId !== null;\n\n // Use analyzer index timestamp if provided, otherwise fall back to model manifest modified time\n const analyzerIndexedAt = indexMeta?.timestamp || model.manifest.modified;\n\n // Construct changeset context as discriminated union\n const changeset_context: VerifyChangesetContext = hasActiveChangeset\n ? {\n active_changeset: activeChangesetId,\n verified_against: \"changeset_view\",\n }\n : {\n active_changeset: null,\n verified_against: \"base_model\",\n };\n\n const hints: string[] = [];\n if (buckets.in_graph_only.length > 0) {\n hints.push(\n \"To close gaps with source attribution, use /dr-map or `dr analyzer endpoints` ingestion \u2014 \"\n + \"manual `dr add` operations cannot be matched by the verifier.\"\n );\n }\n\n return {\n generated_at: new Date().toISOString(),\n project_root: projectRoot,\n analyzer: analyzerName,\n analyzer_indexed_at: analyzerIndexedAt,\n changeset_context,\n layers_verified: [\"api\"],\n buckets,\n summary,\n ...(hints.length > 0 ? { hints } : {}),\n };\n }\n}\n"],
|
|
5
|
-
"mappings": "AAOA,YAAY,UAAU;AACtB,SAAS,cAAc;AACvB,SAAS,aAAa;
|
|
4
|
+
"sourcesContent": ["/**\n * Verify Engine - computes verification reports for API endpoints\n *\n * Implements the core algorithm for comparing graph-discovered routes against\n * model-defined operations, supporting dual-index matching and changeset awareness.\n */\n\nimport * as path from \"path\";\nimport { access } from \"fs/promises\";\nimport { Model } from \"../core/model.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport type {\n VerifyReport,\n VerifyOptions,\n MatchedEntry,\n GraphOnlyEntry,\n ModelOnlyEntry,\n IgnoredEntry,\n VerifyBuckets,\n VerifySummary,\n IndexMeta,\n VerifyChangesetContext,\n} from \"./types.js\";\nimport { IgnoreFileLoader } from \"./verify-ignore.js\";\nimport { SOURCE_CODE_EXT_RE } from \"./cbm-analyzer.js\";\n\n/**\n * A discovered route from the graph\n */\nexport interface DiscoveredRoute {\n id: string;\n http_method?: string;\n http_path?: string;\n handler?: string;\n source_file?: string;\n source_symbol?: string;\n}\n\n/**\n * Model element reference extracted from the API layer\n * Contains the minimal set of fields needed for dual-index matching\n */\ninterface ModelElementRef {\n /** Element ID from the model */\n id: string;\n /** Source file path for primary index matching */\n source_file?: string;\n /** Source symbol for primary index matching */\n source_symbol?: string;\n /** HTTP method for secondary index matching */\n http_method?: string;\n /** HTTP path for secondary index matching */\n http_path?: string;\n}\n\n/**\n * Primary or secondary index entry (holds model element data, not discovered route data)\n */\ninterface IndexEntry {\n modelEntry: ModelElementRef;\n key: string;\n}\n\n/**\n * VerifyEngine - computes verification reports for API endpoints\n */\nexport class VerifyEngine {\n /**\n * Compute a comprehensive verification report\n *\n * Implements the five-step algorithm:\n * 1. Resolve model view via Model.load() + getActiveChangesetId() + getLayer(\"api\")\n * 2. Build primary index on source_reference.locations[0].file + \":\" + symbol\n * and optional secondary index on http_method + \":\" + http_path\n * 3. Load ignore rules\n * 4. Compute four buckets (matched, in_graph_only, in_model_only, ignored)\n * 5. Assemble VerifyReport\n *\n * @param projectRoot Absolute path to the project root\n * @param routes Array of discovered routes from the graph\n * @param options Verification options\n * @returns Comprehensive verification report\n */\n async computeReport(\n projectRoot: string,\n routes: DiscoveredRoute[],\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta\n ): Promise<VerifyReport> {\n // Step 1: Resolve model view\n let model: Model;\n try {\n model = await Model.load(projectRoot);\n } catch (error) {\n const message = getErrorMessage(error);\n // Check for model-not-found errors (matches all 4 error messages from Model.resolveModelPaths)\n if (\n message.includes(\"No DR project\") ||\n message.includes(\"Model not found\") ||\n message.includes(\"no model found\") ||\n message.includes(\"Could not find documentation_robotics\")\n ) {\n throw new Error(\n `Failed to load DR model at ${projectRoot}: Model not found. Please run 'dr init' to initialize the documentation robotics model.`\n );\n }\n // Re-throw original error unchanged to preserve stack trace and error type (e.g., YAML parse errors)\n throw error;\n }\n\n const activeChangesetId = model.getActiveChangesetId();\n\n // Use base layer if changesetAware is explicitly false\n const apiLayer = options.changesetAware === false\n ? await model.getBaseLayer(\"api\")\n : await model.getLayer(\"api\");\n\n if (!apiLayer) {\n // No API layer found - all routes are in_graph_only\n return this.assembleReport(\n projectRoot,\n model,\n {\n matched: [],\n in_graph_only: routes.map((r) => ({\n id: r.id,\n http_method: r.http_method,\n http_path: r.http_path,\n source_file: r.source_file || \"\",\n source_symbol: r.source_symbol || \"\",\n })),\n in_model_only: [],\n ignored: [],\n },\n activeChangesetId,\n options,\n analyzerName,\n indexMeta\n );\n }\n\n // Step 2: Build dual-index\n const primaryIndex = new Map<string, IndexEntry>();\n const secondaryIndex = new Map<string, IndexEntry>();\n\n for (const element of apiLayer.elements.values()) {\n const firstLocation = element.source_reference?.locations?.[0];\n\n // Primary index: file:symbol\n if (firstLocation && firstLocation.file) {\n const symbol = firstLocation.symbol || \"\";\n const key = `${firstLocation.file}:${symbol}`;\n primaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n source_file: firstLocation.file,\n source_symbol: symbol,\n },\n key,\n });\n }\n\n // Secondary index: http_method:http_path\n const attrs = element.attributes as any;\n if (attrs?.http_method && attrs?.http_path) {\n const key = `${attrs.http_method}:${attrs.http_path}`;\n secondaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n http_method: attrs.http_method,\n http_path: attrs.http_path,\n },\n key,\n });\n }\n }\n\n // Warn if any graph routes have no source_symbol \u2014 they will be skipped by primary-index matching\n const emptySymbolCount = routes.filter((r) => !r.source_symbol).length;\n if (emptySymbolCount > 0) {\n console.warn(\n `\\nWarning: ${emptySymbolCount}/${routes.length} graph route(s) have empty source_symbol.` +\n ` Primary-index matching by (source_file, source_symbol) will skip them.\\n` +\n ` Likely cause: the code graph analyzer did not extract handler function names for this language.\\n` +\n ` Secondary-index matching (http_method + http_path) requires those attributes on model operations.\\n` +\n ` To fix: re-index after the analyzer is updated, or manually set http_method/http_path on operations.\\n`\n );\n }\n\n // Step 3: Load ignore rules\n const ignoreFilePath =\n options.ignoreFilePath || path.join(projectRoot, \".dr-verify-ignore.yaml\");\n const ignoreRules = await IgnoreFileLoader.load(ignoreFilePath);\n\n // Step 4: Compute buckets\n const matched: MatchedEntry[] = [];\n const inGraphOnly: GraphOnlyEntry[] = [];\n const ignored: IgnoredEntry[] = [];\n const matchedRouteIds = new Set<string>();\n\n for (const route of routes) {\n let matchedElement: { id: string } | undefined;\n\n // Try primary index match\n if (route.source_file && route.source_symbol) {\n const key = `${route.source_file}:${route.source_symbol}`;\n const primaryMatch = primaryIndex.get(key);\n if (primaryMatch) {\n matchedElement = primaryMatch.modelEntry;\n }\n }\n\n // Fallback to secondary index match\n if (!matchedElement && route.http_method && route.http_path) {\n const key = `${route.http_method}:${route.http_path}`;\n const secondaryMatch = secondaryIndex.get(key);\n if (secondaryMatch) {\n matchedElement = secondaryMatch.modelEntry;\n }\n }\n\n // Check if ignored\n const ignoreReason = IgnoreFileLoader.matches(\n {\n handler: route.handler,\n path: route.http_path,\n element_id: route.id,\n },\n \"route\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: route.id,\n entry_type: \"route\",\n reason: ignoreReason,\n });\n // Update matchedRouteIds even when ignored to prevent false drift\n if (matchedElement) {\n matchedRouteIds.add(matchedElement.id);\n }\n } else if (matchedElement) {\n matched.push({\n id: matchedElement.id,\n type: \"operation\",\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n matchedRouteIds.add(matchedElement.id);\n } else {\n inGraphOnly.push({\n id: route.id,\n http_method: route.http_method,\n http_path: route.http_path,\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n }\n }\n\n // Compute in_model_only: model elements not matched in graph\n const inModelOnly: ModelOnlyEntry[] = [];\n\n // Batch file-existence checks\n const fileChecks: Promise<boolean>[] = [];\n const elementsToCheck: Array<{ id: string; path: string; file: string; symbol: string }> = [];\n let elementsWithoutSourceRef = 0;\n let elementsWithNonIndexableSource = 0;\n\n for (const element of apiLayer.elements.values()) {\n if (matchedRouteIds.has(element.id)) {\n continue; // Already matched\n }\n\n const firstLocation = element.source_reference?.locations?.[0];\n\n if (!firstLocation || !firstLocation.file) {\n // Skip elements with no source_reference file - they cannot drift since they have no code linkage.\n // This includes manually-created elements that are documentation-only.\n elementsWithoutSourceRef++;\n continue;\n }\n\n // CBM only indexes source-code files \u2014 non-code source files (e.g. openapi.yaml)\n // will never appear in the graph, so including them in drift detection produces false positives.\n if (!SOURCE_CODE_EXT_RE.test(firstLocation.file)) {\n elementsWithNonIndexableSource++;\n continue;\n }\n\n elementsToCheck.push({\n id: element.id,\n path: element.path,\n file: firstLocation.file,\n symbol: firstLocation.symbol || \"\",\n });\n\n const filePath = path.join(model.codebaseRoot, firstLocation.file);\n fileChecks.push(\n access(filePath)\n .then(() => true)\n .catch((err: NodeJS.ErrnoException) => {\n // Only treat ENOENT (file not found) as \"file doesn't exist\"\n // Other errors (EACCES, EMFILE, etc.) are treated as \"file exists\" to avoid false negatives\n if (err.code === \"ENOENT\") {\n return false;\n }\n // For permission errors and other issues, assume file exists to be conservative\n return true;\n })\n );\n }\n\n if (elementsWithoutSourceRef > 0) {\n console.warn(\n `\\nWarning: ${elementsWithoutSourceRef} API model element(s) excluded from drift detection` +\n ` \u2014 they have no source_reference with a file location.\\n` +\n ` Drift detection requires a linked source file to compare against the code graph.\\n` +\n ` Fix: dr update <element-id> --source-file <relative-path> --source-symbol <symbol>\\n` +\n ` Elements ingested via /dr-map or dr analyzer endpoints are automatically attributed.\\n`\n );\n }\n\n if (elementsWithNonIndexableSource > 0) {\n console.warn(\n `\\nWarning: ${elementsWithNonIndexableSource} API model element(s) excluded from drift detection` +\n ` \u2014 their source files are not indexable source-code files.\\n` +\n ` Point source_reference to a source-code file (.ts, .js, .py, etc.) for drift detection,\\n` +\n ` or use dr analyzer endpoints to discover routes automatically.\\n`\n );\n }\n\n // Wait for all file checks. All promises have .catch() handlers that return booleans,\n // so they will never reject - Promise.all() is safe and simpler than allSettled.\n const fileExistsResults = await Promise.all(fileChecks);\n\n // Add to in_model_only only if file exists, then check against ignore rules\n for (let i = 0; i < elementsToCheck.length; i++) {\n const fileExists = fileExistsResults[i];\n const elem = elementsToCheck[i];\n\n // Skip only if file definitively doesn't exist (false).\n // Process if file exists (true) or if check is unavailable (undefined).\n if (fileExists === false) {\n continue;\n }\n\n // Check if element matches any ignore rule\n // Use element.path (semantic path) instead of elem.id (UUID)\n const ignoreReason = IgnoreFileLoader.matches(\n {\n element_id: elem.path,\n },\n \"element\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: elem.id,\n entry_type: \"element\",\n reason: ignoreReason,\n });\n } else {\n inModelOnly.push({\n id: elem.id,\n type: \"operation\",\n source_file: elem.file,\n source_symbol: elem.symbol,\n });\n }\n }\n\n // Step 5: Assemble report\n const buckets: VerifyBuckets = {\n matched,\n in_graph_only: inGraphOnly,\n in_model_only: inModelOnly,\n ignored,\n };\n\n return this.assembleReport(\n projectRoot,\n model,\n buckets,\n activeChangesetId,\n options,\n analyzerName,\n indexMeta,\n elementsWithoutSourceRef + elementsWithNonIndexableSource\n );\n }\n\n /**\n * Assemble the final verification report\n *\n * @private\n */\n private assembleReport(\n projectRoot: string,\n model: Model,\n buckets: VerifyBuckets,\n activeChangesetId: string | null,\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta,\n elementsWithoutSourceRef: number = 0\n ): VerifyReport {\n const matchedElements = buckets.matched.length;\n const inModelOnly = buckets.in_model_only.length;\n const ignoredElements = buckets.ignored.filter((e) => e.entry_type === \"element\").length;\n const summary: VerifySummary = {\n matched_count: matchedElements,\n gap_count: buckets.in_graph_only.length,\n drift_count: inModelOnly,\n ignored_count: buckets.ignored.length,\n total_graph_entries: matchedElements + buckets.in_graph_only.length + buckets.ignored.filter((e) => e.entry_type === \"route\").length,\n // Count all API layer elements: those that participated in matching plus those\n // excluded because they lack source_reference (documentation-only elements).\n total_model_entries: matchedElements + inModelOnly + ignoredElements + elementsWithoutSourceRef,\n elements_without_source_ref: elementsWithoutSourceRef,\n };\n\n // Determine changeset context\n const changesetAware = options.changesetAware ?? true;\n const hasActiveChangeset = changesetAware && activeChangesetId !== null;\n\n // Use analyzer index timestamp if provided, otherwise fall back to model manifest modified time\n const analyzerIndexedAt = indexMeta?.timestamp || model.manifest.modified;\n\n // Construct changeset context as discriminated union\n const changeset_context: VerifyChangesetContext = hasActiveChangeset\n ? {\n active_changeset: activeChangesetId,\n verified_against: \"changeset_view\",\n }\n : {\n active_changeset: null,\n verified_against: \"base_model\",\n };\n\n const hints: string[] = [];\n if (buckets.in_graph_only.length > 0) {\n hints.push(\n \"To close gaps with source attribution, use /dr-map or `dr analyzer endpoints` ingestion \u2014 \"\n + \"manual `dr add` operations cannot be matched by the verifier.\"\n );\n }\n\n return {\n generated_at: new Date().toISOString(),\n project_root: projectRoot,\n analyzer: analyzerName,\n analyzer_indexed_at: analyzerIndexedAt,\n changeset_context,\n layers_verified: [\"api\"],\n buckets,\n summary,\n ...(hints.length > 0 ? { hints } : {}),\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, CommanderError } from "commander";
|
|
3
3
|
import { setGlobalOptions } from "./utils/globals.js";
|
|
4
|
+
import { installAnsiSuppressor } from "./utils/ansi-suppressor.js";
|
|
4
5
|
import { initCommand } from "./commands/init.js";
|
|
5
6
|
import { addCommand } from "./commands/add.js";
|
|
6
7
|
import { updateCommand } from "./commands/update.js";
|
|
@@ -22,6 +23,7 @@ import { chatCommand } from "./commands/chat.js";
|
|
|
22
23
|
import { upgradeCommand } from "./commands/upgrade.js";
|
|
23
24
|
import { conformanceCommand } from "./commands/conformance.js";
|
|
24
25
|
import { changesetCommands } from "./commands/changeset.js";
|
|
26
|
+
import { farmCommands } from "./commands/farm.js";
|
|
25
27
|
import { claudeCommands } from "./commands/claude.js";
|
|
26
28
|
import { copilotCommands } from "./commands/copilot.js";
|
|
27
29
|
import { analyzerCommands } from "./commands/analyzer.js";
|
|
@@ -35,11 +37,13 @@ import { auditSnapshotsCommand } from "./commands/audit-snapshots.js";
|
|
|
35
37
|
import { repairAttributeCollisionCommand } from "./commands/repair.js";
|
|
36
38
|
import { initTelemetry, startActiveSpan, shutdownTelemetry } from "./telemetry/index.js";
|
|
37
39
|
import { installConsoleInterceptor } from "./telemetry/console-interceptor.js";
|
|
38
|
-
import { getErrorMessage } from "./utils/errors.js";
|
|
40
|
+
import { getErrorMessage, CLIError } from "./utils/errors.js";
|
|
41
|
+
import { getCliVersion } from "./utils/spec-version.js";
|
|
39
42
|
const isTelemetryEnabled = !1, CONSOLE_LOGGING_COMMANDS = /* @__PURE__ */ new Set(["chat", "validate", "import", "export"]);
|
|
43
|
+
process.stdout.isTTY || (process.env.FORCE_COLOR = "0", process.env.NO_COLOR = "1"), installAnsiSuppressor();
|
|
40
44
|
async function extractExitCode(error) {
|
|
41
|
-
const { CLIError } = await import("./utils/errors.js");
|
|
42
|
-
if (error instanceof
|
|
45
|
+
const { CLIError: CLIError2 } = await import("./utils/errors.js");
|
|
46
|
+
if (error instanceof CLIError2 || error instanceof CommanderError)
|
|
43
47
|
return error.exitCode;
|
|
44
48
|
if (error && typeof error == "object" && "exitCode" in error) {
|
|
45
49
|
const errorObj = error;
|
|
@@ -48,26 +52,28 @@ async function extractExitCode(error) {
|
|
|
48
52
|
}
|
|
49
53
|
return 1;
|
|
50
54
|
}
|
|
51
|
-
const cliVersion =
|
|
52
|
-
(process.argv.includes("--version") || process.argv.includes("-V")) && (process.argv = ["node", "dr", "version"]), program.name("dr").description("Documentation Robotics CLI - Architecture Model Management").option("-v, --verbose", "Enable verbose output").option("--debug", "Enable debug mode").exitOverride().showSuggestionAfterError().hook("preAction", async (thisCommand) => {
|
|
55
|
+
const cliVersion = getCliVersion(), program = new Command();
|
|
56
|
+
(process.argv.includes("--version") || process.argv.includes("-V")) && (process.argv = ["node", "dr", "version"]), program.name("dr").description("Documentation Robotics CLI - Architecture Model Management").option("-v, --verbose", "Enable verbose output").option("--debug", "Enable debug mode").option("--json", "Output as JSON (supported on mutating and query commands)").option("--codebase-path <path>", "Override codebase root path (default: resolved from manifest or project root)").exitOverride().showSuggestionAfterError().hook("preAction", async (thisCommand) => {
|
|
53
57
|
const options = thisCommand.opts();
|
|
54
58
|
setGlobalOptions({
|
|
55
59
|
verbose: options.verbose,
|
|
56
|
-
debug: options.debug
|
|
60
|
+
debug: options.debug,
|
|
61
|
+
json: options.json,
|
|
62
|
+
codebasePath: options.codebasePath
|
|
57
63
|
});
|
|
58
64
|
}), program.command("init [name]").description("Initialize a new architecture model").option("--name <name>", "Model name (overrides positional argument)").option("--author <author>", "Model author").option("--description <desc>", "Model description").addHelpText("after", `
|
|
59
65
|
Examples:
|
|
60
66
|
$ dr init
|
|
61
67
|
$ dr init claudetoreum
|
|
62
68
|
$ dr init "Enterprise Architecture" --author "Team A"
|
|
63
|
-
$ dr init --name "My Project" --description "13-layer federated model"`).action((name, options) => initCommand({ ...options, name: options.name || name })), program.command("add <layer> <type> <name>").description("Add an element to a layer").option("--name <name>", "Element display name (defaults to the name argument)").option("--description <desc>", "Element description").option("--attributes <json>", "Element type-specific attributes as JSON object").option("--source-file <path>", "Source file path (relative from repository root)").option("--source-symbol <name>", "Symbol name (class, function, variable) in source file").option("--source-provenance <type>", "Provenance type: extracted, manual, inferred, generated").option("--source-repo-remote <url>", "Git remote URL for repository context").option("--source-repo-commit <sha>", "Git commit SHA (40 hex characters) for repository context").addHelpText("after", `
|
|
69
|
+
$ dr init --name "My Project" --description "13-layer federated model"`).action((name, options) => initCommand({ ...options, name: options.name || name })), program.command("add <layer> <type> <name>").description("Add an element to a layer").option("--model <path>", "Path to model root (contains model/manifest.yaml)").option("--name <name>", "Element display name (defaults to the name argument)").option("--description <desc>", "Element description").option("--attributes <json>", "Element type-specific attributes as JSON object").option("--source-file <path>", "Source file path (relative from repository root)").option("--source-symbol <name>", "Symbol name (class, function, variable) in source file").option("--source-provenance <type>", "Provenance type: extracted, manual, inferred, generated").option("--source-repo-remote <url>", "Git remote URL for repository context").option("--source-repo-commit <sha>", "Git commit SHA (40 hex characters) for repository context").option("--verbose", "Show detailed output (can also use global -v flag)").addHelpText("after", `
|
|
64
70
|
Examples:
|
|
65
71
|
$ dr add business service "Customer Management"
|
|
66
72
|
$ dr add api operation "Create Customer" --attributes '{"operationId":"createCustomer","summary":"Create a customer","tags":"customers"}'
|
|
67
73
|
$ dr add application component "Customer API" --description "REST API for customer operations"
|
|
68
74
|
$ dr add security policy "Auth Validator" --source-file "src/auth/validator.ts" --source-symbol "validateToken" --source-provenance "extracted"
|
|
69
75
|
|
|
70
|
-
Note: Element IDs are generated automatically in format {layer}.{type}.{kebab-name}`).action(addCommand), program.command("update <id>").description("Update an element").option("--name <name>", "New element name").option("--description <desc>", "New description").option("--type <new-type>", "Change element type (must be valid for the element's layer; incompatible attributes must be cleaned up manually)").option("--attributes <json>", "Updated type-specific attributes (JSON)").option("--source-file <path>", "Source file path (relative from repository root)").option("--source-symbol <name>", "Symbol name (class, function, variable) in source file").option("--source-provenance <type>", "Provenance type: extracted, manual, inferred, generated").option("--source-repo-remote <url>", "Git remote URL for repository context").option("--source-repo-commit <sha>", "Git commit SHA (40 hex characters) for repository context").option("--clear-source-reference", "Remove source reference from element").addHelpText("after", `
|
|
76
|
+
Note: Element IDs are generated automatically in format {layer}.{type}.{kebab-name}`).action((layer, type, name, options) => addCommand(layer, type, name, { ...options, verbose: options.verbose ?? program.opts().verbose })), program.command("update <id>").description("Update an element").option("--model <path>", "Path to model root (contains model/manifest.yaml)").option("--name <name>", "New element name").option("--description <desc>", "New description").option("--type <new-type>", "Change element type (must be valid for the element's layer; incompatible attributes must be cleaned up manually)").option("--attributes <json>", "Updated type-specific attributes (JSON)").option("--source-file <path>", "Source file path (relative from repository root)").option("--source-symbol <name>", "Symbol name (class, function, variable) in source file").option("--source-provenance <type>", "Provenance type: extracted, manual, inferred, generated").option("--source-repo-remote <url>", "Git remote URL for repository context").option("--source-repo-commit <sha>", "Git commit SHA (40 hex characters) for repository context").option("--clear-source-reference", "Remove source reference from element").option("--verbose", "Show detailed output (can also use global -v flag)").addHelpText("after", `
|
|
71
77
|
Note: Changes are written immediately to model files.
|
|
72
78
|
To track changes for review, activate a changeset first:
|
|
73
79
|
$ dr changeset activate my-changeset
|
|
@@ -76,13 +82,13 @@ Examples:
|
|
|
76
82
|
$ dr update api-endpoint-create-customer --name "Create Customer (v2)"
|
|
77
83
|
$ dr update business-service-order --description "Updated description"
|
|
78
84
|
$ dr update application-applicationfunction-process-orders --type applicationservice
|
|
79
|
-
$ dr update security-policy-auth --source-file "src/auth/policy.ts" --source-provenance "extracted"`).action(updateCommand), program.command("delete <id>").description("Delete an element").option("--force", "Skip dependency checks (confirmation is also skipped in non-TTY environments)").option("--cascade", "Remove dependent elements automatically").option("--dry-run", "Show what would be removed without actually removing").addHelpText("after", `
|
|
85
|
+
$ dr update security-policy-auth --source-file "src/auth/policy.ts" --source-provenance "extracted"`).action((id, options) => updateCommand(id, { ...options, verbose: options.verbose ?? program.opts().verbose })), program.command("delete <id>").description("Delete an element").option("--model <path>", "Path to model root (contains model/manifest.yaml)").option("--force", "Skip dependency checks (confirmation is also skipped in non-TTY environments)").option("--cascade", "Remove dependent elements automatically").option("--dry-run", "Show what would be removed without actually removing").option("--verbose", "Show detailed output (can also use global -v flag)").addHelpText("after", `
|
|
80
86
|
Examples:
|
|
81
87
|
$ dr delete api-endpoint-old-endpoint
|
|
82
88
|
$ dr delete api-endpoint-old-endpoint --force
|
|
83
89
|
$ dr delete api-endpoint-old-endpoint --cascade
|
|
84
90
|
$ dr delete api-endpoint-old-endpoint --dry-run
|
|
85
|
-
$ dr delete api-endpoint-old-endpoint --cascade --dry-run`).action(deleteCommand), program.command("show <id>").description("Display element details").option("--model <path>", "Path to model root (contains model/manifest.yaml)").addHelpText("after", `
|
|
91
|
+
$ dr delete api-endpoint-old-endpoint --cascade --dry-run`).action((id, options) => deleteCommand(id, { ...options, verbose: options.verbose ?? program.opts().verbose })), program.command("show <id>").description("Display element details").option("--model <path>", "Path to model root (contains model/manifest.yaml)").addHelpText("after", `
|
|
86
92
|
Examples:
|
|
87
93
|
$ dr show api-endpoint-create-customer
|
|
88
94
|
$ dr show business-service-order-mgmt`).action((id, options) => showCommand(id, options)), program.command("list [layer]").description("List elements in a layer (or all layers if omitted)").option("--type <type>", "Filter by element type").option("--json", "Output as JSON").option("--model <path>", "Path to model root (contains model/manifest.yaml)").addHelpText("after", `
|
|
@@ -91,12 +97,12 @@ Examples:
|
|
|
91
97
|
$ dr list api
|
|
92
98
|
$ dr list business --type businessservice
|
|
93
99
|
$ dr list api --json
|
|
94
|
-
$ dr list --json`).action(listCommand), program.command("search <query>").description("Search for elements by name or ID").option("--layer <layer>", "Limit search to specific layer").option("--type <type>", "Filter by element type").option("--source-file <path>", "Find elements referencing a source file (takes precedence over pattern matching)").option("--json", "Output as JSON").addHelpText("after", `
|
|
100
|
+
$ dr list --json`).action((layer, options) => listCommand(layer, { ...options, json: options.json ?? program.opts().json })), program.command("search <query>").description("Search for elements by name or ID").option("--layer <layer>", "Limit search to specific layer").option("--type <type>", "Filter by element type").option("--source-file <path>", "Find elements referencing a source file (takes precedence over pattern matching)").option("--json", "Output as JSON").addHelpText("after", `
|
|
95
101
|
Examples:
|
|
96
102
|
$ dr search customer
|
|
97
103
|
$ dr search "order processing" --layer business
|
|
98
104
|
$ dr search create-* --type endpoint
|
|
99
|
-
$ dr search "" --source-file src/api/customer.ts`).action(searchCommand), program.command("validate").description("Validate the architecture model").option("--layers <layers...>", "Specific layers to validate").option("--strict", "Treat warnings as errors").option("--verbose", "Show detailed validation output with relationship breakdown").option("--quiet", "Show minimal output").option("--output <path>", "Export validation report to file (JSON or Markdown)").option("--model <path>", "Path to model root (contains model/manifest.yaml)").option("--orphans", "Show only orphaned elements grouped by layer with relationship suggestions").option("--all", "Run all validations (default behavior)").option("--markdown", "Validate markdown structure").option("--schemas", "Validate JSON schemas").option("--schema", "Validate JSON schemas (alias for --schemas)").option("--relationships", "Validate relationships").option("--structure", "Validate documentation structure").option("--naming", "Validate naming conventions").option("--references", "Validate cross-layer references").addHelpText("after", `
|
|
105
|
+
$ dr search "" --source-file src/api/customer.ts`).action((query, options) => searchCommand(query, { ...options, json: options.json ?? program.opts().json })), program.command("validate").description("Validate the architecture model").option("--layers <layers...>", "Specific layers to validate").option("--strict", "Treat warnings as errors").option("--verbose", "Show detailed validation output with relationship breakdown").option("--quiet", "Show minimal output").option("--output <path>", "Export validation report to file (JSON or Markdown)").option("--model <path>", "Path to model root (contains model/manifest.yaml)").option("--orphans", "Show only orphaned elements grouped by layer with relationship suggestions").option("--all", "Run all validations (default behavior)").option("--markdown", "Validate markdown structure").option("--schemas", "Validate JSON schemas").option("--schema", "Validate JSON schemas (alias for --schemas)").option("--relationships", "Validate relationships").option("--structure", "Validate documentation structure").option("--naming", "Validate naming conventions").option("--references", "Validate cross-layer references").option("--scope <scope>", "Validation scope: 'local' (default) or 'composed' for cross-model validation").option("--model-path <mapping...>", "Override filesystem path for external model (format: model-name=/path/to/project-root, can be repeated)").addHelpText("after", `
|
|
100
106
|
Examples:
|
|
101
107
|
$ dr validate
|
|
102
108
|
$ dr validate --all --strict
|
|
@@ -108,7 +114,11 @@ Examples:
|
|
|
108
114
|
$ dr validate --relationships
|
|
109
115
|
$ dr validate --layers business api
|
|
110
116
|
$ dr validate --orphans
|
|
111
|
-
$ dr validate --orphans --output orphans.json
|
|
117
|
+
$ dr validate --orphans --output orphans.json
|
|
118
|
+
$ dr validate --scope composed --model-path auth-service=/path/to/auth-service-repo
|
|
119
|
+
$ dr validate --scope composed --model-path auth-service=/path/to/auth-service-repo --model-path payment-service=/path/to/payment-repo
|
|
120
|
+
|
|
121
|
+
Note: When using --model-path, pass the project root (where documentation-robotics/model/ is located).`).action(validateCommand), program.command("export <format>").description("Export the architecture model to various formats").option("--output <path>", "Output file path (default: print to stdout)").option("--layers <layers...>", "Specific layers to export").option("--model <path>", "Path to model root directory or manifest.yaml file").option("--include-sources", "Include source file paths in PlantUML diagrams as notes").addHelpText("after", `
|
|
112
122
|
Supported formats:
|
|
113
123
|
archimate Export to ArchiMate XML (layers 1, 2, 5, 6)
|
|
114
124
|
openapi Export to OpenAPI 3.0 specification (layer 7)
|
|
@@ -233,9 +243,9 @@ Examples:
|
|
|
233
243
|
$ dr repair:attribute-collision --apply # Restore every recoverable value
|
|
234
244
|
$ dr repair:attribute-collision --format json --output report.json`).action((options) => repairAttributeCollisionCommand(options));
|
|
235
245
|
const relationshipGroup = program.command("relationship").description("Relationship operations");
|
|
236
|
-
relationshipCommands(relationshipGroup);
|
|
246
|
+
relationshipCommands(relationshipGroup, program);
|
|
237
247
|
const elementGroup = program.command("element").description("Element operations");
|
|
238
|
-
elementCommands(elementGroup), schemaCommands(program), catalogCommands(program), docsCommands(program), program.command("trace <elementId>").description("Trace dependencies for an element").option("--direction <dir>", "Trace direction: up (dependencies), down (dependents), both (default)").option("--depth <num>", "Maximum traversal depth").option("--metrics", "Show graph and element metrics").option("--model <path>", "Path to model root (contains model/manifest.yaml)").addHelpText("after", `
|
|
248
|
+
elementCommands(elementGroup, program), schemaCommands(program), catalogCommands(program), docsCommands(program), program.command("trace <elementId>").description("Trace dependencies for an element").option("--direction <dir>", "Trace direction: up (dependencies), down (dependents), both (default)").option("--depth <num>", "Maximum traversal depth").option("--metrics", "Show graph and element metrics").option("--model <path>", "Path to model root (contains model/manifest.yaml)").addHelpText("after", `
|
|
239
249
|
Examples:
|
|
240
250
|
$ dr trace api-endpoint-create-customer
|
|
241
251
|
$ dr trace business-service-order --direction down --metrics
|
|
@@ -256,9 +266,15 @@ Examples:
|
|
|
256
266
|
$ dr visualize --with-danger
|
|
257
267
|
$ dr visualize --viewer-path ./dist/embedded/dr-viewer-bundle
|
|
258
268
|
|
|
259
|
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
269
|
+
Default mode (read-safe permissions) enables:
|
|
270
|
+
- Running 'dr' CLI commands to query the architecture model
|
|
271
|
+
- Reading the codebase
|
|
272
|
+
- Reading the documentation-robotics/ model folder
|
|
273
|
+
- Reading the .dr/ configuration folder
|
|
274
|
+
|
|
275
|
+
Dangerous mode (--with-danger) enables unrestricted access:
|
|
276
|
+
- Claude Code: --dangerously-skip-permissions (full tool access)
|
|
277
|
+
- GitHub Copilot: --allow-all-tools (full tool access)
|
|
262
278
|
|
|
263
279
|
The --viewer-path option allows loading a local build of the web UI:
|
|
264
280
|
- Point to a directory containing index.html and supporting files
|
|
@@ -274,6 +290,58 @@ The --viewer-path option allows loading a local build of the web UI:
|
|
|
274
290
|
withDanger: options.withDanger,
|
|
275
291
|
viewerPath: options.viewerPath
|
|
276
292
|
});
|
|
293
|
+
}), program.command("mcp").description("Start MCP server for AI assistant integration").option("--regenerate-key", "Generate a new MCP API key, overwrite it at the configured storage path, and print it (does not start the server)").option("--transport <type>", "Transport type: stdio or http (default: stdio)", "stdio").option("--port <number>", "Port for HTTP transport (default: 3100, only meaningful with --transport http)", "3100").option("--host <address>", "Host address for HTTP transport (default: 127.0.0.1, only meaningful with --transport http)", "127.0.0.1").addHelpText("after", `
|
|
294
|
+
Examples:
|
|
295
|
+
|
|
296
|
+
Stdio transport (default):
|
|
297
|
+
$ dr mcp
|
|
298
|
+
|
|
299
|
+
HTTP transport:
|
|
300
|
+
$ dr mcp --transport http # HTTP on localhost:3100
|
|
301
|
+
$ dr mcp --transport http --port 8200 # HTTP on custom port
|
|
302
|
+
$ dr mcp --transport http --host 0.0.0.0 --port 3100 # Listen on all interfaces
|
|
303
|
+
|
|
304
|
+
Key management:
|
|
305
|
+
$ dr mcp --regenerate-key # Generate new API key
|
|
306
|
+
|
|
307
|
+
Initial Setup:
|
|
308
|
+
On first launch, generates an API key and asks where to store it (interactive
|
|
309
|
+
sessions only; non-interactive launches default to ~/.dr-mcp-key). The key is
|
|
310
|
+
printed to stderr on every launch and must be supplied via environment variable.
|
|
311
|
+
|
|
312
|
+
Stdio Configuration:
|
|
313
|
+
For stdio transport, supply the key via environment variable:
|
|
314
|
+
|
|
315
|
+
{
|
|
316
|
+
"command": "dr",
|
|
317
|
+
"args": ["mcp"],
|
|
318
|
+
"env": { "DR_MCP_API_KEY": "<your-api-key>" }
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
HTTP Configuration:
|
|
322
|
+
For HTTP transport, include the API key as a Bearer token in the Authorization header:
|
|
323
|
+
|
|
324
|
+
{
|
|
325
|
+
"url": "http://localhost:3100/mcp",
|
|
326
|
+
"headers": {
|
|
327
|
+
"Authorization": "Bearer <your-api-key>"
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
Use --regenerate-key to rotate the key (e.g. after a suspected leak) without
|
|
332
|
+
manually deleting the stored key file or config.`).action(async (options) => {
|
|
333
|
+
const { mcpCommand } = await import("./commands/mcp.js"), transport = options.transport.toLowerCase();
|
|
334
|
+
if (transport !== "stdio" && transport !== "http")
|
|
335
|
+
throw new CLIError(`Invalid transport: ${transport}. Allowed values: stdio, http`, 1);
|
|
336
|
+
const port = parseInt(options.port, 10);
|
|
337
|
+
if (!Number.isFinite(port) || port < 1 || port > 65535)
|
|
338
|
+
throw new CLIError(`Invalid port: ${options.port}. Must be a number between 1 and 65535`, 1);
|
|
339
|
+
await mcpCommand({
|
|
340
|
+
regenerateKey: options.regenerateKey,
|
|
341
|
+
transport,
|
|
342
|
+
port,
|
|
343
|
+
host: options.host
|
|
344
|
+
});
|
|
277
345
|
}), program.command("chat [client] [withDanger]").description("Interactive chat with AI about the architecture model").addHelpText("after", `
|
|
278
346
|
Arguments:
|
|
279
347
|
client Optional AI client to use: "claude-code" or "github-copilot"
|
|
@@ -287,9 +355,15 @@ Examples:
|
|
|
287
355
|
$ dr chat claude-code with-danger # Use Claude Code with dangerous mode
|
|
288
356
|
$ dr chat github-copilot with-danger # Use GitHub Copilot with dangerous mode
|
|
289
357
|
|
|
290
|
-
|
|
291
|
-
-
|
|
292
|
-
-
|
|
358
|
+
Default mode (read-safe permissions) enables:
|
|
359
|
+
- Running 'dr' CLI commands to query the architecture model
|
|
360
|
+
- Reading the codebase
|
|
361
|
+
- Reading the documentation-robotics/ model folder
|
|
362
|
+
- Reading the .dr/ configuration folder
|
|
363
|
+
|
|
364
|
+
Dangerous mode (--with-danger) enables unrestricted access:
|
|
365
|
+
- Claude Code: --dangerously-skip-permissions (full tool access)
|
|
366
|
+
- GitHub Copilot: --allow-all-tools (full tool access)
|
|
293
367
|
|
|
294
368
|
This launches an interactive chat interface where you can ask AI questions
|
|
295
369
|
about your architecture model. Supports Claude Code CLI and GitHub Copilot CLI.
|
|
@@ -322,10 +396,10 @@ Examples:
|
|
|
322
396
|
$ dr conformance --verbose`).action(async (options) => {
|
|
323
397
|
await conformanceCommand({
|
|
324
398
|
layers: options.layers,
|
|
325
|
-
json: options.json,
|
|
399
|
+
json: options.json ?? program.opts().json,
|
|
326
400
|
verbose: options.verbose ?? program.opts().verbose
|
|
327
401
|
});
|
|
328
|
-
}), changesetCommands(program), claudeCommands(program), copilotCommands(program), analyzerCommands(program), (async () => {
|
|
402
|
+
}), changesetCommands(program), farmCommands(program), claudeCommands(program), copilotCommands(program), analyzerCommands(program), (async () => {
|
|
329
403
|
let exitCode = 0;
|
|
330
404
|
try {
|
|
331
405
|
if (isTelemetryEnabled) {
|
|
@@ -346,8 +420,8 @@ Examples:
|
|
|
346
420
|
// SpanStatusCode.ERROR
|
|
347
421
|
message: getErrorMessage(error)
|
|
348
422
|
}));
|
|
349
|
-
const { CLIError } = await import("./utils/errors.js");
|
|
350
|
-
error instanceof CommanderError || (error instanceof
|
|
423
|
+
const { CLIError: CLIError2 } = await import("./utils/errors.js"), { isJson } = await import("./utils/globals.js");
|
|
424
|
+
error instanceof CommanderError || (error instanceof CLIError2 ? isJson() || console.error(error.format()) : error instanceof Error ? console.error(error.message) : console.error(String(error))), exitCode = await extractExitCode(error);
|
|
351
425
|
}
|
|
352
426
|
}, {
|
|
353
427
|
"cli.command": commandName,
|
|
@@ -359,8 +433,8 @@ Examples:
|
|
|
359
433
|
try {
|
|
360
434
|
await program.parseAsync(process.argv);
|
|
361
435
|
} catch (error) {
|
|
362
|
-
const { CLIError } = await import("./utils/errors.js");
|
|
363
|
-
error instanceof CommanderError || (error instanceof
|
|
436
|
+
const { CLIError: CLIError2 } = await import("./utils/errors.js"), { isJson } = await import("./utils/globals.js");
|
|
437
|
+
error instanceof CommanderError || (error instanceof CLIError2 ? isJson() || console.error(error.format()) : error instanceof Error ? console.error(error.message) : console.error(String(error)));
|
|
364
438
|
const exitCodeFromError = await extractExitCode(error);
|
|
365
439
|
process.exit(exitCodeFromError);
|
|
366
440
|
}
|