@event4u/agent-config 17.2.1 → 17.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/skills/install-agent-config/SKILL.md +9 -9
- package/CHANGELOG.md +167 -193
- package/CREDITS.md +2 -2
- package/README.md +1 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +16 -12
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/check_memory.ts +1 -1
- package/dist/cli/commands/recordTriggerEval.js +1 -1
- package/dist/cli/registry.js +2 -2
- package/dist/cli-delegate/{chunk-KR5ZIFPO.js → chunk-5AYJGUA3.js} +1 -3
- package/dist/cli-delegate/chunk-DIFZBP4G.js +1336 -0
- package/dist/cli-delegate/chunk-E7ACTJTJ.js +6792 -0
- package/dist/cli-delegate/{chunk-VYZGJLCA.js → chunk-RKNT4LIK.js} +22 -1286
- package/dist/cli-delegate/{chunk-BZJ4Y3CI.js → chunk-WY5ZZPGL.js} +2 -2
- package/dist/cli-delegate/{chunk-CMZEKSNN.js → chunk-X6LZ7W5A.js} +73 -10
- package/dist/cli-delegate/cmd_conformance.js +190 -192
- package/dist/cli-delegate/cmd_doctor.js +5 -5
- package/dist/cli-delegate/cmd_doctor_neighbours.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +8 -4
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_update.js +24 -13
- package/dist/cli-delegate/cmd_upgrade.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/cli-delegate/dist-25AZAHOP.js +3240 -0
- package/dist/cli-delegate/dist-P7MFPMF3.js +411 -0
- package/dist/cli-delegate/dist-XAS5ZLQB.js +6 -0
- package/dist/cli-delegate/{upgrade_unattended-VVL6JLSX.js → upgrade_unattended-QCWOKTUN.js} +178 -163
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +3 -3
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/code_graph_refresh.chunk.cjs +443 -0
- package/dist/hooks/code_graph_refresh.chunk.cjs.sha256 +1 -0
- package/dist/hooks/dispatch.js +1001 -938
- package/dist/hooks/dispatch.sha256 +1 -1
- package/dist/hooks/egress_engine.chunk.cjs +57 -7
- package/dist/hooks/egress_engine.chunk.cjs.sha256 +1 -1
- package/dist/hooks/update_notice.chunk.cjs +914 -0
- package/dist/hooks/update_notice.chunk.cjs.sha256 +1 -0
- package/dist/install/install.mjs +6974 -6037
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -0
- package/dist/mcp/registry-manifest.json +3 -2
- package/dist/mcp/server.json +13 -4
- package/dist/mcp/server.mjs +71202 -63282
- package/dist/scripts/_lib/egress_rules.js +8 -2
- package/dist/scripts/condense.js +18 -15
- package/dist/server/routes/discovery.js +1 -1
- package/dist/server/routes/install.js +1 -1
- package/dist/server/routes/ping.js +1 -1
- package/dist/server/routes/schema.js +4 -4
- package/dist/server/routes/settings.js +2 -6
- package/dist/server/routes/wizard.js +6 -6
- package/dist/server/schemas/settings.js +42 -42
- package/dist/shared/settingsSurface.js +1 -1
- package/dist/shared/userMd/schema.js +1 -1
- package/dist/shared/zodJsonSchema.js +47 -0
- package/dist/ui/assets/index-B0bcIgi0.js +77 -0
- package/dist/ui/index.html +1 -1
- package/package.json +6 -3
- package/src/agent-src/templates/scripts/check_memory.ts +1 -1
- package/src/config/ci-local-parity.yml +11 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +9 -8
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/gitignore-block.txt +6 -5
- package/src/config/hook-bundle-budget.json +46 -6
- package/src/config/pack-size-budget.json +12 -1
- package/src/config/packed-binary-manifest.json +18 -0
- package/src/config/sigstore/npm_keys.json +50 -0
- package/src/config/sigstore/trust-material.json +9 -0
- package/src/config/sigstore/trusted_root.json +126 -0
- package/src/config/skill-body-budget.json +7 -1
- package/src/scripts/_cli/cmd_settings_set.ts +3 -2
- package/src/scripts/_cli/cmd_update.ts +48 -20
- package/src/scripts/_cli/cmd_upgrade.ts +7 -1
- package/src/scripts/_cli/upgrade_unattended.ts +90 -76
- package/src/scripts/_dispatch.bash +4 -3
- package/src/scripts/_lib/confusables.ts +0 -19
- package/src/scripts/_lib/egress_normalize.ts +53 -23
- package/src/scripts/_lib/egress_rules.ts +8 -2
- package/src/scripts/_lib/egress_text.ts +5 -3
- package/src/scripts/_lib/egress_token.ts +7 -5
- package/src/scripts/_lib/gate_collector_experimental.ts +633 -0
- package/src/scripts/_lib/git_common_dir.ts +10 -0
- package/src/scripts/_lib/graph_feeder_record.ts +3 -2
- package/src/scripts/_lib/install_reach_checks.ts +32 -2
- package/src/scripts/_lib/lineage_backfill.ts +136 -0
- package/src/scripts/_lib/lineage_ledger.ts +211 -0
- package/src/scripts/_lib/lineage_map.ts +55 -0
- package/src/scripts/_lib/lineage_query.ts +140 -0
- package/src/scripts/_lib/lineage_seed.ts +116 -0
- package/src/scripts/_lib/lineage_watch.ts +249 -0
- package/src/scripts/_lib/link_crypto.ts +8 -4
- package/src/scripts/_lib/listing_manifests.ts +85 -0
- package/src/scripts/_lib/neighbour_census.ts +1 -0
- package/src/scripts/_lib/production_reach.ts +3 -3
- package/src/scripts/_lib/project_registry.ts +227 -0
- package/src/scripts/_lib/provenance_verify.ts +14 -242
- package/src/scripts/_lib/release_settings_surface.ts +2 -6
- package/src/scripts/_lib/sigstore_provenance.ts +177 -0
- package/src/scripts/_lib/source_id.ts +48 -0
- package/src/scripts/_lib/source_shape.ts +4 -0
- package/src/scripts/_lib/unattended_gates.ts +6 -1
- package/src/scripts/adoption_status.ts +231 -27
- package/src/scripts/advisories.ts +26 -10
- package/src/scripts/ai_council/council_egress.ts +5 -6
- package/src/scripts/bench_graph_feeder_latency.ts +10 -3
- package/src/scripts/build_mcp_registry_manifest.ts +23 -12
- package/src/scripts/check_dependency_floors.ts +14 -0
- package/src/scripts/check_egress_mutations.ts +1 -0
- package/src/scripts/check_release_manifest_content.ts +17 -2
- package/src/scripts/check_release_pr_shape.ts +5 -0
- package/src/scripts/code_graph/build.ts +145 -45
- package/src/scripts/code_graph/cli.ts +382 -35
- package/src/scripts/code_graph/conventions_default.ts +247 -0
- package/src/scripts/code_graph/detect.ts +5 -5
- package/src/scripts/code_graph/estate_cli.ts +135 -0
- package/src/scripts/code_graph/estate_index.ts +394 -0
- package/src/scripts/code_graph/estate_manifests.ts +223 -0
- package/src/scripts/code_graph/estate_map.ts +141 -0
- package/src/scripts/code_graph/extract.ts +8 -2
- package/src/scripts/code_graph/extract_more.ts +241 -2
- package/src/scripts/code_graph/graph_budget.ts +158 -0
- package/src/scripts/code_graph/graph_home.ts +80 -0
- package/src/scripts/code_graph/lineage_cli.ts +315 -0
- package/src/scripts/code_graph/loader.ts +10 -2
- package/src/scripts/code_graph/map.ts +195 -53
- package/src/scripts/code_graph/map_model.ts +113 -20
- package/src/scripts/code_graph/map_verify.ts +80 -5
- package/src/scripts/code_graph/map_viewer/viewer.css +18 -0
- package/src/scripts/code_graph/map_viewer/viewer.js +146 -15
- package/src/scripts/code_graph/passes.ts +173 -0
- package/src/scripts/code_graph/project_ref.ts +25 -0
- package/src/scripts/code_graph/projects_cli.ts +254 -0
- package/src/scripts/code_graph/providers/angular.ts +55 -0
- package/src/scripts/code_graph/providers/aspnetcore.ts +182 -0
- package/src/scripts/code_graph/providers/catalog.ts +299 -0
- package/src/scripts/code_graph/providers/django.ts +206 -0
- package/src/scripts/code_graph/providers/express.ts +25 -0
- package/src/scripts/code_graph/providers/fastapi.ts +49 -0
- package/src/scripts/code_graph/providers/flask.ts +99 -0
- package/src/scripts/code_graph/providers/go_http.ts +121 -0
- package/src/scripts/code_graph/providers/index.ts +103 -0
- package/src/scripts/code_graph/{laravel_overlay.ts → providers/laravel.ts} +113 -7
- package/src/scripts/code_graph/providers/nestjs.ts +34 -0
- package/src/scripts/code_graph/providers/nextjs.ts +91 -0
- package/src/scripts/code_graph/providers/nuxt.ts +55 -0
- package/src/scripts/code_graph/providers/rails.ts +204 -0
- package/src/scripts/code_graph/providers/react-router.ts +58 -0
- package/src/scripts/code_graph/providers/shared/detect.ts +78 -0
- package/src/scripts/code_graph/providers/shared/extractors.ts +249 -0
- package/src/scripts/code_graph/providers/shared/js.ts +252 -0
- package/src/scripts/code_graph/providers/shared/kit.ts +210 -0
- package/src/scripts/code_graph/providers/shared/php.ts +64 -0
- package/src/scripts/code_graph/providers/shared/python.ts +223 -0
- package/src/scripts/code_graph/providers/spring.ts +175 -0
- package/src/scripts/code_graph/providers/sqlalchemy.ts +62 -0
- package/src/scripts/code_graph/providers/sveltekit.ts +59 -0
- package/src/scripts/code_graph/providers/symfony.ts +142 -0
- package/src/scripts/code_graph/providers/typeorm.ts +58 -0
- package/src/scripts/code_graph/providers/types.ts +56 -0
- package/src/scripts/code_graph/providers/vue-router.ts +21 -0
- package/src/scripts/code_graph/providers/wordpress.ts +128 -0
- package/src/scripts/code_graph/query.ts +9 -9
- package/src/scripts/code_graph/refresh_cli.ts +81 -0
- package/src/scripts/code_graph/refresh_lock.ts +197 -0
- package/src/scripts/code_graph/repo_identity.ts +169 -0
- package/src/scripts/code_graph/resolution_tiers.ts +19 -7
- package/src/scripts/code_graph/scip_reader.ts +2 -2
- package/src/scripts/code_graph/sqlite_store.ts +31 -28
- package/src/scripts/code_graph/types.ts +79 -41
- package/src/scripts/code_graph/validate.ts +6 -8
- package/src/scripts/condense.ts +15 -15
- package/src/scripts/generate_settings_reference.ts +2 -6
- package/src/scripts/hook_chunks.mjs +11 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +43 -23
- package/src/scripts/hooks/code_graph_refresh_hook.ts +163 -0
- package/src/scripts/hooks/dispatch_hook.ts +6 -7
- package/src/scripts/hooks/host_semantics.ts +72 -0
- package/src/scripts/hooks/lazy_concern.ts +8 -0
- package/src/scripts/hooks/user_notice.ts +105 -0
- package/src/scripts/install-hooks.sh +54 -33
- package/src/scripts/install.ts +2 -6
- package/src/scripts/lint_harvest_provenance.ts +137 -11
- package/src/scripts/lint_hook_manifest.ts +2 -0
- package/src/scripts/lint_marketplace.ts +33 -0
- package/src/scripts/lint_mcp_registry_manifest.ts +36 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +70 -15
- package/src/scripts/mcp_server/graph_tools.ts +165 -41
- package/src/scripts/network_sources.ts +24 -6
- package/src/scripts/release.ts +2 -5
- package/src/scripts/release_drill.ts +4 -1
- package/src/scripts/release_env.ts +17 -0
- package/src/scripts/release_mix_taxonomy.json +3 -0
- package/src/scripts/schemas/provenance-mechanism.schema.json +46 -0
- package/src/scripts/update_notice_hook.ts +75 -7
- package/src/server/schemas/settings.ts +42 -42
- package/src/shared/settingsSurface.ts +1 -1
- package/src/shared/userMd/schema.ts +1 -1
- package/src/shared/zodJsonSchema.ts +41 -0
- package/src/vendor/grammars/README.md +8 -6
- package/src/vendor/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/cli-delegate/chunk-XIAPY44N.js +0 -4587
- package/dist/ui/assets/index-w70Ado09.js +0 -23
- /package/dist/cli-delegate/{chunk-FKYZOWVD.js → chunk-QBAFP67O.js} +0 -0
- /package/dist/cli-delegate/{chunk-BMGUGSY6.js → chunk-TQG6UMPC.js} +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A repository's identity on this machine (ADR-299, council decision O2).
|
|
3
|
+
*
|
|
4
|
+
* `repoId` is 12 hex of an HMAC-SHA256, keyed by a random key created once
|
|
5
|
+
* under the user-global `runtime/projects/`, over the canonical origin URL —
|
|
6
|
+
* userinfo, tokens, query and fragment removed, host lower-cased, default port
|
|
7
|
+
* dropped, ssh / scp / https forms and a trailing `.git` collapsed. Without a
|
|
8
|
+
* remote the main checkout's realpath stands in. Keyed rather than a plain hash
|
|
9
|
+
* because a hosting URL is dictionary-guessable and may carry a token.
|
|
10
|
+
*
|
|
11
|
+
* A linked worktree keeps its parent's `repoId` and gets an `instance`, so its
|
|
12
|
+
* writable caches never collide with the main checkout's while estate queries
|
|
13
|
+
* still count one project. Two separate clones of one origin share `repoId` too,
|
|
14
|
+
* so `checkout` — keyed over the main checkout's realpath — is what keeps their
|
|
15
|
+
* writable homes apart.
|
|
16
|
+
*
|
|
17
|
+
* File-based like `_lib/git_common_dir.ts`: an inherited `GIT_DIR` would
|
|
18
|
+
* redirect a `git config` subprocess to the wrong repository.
|
|
19
|
+
*/
|
|
20
|
+
import { createHmac, randomBytes } from 'node:crypto';
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
|
|
24
|
+
import { git_common_dir } from '../_lib/git_common_dir.js';
|
|
25
|
+
import { type EnvMap, event4u_root } from '../_lib/user_global_paths.js';
|
|
26
|
+
|
|
27
|
+
export interface RepoIdentity {
|
|
28
|
+
repoId: string;
|
|
29
|
+
/** Set for a linked worktree only. */
|
|
30
|
+
instance: string | null;
|
|
31
|
+
/** 8 hex over the main checkout's realpath: distinct per clone, shared by its worktrees. */
|
|
32
|
+
checkout: string;
|
|
33
|
+
source: 'origin' | 'realpath';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEFAULT_PORTS: Record<string, string> = { 'ssh:': '22', 'git+ssh:': '22', 'https:': '443', 'http:': '80', 'git:': '9418' };
|
|
37
|
+
|
|
38
|
+
export function projectsDir(env?: EnvMap | null): string {
|
|
39
|
+
return path.join(event4u_root(env), 'runtime', 'projects');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** `dir` overrides the projects directory, for a caller (advisories) that carries its own. */
|
|
43
|
+
export function machineKeyPath(env?: EnvMap | null, dir: string = projectsDir(env)): string {
|
|
44
|
+
return path.join(dir, 'machine.key');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The machine key, created on first use. Published with a hard link so a
|
|
49
|
+
* concurrent first use either wins or reads the winner's complete key — never
|
|
50
|
+
* an empty file. A key that exists but is malformed throws: replacing it would
|
|
51
|
+
* silently re-key every repository on the machine.
|
|
52
|
+
*/
|
|
53
|
+
export function machineKey(env?: EnvMap | null, dir?: string): Buffer {
|
|
54
|
+
const p = machineKeyPath(env, dir);
|
|
55
|
+
const read = (): Buffer => {
|
|
56
|
+
const hex = fs.readFileSync(p, 'utf-8').trim();
|
|
57
|
+
if (!/^[0-9a-f]{64}$/.test(hex)) throw new Error(`malformed machine key: ${p}`);
|
|
58
|
+
return Buffer.from(hex, 'hex');
|
|
59
|
+
};
|
|
60
|
+
if (fs.existsSync(p)) return read();
|
|
61
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
62
|
+
const tmp = `${p}.${process.pid}-${randomBytes(4).toString('hex')}.tmp`;
|
|
63
|
+
fs.writeFileSync(tmp, `${randomBytes(32).toString('hex')}\n`, { mode: 0o600 });
|
|
64
|
+
try {
|
|
65
|
+
fs.linkSync(tmp, p);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
if ((e as NodeJS.ErrnoException).code !== 'EEXIST') throw e;
|
|
68
|
+
} finally {
|
|
69
|
+
fs.rmSync(tmp, { force: true });
|
|
70
|
+
}
|
|
71
|
+
return read();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function trimPath(p: string): string {
|
|
75
|
+
return p
|
|
76
|
+
.replace(/\/{2,}/g, '/')
|
|
77
|
+
.replace(/^\/+|\/+$/g, '')
|
|
78
|
+
.replace(/\.git$/i, '')
|
|
79
|
+
.replace(/\/+$/, '');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The credential-free form two spellings of one remote share. A relative local
|
|
84
|
+
* path resolves against `base`, the repository root, because the process cwd
|
|
85
|
+
* is wherever the CLI happened to be started.
|
|
86
|
+
*/
|
|
87
|
+
export function canonicalOrigin(raw: string, base?: string): string {
|
|
88
|
+
const url = raw.trim();
|
|
89
|
+
const scp = /^(?:[^@/]+@)?([^:/]+):(?!\/\/)(.+)$/.exec(url);
|
|
90
|
+
if (!url.includes('://') && scp && !/^[a-zA-Z]:[\\/]/.test(url)) {
|
|
91
|
+
return `${(scp[1] as string).toLowerCase()}/${trimPath(scp[2] as string)}`;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const u = new URL(url);
|
|
95
|
+
if (u.protocol === 'file:') return `file:${trimPath(decodeURIComponent(u.pathname))}`;
|
|
96
|
+
const port = u.port && u.port !== DEFAULT_PORTS[u.protocol] ? `:${u.port}` : '';
|
|
97
|
+
return `${u.hostname.toLowerCase()}${port}/${trimPath(decodeURIComponent(u.pathname))}`;
|
|
98
|
+
} catch {
|
|
99
|
+
return `file:${trimPath(base === undefined ? path.resolve(url) : path.resolve(base, url))}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The `origin` URL, else the first remote's, read from the common git config. */
|
|
104
|
+
export function remoteUrl(root: string): string | null {
|
|
105
|
+
const common = git_common_dir(root);
|
|
106
|
+
if (common === null) return null;
|
|
107
|
+
let text: string;
|
|
108
|
+
try {
|
|
109
|
+
text = fs.readFileSync(path.join(common, 'config'), 'utf-8');
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const urls = new Map<string, string>();
|
|
114
|
+
let remote: string | null = null;
|
|
115
|
+
for (const line of text.split(/\r?\n/)) {
|
|
116
|
+
const section = /^\s*\[\s*remote\s+"([^"]+)"\s*\]/.exec(line);
|
|
117
|
+
if (section) {
|
|
118
|
+
remote = section[1] as string;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (/^\s*\[/.test(line)) {
|
|
122
|
+
remote = null;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const kv = /^\s*url\s*=\s*(.+?)\s*$/.exec(line);
|
|
126
|
+
if (remote !== null && kv && !urls.has(remote)) urls.set(remote, (kv[1] as string).replace(/^"|"$/g, ''));
|
|
127
|
+
}
|
|
128
|
+
return urls.get('origin') ?? urls.values().next().value ?? null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function realpathOr(p: string): string {
|
|
132
|
+
try {
|
|
133
|
+
return fs.realpathSync(p);
|
|
134
|
+
} catch {
|
|
135
|
+
return path.resolve(p);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hmac(key: Buffer, value: string): string {
|
|
140
|
+
return createHmac('sha256', key).update(value).digest('hex');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function repoIdentity(root: string, env?: EnvMap | null, dir?: string): RepoIdentity {
|
|
144
|
+
const key = machineKey(env, dir);
|
|
145
|
+
const real = realpathOr(root);
|
|
146
|
+
const common = git_common_dir(real);
|
|
147
|
+
let linked = false;
|
|
148
|
+
try {
|
|
149
|
+
linked = !fs.statSync(path.join(real, '.git')).isDirectory();
|
|
150
|
+
} catch {
|
|
151
|
+
linked = false;
|
|
152
|
+
}
|
|
153
|
+
const origin = remoteUrl(real);
|
|
154
|
+
const main = common !== null && path.basename(common) === '.git' ? path.dirname(common) : real;
|
|
155
|
+
let source: RepoIdentity['source'] = 'origin';
|
|
156
|
+
let canonical: string;
|
|
157
|
+
if (origin !== null) {
|
|
158
|
+
canonical = `origin:${canonicalOrigin(origin, main)}`;
|
|
159
|
+
} else {
|
|
160
|
+
source = 'realpath';
|
|
161
|
+
canonical = `path:${main}`;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
repoId: hmac(key, canonical).slice(0, 12),
|
|
165
|
+
instance: linked ? hmac(key, `instance:${real}`).slice(0, 8) : null,
|
|
166
|
+
checkout: hmac(key, `checkout:${main}`).slice(0, 8),
|
|
167
|
+
source,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -108,9 +108,10 @@ function isRecord(v: unknown): v is Record<string, unknown> {
|
|
|
108
108
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/** Normalise a repo-relative path fragment: no leading `./`, no trailing
|
|
111
|
+
/** Normalise a repo-relative path fragment: no leading `./`, no trailing `/`, and `.` is the root (`''`). */
|
|
112
112
|
function normalizeFragment(p: string): string {
|
|
113
|
-
|
|
113
|
+
const s = p.replace(/^(\.\/)+/, '').replace(/\/+$/, '');
|
|
114
|
+
return s === '.' ? '' : s;
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
/**
|
|
@@ -119,14 +120,19 @@ function normalizeFragment(p: string): string {
|
|
|
119
120
|
* `baseUrl` is resolved relative to the tsconfig's own directory, which for a
|
|
120
121
|
* root tsconfig is the repo root — so `baseUrl: "./src"` plus `"@shared/*":
|
|
121
122
|
* ["shared/*"]` yields the repo-relative prefix `src/shared/`.
|
|
123
|
+
*
|
|
124
|
+
* A `baseUrl` also resolves every other non-relative specifier against
|
|
125
|
+
* itself, which TypeScript does after the `paths` entries: `baseUrl: "."`
|
|
126
|
+
* makes `components/cart` the file `components/cart.tsx`. That fallback is the
|
|
127
|
+
* last rule, an empty wildcard prefix, so a `paths` entry always wins.
|
|
122
128
|
*/
|
|
123
129
|
export function aliasRulesFrom(tsconfig: unknown): AliasRule[] {
|
|
124
130
|
if (!isRecord(tsconfig)) return [];
|
|
125
131
|
const co = tsconfig['compilerOptions'];
|
|
126
132
|
if (!isRecord(co)) return [];
|
|
127
|
-
const paths = co['paths'];
|
|
128
|
-
|
|
129
|
-
const baseUrl =
|
|
133
|
+
const paths = isRecord(co['paths']) ? co['paths'] : {};
|
|
134
|
+
const hasBaseUrl = typeof co['baseUrl'] === 'string';
|
|
135
|
+
const baseUrl = hasBaseUrl ? normalizeFragment(co['baseUrl'] as string) : '';
|
|
130
136
|
const rules: AliasRule[] = [];
|
|
131
137
|
for (const [pattern, valueRaw] of Object.entries(paths)) {
|
|
132
138
|
if (!Array.isArray(valueRaw)) continue;
|
|
@@ -136,7 +142,8 @@ export function aliasRulesFrom(tsconfig: unknown): AliasRule[] {
|
|
|
136
142
|
for (const v of valueRaw) {
|
|
137
143
|
if (typeof v !== 'string') continue;
|
|
138
144
|
const tgt = wildcard ? (v.endsWith('*') ? v.slice(0, -1) : v) : v;
|
|
139
|
-
const
|
|
145
|
+
const frag = normalizeFragment(tgt);
|
|
146
|
+
const joined = baseUrl && frag ? `${baseUrl}/${frag}` : baseUrl || frag;
|
|
140
147
|
targets.push(joined.replace(/\/+$/, ''));
|
|
141
148
|
}
|
|
142
149
|
if (targets.length) rules.push({ prefix, wildcard, targets });
|
|
@@ -144,6 +151,7 @@ export function aliasRulesFrom(tsconfig: unknown): AliasRule[] {
|
|
|
144
151
|
// Longest prefix first: `@shared/deep/` must beat `@shared/` when both are
|
|
145
152
|
// declared, which is TypeScript's own rule and not a preference.
|
|
146
153
|
rules.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
154
|
+
if (hasBaseUrl) rules.push({ prefix: '', wildcard: true, targets: [baseUrl] });
|
|
147
155
|
return rules;
|
|
148
156
|
}
|
|
149
157
|
|
|
@@ -196,9 +204,13 @@ export function resolveAlias(spec: string, config: ResolutionConfig, fileIds: Re
|
|
|
196
204
|
for (const rule of config.aliases) {
|
|
197
205
|
if (rule.wildcard) {
|
|
198
206
|
if (!spec.startsWith(rule.prefix)) continue;
|
|
207
|
+
// The baseUrl fallback never claims a relative or absolute specifier.
|
|
208
|
+
if (rule.prefix === '' && /^[./]/.test(spec)) continue;
|
|
199
209
|
const rest = spec.slice(rule.prefix.length);
|
|
200
210
|
for (const t of rule.targets) {
|
|
201
|
-
const
|
|
211
|
+
const candidate = rest && t ? `${t}/${rest}` : rest || t;
|
|
212
|
+
if (!candidate) continue;
|
|
213
|
+
const hit = firstExisting(candidate, fileIds);
|
|
202
214
|
if (hit) return hit;
|
|
203
215
|
}
|
|
204
216
|
} else {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* still reads. A malformed buffer throws; the caller treats that as "no SCIP".
|
|
15
15
|
*/
|
|
16
16
|
import type { CodeEdge, CodeNode } from './types.js';
|
|
17
|
-
import {
|
|
17
|
+
import { isStatedResolution } from './types.js';
|
|
18
18
|
|
|
19
19
|
export interface ScipOccurrence {
|
|
20
20
|
/** 0-based start line. */
|
|
@@ -192,7 +192,7 @@ export function supersededByScip(scip: readonly CodeEdge[], nodes: readonly Code
|
|
|
192
192
|
named.set(e.source, set);
|
|
193
193
|
}
|
|
194
194
|
return (e: CodeEdge): boolean => {
|
|
195
|
-
if (
|
|
195
|
+
if (isStatedResolution(e.resolved_via)) return false;
|
|
196
196
|
const set = named.get(e.source);
|
|
197
197
|
if (!set) return false;
|
|
198
198
|
const target = label.get(e.target) ?? e.target.replace(/^symbol:/, '');
|
|
@@ -52,8 +52,11 @@ import type { CodeEdge, CodeGraph, CodeNode } from './types.js';
|
|
|
52
52
|
* back cleanly, answer `tests-for` with the empty set, and the caller could not
|
|
53
53
|
* tell "nothing tests this" from "this twin predates the relation". An index
|
|
54
54
|
* that answers a question it cannot answer is worse than one that refuses.
|
|
55
|
+
*
|
|
56
|
+
* 4 → 5 (`road-to-graph-providers-for-every-stack` 2.1): `edges` gained
|
|
57
|
+
* `provider_id`, the convention provider that drew a convention edge.
|
|
55
58
|
*/
|
|
56
|
-
export const GRAPH_STORE_VERSION =
|
|
59
|
+
export const GRAPH_STORE_VERSION = 6;
|
|
57
60
|
|
|
58
61
|
/**
|
|
59
62
|
* Edge count at or above which the indexed read path replaces the blob path.
|
|
@@ -78,11 +81,10 @@ export function sqliteTwinPath(jsonPath: string): string {
|
|
|
78
81
|
* JSON path remains the source of truth either way.
|
|
79
82
|
*
|
|
80
83
|
* Storage model: nodes/edges land in real tables (queryable, and the FTS-free
|
|
81
|
-
* structure the rollback triggers measure)
|
|
82
|
-
* the
|
|
83
|
-
* byte-identical answers without re-deriving key order.
|
|
84
|
+
* structure the rollback triggers measure). The JSON is the only full copy;
|
|
85
|
+
* the twin is a disposable, rebuildable projection of it.
|
|
84
86
|
*/
|
|
85
|
-
export function emitSqliteTwin(graph: CodeGraph,
|
|
87
|
+
export function emitSqliteTwin(graph: CodeGraph, jsonPath: string): boolean {
|
|
86
88
|
if (!isSqliteAvailableSync()) return false;
|
|
87
89
|
const twin = sqliteTwinPath(jsonPath);
|
|
88
90
|
const tmp = `${twin}.tmp-${process.pid}`;
|
|
@@ -101,7 +103,7 @@ export function emitSqliteTwin(graph: CodeGraph, serialized: string, jsonPath: s
|
|
|
101
103
|
db.exec(
|
|
102
104
|
'CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);' +
|
|
103
105
|
'CREATE TABLE nodes (id TEXT PRIMARY KEY, label TEXT NOT NULL, kind TEXT, source_file TEXT, l0 INTEGER, l1 INTEGER, l2 INTEGER, l3 INTEGER);' +
|
|
104
|
-
'CREATE TABLE edges (source TEXT NOT NULL, target TEXT NOT NULL, relation TEXT NOT NULL, confidence TEXT NOT NULL, candidates TEXT, ambiguity_reason TEXT, resolved_via TEXT, provider TEXT);' +
|
|
106
|
+
'CREATE TABLE edges (source TEXT NOT NULL, target TEXT NOT NULL, relation TEXT NOT NULL, confidence TEXT NOT NULL, candidates TEXT, ambiguity_reason TEXT, resolved_via TEXT, provider TEXT, provider_id TEXT);' +
|
|
105
107
|
'CREATE INDEX edges_source ON edges(source);' +
|
|
106
108
|
'CREATE INDEX edges_target ON edges(target);' +
|
|
107
109
|
// Seed resolution and the changed-file seed set are the two
|
|
@@ -117,7 +119,6 @@ export function emitSqliteTwin(graph: CodeGraph, serialized: string, jsonPath: s
|
|
|
117
119
|
meta.run('source_checksum', graph.source_checksum);
|
|
118
120
|
meta.run('json_size', String(stat.size));
|
|
119
121
|
meta.run('json_mtime_ms', String(stat.mtimeMs));
|
|
120
|
-
meta.run('graph_json', serialized);
|
|
121
122
|
// Counts live in meta so a reader can pick its path from one row
|
|
122
123
|
// instead of a COUNT(*) over the table it is deciding whether to
|
|
123
124
|
// read at all.
|
|
@@ -140,8 +141,8 @@ export function emitSqliteTwin(graph: CodeGraph, serialized: string, jsonPath: s
|
|
|
140
141
|
);
|
|
141
142
|
}
|
|
142
143
|
const insEdge = db.prepare(
|
|
143
|
-
'INSERT INTO edges (source, target, relation, confidence, candidates, ambiguity_reason, resolved_via, provider) ' +
|
|
144
|
-
'VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
|
144
|
+
'INSERT INTO edges (source, target, relation, confidence, candidates, ambiguity_reason, resolved_via, provider, provider_id) ' +
|
|
145
|
+
'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
145
146
|
);
|
|
146
147
|
for (const e of graph.edges) {
|
|
147
148
|
insEdge.run(
|
|
@@ -153,6 +154,7 @@ export function emitSqliteTwin(graph: CodeGraph, serialized: string, jsonPath: s
|
|
|
153
154
|
e.ambiguity_reason ?? null,
|
|
154
155
|
e.resolved_via,
|
|
155
156
|
e.provider,
|
|
157
|
+
e.provider_id ?? null,
|
|
156
158
|
);
|
|
157
159
|
}
|
|
158
160
|
db.exec('COMMIT');
|
|
@@ -172,39 +174,38 @@ export function emitSqliteTwin(graph: CodeGraph, serialized: string, jsonPath: s
|
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
177
|
+
* Whether the twin beside `jsonPath` mirrors it: present, the current store
|
|
178
|
+
* version, and recorded against the JSON's present size and mtime. A corrupt
|
|
179
|
+
* twin is a non-event — it is dropped so the next load re-emits it.
|
|
180
|
+
*
|
|
181
|
+
* The twin used to carry the whole serialized graph a second time and this
|
|
182
|
+
* returned it; that blob was the same bytes as the JSON and doubled the twin
|
|
183
|
+
* (road-to-a-graph-that-follows-you D2). The JSON is the only full copy now.
|
|
179
184
|
*/
|
|
180
|
-
export function
|
|
181
|
-
if (!isSqliteAvailableSync()) return
|
|
185
|
+
export function twinIsCurrent(jsonPath: string): boolean {
|
|
186
|
+
if (!isSqliteAvailableSync()) return false;
|
|
182
187
|
const twin = sqliteTwinPath(jsonPath);
|
|
183
|
-
if (!fs.existsSync(twin)) return
|
|
188
|
+
if (!fs.existsSync(twin)) return false;
|
|
184
189
|
let stat: fs.Stats;
|
|
185
190
|
try {
|
|
186
191
|
stat = fs.statSync(jsonPath);
|
|
187
192
|
} catch {
|
|
188
|
-
return
|
|
193
|
+
return false; // canonical JSON gone — the twin has no truth to mirror
|
|
189
194
|
}
|
|
190
195
|
try {
|
|
191
196
|
const { DatabaseSync } = loadSqliteSync('code_graph.sqlite_store');
|
|
192
197
|
const db = new DatabaseSync(twin, { readOnly: true });
|
|
193
198
|
try {
|
|
194
|
-
if (readUserVersion(db) !== GRAPH_STORE_VERSION) return
|
|
199
|
+
if (readUserVersion(db) !== GRAPH_STORE_VERSION) return false;
|
|
195
200
|
const rows = db.prepare('SELECT key, value FROM meta').all() as { key: string; value: string }[];
|
|
196
201
|
const meta = new Map(rows.map((r) => [r.key, r.value]));
|
|
197
|
-
|
|
198
|
-
if (meta.get('json_mtime_ms') !== String(stat.mtimeMs)) return null;
|
|
199
|
-
const serialized = meta.get('graph_json');
|
|
200
|
-
return serialized && serialized.length > 0 ? serialized : null;
|
|
202
|
+
return meta.get('json_size') === String(stat.size) && meta.get('json_mtime_ms') === String(stat.mtimeMs);
|
|
201
203
|
} finally {
|
|
202
204
|
db.close();
|
|
203
205
|
}
|
|
204
206
|
} catch {
|
|
205
|
-
// Corrupt twin is a non-event: drop it so the next build re-emits.
|
|
206
207
|
fs.rmSync(twin, { force: true });
|
|
207
|
-
return
|
|
208
|
+
return false;
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
|
|
@@ -214,9 +215,9 @@ export function loadSerializedFromTwin(jsonPath: string): string | null {
|
|
|
214
215
|
*
|
|
215
216
|
* Every export it had took a PATH and returned the whole graph: the serialized
|
|
216
217
|
* blob, or a pair of counts. So "the query path prefers SQLite" was true and
|
|
217
|
-
* bought nothing —
|
|
218
|
-
*
|
|
219
|
-
*
|
|
218
|
+
* bought nothing — the loader handed back the entire serialization, and the
|
|
219
|
+
* caller then parsed it and built a `Map` over every node and every edge. The
|
|
220
|
+
* twin was a cheaper blob transport, never an index.
|
|
220
221
|
*
|
|
221
222
|
* These reads take an id, a label, or a file, and touch only the rows that
|
|
222
223
|
* answer. Statements are prepared once per handle because a BFS calls
|
|
@@ -271,6 +272,7 @@ interface EdgeRow {
|
|
|
271
272
|
ambiguity_reason: string | null;
|
|
272
273
|
resolved_via: string | null;
|
|
273
274
|
provider: string | null;
|
|
275
|
+
provider_id: string | null;
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
function toNode(r: NodeRow): CodeNode {
|
|
@@ -303,13 +305,14 @@ function toEdge(r: EdgeRow): CodeEdge {
|
|
|
303
305
|
if (r.ambiguity_reason === 'receiver-unknown' || r.ambiguity_reason === 'hierarchy-unresolved') {
|
|
304
306
|
e.ambiguity_reason = r.ambiguity_reason;
|
|
305
307
|
}
|
|
308
|
+
if (r.provider_id) e.provider_id = r.provider_id;
|
|
306
309
|
return e;
|
|
307
310
|
}
|
|
308
311
|
|
|
309
312
|
/**
|
|
310
313
|
* Open the twin for INDEXED reads, or null when it cannot serve them.
|
|
311
314
|
*
|
|
312
|
-
* Null on exactly the conditions `
|
|
315
|
+
* Null on exactly the conditions `twinIsCurrent` refuses on —
|
|
313
316
|
* absent, stale against the JSON's size/mtime, corrupt, version-mismatched, no
|
|
314
317
|
* `node:sqlite` — so the two paths agree about when the twin is trustworthy
|
|
315
318
|
* and a caller never has to ask twice.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/** Launch set — the stacks this suite's consumers actually run. */
|
|
12
|
-
export type Lang = 'php' | 'typescript' | 'javascript' | 'python' | 'go' | 'java';
|
|
12
|
+
export type Lang = 'php' | 'typescript' | 'javascript' | 'python' | 'go' | 'java' | 'csharp' | 'ruby';
|
|
13
13
|
|
|
14
14
|
/** Same scale as `discovery_graph.ts::EdgeConfidence` (reused, not forked). */
|
|
15
15
|
export type EdgeConfidence = 'EXTRACTED' | 'INFERRED' | 'AMBIGUOUS';
|
|
@@ -29,11 +29,12 @@ export type EdgeConfidence = 'EXTRACTED' | 'INFERRED' | 'AMBIGUOUS';
|
|
|
29
29
|
* grammar can see whether a file is a test.
|
|
30
30
|
*/
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
* (`
|
|
34
|
-
* renders, a
|
|
35
|
-
* file it reads, a migration to its table, a model to its table, a
|
|
36
|
-
* the model
|
|
32
|
+
* `routes-to` … `injects` are the convention links a registered provider reads
|
|
33
|
+
* by rule (`providers/`, ADR-298): a route to its handler, code to the template
|
|
34
|
+
* it renders, a template to the template it includes, code to the translation
|
|
35
|
+
* or config file it reads, a migration to its table, a model to its table, a
|
|
36
|
+
* model to the model a relation names, a class to the service a container
|
|
37
|
+
* injects into it. The vocabulary is fixed and names no framework.
|
|
37
38
|
*/
|
|
38
39
|
export type Relation =
|
|
39
40
|
| 'calls'
|
|
@@ -49,6 +50,7 @@ export type Relation =
|
|
|
49
50
|
| 'migrates'
|
|
50
51
|
| 'persists'
|
|
51
52
|
| 'relates'
|
|
53
|
+
| 'injects'
|
|
52
54
|
/** A Markdown document links to, or names, the file (`docs_layer.ts`). */
|
|
53
55
|
| 'documents';
|
|
54
56
|
|
|
@@ -86,14 +88,24 @@ export type ResolvedVia =
|
|
|
86
88
|
| 'path-alias'
|
|
87
89
|
| 'psr4'
|
|
88
90
|
| 'route-table'
|
|
89
|
-
/** A
|
|
91
|
+
/** A template directive, a render / translation / config call naming a file, or a Markdown link. */
|
|
90
92
|
| 'directive-scan'
|
|
91
|
-
/** A
|
|
93
|
+
/** A schema or migration call creating a table, or a model mapped to such a table. */
|
|
92
94
|
| 'schema-call'
|
|
93
|
-
/**
|
|
94
|
-
| '
|
|
95
|
+
/** An ORM relation declared inside a model, naming the related model. */
|
|
96
|
+
| 'orm-relation'
|
|
97
|
+
/** A dependency-injection declaration naming the injected class. */
|
|
98
|
+
| 'di-binding'
|
|
99
|
+
/** A string literal equal to exactly one file's path (the default pass). */
|
|
100
|
+
| 'literal-path'
|
|
101
|
+
/** A dotted name equal to exactly one template's path (the default pass). */
|
|
102
|
+
| 'literal-dotted'
|
|
103
|
+
/** A page module whose path is its route (the default pass). */
|
|
104
|
+
| 'fs-route'
|
|
95
105
|
/** Stated by a compiler-grade SCIP index the project ships (`scip_reader.ts`). */
|
|
96
106
|
| 'compiler-index'
|
|
107
|
+
/** C#: the one type of that name in the namespaces the file can see (its own, their parents, its `using`s). */
|
|
108
|
+
| 'namespace-scope'
|
|
97
109
|
| 'test-import'
|
|
98
110
|
| 'name-lookup'
|
|
99
111
|
| 'dynamic'
|
|
@@ -131,33 +143,40 @@ export type EdgeProvider =
|
|
|
131
143
|
| 'scip';
|
|
132
144
|
|
|
133
145
|
/**
|
|
134
|
-
*
|
|
146
|
+
* Every mechanism, classified as a statement (the source names the target) or
|
|
147
|
+
* a guess. Trust is this ALLOWLIST (ADR-298): it replaced a denylist of guesses,
|
|
148
|
+
* under which a new mechanism was trusted merely because nobody had listed it —
|
|
149
|
+
* wrong once several providers emit edges. A string that is no member at all
|
|
150
|
+
* (an older or foreign graph) is a guess. The type below fails to compile when a
|
|
151
|
+
* `ResolvedVia` member is in neither list.
|
|
135
152
|
*
|
|
136
|
-
* `name-lookup` is the repo-wide same-name table (or a lookup that
|
|
137
|
-
* nothing); `dynamic` is a receiver the extractor cannot type
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* mechanism while its own evidence was a guess LAUNDERS the guess. A PHP
|
|
144
|
-
* `use Two\Mailer` with no `composer.json` binds by base name, so the `imports`
|
|
145
|
-
* edge is `name-lookup` and may point at `One\Mailer`; deriving a `tests` edge
|
|
146
|
-
* from it and tagging that `test-import` made a wrong target trustworthy to
|
|
147
|
-
* every consumer of the filter. One set, two readers, and the build pass can no
|
|
148
|
-
* longer disagree with the query tier about what a guess is.
|
|
149
|
-
*
|
|
150
|
-
* The accepted-edge filter is a DENYLIST, so a new value is accepted by
|
|
151
|
-
* default — which is exactly backwards for `foreign`. It must be listed here
|
|
152
|
-
* or a consumer's graph would silently answer the gate verbs as if this
|
|
153
|
-
* package had resolved its edges. (Kept out of the literal: esbuild carries a
|
|
154
|
-
* comment inside an expression into the hook bundle.)
|
|
153
|
+
* The guesses: `name-lookup` is the repo-wide same-name table (or a lookup that
|
|
154
|
+
* found nothing); `dynamic` is a receiver the extractor cannot type; `foreign`
|
|
155
|
+
* was read from another tool's graph and only mapped. The BUILD pass needs the
|
|
156
|
+
* split as much as the query tier: a derived edge stamped with a trustworthy
|
|
157
|
+
* mechanism while its own evidence was a guess LAUNDERS the guess — a PHP
|
|
158
|
+
* `use Two\Mailer` with no `composer.json` binds by base name, and a `tests`
|
|
159
|
+
* edge derived from it made a wrong target trustworthy to every consumer.
|
|
155
160
|
*/
|
|
156
|
-
|
|
161
|
+
// One space-separated string per list: this module ships in the hook bundle,
|
|
162
|
+
// where an array literal costs a line per entry. `Words` splits it in the type.
|
|
163
|
+
const STATED_WORDS =
|
|
164
|
+
'same-file import-specifier path-alias psr4 route-table directive-scan schema-call orm-relation di-binding compiler-index namespace-scope test-import literal-path literal-dotted fs-route' as const;
|
|
165
|
+
const GUESS_WORDS = 'name-lookup dynamic foreign' as const;
|
|
166
|
+
type Words<S extends string> = S extends `${infer H} ${infer T}` ? H | Words<T> : S;
|
|
167
|
+
/** Mechanisms in neither list; must be `never` (a test asserts it). */
|
|
168
|
+
export type UnclassifiedResolvedVia = Exclude<ResolvedVia, Words<typeof STATED_WORDS> | Words<typeof GUESS_WORDS>>;
|
|
169
|
+
/** Words that are no mechanism at all; must be `never` too. */
|
|
170
|
+
export type UnknownTrustWord = Exclude<Words<typeof STATED_WORDS> | Words<typeof GUESS_WORDS>, ResolvedVia>;
|
|
171
|
+
/** Fails to compile while either list is incomplete or carries a stray word. */
|
|
172
|
+
export type TrustListsComplete = [UnclassifiedResolvedVia | UnknownTrustWord] extends [never] ? true : never;
|
|
173
|
+
export const TRUST_LISTS_COMPLETE: TrustListsComplete = true;
|
|
174
|
+
export const STATED_RESOLVED_VIA: ReadonlySet<string> = new Set(STATED_WORDS.split(' '));
|
|
175
|
+
export const GUESS_RESOLVED_VIA: ReadonlySet<ResolvedVia> = new Set(GUESS_WORDS.split(' ') as ResolvedVia[]);
|
|
157
176
|
|
|
158
|
-
/** Does this edge's target rest on a stated fact
|
|
177
|
+
/** Does this edge's target rest on a stated fact? Anything not listed as stated is a guess. */
|
|
159
178
|
export function isStatedResolution(resolved_via: ResolvedVia): boolean {
|
|
160
|
-
return
|
|
179
|
+
return STATED_RESOLVED_VIA.has(resolved_via);
|
|
161
180
|
}
|
|
162
181
|
|
|
163
182
|
export interface CodeNode {
|
|
@@ -188,9 +207,9 @@ export interface CodeNode {
|
|
|
188
207
|
| 'type'
|
|
189
208
|
| 'enum'
|
|
190
209
|
| 'skipped'
|
|
191
|
-
/** A declared route, `<VERB> <path>` (
|
|
210
|
+
/** A declared route or dispatched entry, `<VERB> <path>` (a convention provider). */
|
|
192
211
|
| 'route'
|
|
193
|
-
/** A database table a migration creates (
|
|
212
|
+
/** A database table a migration or schema creates (a convention provider). */
|
|
194
213
|
| 'table';
|
|
195
214
|
source_file: string;
|
|
196
215
|
/** 1-based [startLine, startCol, endLine, endCol]; [] for synthetic nodes. */
|
|
@@ -240,6 +259,14 @@ export interface CodeEdge {
|
|
|
240
259
|
* target.
|
|
241
260
|
*/
|
|
242
261
|
ambiguity_reason?: 'receiver-unknown' | 'hierarchy-unresolved';
|
|
262
|
+
/** The convention provider that drew this edge (ADR-298); absent on every other edge. */
|
|
263
|
+
provider_id?: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** A convention provider active in this build, with the package roots it applied to. */
|
|
267
|
+
export interface ActiveProviderRecord {
|
|
268
|
+
id: string;
|
|
269
|
+
roots: string[];
|
|
243
270
|
}
|
|
244
271
|
|
|
245
272
|
export interface CodeGraph {
|
|
@@ -267,13 +294,15 @@ export interface CodeGraph {
|
|
|
267
294
|
suppressed_edge_counts: {
|
|
268
295
|
dynamic_no_candidate: number;
|
|
269
296
|
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
* method of the same name. Present only
|
|
273
|
-
*
|
|
297
|
+
* Base edges a convention provider proved false and removed, per
|
|
298
|
+
* provider id — e.g. a fluent route-builder call that name lookup had
|
|
299
|
+
* bound to an unrelated in-repo method of the same name. Present only
|
|
300
|
+
* when some count is non-zero.
|
|
274
301
|
*/
|
|
275
|
-
|
|
302
|
+
contested_by_provider?: Record<string, number>;
|
|
276
303
|
};
|
|
304
|
+
/** Convention providers active in this build; present only when one was. */
|
|
305
|
+
providers?: ActiveProviderRecord[];
|
|
277
306
|
nodes: CodeNode[];
|
|
278
307
|
edges: CodeEdge[];
|
|
279
308
|
}
|
|
@@ -307,8 +336,13 @@ export interface CodeGraph {
|
|
|
307
336
|
* Bumped 4 → 5 by `road-to-a-map-that-draws-itself` 1.3: same-file
|
|
308
337
|
* same-name declarations now carry `@2`, `@3` suffixes, so a cache written
|
|
309
338
|
* at v4 holds ids a v5 build would not produce.
|
|
339
|
+
*
|
|
340
|
+
* Bumped 5 → 6 by `road-to-graph-providers-for-every-stack` 2.1: framework
|
|
341
|
+
* names left the schema (`orm-relation`, `contested_by_provider`), provider
|
|
342
|
+
* edges carry `provider_id`, the graph records its active providers, and the
|
|
343
|
+
* vocabulary gained `injects` / `di-binding`.
|
|
310
344
|
*/
|
|
311
|
-
export const SCHEMA_VERSION =
|
|
345
|
+
export const SCHEMA_VERSION = 6;
|
|
312
346
|
|
|
313
347
|
/** Deterministic per-file byte cap — files above this become a SKIPPED node. */
|
|
314
348
|
export const MAX_FILE_BYTES = 1_000_000;
|
|
@@ -329,6 +363,8 @@ export const EXT_LANG: Readonly<Record<string, Lang>> = {
|
|
|
329
363
|
'.py': 'python',
|
|
330
364
|
'.go': 'go',
|
|
331
365
|
'.java': 'java',
|
|
366
|
+
'.cs': 'csharp',
|
|
367
|
+
'.rb': 'ruby',
|
|
332
368
|
'.vue': 'typescript',
|
|
333
369
|
};
|
|
334
370
|
|
|
@@ -340,6 +376,8 @@ export const GRAMMAR_WASM: Readonly<Record<Lang, string>> = {
|
|
|
340
376
|
python: 'tree-sitter-python.wasm',
|
|
341
377
|
go: 'tree-sitter-go.wasm',
|
|
342
378
|
java: 'tree-sitter-java.wasm',
|
|
379
|
+
csharp: 'tree-sitter-c_sharp.wasm',
|
|
380
|
+
ruby: 'tree-sitter-ruby.wasm',
|
|
343
381
|
};
|
|
344
382
|
|
|
345
383
|
/** The single ABI version the pinned grammar set is built against. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Schema gate — validate a graph.json (native cache or consumer-shipped)
|
|
3
3
|
* before any consumer trusts it. Pure, no IO.
|
|
4
4
|
*/
|
|
5
|
-
import type
|
|
5
|
+
import { type CodeGraph, GUESS_RESOLVED_VIA, STATED_RESOLVED_VIA } from './types.js';
|
|
6
6
|
|
|
7
7
|
/** One space-separated string per vocabulary: this module ships in the hook bundle, paid on every dispatch. */
|
|
8
8
|
const words = (s: string): ReadonlySet<string> => new Set(s.split(' '));
|
|
@@ -10,15 +10,11 @@ const words = (s: string): ReadonlySet<string> => new Set(s.split(' '));
|
|
|
10
10
|
/** Mirrors `Relation` in types.ts. `tests` is derived by the build pass (3.2),
|
|
11
11
|
* not extracted, and is validated on exactly the same terms as the rest. */
|
|
12
12
|
const RELATIONS = words(
|
|
13
|
-
'calls imports uses inherits member tests routes-to renders includes reads migrates persists relates documents',
|
|
13
|
+
'calls imports uses inherits member tests routes-to renders includes reads migrates persists relates injects documents',
|
|
14
14
|
);
|
|
15
15
|
const CONFIDENCES = new Set(['EXTRACTED', 'INFERRED', 'AMBIGUOUS']);
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
// `foreign`: adapted from a consumer's own graph file; see `foreign.ts`.
|
|
19
|
-
const RESOLVED_VIA = words(
|
|
20
|
-
'same-file import-specifier path-alias psr4 route-table directive-scan schema-call eloquent-relation compiler-index test-import name-lookup dynamic foreign',
|
|
21
|
-
);
|
|
16
|
+
/** Every `ResolvedVia` member: the trust lists in types.ts, so the two cannot drift. */
|
|
17
|
+
const RESOLVED_VIA: ReadonlySet<string> = new Set([...STATED_RESOLVED_VIA, ...GUESS_RESOLVED_VIA]);
|
|
22
18
|
const PROVIDERS = new Set(['native', 'foreign', 'scip']);
|
|
23
19
|
const KINDS = words('file class interface trait function method constant type enum skipped route table');
|
|
24
20
|
|
|
@@ -74,6 +70,8 @@ export function validateGraph(g: unknown): ValidateResult {
|
|
|
74
70
|
if (ee['provider'] === undefined) push(`edge[${i}].provider is required`);
|
|
75
71
|
else if (!PROVIDERS.has(ee['provider'] as string))
|
|
76
72
|
push(`edge[${i}].provider invalid: ${String(ee['provider'])}`);
|
|
73
|
+
if (ee['provider_id'] !== undefined && typeof ee['provider_id'] !== 'string')
|
|
74
|
+
push(`edge[${i}].provider_id must be a string`);
|
|
77
75
|
// a source must be a known node; targets may be unresolved `symbol:` refs
|
|
78
76
|
if (typeof ee['source'] === 'string' && !ids.has(ee['source'] as string))
|
|
79
77
|
push(`edge[${i}].source is not a known node: ${String(ee['source'])}`);
|