@optave/codegraph 3.11.2 → 3.13.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 +73 -37
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +2 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +1 -0
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +272 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/triage.js +1 -1
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/shared/options.d.ts +2 -1
- package/dist/cli/shared/options.d.ts.map +1 -1
- package/dist/cli/shared/options.js +11 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +8 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts +2 -0
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +24 -2
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/call-resolver.d.ts +16 -10
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
- package/dist/domain/graph/builder/call-resolver.js +251 -34
- package/dist/domain/graph/builder/call-resolver.js.map +1 -1
- package/dist/domain/graph/builder/cha.d.ts +69 -0
- package/dist/domain/graph/builder/cha.d.ts.map +1 -0
- package/dist/domain/graph/builder/cha.js +158 -0
- package/dist/domain/graph/builder/cha.js.map +1 -0
- package/dist/domain/graph/builder/context.d.ts +3 -0
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +2 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +25 -1
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +178 -5
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +74 -2
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +37 -2
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +704 -34
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +3 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +4 -0
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.js +783 -37
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts +1 -0
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +10 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/journal.js +1 -1
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/resolver/points-to.d.ts +53 -0
- package/dist/domain/graph/resolver/points-to.d.ts.map +1 -0
- package/dist/domain/graph/resolver/points-to.js +213 -0
- package/dist/domain/graph/resolver/points-to.js.map +1 -0
- package/dist/domain/graph/resolver/ts-resolver.d.ts +9 -0
- package/dist/domain/graph/resolver/ts-resolver.d.ts.map +1 -0
- package/dist/domain/graph/resolver/ts-resolver.js +476 -0
- package/dist/domain/graph/resolver/ts-resolver.js.map +1 -0
- package/dist/domain/parser.d.ts +12 -4
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +83 -20
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +35 -2
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
- package/dist/domain/wasm-worker-pool.js +34 -0
- package/dist/domain/wasm-worker-pool.js.map +1 -1
- package/dist/domain/wasm-worker-protocol.d.ts +15 -1
- package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
- package/dist/extractors/c.js +3 -3
- package/dist/extractors/c.js.map +1 -1
- package/dist/extractors/clojure.js +1 -1
- package/dist/extractors/clojure.js.map +1 -1
- package/dist/extractors/cpp.d.ts.map +1 -1
- package/dist/extractors/cpp.js +45 -4
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/csharp.d.ts.map +1 -1
- package/dist/extractors/csharp.js +37 -8
- package/dist/extractors/csharp.js.map +1 -1
- package/dist/extractors/cuda.d.ts.map +1 -1
- package/dist/extractors/cuda.js +45 -4
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/elixir.js +6 -6
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/fsharp.js +1 -1
- package/dist/extractors/fsharp.js.map +1 -1
- package/dist/extractors/go.js +5 -5
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/haskell.js +1 -1
- package/dist/extractors/haskell.js.map +1 -1
- package/dist/extractors/helpers.d.ts +11 -0
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +10 -9
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts +2 -0
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +1812 -71
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +5 -5
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/lua.js +1 -1
- package/dist/extractors/lua.js.map +1 -1
- package/dist/extractors/objc.js +3 -3
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/ocaml.js +1 -1
- package/dist/extractors/ocaml.js.map +1 -1
- package/dist/extractors/php.js +2 -2
- package/dist/extractors/php.js.map +1 -1
- package/dist/extractors/python.js +7 -7
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/ruby.js +2 -2
- package/dist/extractors/ruby.js.map +1 -1
- package/dist/extractors/scala.js +1 -1
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/solidity.js +1 -1
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +4 -4
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +4 -4
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/structure-query.d.ts +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +6 -6
- package/dist/features/structure-query.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/config.d.ts +85 -2
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +408 -19
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/native.d.ts +11 -0
- package/dist/infrastructure/native.d.ts.map +1 -1
- package/dist/infrastructure/native.js +78 -5
- package/dist/infrastructure/native.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +27 -0
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +59 -1
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +5 -0
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/structure.d.ts +1 -1
- package/dist/presentation/structure.d.ts.map +1 -1
- package/dist/presentation/structure.js +2 -2
- package/dist/presentation/structure.js.map +1 -1
- package/dist/types.d.ts +221 -0
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +7 -8
- package/src/cli/commands/audit.ts +2 -1
- package/src/cli/commands/batch.ts +1 -0
- package/src/cli/commands/build.ts +6 -1
- package/src/cli/commands/config.ts +353 -0
- package/src/cli/commands/triage.ts +1 -1
- package/src/cli/index.ts +10 -0
- package/src/cli/shared/options.ts +11 -1
- package/src/cli/types.ts +2 -0
- package/src/db/migrations.ts +8 -1
- package/src/domain/analysis/module-map.ts +29 -1
- package/src/domain/graph/builder/call-resolver.ts +263 -35
- package/src/domain/graph/builder/cha.ts +192 -0
- package/src/domain/graph/builder/context.ts +3 -0
- package/src/domain/graph/builder/helpers.ts +195 -5
- package/src/domain/graph/builder/incremental.ts +80 -1
- package/src/domain/graph/builder/pipeline.ts +49 -2
- package/src/domain/graph/builder/stages/build-edges.ts +867 -32
- package/src/domain/graph/builder/stages/detect-changes.ts +4 -2
- package/src/domain/graph/builder/stages/finalize.ts +4 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +910 -43
- package/src/domain/graph/builder/stages/resolve-imports.ts +15 -1
- package/src/domain/graph/journal.ts +1 -1
- package/src/domain/graph/resolver/points-to.ts +254 -0
- package/src/domain/graph/resolver/ts-resolver.ts +536 -0
- package/src/domain/parser.ts +86 -17
- package/src/domain/wasm-worker-entry.ts +35 -2
- package/src/domain/wasm-worker-pool.ts +22 -0
- package/src/domain/wasm-worker-protocol.ts +15 -0
- package/src/extractors/c.ts +3 -3
- package/src/extractors/clojure.ts +1 -1
- package/src/extractors/cpp.ts +47 -4
- package/src/extractors/csharp.ts +33 -9
- package/src/extractors/cuda.ts +47 -4
- package/src/extractors/elixir.ts +6 -6
- package/src/extractors/fsharp.ts +1 -1
- package/src/extractors/go.ts +5 -5
- package/src/extractors/haskell.ts +1 -1
- package/src/extractors/helpers.ts +43 -0
- package/src/extractors/java.ts +10 -9
- package/src/extractors/javascript.ts +1929 -72
- package/src/extractors/kotlin.ts +5 -5
- package/src/extractors/lua.ts +1 -1
- package/src/extractors/objc.ts +3 -3
- package/src/extractors/ocaml.ts +1 -1
- package/src/extractors/php.ts +2 -2
- package/src/extractors/python.ts +7 -7
- package/src/extractors/ruby.ts +2 -2
- package/src/extractors/scala.ts +1 -1
- package/src/extractors/solidity.ts +1 -1
- package/src/extractors/swift.ts +4 -4
- package/src/extractors/zig.ts +4 -4
- package/src/features/structure-query.ts +7 -7
- package/src/index.ts +5 -1
- package/src/infrastructure/config.ts +494 -20
- package/src/infrastructure/native.ts +87 -5
- package/src/infrastructure/registry.ts +82 -1
- package/src/presentation/queries-cli/overview.ts +15 -1
- package/src/presentation/structure.ts +3 -3
- package/src/types.ts +235 -0
- package/grammars/tree-sitter-erlang.wasm +0 -0
|
@@ -16,7 +16,10 @@ export const command: CommandDefinition = {
|
|
|
16
16
|
],
|
|
17
17
|
async execute([dir], opts, ctx) {
|
|
18
18
|
const root = path.resolve(dir || '.');
|
|
19
|
-
const
|
|
19
|
+
const globalOpts = ctx.program.opts();
|
|
20
|
+
const engine = globalOpts.engine;
|
|
21
|
+
// Prompt for global-config consent on interactive TTY builds (§4.3).
|
|
22
|
+
const promptForConsent = !process.env.CI && !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
20
23
|
await buildGraph(root, {
|
|
21
24
|
incremental: opts.incremental as boolean,
|
|
22
25
|
ast: opts.ast as boolean,
|
|
@@ -25,6 +28,8 @@ export const command: CommandDefinition = {
|
|
|
25
28
|
dataflow: opts.dataflow as boolean,
|
|
26
29
|
cfg: opts.cfg as boolean,
|
|
27
30
|
dbPath: opts.db ? path.resolve(opts.db as string) : undefined,
|
|
31
|
+
userConfig: globalOpts.userConfig as string | boolean | undefined,
|
|
32
|
+
promptForConsent,
|
|
28
33
|
});
|
|
29
34
|
},
|
|
30
35
|
};
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
clearConfigCache,
|
|
6
|
+
DEFAULTS,
|
|
7
|
+
getDefaultUserConfigPath,
|
|
8
|
+
loadConfig,
|
|
9
|
+
loadConfigWithProvenance,
|
|
10
|
+
resolveUserConfigPath,
|
|
11
|
+
} from '../../infrastructure/config.js';
|
|
12
|
+
import {
|
|
13
|
+
getUserConfigConsent,
|
|
14
|
+
listUserConfigConsent,
|
|
15
|
+
REGISTRY_PATH,
|
|
16
|
+
setUserConfigConsent,
|
|
17
|
+
} from '../../infrastructure/registry.js';
|
|
18
|
+
import { formatTable } from '../../presentation/table.js';
|
|
19
|
+
import type { ConfigSource } from '../../types.js';
|
|
20
|
+
import type { CommandDefinition } from '../types.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Recursively flatten a nested config object to dot-notation key/value pairs.
|
|
24
|
+
* Arrays and null values are serialised to strings.
|
|
25
|
+
*/
|
|
26
|
+
function flattenConfig(
|
|
27
|
+
obj: Record<string, unknown>,
|
|
28
|
+
prefix = '',
|
|
29
|
+
): Array<{ key: string; value: string }> {
|
|
30
|
+
const out: Array<{ key: string; value: string }> = [];
|
|
31
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
32
|
+
const fullKey = prefix ? `${prefix}.${k}` : k;
|
|
33
|
+
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
34
|
+
out.push(...flattenConfig(v as Record<string, unknown>, fullKey));
|
|
35
|
+
} else if (Array.isArray(v)) {
|
|
36
|
+
out.push({ key: fullKey, value: v.length === 0 ? '[]' : JSON.stringify(v) });
|
|
37
|
+
} else {
|
|
38
|
+
out.push({ key: fullKey, value: v === null ? 'null' : String(v) });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Expand a top-level provenance map (e.g. { build: 'project' }) to cover every
|
|
46
|
+
* flattened dot-notation key (e.g. 'build.incremental' → 'project').
|
|
47
|
+
*/
|
|
48
|
+
function expandProvenance(
|
|
49
|
+
flatEntries: Array<{ key: string; value: string }>,
|
|
50
|
+
provenance: Record<string, ConfigSource>,
|
|
51
|
+
): Map<string, ConfigSource> {
|
|
52
|
+
const map = new Map<string, ConfigSource>();
|
|
53
|
+
for (const { key } of flatEntries) {
|
|
54
|
+
// Provenance is keyed by top-level section (e.g. 'build', 'llm'), so
|
|
55
|
+
// extract the first segment to find the governing provenance entry.
|
|
56
|
+
const topLevel = key.split('.')[0] ?? key;
|
|
57
|
+
map.set(key, provenance[topLevel] ?? 'default');
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render the effective config as a human-readable Key/Value/Source table.
|
|
64
|
+
* All rows are shown, sorted so non-default overrides appear first, then
|
|
65
|
+
* remaining defaults alphabetically.
|
|
66
|
+
*/
|
|
67
|
+
function renderConfigTable(
|
|
68
|
+
config: Record<string, unknown>,
|
|
69
|
+
provenance: Record<string, ConfigSource>,
|
|
70
|
+
): string {
|
|
71
|
+
const flat = flattenConfig(config);
|
|
72
|
+
const sourceMap = expandProvenance(flat, provenance);
|
|
73
|
+
|
|
74
|
+
// Show all entries — sorting non-defaults first, then alphabetically
|
|
75
|
+
const rows = flat
|
|
76
|
+
.slice()
|
|
77
|
+
.sort((a, b) => {
|
|
78
|
+
const sa = sourceMap.get(a.key) ?? 'default';
|
|
79
|
+
const sb = sourceMap.get(b.key) ?? 'default';
|
|
80
|
+
// Non-defaults first
|
|
81
|
+
if (sa !== 'default' && sb === 'default') return -1;
|
|
82
|
+
if (sa === 'default' && sb !== 'default') return 1;
|
|
83
|
+
return a.key.localeCompare(b.key);
|
|
84
|
+
})
|
|
85
|
+
.map(({ key, value }) => [key, value, sourceMap.get(key) ?? 'default']);
|
|
86
|
+
|
|
87
|
+
const keyWidth = Math.max(3, ...rows.map((r) => r[0]!.length));
|
|
88
|
+
const valWidth = Math.max(5, ...rows.map((r) => r[1]!.length));
|
|
89
|
+
// Source column is always short ('default', 'user', 'project', 'env')
|
|
90
|
+
const srcWidth = 7;
|
|
91
|
+
|
|
92
|
+
return `${formatTable({
|
|
93
|
+
columns: [
|
|
94
|
+
{ header: 'Key', width: keyWidth },
|
|
95
|
+
{ header: 'Value', width: valWidth },
|
|
96
|
+
{ header: 'Source', width: srcWidth },
|
|
97
|
+
],
|
|
98
|
+
rows: rows as string[][],
|
|
99
|
+
indent: 0,
|
|
100
|
+
})}\n`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build a scaffolded global config JSON file.
|
|
105
|
+
* Produces valid JSON with common sections pre-populated at their defaults.
|
|
106
|
+
* Uses DEFAULTS so the values always reflect the current schema.
|
|
107
|
+
*
|
|
108
|
+
* All keys are optional — users can delete sections they don't need.
|
|
109
|
+
*/
|
|
110
|
+
function buildInitTemplate(): string {
|
|
111
|
+
// Build a plain object — no comments in JSON, but keep it self-explanatory.
|
|
112
|
+
// Unknown top-level keys are silently ignored by mergeConfig.
|
|
113
|
+
const template: Record<string, unknown> = {
|
|
114
|
+
// LLM provider for AI features (codegraph explain, context, etc.)
|
|
115
|
+
// Use apiKeyCommand to pull the key from a secret manager at runtime.
|
|
116
|
+
// Scope to specific repos with:
|
|
117
|
+
// { "appliesTo": ["~/projects/*"], "config": { ... } }
|
|
118
|
+
llm: {
|
|
119
|
+
provider: DEFAULTS.llm.provider,
|
|
120
|
+
model: DEFAULTS.llm.model,
|
|
121
|
+
baseUrl: DEFAULTS.llm.baseUrl,
|
|
122
|
+
apiKey: DEFAULTS.llm.apiKey,
|
|
123
|
+
apiKeyCommand: DEFAULTS.llm.apiKeyCommand,
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
query: {
|
|
127
|
+
defaultDepth: DEFAULTS.query.defaultDepth,
|
|
128
|
+
defaultLimit: DEFAULTS.query.defaultLimit,
|
|
129
|
+
excludeTests: DEFAULTS.query.excludeTests,
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
build: {
|
|
133
|
+
incremental: DEFAULTS.build.incremental,
|
|
134
|
+
typescriptResolver: DEFAULTS.build.typescriptResolver,
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
ci: {
|
|
138
|
+
failOnCycles: DEFAULTS.ci.failOnCycles,
|
|
139
|
+
impactThreshold: DEFAULTS.ci.impactThreshold,
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
search: {
|
|
143
|
+
defaultMinScore: DEFAULTS.search.defaultMinScore,
|
|
144
|
+
topK: DEFAULTS.search.topK,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return `${JSON.stringify(template, null, 2)}\n`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const command: CommandDefinition = {
|
|
152
|
+
name: 'config',
|
|
153
|
+
description: 'Show or manage codegraph configuration (project + user-level global config)',
|
|
154
|
+
options: [
|
|
155
|
+
['-j, --json', 'Output as JSON'],
|
|
156
|
+
['--explain', 'Show per-key provenance (default / user / project / env)'],
|
|
157
|
+
['--enable-global', 'Record consent to apply the global config to this repo'],
|
|
158
|
+
['--disable-global', 'Record consent to skip the global config for this repo'],
|
|
159
|
+
['--list-global', 'List all repos with a recorded consent decision'],
|
|
160
|
+
[
|
|
161
|
+
'--init',
|
|
162
|
+
'Scaffold a global config file at the default XDG location with all sections pre-populated',
|
|
163
|
+
],
|
|
164
|
+
['--edit', 'Open the global config file in $EDITOR (prints the path if $EDITOR is unset)'],
|
|
165
|
+
],
|
|
166
|
+
execute(_args, opts, ctx) {
|
|
167
|
+
const rootDir = path.resolve('.');
|
|
168
|
+
|
|
169
|
+
// ── Init: scaffold global config ───────────────────────────────────
|
|
170
|
+
|
|
171
|
+
if (opts.init) {
|
|
172
|
+
const targetPath = getDefaultUserConfigPath();
|
|
173
|
+
if (fs.existsSync(targetPath)) {
|
|
174
|
+
process.stderr.write(
|
|
175
|
+
`Global config already exists at ${targetPath}\n` +
|
|
176
|
+
`Run \`codegraph config --edit\` to open it, or delete it and re-run --init.\n`,
|
|
177
|
+
);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
181
|
+
fs.writeFileSync(targetPath, buildInitTemplate(), 'utf-8');
|
|
182
|
+
process.stdout.write(`Created global config at ${targetPath}\n`);
|
|
183
|
+
process.stdout.write(
|
|
184
|
+
`Next steps:\n` +
|
|
185
|
+
` 1. Edit the file: codegraph config --edit\n` +
|
|
186
|
+
` 2. Enable it for this repo: codegraph config --enable-global\n`,
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Edit: open global config in $EDITOR ────────────────────────────
|
|
192
|
+
|
|
193
|
+
if (opts.edit) {
|
|
194
|
+
// Prefer the existing file; fall back to the default path so the user
|
|
195
|
+
// can create-and-edit in one step even before running --init.
|
|
196
|
+
const filePath = resolveUserConfigPath() ?? getDefaultUserConfigPath();
|
|
197
|
+
|
|
198
|
+
const editor = process.env.EDITOR || process.env.VISUAL;
|
|
199
|
+
if (!editor) {
|
|
200
|
+
process.stdout.write(`${filePath}\n`);
|
|
201
|
+
process.stderr.write(
|
|
202
|
+
`$EDITOR is not set. Set it in your shell profile (e.g. export EDITOR=nano)\n` +
|
|
203
|
+
`or open the file manually at the path printed above.\n`,
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Ensure the directory exists so the editor can create the file
|
|
209
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
210
|
+
|
|
211
|
+
const result = spawnSync(editor, [filePath], { stdio: 'inherit' });
|
|
212
|
+
if (result.error) {
|
|
213
|
+
process.stderr.write(`Failed to launch editor "${editor}": ${result.error.message}\n`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
if (result.status !== 0) {
|
|
217
|
+
process.exit(result.status ?? 1);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Consent management ─────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
if (opts.enableGlobal) {
|
|
225
|
+
setUserConfigConsent(rootDir, 'enabled');
|
|
226
|
+
clearConfigCache();
|
|
227
|
+
const globalPath = resolveUserConfigPath();
|
|
228
|
+
if (!globalPath) {
|
|
229
|
+
process.stderr.write(
|
|
230
|
+
`Consent recorded: "enabled" for ${rootDir}\n` +
|
|
231
|
+
`Note: no global config file found. Create one at ~/.config/codegraph/config.json\n`,
|
|
232
|
+
);
|
|
233
|
+
} else {
|
|
234
|
+
process.stderr.write(
|
|
235
|
+
`Consent recorded: "enabled" for ${rootDir}\n` + `Global config: ${globalPath}\n`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (opts.disableGlobal) {
|
|
242
|
+
setUserConfigConsent(rootDir, 'disabled');
|
|
243
|
+
clearConfigCache();
|
|
244
|
+
process.stderr.write(`Consent recorded: "disabled" for ${rootDir}\n`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (opts.listGlobal) {
|
|
249
|
+
const entries = listUserConfigConsent(REGISTRY_PATH);
|
|
250
|
+
if (opts.json) {
|
|
251
|
+
process.stdout.write(`${JSON.stringify(entries, null, 2)}\n`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (entries.length === 0) {
|
|
255
|
+
process.stdout.write('No repos have a recorded global-config consent decision.\n');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
process.stdout.write('Global config consent decisions:\n\n');
|
|
259
|
+
for (const { path: p, decision } of entries) {
|
|
260
|
+
process.stdout.write(
|
|
261
|
+
` ${decision === 'enabled' ? '✔' : '✘'} ${decision.padEnd(8)} ${p}\n`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Explain mode ───────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
if (opts.explain) {
|
|
270
|
+
const { config, provenance, appliedGlobalPath, consentDecision } = loadConfigWithProvenance(
|
|
271
|
+
rootDir,
|
|
272
|
+
{
|
|
273
|
+
userConfig: ctx.program.opts().userConfig,
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
const globalPath = resolveUserConfigPath();
|
|
277
|
+
const consent = getUserConfigConsent(rootDir);
|
|
278
|
+
|
|
279
|
+
if (opts.json) {
|
|
280
|
+
process.stdout.write(
|
|
281
|
+
`${JSON.stringify(
|
|
282
|
+
{
|
|
283
|
+
config,
|
|
284
|
+
provenance,
|
|
285
|
+
appliedGlobalPath,
|
|
286
|
+
globalFilePath: globalPath,
|
|
287
|
+
consentDecision: consentDecision ?? consent ?? 'undecided',
|
|
288
|
+
},
|
|
289
|
+
null,
|
|
290
|
+
2,
|
|
291
|
+
)}\n`,
|
|
292
|
+
);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Human-readable explain output
|
|
297
|
+
process.stdout.write('=== Codegraph config provenance ===\n\n');
|
|
298
|
+
|
|
299
|
+
const consentStr = consentDecision ?? consent ?? 'undecided';
|
|
300
|
+
process.stdout.write(`Global config file : ${globalPath ?? '(none found)'}\n`);
|
|
301
|
+
process.stdout.write(`Applied this run : ${appliedGlobalPath ? 'yes' : 'no'}\n`);
|
|
302
|
+
process.stdout.write(`Consent for repo : ${consentStr}\n`);
|
|
303
|
+
process.stdout.write(
|
|
304
|
+
` (change with \`codegraph config --enable-global\` or \`--disable-global\`)\n`,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (!globalPath) {
|
|
308
|
+
process.stdout.write(
|
|
309
|
+
`\nDiscovery hint: create a global config at ~/.config/codegraph/config.json\n` +
|
|
310
|
+
`then run \`codegraph config --enable-global\` in repos where you want it applied.\n`,
|
|
311
|
+
);
|
|
312
|
+
} else if (!appliedGlobalPath) {
|
|
313
|
+
process.stdout.write(
|
|
314
|
+
`\nDiscovery hint: global config exists but is not applied to this repo.\n` +
|
|
315
|
+
`Run \`codegraph config --enable-global\` to enable it here.\n`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
process.stdout.write('\n--- Per-key provenance ---\n\n');
|
|
320
|
+
const provenanceEntries = Object.entries(provenance).sort(([a], [b]) => a.localeCompare(b));
|
|
321
|
+
for (const [key, source] of provenanceEntries) {
|
|
322
|
+
process.stdout.write(` ${source.padEnd(8)} ${key}\n`);
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Default: print effective config ────────────────────────────────
|
|
328
|
+
|
|
329
|
+
const globalPath = resolveUserConfigPath();
|
|
330
|
+
const consent = getUserConfigConsent(rootDir);
|
|
331
|
+
|
|
332
|
+
if (opts.json) {
|
|
333
|
+
const config = loadConfig(rootDir, { userConfig: ctx.program.opts().userConfig });
|
|
334
|
+
process.stdout.write(`${JSON.stringify(config, null, 2)}\n`);
|
|
335
|
+
} else {
|
|
336
|
+
// Human-readable table: Key | Value | Source
|
|
337
|
+
const { config, provenance } = loadConfigWithProvenance(rootDir, {
|
|
338
|
+
userConfig: ctx.program.opts().userConfig,
|
|
339
|
+
});
|
|
340
|
+
process.stdout.write(
|
|
341
|
+
renderConfigTable(config as unknown as Record<string, unknown>, provenance),
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
if (globalPath && !consent) {
|
|
345
|
+
process.stderr.write(
|
|
346
|
+
`\nℹ Global config found at ${globalPath} — not applied to this repo.\n` +
|
|
347
|
+
` Run \`codegraph config --enable-global\` to opt in, or\n` +
|
|
348
|
+
` \`codegraph config --disable-global\` to dismiss this notice.\n`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
};
|
|
@@ -31,7 +31,7 @@ async function runHotspots(opts: CommandOpts, ctx: CliContext): Promise<void> {
|
|
|
31
31
|
offset: opts.offset ? parseInt(opts.offset as string, 10) : undefined,
|
|
32
32
|
noTests: ctx.resolveNoTests(opts),
|
|
33
33
|
});
|
|
34
|
-
if (!ctx.outputResult(data, '
|
|
34
|
+
if (!ctx.outputResult(data, 'items', opts)) {
|
|
35
35
|
console.log(formatHotspots(data));
|
|
36
36
|
}
|
|
37
37
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
+
import { setUserConfigOverride } from '../infrastructure/config.js';
|
|
5
6
|
import { setVerbose } from '../infrastructure/logger.js';
|
|
6
7
|
import { checkForUpdates, printUpdateNotification } from '../infrastructure/update-check.js';
|
|
7
8
|
import { ConfigError } from '../shared/errors.js';
|
|
@@ -25,9 +26,16 @@ program
|
|
|
25
26
|
.version(pkg.version)
|
|
26
27
|
.option('-v, --verbose', 'Enable verbose/debug output')
|
|
27
28
|
.option('--engine <engine>', 'Parser engine: native, wasm, or auto (default: auto)', 'auto')
|
|
29
|
+
.option('--user-config [path]', 'Apply global user config for this run (optional custom path)')
|
|
30
|
+
.option('--no-user-config', 'Skip global user config for this run')
|
|
28
31
|
.hook('preAction', (thisCommand) => {
|
|
29
32
|
const opts = thisCommand.opts();
|
|
30
33
|
if (opts.verbose) setVerbose(true);
|
|
34
|
+
// Wire user-config flags into the config loader before any command runs.
|
|
35
|
+
// Commander sets opts.userConfig = true (bare flag), a string (path), or undefined.
|
|
36
|
+
// opts.userConfig is false when --no-user-config is passed (Commander negation).
|
|
37
|
+
const uc = opts.userConfig as string | boolean | undefined;
|
|
38
|
+
setUserConfigOverride(uc);
|
|
31
39
|
})
|
|
32
40
|
.hook('postAction', async (_thisCommand, actionCommand) => {
|
|
33
41
|
const name = actionCommand.name();
|
|
@@ -67,6 +75,8 @@ const ctx: CliContext = {
|
|
|
67
75
|
function registerCommand(parent: Command, def: CommandDefinition): Command {
|
|
68
76
|
const cmd = parent.command(def.name).description(def.description);
|
|
69
77
|
|
|
78
|
+
if (def.alias) cmd.alias(def.alias);
|
|
79
|
+
|
|
70
80
|
if (def.queryOpts) applyQueryOpts(cmd);
|
|
71
81
|
|
|
72
82
|
for (const opt of def.options || []) {
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { loadConfig } from '../../infrastructure/config.js';
|
|
3
|
+
import type { CodegraphConfig } from '../../types.js';
|
|
3
4
|
import type { CommandOpts } from '../types.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// Deferred so global --user-config / --no-user-config flags are parsed
|
|
7
|
+
// before config is first accessed (Commander parses flags before any command
|
|
8
|
+
// action runs, but module-level code executes at import time).
|
|
9
|
+
let _config: CodegraphConfig | undefined;
|
|
10
|
+
const config: CodegraphConfig = new Proxy({} as CodegraphConfig, {
|
|
11
|
+
get(_t, prop: string) {
|
|
12
|
+
if (_config === undefined) _config = loadConfig(process.cwd());
|
|
13
|
+
return _config[prop as keyof CodegraphConfig];
|
|
14
|
+
},
|
|
15
|
+
}) as CodegraphConfig;
|
|
6
16
|
|
|
7
17
|
/**
|
|
8
18
|
* Attach the common query options shared by most analysis commands.
|
package/src/cli/types.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface CliContext {
|
|
|
25
25
|
export interface CommandDefinition {
|
|
26
26
|
name: string;
|
|
27
27
|
description: string;
|
|
28
|
+
/** Optional Commander.js alias (e.g. 'explain' for the 'audit' command). */
|
|
29
|
+
alias?: string;
|
|
28
30
|
queryOpts?: boolean;
|
|
29
31
|
options?: Array<[string, string, ...unknown[]]>;
|
|
30
32
|
validate?(args: string[], opts: CommandOpts, ctx: CliContext): string | undefined;
|
package/src/db/migrations.ts
CHANGED
|
@@ -8,7 +8,7 @@ interface Migration {
|
|
|
8
8
|
up: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
// IMPORTANT: Migration DDL is mirrored in crates/codegraph-core/src/
|
|
11
|
+
// IMPORTANT: Migration DDL is mirrored in crates/codegraph-core/src/db/connection.rs.
|
|
12
12
|
// Any changes here MUST be reflected there (and vice-versa).
|
|
13
13
|
export const MIGRATIONS: Migration[] = [
|
|
14
14
|
{
|
|
@@ -256,6 +256,13 @@ export const MIGRATIONS: Migration[] = [
|
|
|
256
256
|
CREATE INDEX IF NOT EXISTS idx_edges_kind_source ON edges(kind, source_id);
|
|
257
257
|
`,
|
|
258
258
|
},
|
|
259
|
+
{
|
|
260
|
+
version: 17,
|
|
261
|
+
up: `
|
|
262
|
+
ALTER TABLE edges ADD COLUMN technique TEXT;
|
|
263
|
+
CREATE INDEX IF NOT EXISTS idx_edges_technique ON edges(technique);
|
|
264
|
+
`,
|
|
265
|
+
},
|
|
259
266
|
];
|
|
260
267
|
|
|
261
268
|
interface PragmaColumnInfo {
|
|
@@ -163,6 +163,26 @@ function getEmbeddingsInfo(db: BetterSqlite3Database) {
|
|
|
163
163
|
return null;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
function countCallEdgesByTechnique(
|
|
167
|
+
db: BetterSqlite3Database,
|
|
168
|
+
testFilter: string,
|
|
169
|
+
): Record<string, number> {
|
|
170
|
+
// testFilter uses n.file — join source node to apply the same file-scope as
|
|
171
|
+
// the rest of computeQualityMetrics so --no-tests is consistent.
|
|
172
|
+
const rows = db
|
|
173
|
+
.prepare(
|
|
174
|
+
`SELECT e.technique, COUNT(*) as c
|
|
175
|
+
FROM edges e
|
|
176
|
+
JOIN nodes n ON e.source_id = n.id
|
|
177
|
+
WHERE e.kind = 'calls' AND e.technique IS NOT NULL ${testFilter}
|
|
178
|
+
GROUP BY e.technique`,
|
|
179
|
+
)
|
|
180
|
+
.all() as Array<{ technique: string; c: number }>;
|
|
181
|
+
const byTechnique: Record<string, number> = {};
|
|
182
|
+
for (const r of rows) byTechnique[r.technique] = r.c;
|
|
183
|
+
return byTechnique;
|
|
184
|
+
}
|
|
185
|
+
|
|
166
186
|
function computeQualityMetrics(
|
|
167
187
|
db: BetterSqlite3Database,
|
|
168
188
|
testFilter: string,
|
|
@@ -205,13 +225,16 @@ function computeQualityMetrics(
|
|
|
205
225
|
const falsePositiveRatio = totalCallEdges > 0 ? fpEdgeCount / totalCallEdges : 0;
|
|
206
226
|
|
|
207
227
|
const score = computeQualityScore(callerCoverage, callConfidence, falsePositiveRatio);
|
|
228
|
+
const byTechnique = countCallEdgesByTechnique(db, testFilter);
|
|
208
229
|
|
|
209
230
|
return {
|
|
210
231
|
score,
|
|
211
232
|
callerCoverage: {
|
|
212
233
|
ratio: callerCoverage,
|
|
234
|
+
percentage: Math.round(callerCoverage * 100),
|
|
213
235
|
covered: callableWithCallers,
|
|
214
236
|
total: totalCallable,
|
|
237
|
+
byTechnique: Object.keys(byTechnique).length > 0 ? byTechnique : undefined,
|
|
215
238
|
},
|
|
216
239
|
callConfidence: {
|
|
217
240
|
ratio: callConfidence,
|
|
@@ -388,6 +411,7 @@ function buildStatsFromNative(
|
|
|
388
411
|
db: BetterSqlite3Database,
|
|
389
412
|
nativeStats: NativeGraphStats,
|
|
390
413
|
config: any,
|
|
414
|
+
noTests: boolean,
|
|
391
415
|
jsSections: {
|
|
392
416
|
files: ReturnType<typeof countFilesByLanguage>;
|
|
393
417
|
fileCycles: unknown[];
|
|
@@ -413,6 +437,8 @@ function buildStatsFromNative(
|
|
|
413
437
|
for (const fp of falsePositiveWarnings) fpEdgeCount += fp.callerCount;
|
|
414
438
|
const falsePositiveRatio = s.quality.callEdges > 0 ? fpEdgeCount / s.quality.callEdges : 0;
|
|
415
439
|
const score = computeQualityScore(callerCoverage, callConfidence, falsePositiveRatio);
|
|
440
|
+
const testFilter = testFilterSQL('n.file', noTests);
|
|
441
|
+
const byTechnique = countCallEdgesByTechnique(db, testFilter);
|
|
416
442
|
|
|
417
443
|
return {
|
|
418
444
|
nodes: { total: s.totalNodes, byKind: nodesByKind },
|
|
@@ -432,8 +458,10 @@ function buildStatsFromNative(
|
|
|
432
458
|
score,
|
|
433
459
|
callerCoverage: {
|
|
434
460
|
ratio: callerCoverage,
|
|
461
|
+
percentage: Math.round(callerCoverage * 100),
|
|
435
462
|
covered: s.quality.callableWithCallers,
|
|
436
463
|
total: s.quality.callableTotal,
|
|
464
|
+
byTechnique: Object.keys(byTechnique).length > 0 ? byTechnique : undefined,
|
|
437
465
|
},
|
|
438
466
|
callConfidence: {
|
|
439
467
|
ratio: callConfidence,
|
|
@@ -508,7 +536,7 @@ export function statsData(customDbPath: string, opts: { noTests?: boolean; confi
|
|
|
508
536
|
|
|
509
537
|
const nativeStats = nativeDb?.getGraphStats?.(noTests);
|
|
510
538
|
return nativeStats
|
|
511
|
-
? buildStatsFromNative(db, nativeStats, config, jsSections)
|
|
539
|
+
? buildStatsFromNative(db, nativeStats, config, noTests, jsSections)
|
|
512
540
|
: buildStatsFromJs(db, noTests, config, jsSections);
|
|
513
541
|
} finally {
|
|
514
542
|
close();
|