@opensip-cli/graph 0.1.6 → 0.1.8
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 +2 -2
- package/dist/__tests__/cli/graph-execute.test.js +7 -5
- package/dist/__tests__/cli/graph-execute.test.js.map +1 -1
- package/dist/__tests__/cli/graph.test.js +18 -12
- package/dist/__tests__/cli/graph.test.js.map +1 -1
- package/dist/__tests__/internal-surface.test.js +3 -0
- package/dist/__tests__/internal-surface.test.js.map +1 -1
- package/dist/__tests__/persistence/session-replay.test.js +7 -4
- package/dist/__tests__/persistence/session-replay.test.js.map +1 -1
- package/dist/__tests__/rules/registry.test.js +2 -2
- package/dist/__tests__/rules/registry.test.js.map +1 -1
- package/dist/__tests__/test-utils/with-graph-scope.d.ts.map +1 -1
- package/dist/__tests__/test-utils/with-graph-scope.js +2 -2
- package/dist/__tests__/test-utils/with-graph-scope.js.map +1 -1
- package/dist/__tests__/tool-branches.test.d.ts +3 -3
- package/dist/__tests__/tool-branches.test.js +25 -14
- package/dist/__tests__/tool-branches.test.js.map +1 -1
- package/dist/__tests__/tool-register.test.js +61 -77
- package/dist/__tests__/tool-register.test.js.map +1 -1
- package/dist/__tests__/tool.test.d.ts +4 -4
- package/dist/__tests__/tool.test.js +31 -16
- package/dist/__tests__/tool.test.js.map +1 -1
- package/dist/cli/__tests__/build-envelope.test.js +77 -0
- package/dist/cli/__tests__/build-envelope.test.js.map +1 -1
- package/dist/cli/__tests__/graph-envelope-view.test.d.ts +2 -0
- package/dist/cli/__tests__/graph-envelope-view.test.d.ts.map +1 -0
- package/dist/cli/__tests__/graph-envelope-view.test.js +97 -0
- package/dist/cli/__tests__/graph-envelope-view.test.js.map +1 -0
- package/dist/cli/__tests__/graph-list.test.d.ts +2 -0
- package/dist/cli/__tests__/graph-list.test.d.ts.map +1 -0
- package/dist/cli/__tests__/graph-list.test.js +20 -0
- package/dist/cli/__tests__/graph-list.test.js.map +1 -0
- package/dist/cli/__tests__/shard-worker.test.js +22 -0
- package/dist/cli/__tests__/shard-worker.test.js.map +1 -1
- package/dist/cli/equivalence-check-command.d.ts.map +1 -1
- package/dist/cli/equivalence-check-command.js +0 -2
- package/dist/cli/equivalence-check-command.js.map +1 -1
- package/dist/cli/graph/graph-aux-command-specs.d.ts +54 -27
- package/dist/cli/graph/graph-aux-command-specs.d.ts.map +1 -1
- package/dist/cli/graph/graph-aux-command-specs.js +359 -265
- package/dist/cli/graph/graph-aux-command-specs.js.map +1 -1
- package/dist/cli/graph-envelope-view.d.ts +41 -0
- package/dist/cli/graph-envelope-view.d.ts.map +1 -0
- package/dist/cli/graph-envelope-view.js +133 -0
- package/dist/cli/graph-envelope-view.js.map +1 -0
- package/dist/cli/graph-list.d.ts +29 -0
- package/dist/cli/graph-list.d.ts.map +1 -0
- package/dist/cli/graph-list.js +44 -0
- package/dist/cli/graph-list.js.map +1 -0
- package/dist/cli/graph-report.d.ts +16 -6
- package/dist/cli/graph-report.d.ts.map +1 -1
- package/dist/cli/graph-report.js +10 -6
- package/dist/cli/graph-report.js.map +1 -1
- package/dist/cli/graph-runner.d.ts.map +1 -1
- package/dist/cli/graph-runner.js +23 -10
- package/dist/cli/graph-runner.js.map +1 -1
- package/dist/cli/graph-worker.d.ts.map +1 -1
- package/dist/cli/graph-worker.js +1 -0
- package/dist/cli/graph-worker.js.map +1 -1
- package/dist/cli/graph.d.ts.map +1 -1
- package/dist/cli/graph.js +30 -25
- package/dist/cli/graph.js.map +1 -1
- package/dist/cli/lookup.d.ts.map +1 -1
- package/dist/cli/lookup.js +0 -2
- package/dist/cli/lookup.js.map +1 -1
- package/dist/cli/orchestrate/__tests__/shard-runner-correlation.test.d.ts +22 -0
- package/dist/cli/orchestrate/__tests__/shard-runner-correlation.test.d.ts.map +1 -0
- package/dist/cli/orchestrate/__tests__/shard-runner-correlation.test.js +208 -0
- package/dist/cli/orchestrate/__tests__/shard-runner-correlation.test.js.map +1 -0
- package/dist/cli/orchestrate/shard-model.d.ts +17 -0
- package/dist/cli/orchestrate/shard-model.d.ts.map +1 -1
- package/dist/cli/orchestrate/shard-runner.d.ts +31 -1
- package/dist/cli/orchestrate/shard-runner.d.ts.map +1 -1
- package/dist/cli/orchestrate/shard-runner.js +215 -11
- package/dist/cli/orchestrate/shard-runner.js.map +1 -1
- package/dist/cli/orchestrate/sharded-graph.d.ts.map +1 -1
- package/dist/cli/orchestrate/sharded-graph.js +32 -3
- package/dist/cli/orchestrate/sharded-graph.js.map +1 -1
- package/dist/cli/shard-worker.d.ts.map +1 -1
- package/dist/cli/shard-worker.js +127 -11
- package/dist/cli/shard-worker.js.map +1 -1
- package/dist/cli/symbol-index.d.ts.map +1 -1
- package/dist/cli/symbol-index.js +0 -2
- package/dist/cli/symbol-index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +5 -0
- package/dist/internal.js.map +1 -1
- package/dist/persistence/schema.d.ts.map +1 -1
- package/dist/persistence/schema.js +18 -0
- package/dist/persistence/schema.js.map +1 -1
- package/dist/persistence/session-replay.d.ts +9 -4
- package/dist/persistence/session-replay.d.ts.map +1 -1
- package/dist/persistence/session-replay.js +8 -12
- package/dist/persistence/session-replay.js.map +1 -1
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +37 -118
- package/dist/tool.js.map +1 -1
- package/package.json +22 -26
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
// @fitness-ignore-file no-direct-stdout-in-tool-engine -- auxiliary subcommand status line: `graph
|
|
1
|
+
// @fitness-ignore-file no-direct-stdout-in-tool-engine -- auxiliary subcommand status line: `graph export --format baseline` writes the JSON baseline to a file and prints a one-line "Exported graph baseline to <path>" confirmation (the --json path uses cli.emitJson). This is not the signal-envelope run output (ADR-0011), which routes through the composition root.
|
|
2
2
|
// @fitness-ignore-file detached-promises -- async command handlers invoke synchronous helpers (runCatalogJsonMode/runSarifExportMode/handleGraphError all return void); the heuristic flags them inside the async handlers. Matches the sibling graph CLI files (graph.ts, graph-modes.ts, orchestrate.ts).
|
|
3
3
|
// @fitness-ignore-file only-documented-toolcli-seams -- same rationale as the no-direct-stdout waiver above: the one-line "Exported graph baseline to <path>" status confirmation after a file write; the --json path uses cli.emitJson. Not run output through a ToolCliContext seam.
|
|
4
4
|
/**
|
|
5
|
-
* graph-aux-command-specs — the declarative graph auxiliary commands
|
|
6
|
-
* launch Phase 5 Task 5.2).
|
|
5
|
+
* graph-aux-command-specs — the declarative graph auxiliary commands.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* helper's raw `.option()`/`.argument()` calls translate 1:1 to
|
|
7
|
+
* The host mounts each spec via `mountCommandSpec`; the tool no longer touches
|
|
8
|
+
* Commander. Each helper's raw `.option()`/`.argument()` calls translate 1:1 to
|
|
11
9
|
* `OptionSpec`/`ArgSpec`; positional arguments arrive on the parsed-opts object
|
|
12
10
|
* under the `_args` key (the host's uniform positional convention — see
|
|
13
11
|
* `mountCommandSpec`).
|
|
14
12
|
*
|
|
13
|
+
* The canonical surface is the nested `<tool> <verb>` grammar — `graph recipes`
|
|
14
|
+
* / `graph lookup` / `graph index` / `graph list` / `graph export` all mount as
|
|
15
|
+
* children of the `graph` primary (`parent: 'graph'`). The legacy flat-root
|
|
16
|
+
* aliases (`graph-recipes` / `graph-lookup` / `graph-symbol-index` /
|
|
17
|
+
* `graph-baseline-export` / `catalog-export` / `sarif-export`) were removed once
|
|
18
|
+
* their deprecation window closed.
|
|
19
|
+
*
|
|
15
20
|
* Output modes:
|
|
16
|
-
* - `graph
|
|
17
|
-
* the host dispatches it through the shared seam (`--json` →
|
|
18
|
-
* render). Byte-identical to the former `if (json) emitJson else
|
|
21
|
+
* - `graph recipes` / `graph list` → `command-result`: the handler returns the
|
|
22
|
+
* list result; the host dispatches it through the shared seam (`--json` →
|
|
23
|
+
* JSON, else render). Byte-identical to the former `if (json) emitJson else
|
|
24
|
+
* render` body.
|
|
19
25
|
* - every other aux command → `raw-stream`: each owns its full IO (writes a
|
|
20
26
|
* file and/or prints a line, sets its own exit code, owns its `--json`
|
|
21
27
|
* branch) — the documented non-Ink exception. The host renders nothing.
|
|
@@ -23,6 +29,7 @@
|
|
|
23
29
|
import { commonFlags, EXIT_CODES } from '@opensip-cli/contracts';
|
|
24
30
|
import { ConfigurationError, defineCommand, logger } from '@opensip-cli/core';
|
|
25
31
|
import { executeEquivalenceCheck } from '../equivalence-check-command.js';
|
|
32
|
+
import { listGraphRules } from '../graph-list.js';
|
|
26
33
|
import { runCatalogJsonMode } from '../graph-modes.js';
|
|
27
34
|
import { listGraphRecipes } from '../graph-recipes.js';
|
|
28
35
|
import { handleGraphError } from '../graph.js';
|
|
@@ -32,38 +39,202 @@ import { runSarifExportMode } from '../sarif-export.js';
|
|
|
32
39
|
import { executeShardWorker } from '../shard-worker.js';
|
|
33
40
|
import { executeSymbolIndex } from '../symbol-index.js';
|
|
34
41
|
// Shared --cwd flag string for the auxiliary subcommands that declare it as a
|
|
35
|
-
// tool option (
|
|
36
|
-
//
|
|
37
|
-
//
|
|
42
|
+
// tool option (the index command keeps a custom description). Sourced from the
|
|
43
|
+
// ADR-0021 common-flag registry so the string matches the run command's --cwd
|
|
44
|
+
// and cannot drift.
|
|
38
45
|
const OPT_CWD = commonFlags.cwd.flags;
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// literal is declared once (sonarjs/no-duplicate-string) and stays a typed
|
|
43
|
-
// CommandOutputMode member.
|
|
46
|
+
// Shared output mode for the file-/stdout-writing aux commands. Extracted to a
|
|
47
|
+
// const so the literal is declared once (sonarjs/no-duplicate-string) and stays
|
|
48
|
+
// a typed CommandOutputMode member.
|
|
44
49
|
const RAW_STREAM = 'raw-stream';
|
|
50
|
+
// Shared `rawStreamReason` for the file-writing aux commands (the index command
|
|
51
|
+
// + the canonical `graph export`). Declared once so the literal is not
|
|
52
|
+
// duplicated (sonarjs/no-duplicate-string).
|
|
53
|
+
const REASON_FILE_EXPORT = 'file-export';
|
|
45
54
|
/** Read the single trailing positional (`<name>` / `<specPath>`) off the parsed opts. */
|
|
46
55
|
function firstArg(opts) {
|
|
47
56
|
const args = (opts._args ?? []);
|
|
48
57
|
return args[0] ?? '';
|
|
49
58
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// EXPORT OPTION SPECS (the canonical `graph export --format <fmt>` command)
|
|
61
|
+
//
|
|
62
|
+
// The canonical `graph export` command declares `--format <fmt>` (required) plus
|
|
63
|
+
// the UNION of the per-format flags as OPTIONAL and validates the required
|
|
64
|
+
// subset per format at runtime. Each OptionSpec is declared once here.
|
|
65
|
+
// =============================================================================
|
|
66
|
+
/** `--out` for the baseline export — JSON fingerprints. */
|
|
67
|
+
const OPT_BASELINE_OUT = {
|
|
68
|
+
flag: '--out',
|
|
69
|
+
value: '<path>',
|
|
70
|
+
description: 'Output file path for the JSON baseline',
|
|
71
|
+
};
|
|
72
|
+
/** `--catalog-output` (catalog export). */
|
|
73
|
+
const OPT_CATALOG_OUTPUT = {
|
|
74
|
+
flag: '--catalog-output',
|
|
75
|
+
value: '<path>',
|
|
76
|
+
description: 'Output file path for the CatalogExport JSON',
|
|
77
|
+
};
|
|
78
|
+
/** `--output-sarif` (sarif export). */
|
|
79
|
+
const OPT_OUTPUT_SARIF = {
|
|
80
|
+
flag: '--output-sarif',
|
|
81
|
+
value: '<path>',
|
|
82
|
+
description: 'Output file path for the SARIF v2.1.0 document',
|
|
83
|
+
};
|
|
84
|
+
/** `--tenant-id` (catalog/sarif export). */
|
|
85
|
+
const OPT_TENANT_ID_CATALOG = {
|
|
86
|
+
flag: '--tenant-id',
|
|
87
|
+
value: '<id>',
|
|
88
|
+
description: 'Tenant scope stamped on every row + provenance',
|
|
89
|
+
};
|
|
90
|
+
/** `--repo-id` (catalog/sarif export). */
|
|
91
|
+
const OPT_REPO_ID_CATALOG = {
|
|
92
|
+
flag: '--repo-id',
|
|
93
|
+
value: '<id>',
|
|
94
|
+
description: 'Repository scope stamped on every row',
|
|
95
|
+
};
|
|
96
|
+
/** `--git-sha` (catalog export). */
|
|
97
|
+
const OPT_GIT_SHA = {
|
|
98
|
+
flag: '--git-sha',
|
|
99
|
+
value: '<sha>',
|
|
100
|
+
description: 'Commit SHA the catalog was extracted at',
|
|
101
|
+
};
|
|
102
|
+
/** `--run-id` (catalog/sarif export). */
|
|
103
|
+
const OPT_RUN_ID_CATALOG = {
|
|
104
|
+
flag: '--run-id',
|
|
105
|
+
value: '<uuid>',
|
|
106
|
+
description: 'Run id for provenance (auto-generated if absent)',
|
|
107
|
+
};
|
|
108
|
+
/** `--mode` (catalog export). */
|
|
109
|
+
const OPT_MODE = {
|
|
110
|
+
flag: '--mode',
|
|
111
|
+
value: '<mode>',
|
|
112
|
+
description: "'initial' (full rebuild) or 'incremental' (reuse cache when present)",
|
|
113
|
+
default: 'initial',
|
|
114
|
+
};
|
|
115
|
+
/** `--changed-file` (catalog export) — repeatable accumulator. */
|
|
116
|
+
const OPT_CHANGED_FILE = {
|
|
117
|
+
flag: '--changed-file',
|
|
118
|
+
value: '<relPath>',
|
|
119
|
+
description: 'Changed file (repeatable). Advisory today — the engine derives the true changed set from fingerprint diffs; recorded for observability.',
|
|
120
|
+
arrayDefault: [],
|
|
121
|
+
parse: (val, prev) => [...prev, val],
|
|
122
|
+
};
|
|
123
|
+
/** `--language` (catalog/sarif export). */
|
|
124
|
+
const OPT_LANGUAGE = {
|
|
125
|
+
flag: '--language',
|
|
126
|
+
value: '<name>',
|
|
127
|
+
description: 'Force a specific language adapter (suppresses auto-detection)',
|
|
128
|
+
};
|
|
129
|
+
/** `--resolution` (catalog/sarif export) — host-validated choices enum. */
|
|
130
|
+
const OPT_RESOLUTION = {
|
|
131
|
+
flag: '--resolution',
|
|
132
|
+
value: '<mode>',
|
|
133
|
+
description: 'Edge resolution tier: exact (semantic) or fast (syntactic, no type checker)',
|
|
134
|
+
default: 'exact',
|
|
135
|
+
choices: ['exact', 'fast'],
|
|
136
|
+
};
|
|
137
|
+
/** Run graph analysis and write OpenSIP-convention SARIF to `--output-sarif`. */
|
|
138
|
+
async function runGraphSarifExport(opts, cli) {
|
|
139
|
+
try {
|
|
140
|
+
const resolution = opts.resolution === 'fast' ? 'fast' : 'exact';
|
|
141
|
+
const result = await runGraph({
|
|
142
|
+
cwd: opts.cwd,
|
|
143
|
+
noCache: true,
|
|
144
|
+
resolution,
|
|
145
|
+
language: opts.language,
|
|
146
|
+
datastore: cli.scope.datastore(),
|
|
147
|
+
});
|
|
148
|
+
await runSarifExportMode({
|
|
149
|
+
outputSarif: opts.outputSarif,
|
|
150
|
+
tenantId: opts.tenantId,
|
|
151
|
+
repoId: opts.repoId,
|
|
152
|
+
runId: opts.runId,
|
|
153
|
+
}, result.signals, cli);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
handleGraphError('sarif-export', error, cli);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Run graph analysis and write the CatalogExport JSON to `--catalog-output`. */
|
|
160
|
+
async function runGraphCatalogExport(opts, cli) {
|
|
161
|
+
const startedAt = new Date().toISOString();
|
|
162
|
+
try {
|
|
163
|
+
// `--resolution`'s value is `exact`/`fast` by construction (declared
|
|
164
|
+
// `choices`); the mount layer rejected any other value before we got here.
|
|
165
|
+
const resolution = opts.resolution === 'fast' ? 'fast' : 'exact';
|
|
166
|
+
const incremental = opts.mode === 'incremental';
|
|
167
|
+
const changedFiles = opts.changedFile ?? [];
|
|
168
|
+
if (incremental && changedFiles.length > 0) {
|
|
169
|
+
// Advisory only: the incremental path self-derives the changed set from
|
|
170
|
+
// on-disk fingerprint diffs, so a caller-supplied set does not (yet)
|
|
171
|
+
// narrow the walk. Logged for observability.
|
|
172
|
+
logger.info({
|
|
173
|
+
evt: 'graph.cli.catalog_export.changed_files_advisory',
|
|
174
|
+
module: 'graph:cli',
|
|
175
|
+
runId: opts.runId,
|
|
176
|
+
changedFileCount: changedFiles.length,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const result = await runGraph({
|
|
180
|
+
cwd: opts.cwd,
|
|
181
|
+
noCache: !incremental,
|
|
182
|
+
resolution,
|
|
183
|
+
language: opts.language,
|
|
184
|
+
datastore: cli.scope.datastore(),
|
|
185
|
+
});
|
|
186
|
+
runCatalogJsonMode({
|
|
187
|
+
cwd: opts.cwd,
|
|
188
|
+
catalogOutput: opts.catalogOutput,
|
|
189
|
+
tenantId: opts.tenantId,
|
|
190
|
+
repoId: opts.repoId,
|
|
191
|
+
gitSha: opts.gitSha,
|
|
192
|
+
runId: opts.runId,
|
|
193
|
+
}, result, cli, startedAt);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
handleGraphError('catalog-export', error, cli);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Export the graph gate fingerprint baseline (JSON) to `--out` via the host
|
|
201
|
+
* baseline seam (ADR-0036). Maps the ConfigurationError "no baseline" path to
|
|
202
|
+
* exit 2 for both the `--json` and plain-text boundaries.
|
|
203
|
+
*/
|
|
204
|
+
async function runGraphBaselineExport(opts, cli) {
|
|
205
|
+
try {
|
|
206
|
+
await cli.exportBaselineFingerprints('graph', opts.out);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
210
|
+
const exitCode = error instanceof ConfigurationError
|
|
211
|
+
? EXIT_CODES.CONFIGURATION_ERROR
|
|
212
|
+
: EXIT_CODES.RUNTIME_ERROR;
|
|
213
|
+
logger.warn({
|
|
214
|
+
evt: 'cli.graph.baseline_export.failed',
|
|
215
|
+
module: 'graph:cli',
|
|
216
|
+
message,
|
|
217
|
+
exitCode,
|
|
218
|
+
});
|
|
219
|
+
if (opts.json === true) {
|
|
220
|
+
cli.emitError({ message, exitCode });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
cli.setExitCode(exitCode);
|
|
224
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const result = { type: 'graph-baseline-export', outPath: opts.out };
|
|
228
|
+
if (opts.json === true) {
|
|
229
|
+
cli.emitJson(result);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
process.stdout.write(`Exported graph baseline to ${opts.out}\n`);
|
|
233
|
+
}
|
|
64
234
|
/** `graph-shard-worker` — [internal] build one shard from a spec file. */
|
|
65
235
|
export const graphShardWorkerCommandSpec = defineCommand({
|
|
66
236
|
name: 'graph-shard-worker',
|
|
237
|
+
visibility: 'internal',
|
|
67
238
|
description: '[internal] Build one shard from a spec file and emit a ShardBuildResult JSON (spawned by the sharded build)',
|
|
68
239
|
commonFlags: [],
|
|
69
240
|
args: [{ name: 'specPath', description: 'Path to a JSON ShardWorkerSpec file' }],
|
|
@@ -84,6 +255,7 @@ export const graphShardWorkerCommandSpec = defineCommand({
|
|
|
84
255
|
*/
|
|
85
256
|
export const graphEquivalenceCheckCommandSpec = defineCommand({
|
|
86
257
|
name: 'graph-equivalence-check',
|
|
258
|
+
visibility: 'internal',
|
|
87
259
|
description: '[internal] Verify the sharded build is byte-equivalent to the exact build on a real repo (gates production edge divergence against a committed budget)',
|
|
88
260
|
commonFlags: [],
|
|
89
261
|
options: [
|
|
@@ -111,280 +283,202 @@ export const graphEquivalenceCheckCommandSpec = defineCommand({
|
|
|
111
283
|
await executeEquivalenceCheck({ cwd: opts.cwd, budget: opts.budget, updateBudget: opts.updateBudget }, cli);
|
|
112
284
|
},
|
|
113
285
|
});
|
|
114
|
-
/**
|
|
115
|
-
export
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
286
|
+
/**
|
|
287
|
+
* The canonical graph export formats (taxonomy spec Q2: one `export` subcommand
|
|
288
|
+
* dispatching on `--format`, not nested argv). Declared as a `choices` enum so
|
|
289
|
+
* the host validates the value at mount and `graph export --format <bad>` is
|
|
290
|
+
* rejected before the handler runs.
|
|
291
|
+
*/
|
|
292
|
+
export const GRAPH_EXPORT_FORMATS = ['sarif', 'catalog', 'baseline'];
|
|
293
|
+
/**
|
|
294
|
+
* Validate that the per-format required flags are present on the canonical
|
|
295
|
+
* `graph export` opts. Returns `true` when the required subset is satisfied;
|
|
296
|
+
* otherwise reports the missing flags (to the `--json` channel or stderr, like
|
|
297
|
+
* the shared export error paths) + sets exit 2 (CONFIGURATION_ERROR) and returns
|
|
298
|
+
* `false`. Keeps the per-format required-flag validation in one place without
|
|
299
|
+
* making all format-specific subsets simultaneously mandatory on one spec.
|
|
300
|
+
*/
|
|
301
|
+
function requireExportFlags(format, present, required, cli) {
|
|
302
|
+
const missing = required.filter((flag) => {
|
|
303
|
+
// Flag names map to camelCase opt keys (`--output-sarif` → `outputSarif`).
|
|
304
|
+
const key = flag.replace(/^--/, '').replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
305
|
+
const value = present[key];
|
|
306
|
+
return value === undefined || value === '';
|
|
307
|
+
});
|
|
308
|
+
if (missing.length === 0)
|
|
309
|
+
return true;
|
|
310
|
+
const message = `graph export --format ${format} requires ${missing.join(', ')}.`;
|
|
311
|
+
const exitCode = EXIT_CODES.CONFIGURATION_ERROR;
|
|
312
|
+
logger.warn({ evt: 'cli.graph.export.missing_flags', module: 'graph:cli', format, missing });
|
|
313
|
+
if (present.json === true) {
|
|
314
|
+
cli.emitError({ message, exitCode });
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
cli.setExitCode(exitCode);
|
|
318
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* `graph export --format sarif|catalog|baseline` — the canonical graph export
|
|
323
|
+
* command (taxonomy spec Q2). Mounts as a SUBCOMMAND of the `graph` primary
|
|
324
|
+
* (`parent: 'graph'`, via the nested-mount capability), so it shares the root
|
|
325
|
+
* with `fit export` without colliding (both declare `name: 'export'`).
|
|
326
|
+
*
|
|
327
|
+
* The legacy flat-root commands (`sarif-export`/`catalog-export`/
|
|
328
|
+
* `graph-baseline-export`) were removed. The canonical spec declares `--format`
|
|
329
|
+
* (required) + the UNION of the per-format flags as OPTIONAL and validates the
|
|
330
|
+
* required subset per format at runtime (`requireExportFlags` →
|
|
331
|
+
* ConfigurationError → exit 2).
|
|
332
|
+
*/
|
|
333
|
+
export const graphExportCommandSpec = defineCommand({
|
|
334
|
+
name: 'export',
|
|
335
|
+
parent: 'graph',
|
|
336
|
+
description: 'Export graph analysis artifacts: --format sarif (SARIF v2.1.0 findings), catalog (CatalogExport JSON), or baseline (gate fingerprint JSON)',
|
|
148
337
|
commonFlags: ['cwd', 'json'],
|
|
149
338
|
options: [
|
|
150
339
|
{
|
|
151
|
-
flag: '--
|
|
152
|
-
value: '<
|
|
153
|
-
description: '
|
|
340
|
+
flag: '--format',
|
|
341
|
+
value: '<fmt>',
|
|
342
|
+
description: 'Export artifact: sarif | catalog | baseline',
|
|
154
343
|
required: true,
|
|
344
|
+
choices: [...GRAPH_EXPORT_FORMATS],
|
|
155
345
|
},
|
|
346
|
+
// Union of the per-format flags, all OPTIONAL here (the required subset is
|
|
347
|
+
// validated per-format at runtime). --cwd is a common flag (declared above),
|
|
348
|
+
// so it is NOT repeated here.
|
|
349
|
+
OPT_OUTPUT_SARIF,
|
|
350
|
+
OPT_CATALOG_OUTPUT,
|
|
351
|
+
OPT_TENANT_ID_CATALOG,
|
|
352
|
+
OPT_REPO_ID_CATALOG,
|
|
353
|
+
OPT_GIT_SHA,
|
|
354
|
+
OPT_RUN_ID_CATALOG,
|
|
355
|
+
OPT_MODE,
|
|
356
|
+
OPT_CHANGED_FILE,
|
|
357
|
+
OPT_LANGUAGE,
|
|
358
|
+
OPT_RESOLUTION,
|
|
359
|
+
OPT_BASELINE_OUT,
|
|
156
360
|
],
|
|
157
361
|
scope: 'project',
|
|
158
362
|
output: RAW_STREAM,
|
|
159
|
-
rawStreamReason:
|
|
363
|
+
rawStreamReason: REASON_FILE_EXPORT,
|
|
160
364
|
handler: async (rawOpts, cli) => {
|
|
161
365
|
const opts = rawOpts;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (opts.json === true) {
|
|
180
|
-
cli.emitError({ message, exitCode });
|
|
366
|
+
switch (opts.format) {
|
|
367
|
+
case 'sarif': {
|
|
368
|
+
if (!requireExportFlags('sarif', opts, ['--output-sarif', '--tenant-id', '--repo-id'], cli))
|
|
369
|
+
return;
|
|
370
|
+
await runGraphSarifExport(opts, cli);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
case 'catalog': {
|
|
374
|
+
if (!requireExportFlags('catalog', opts, ['--catalog-output', '--tenant-id', '--repo-id', '--git-sha'], cli))
|
|
375
|
+
return;
|
|
376
|
+
await runGraphCatalogExport(opts, cli);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
case 'baseline': {
|
|
380
|
+
if (!requireExportFlags('baseline', opts, ['--out'], cli))
|
|
381
|
+
return;
|
|
382
|
+
await runGraphBaselineExport(opts, cli);
|
|
181
383
|
return;
|
|
182
384
|
}
|
|
183
|
-
cli.setExitCode(exitCode);
|
|
184
|
-
process.stderr.write(`Error: ${message}\n`);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
const result = { type: 'graph-baseline-export', outPath: opts.out };
|
|
188
|
-
if (opts.json === true) {
|
|
189
|
-
cli.emitJson(result);
|
|
190
|
-
return;
|
|
191
385
|
}
|
|
192
|
-
process.stdout.write(`Exported graph baseline to ${opts.out}\n`);
|
|
193
386
|
},
|
|
194
387
|
});
|
|
388
|
+
// =============================================================================
|
|
389
|
+
// GROUPED <tool> <verb> CHILDREN (the canonical Tier-2 grammar)
|
|
390
|
+
//
|
|
391
|
+
// `graph recipes` / `graph lookup` / `graph index` / `graph list` mount as
|
|
392
|
+
// SUBCOMMANDS of the `graph` primary via the nested-mount capability
|
|
393
|
+
// (`parent: 'graph'`). They own their handler bodies directly (calling the
|
|
394
|
+
// shared engine functions) — the legacy flat `graph-recipes` / `graph-lookup` /
|
|
395
|
+
// `graph-symbol-index` aliases were removed.
|
|
396
|
+
// =============================================================================
|
|
195
397
|
/**
|
|
196
|
-
* `
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* (
|
|
200
|
-
* --catalog-output` shape was retired by the split, so docs/consumers must
|
|
201
|
-
* target `catalog-export`.
|
|
398
|
+
* `graph recipes` — list available graph recipes (mirrors `fit recipes`). Reuses
|
|
399
|
+
* the shared ListRecipesResult contract + viewListRecipes renderer.
|
|
400
|
+
* `command-result`: the host dispatches the returned result through the shared
|
|
401
|
+
* seam (`--json` → JSON, else render).
|
|
202
402
|
*/
|
|
203
|
-
export const
|
|
204
|
-
name: '
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
flag: '--repo-id',
|
|
222
|
-
value: '<id>',
|
|
223
|
-
description: 'Repository scope stamped on every row',
|
|
224
|
-
required: true,
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
flag: '--git-sha',
|
|
228
|
-
value: '<sha>',
|
|
229
|
-
description: 'Commit SHA the catalog was extracted at',
|
|
230
|
-
required: true,
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
flag: '--run-id',
|
|
234
|
-
value: '<uuid>',
|
|
235
|
-
description: 'Run id for provenance (auto-generated if absent)',
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
flag: '--mode',
|
|
239
|
-
value: '<mode>',
|
|
240
|
-
description: "'initial' (full rebuild) or 'incremental' (reuse cache when present)",
|
|
241
|
-
default: 'initial',
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
flag: '--changed-file',
|
|
245
|
-
value: '<relPath>',
|
|
246
|
-
description: 'Changed file (repeatable). Advisory today — the engine derives the true changed set from fingerprint diffs; recorded for observability.',
|
|
247
|
-
arrayDefault: [],
|
|
248
|
-
parse: (val, prev) => [...prev, val],
|
|
249
|
-
},
|
|
250
|
-
{ flag: OPT_CWD, description: OPT_DESC_CWD, default: process.cwd() },
|
|
251
|
-
{
|
|
252
|
-
flag: '--language',
|
|
253
|
-
value: '<name>',
|
|
254
|
-
description: 'Force a specific language adapter (suppresses auto-detection)',
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
flag: '--resolution',
|
|
258
|
-
value: '<mode>',
|
|
259
|
-
description: 'Edge resolution tier: exact (semantic) or fast (syntactic, no type checker)',
|
|
260
|
-
default: 'exact',
|
|
261
|
-
choices: ['exact', 'fast'],
|
|
262
|
-
},
|
|
263
|
-
],
|
|
403
|
+
export const graphRecipesGroupedCommandSpec = defineCommand({
|
|
404
|
+
name: 'recipes',
|
|
405
|
+
parent: 'graph',
|
|
406
|
+
description: 'List available graph recipes',
|
|
407
|
+
commonFlags: ['json'],
|
|
408
|
+
scope: 'project',
|
|
409
|
+
output: 'command-result',
|
|
410
|
+
handler: async () => listGraphRecipes(),
|
|
411
|
+
});
|
|
412
|
+
/** `graph lookup <name>` — look up function occurrences by simple name. */
|
|
413
|
+
export const graphLookupGroupedCommandSpec = defineCommand({
|
|
414
|
+
name: 'lookup',
|
|
415
|
+
parent: 'graph',
|
|
416
|
+
description: 'Look up function occurrences by simple name from the persisted catalog',
|
|
417
|
+
commonFlags: ['json'],
|
|
418
|
+
args: [{ name: 'name', description: 'Function simple name to look up (e.g. "saveBaseline")' }],
|
|
264
419
|
scope: 'project',
|
|
265
420
|
output: RAW_STREAM,
|
|
266
|
-
rawStreamReason: '
|
|
421
|
+
rawStreamReason: 'lookup',
|
|
267
422
|
handler: async (rawOpts, cli) => {
|
|
268
423
|
const opts = rawOpts;
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
// `--resolution`'s value is `exact`/`fast` by construction (declared
|
|
272
|
-
// `choices`); the mount layer rejected any other value before we got here.
|
|
273
|
-
const resolution = opts.resolution === 'fast' ? 'fast' : 'exact';
|
|
274
|
-
const incremental = opts.mode === 'incremental';
|
|
275
|
-
const changedFiles = opts.changedFile ?? [];
|
|
276
|
-
if (incremental && changedFiles.length > 0) {
|
|
277
|
-
// Advisory only: the incremental path self-derives the changed set from
|
|
278
|
-
// on-disk fingerprint diffs, so a caller-supplied set does not (yet)
|
|
279
|
-
// narrow the walk. Logged for observability.
|
|
280
|
-
logger.info({
|
|
281
|
-
evt: 'graph.cli.catalog_export.changed_files_advisory',
|
|
282
|
-
module: 'graph:cli',
|
|
283
|
-
runId: opts.runId,
|
|
284
|
-
changedFileCount: changedFiles.length,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
const result = await runGraph({
|
|
288
|
-
cwd: opts.cwd,
|
|
289
|
-
noCache: !incremental,
|
|
290
|
-
resolution,
|
|
291
|
-
language: opts.language,
|
|
292
|
-
datastore: cli.scope.datastore(),
|
|
293
|
-
});
|
|
294
|
-
runCatalogJsonMode({
|
|
295
|
-
cwd: opts.cwd,
|
|
296
|
-
catalogOutput: opts.catalogOutput,
|
|
297
|
-
tenantId: opts.tenantId,
|
|
298
|
-
repoId: opts.repoId,
|
|
299
|
-
gitSha: opts.gitSha,
|
|
300
|
-
runId: opts.runId,
|
|
301
|
-
}, result, cli, startedAt);
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
handleGraphError('catalog-export', error, cli);
|
|
305
|
-
}
|
|
424
|
+
await executeLookup({ name: firstArg(opts), json: opts.json }, cli);
|
|
306
425
|
},
|
|
307
426
|
});
|
|
308
427
|
/**
|
|
309
|
-
* `
|
|
310
|
-
* file
|
|
311
|
-
*
|
|
428
|
+
* `graph index` — emit a `symbolindex.json` artifact (name→file:line and
|
|
429
|
+
* file→names) from the persisted catalog. It BOTH builds and queries the symbol
|
|
430
|
+
* index.
|
|
431
|
+
*
|
|
432
|
+
* Q7 (open): `graph index` currently both builds and queries; the build/query
|
|
433
|
+
* verb split (`graph index build` | `graph index query`) is deferred. Do NOT add
|
|
434
|
+
* a `--build` flag here without resolving Q7.
|
|
312
435
|
*/
|
|
313
|
-
export const
|
|
314
|
-
name: '
|
|
315
|
-
|
|
436
|
+
export const graphIndexGroupedCommandSpec = defineCommand({
|
|
437
|
+
name: 'index',
|
|
438
|
+
parent: 'graph',
|
|
439
|
+
description: 'Emit a symbolindex.json artifact (name→file:line and file→names) from the persisted catalog',
|
|
316
440
|
commonFlags: [],
|
|
317
441
|
options: [
|
|
442
|
+
// --cwd keeps its command-specific description (the out path resolves
|
|
443
|
+
// against it), so it is declared as a tool option rather than the common
|
|
444
|
+
// flag. The literal default is `process.cwd()`, evaluated once at module
|
|
445
|
+
// load (CLI startup) — equivalent to the former register-time evaluation.
|
|
318
446
|
{
|
|
319
|
-
flag:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
required: true,
|
|
323
|
-
},
|
|
324
|
-
{ flag: '--tenant-id', value: '<id>', description: 'Tenant scope for the run', required: true },
|
|
325
|
-
{
|
|
326
|
-
flag: '--repo-id',
|
|
327
|
-
value: '<id>',
|
|
328
|
-
description: 'Repository scope for the run',
|
|
329
|
-
required: true,
|
|
330
|
-
},
|
|
331
|
-
{
|
|
332
|
-
flag: '--run-id',
|
|
333
|
-
value: '<uuid>',
|
|
334
|
-
description: 'Run id for trace correlation (auto-generated if absent)',
|
|
335
|
-
},
|
|
336
|
-
{ flag: OPT_CWD, description: OPT_DESC_CWD, default: process.cwd() },
|
|
337
|
-
{
|
|
338
|
-
flag: '--language',
|
|
339
|
-
value: '<name>',
|
|
340
|
-
description: 'Force a specific language adapter (suppresses auto-detection)',
|
|
447
|
+
flag: OPT_CWD,
|
|
448
|
+
description: 'Target directory (out path resolves against this)',
|
|
449
|
+
default: process.cwd(),
|
|
341
450
|
},
|
|
342
451
|
{
|
|
343
|
-
flag: '--
|
|
344
|
-
value: '<
|
|
345
|
-
description: '
|
|
346
|
-
default: '
|
|
347
|
-
choices: ['exact', 'fast'],
|
|
452
|
+
flag: '--out',
|
|
453
|
+
value: '<path>',
|
|
454
|
+
description: 'Output file path',
|
|
455
|
+
default: 'symbolindex.json',
|
|
348
456
|
},
|
|
349
457
|
],
|
|
350
458
|
scope: 'project',
|
|
351
459
|
output: RAW_STREAM,
|
|
352
|
-
rawStreamReason:
|
|
353
|
-
handler:
|
|
460
|
+
rawStreamReason: REASON_FILE_EXPORT,
|
|
461
|
+
handler: (rawOpts, cli) => {
|
|
354
462
|
const opts = rawOpts;
|
|
355
|
-
|
|
356
|
-
const resolution = opts.resolution === 'fast' ? 'fast' : 'exact';
|
|
357
|
-
const result = await runGraph({
|
|
358
|
-
cwd: opts.cwd,
|
|
359
|
-
noCache: true,
|
|
360
|
-
resolution,
|
|
361
|
-
language: opts.language,
|
|
362
|
-
datastore: cli.scope.datastore(),
|
|
363
|
-
});
|
|
364
|
-
await runSarifExportMode({
|
|
365
|
-
outputSarif: opts.outputSarif,
|
|
366
|
-
tenantId: opts.tenantId,
|
|
367
|
-
repoId: opts.repoId,
|
|
368
|
-
runId: opts.runId,
|
|
369
|
-
}, result.signals, cli);
|
|
370
|
-
}
|
|
371
|
-
catch (error) {
|
|
372
|
-
handleGraphError('sarif-export', error, cli);
|
|
373
|
-
}
|
|
463
|
+
executeSymbolIndex({ cwd: opts.cwd, out: opts.out }, cli);
|
|
374
464
|
},
|
|
375
465
|
});
|
|
376
466
|
/**
|
|
377
|
-
* `graph
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
467
|
+
* `graph list` — list available graph rules (the natural analog of `fit list`,
|
|
468
|
+
* which lists checks): graph *rules* are the listable surface.
|
|
469
|
+
*
|
|
470
|
+
* `command-result`: the handler returns a `ListChecksResult`; the host
|
|
471
|
+
* dispatches it through the shared seam (`--json` → JSON, else the shared
|
|
472
|
+
* `viewListChecks` renderer with the graph-supplied title) — the same path
|
|
473
|
+
* `graph recipes` / `fit list` use.
|
|
381
474
|
*/
|
|
382
|
-
export const
|
|
383
|
-
name: '
|
|
384
|
-
|
|
385
|
-
|
|
475
|
+
export const graphListCommandSpec = defineCommand({
|
|
476
|
+
name: 'list',
|
|
477
|
+
parent: 'graph',
|
|
478
|
+
description: 'List available graph rules',
|
|
479
|
+
commonFlags: ['cwd', 'json'],
|
|
386
480
|
scope: 'project',
|
|
387
481
|
output: 'command-result',
|
|
388
|
-
handler: async () =>
|
|
482
|
+
handler: async () => listGraphRules(),
|
|
389
483
|
});
|
|
390
484
|
//# sourceMappingURL=graph-aux-command-specs.js.map
|