@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
|
@@ -54,10 +54,49 @@ export interface MapRenderCheck {
|
|
|
54
54
|
pageErrors: number;
|
|
55
55
|
externalRequests: number;
|
|
56
56
|
horizontalScroll: boolean;
|
|
57
|
-
views: Array<{ view: string; labels: number; outside: number; overlapping: number }>;
|
|
57
|
+
views: Array<{ view: string; labels: number; outside: number; overlapping: number; orb?: OrbSpread }>;
|
|
58
|
+
/** Area-list entries whose name is cut off, or whose colour dot sits on a line of its own. */
|
|
59
|
+
sidebar: { entries: number; clipped: number; orphanedDot: number };
|
|
58
60
|
}>;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/**
|
|
64
|
+
* How the area bubbles of one orb view sit: overlapping near-side pairs, and how
|
|
65
|
+
* far they bunch into one cap — the length of the mean of their unit vectors on
|
|
66
|
+
* the sphere, 0 for an even spread and 1 for every bubble on one point.
|
|
67
|
+
*/
|
|
68
|
+
export interface OrbSpread {
|
|
69
|
+
bubbles: number;
|
|
70
|
+
concentration: number;
|
|
71
|
+
overlapping: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Near-side bubbles overlapping by more than a sliver count; far-side ones are seen through the near side. */
|
|
75
|
+
export function orbSpread(nodes: ReadonlyArray<{ x: number; y: number; r: number; near: boolean; u?: number[] }>): OrbSpread {
|
|
76
|
+
const near = nodes.filter((n) => n.near);
|
|
77
|
+
let overlapping = 0;
|
|
78
|
+
for (let i = 0; i < near.length; i++)
|
|
79
|
+
for (let j = i + 1; j < near.length; j++) {
|
|
80
|
+
const a = near[i] as (typeof near)[number];
|
|
81
|
+
const b = near[j] as (typeof near)[number];
|
|
82
|
+
// The viewer shrinks a crowded bubble down to a dot (ORB_DOT px): two dots touching
|
|
83
|
+
// hide nothing, so only a pair where both still read as bubbles counts.
|
|
84
|
+
if (a.r <= ORB_DOT || b.r <= ORB_DOT) continue;
|
|
85
|
+
if (Math.hypot(a.x - b.x, a.y - b.y) < (a.r + b.r) * ORB_OVERLAP_TOLERANCE) overlapping += 1;
|
|
86
|
+
}
|
|
87
|
+
const sum = [0, 0, 0];
|
|
88
|
+
for (const n of nodes) for (let k = 0; k < 3; k++) sum[k] = (sum[k] as number) + (n.u?.[k] ?? 0);
|
|
89
|
+
const concentration = nodes.length ? Math.hypot(...(sum as [number, number, number])) / nodes.length : 0;
|
|
90
|
+
return { bubbles: nodes.length, concentration: Math.round(concentration * 100) / 100, overlapping };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The smallest radius the viewer shrinks a crowded orb bubble to (`viewer.js`). */
|
|
94
|
+
const ORB_DOT = 2;
|
|
95
|
+
/** Two bubbles overlap when their centres are closer than this share of their summed radii. */
|
|
96
|
+
const ORB_OVERLAP_TOLERANCE = 0.9;
|
|
97
|
+
/** Above this, the bubbles bunch into one cap. An even spread of 30 sits near 0.1; one hemisphere at 0.5. */
|
|
98
|
+
export const ORB_MAX_CONCENTRATION = 0.35;
|
|
99
|
+
|
|
61
100
|
interface Box {
|
|
62
101
|
x: number;
|
|
63
102
|
y: number;
|
|
@@ -112,23 +151,59 @@ export async function verifyMap(htmlPath: string): Promise<MapRenderCheck> {
|
|
|
112
151
|
});
|
|
113
152
|
await page.goto(url);
|
|
114
153
|
await page.waitForFunction(() => (window as unknown as { __mapLabelBoxes?: unknown }).__mapLabelBoxes !== undefined);
|
|
115
|
-
const read = async (view: string): Promise<{ view: string; labels: number; outside: number; overlapping: number }> => {
|
|
154
|
+
const read = async (view: string): Promise<{ view: string; labels: number; outside: number; overlapping: number; orb?: OrbSpread }> => {
|
|
116
155
|
const r = (await page.evaluate(() => (window as unknown as { __mapLabelBoxes: { boxes: Box[]; width: number; height: number } }).__mapLabelBoxes)) as {
|
|
117
156
|
boxes: Box[];
|
|
118
157
|
width: number;
|
|
119
158
|
height: number;
|
|
120
159
|
};
|
|
121
|
-
|
|
160
|
+
const bubbles = await page.evaluate(
|
|
161
|
+
() => (window as unknown as { __mapOrbNodes?: Array<{ x: number; y: number; r: number; near: boolean; u?: number[] }> }).__mapOrbNodes ?? [],
|
|
162
|
+
);
|
|
163
|
+
return { view, labels: r.boxes.length, ...labelProblems(r.boxes, r.width, r.height), ...(bubbles.length ? { orb: orbSpread(bubbles) } : {}) };
|
|
122
164
|
};
|
|
165
|
+
const sidebar = await page.evaluate(() => {
|
|
166
|
+
const entries = [...document.querySelectorAll('#panel .plist button')].filter((b) => b.querySelector('.swatch'));
|
|
167
|
+
let clipped = 0;
|
|
168
|
+
let orphanedDot = 0;
|
|
169
|
+
for (const b of entries) {
|
|
170
|
+
const name = (b.querySelector('.fname') ?? b) as HTMLElement | null;
|
|
171
|
+
const dot = b.querySelector('.swatch') as HTMLElement | null;
|
|
172
|
+
if (name && name.scrollWidth > name.clientWidth + 1) clipped += 1;
|
|
173
|
+
if (name && dot && dot.getBoundingClientRect().top > name.getBoundingClientRect().bottom) orphanedDot += 1;
|
|
174
|
+
}
|
|
175
|
+
return { entries: entries.length, clipped, orphanedDot };
|
|
176
|
+
});
|
|
123
177
|
const views = [await read('overview')];
|
|
178
|
+
// The orb, as it opens and turned half a revolution: the far side's labels
|
|
179
|
+
// must stay hidden and the near side's must still fit and not overlap.
|
|
180
|
+
const orb = page.locator('#dim');
|
|
181
|
+
if ((await orb.count()) > 0) {
|
|
182
|
+
await orb.click();
|
|
183
|
+
views.push(await read('overview-orb'));
|
|
184
|
+
await page.evaluate(() => {
|
|
185
|
+
for (let i = 0; i < 21; i++) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
|
|
186
|
+
});
|
|
187
|
+
views.push(await read('overview-orb-turned'));
|
|
188
|
+
await orb.click();
|
|
189
|
+
}
|
|
124
190
|
const firstArea = page.locator('#panel .plist button').first();
|
|
125
191
|
if ((await firstArea.count()) > 0) {
|
|
126
192
|
await firstArea.click();
|
|
127
193
|
views.push(await read('largest-area'));
|
|
128
194
|
}
|
|
129
195
|
const horizontalScroll = await page.evaluate(() => document.documentElement.scrollWidth > window.innerWidth);
|
|
130
|
-
out.viewports.push({ name: vp.name, pageErrors, externalRequests, horizontalScroll, views });
|
|
131
|
-
|
|
196
|
+
out.viewports.push({ name: vp.name, pageErrors, externalRequests, horizontalScroll, views, sidebar });
|
|
197
|
+
const orbBad = (o: OrbSpread | undefined): boolean =>
|
|
198
|
+
!!o && (o.overlapping > 0 || (o.bubbles >= 6 && o.concentration > ORB_MAX_CONCENTRATION));
|
|
199
|
+
if (
|
|
200
|
+
pageErrors ||
|
|
201
|
+
externalRequests ||
|
|
202
|
+
horizontalScroll ||
|
|
203
|
+
sidebar.clipped ||
|
|
204
|
+
sidebar.orphanedDot ||
|
|
205
|
+
views.some((v) => v.outside || v.overlapping || orbBad(v.orb))
|
|
206
|
+
) {
|
|
132
207
|
out.verification = 'failed';
|
|
133
208
|
}
|
|
134
209
|
await page.close();
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
--change: #d1495b;
|
|
13
13
|
--good: #2e8b57;
|
|
14
14
|
--contested: #d98b1d;
|
|
15
|
+
--orb-light: rgba(255, 255, 255, 0.95);
|
|
16
|
+
--orb-dark: rgba(214, 220, 232, 0.55);
|
|
15
17
|
}
|
|
16
18
|
@media (prefers-color-scheme: dark) {
|
|
17
19
|
:root:not([data-theme='light']) {
|
|
@@ -28,6 +30,8 @@
|
|
|
28
30
|
--change: #ff7a8a;
|
|
29
31
|
--good: #5fd39a;
|
|
30
32
|
--contested: #ffb347;
|
|
33
|
+
--orb-light: rgba(64, 72, 90, 0.55);
|
|
34
|
+
--orb-dark: rgba(20, 22, 26, 0.2);
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
:root[data-theme='dark'] {
|
|
@@ -44,6 +48,8 @@
|
|
|
44
48
|
--change: #ff7a8a;
|
|
45
49
|
--good: #5fd39a;
|
|
46
50
|
--contested: #ffb347;
|
|
51
|
+
--orb-light: rgba(64, 72, 90, 0.55);
|
|
52
|
+
--orb-dark: rgba(20, 22, 26, 0.2);
|
|
47
53
|
}
|
|
48
54
|
* {
|
|
49
55
|
box-sizing: border-box;
|
|
@@ -203,6 +209,18 @@ aside h3 {
|
|
|
203
209
|
background: transparent;
|
|
204
210
|
padding: 2px 4px;
|
|
205
211
|
}
|
|
212
|
+
.plist button.aentry {
|
|
213
|
+
display: grid;
|
|
214
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
215
|
+
column-gap: 6px;
|
|
216
|
+
align-items: start;
|
|
217
|
+
}
|
|
218
|
+
.aentry .swatch {
|
|
219
|
+
margin: 4px 0 0;
|
|
220
|
+
}
|
|
221
|
+
.alabel {
|
|
222
|
+
min-width: 0;
|
|
223
|
+
}
|
|
206
224
|
.plist button:hover {
|
|
207
225
|
background: var(--bg);
|
|
208
226
|
}
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
search: 'Search files and areas',
|
|
30
30
|
files: function (n) { return n === 1 ? '1 file' : n + ' files'; },
|
|
31
31
|
role: 'Role',
|
|
32
|
+
linkedBy: 'Linked by provider',
|
|
32
33
|
area: 'Area',
|
|
33
34
|
usedBy: 'Used by',
|
|
34
35
|
uses: 'Uses',
|
|
@@ -56,6 +57,8 @@
|
|
|
56
57
|
zoomIn: 'Zoom in',
|
|
57
58
|
zoomOut: 'Zoom out',
|
|
58
59
|
reset: 'Fit',
|
|
60
|
+
dim3d: '3D',
|
|
61
|
+
dimHint: 'Show the map as an orb. Drag to turn it, Shift+drag to move it, arrow keys turn it too.',
|
|
59
62
|
none: 'none',
|
|
60
63
|
hint: 'Click an area to open it. Click a file to inspect it. Esc goes back.',
|
|
61
64
|
roles: {
|
|
@@ -75,6 +78,7 @@
|
|
|
75
78
|
search: 'Dateien und Bereiche suchen',
|
|
76
79
|
files: function (n) { return n === 1 ? '1 Datei' : n + ' Dateien'; },
|
|
77
80
|
role: 'Rolle',
|
|
81
|
+
linkedBy: 'Verbunden durch Provider',
|
|
78
82
|
area: 'Bereich',
|
|
79
83
|
usedBy: 'Verwendet von',
|
|
80
84
|
uses: 'Verwendet',
|
|
@@ -102,6 +106,8 @@
|
|
|
102
106
|
zoomIn: 'Vergrößern',
|
|
103
107
|
zoomOut: 'Verkleinern',
|
|
104
108
|
reset: 'Einpassen',
|
|
109
|
+
dim3d: '3D',
|
|
110
|
+
dimHint: 'Karte als Kugel zeigen. Ziehen dreht sie, Shift+Ziehen verschiebt sie, die Pfeiltasten drehen ebenfalls.',
|
|
105
111
|
none: 'keine',
|
|
106
112
|
hint: 'Bereich anklicken zum Öffnen, Datei anklicken zum Ansehen. Esc geht zurück.',
|
|
107
113
|
roles: {
|
|
@@ -168,6 +174,10 @@
|
|
|
168
174
|
var searchInput = document.getElementById('search');
|
|
169
175
|
var results = document.getElementById('results');
|
|
170
176
|
|
|
177
|
+
// The orb starts turned a little, so it reads as a sphere and not as a disc.
|
|
178
|
+
var ORB_YAW = 0.35;
|
|
179
|
+
var ORB_PITCH = -0.3;
|
|
180
|
+
|
|
171
181
|
var state = {
|
|
172
182
|
level: 'overview', // overview | area
|
|
173
183
|
area: -1,
|
|
@@ -176,7 +186,9 @@
|
|
|
176
186
|
showGuesses: false,
|
|
177
187
|
showTests: true,
|
|
178
188
|
lens: 'areas', // areas | folders
|
|
179
|
-
view: { scale: 1, x: 0, y: 0 }
|
|
189
|
+
view: { scale: 1, x: 0, y: 0 },
|
|
190
|
+
orb: false,
|
|
191
|
+
rot: { yaw: ORB_YAW, pitch: ORB_PITCH }
|
|
180
192
|
};
|
|
181
193
|
var drawn = []; // {kind, idx, x, y, r} in screen space, for hit tests
|
|
182
194
|
window.__mapLabelBoxes = { boxes: [], width: 0, height: 0 };
|
|
@@ -202,6 +214,34 @@
|
|
|
202
214
|
return [s.w / 2 + (x * k) + state.view.x, s.h / 2 + (y * k) + state.view.y];
|
|
203
215
|
}
|
|
204
216
|
|
|
217
|
+
// The orb: the precomputed 2D layout wrapped onto a sphere with the inverse
|
|
218
|
+
// Lambert azimuthal equal-area projection — the layout's centre faces the
|
|
219
|
+
// viewer, its rim wraps to the back. A node's distance from the centre and
|
|
220
|
+
// its direction are both replaced by their RANK (`share`, `turn`, 0..1),
|
|
221
|
+
// order kept, so equal shares of nodes cover equal shares of the sphere.
|
|
222
|
+
// Raw coordinates put most areas on one cap: a force layout is dense in the
|
|
223
|
+
// middle and lopsided around it.
|
|
224
|
+
function orbPoint(share, turn) {
|
|
225
|
+
var rho = 2 * Math.sqrt(share) * 0.97;
|
|
226
|
+
var phi = turn * Math.PI * 2;
|
|
227
|
+
var f = Math.sqrt(Math.max(0, 1 - (rho * rho) / 4));
|
|
228
|
+
var px = Math.cos(phi) * rho * f;
|
|
229
|
+
var py = Math.sin(phi) * rho * f;
|
|
230
|
+
var pz = 1 - (rho * rho) / 2;
|
|
231
|
+
var cy = Math.cos(state.rot.yaw);
|
|
232
|
+
var sy = Math.sin(state.rot.yaw);
|
|
233
|
+
var x1 = px * cy + pz * sy;
|
|
234
|
+
var z1 = -px * sy + pz * cy;
|
|
235
|
+
var cp = Math.cos(state.rot.pitch);
|
|
236
|
+
var sp = Math.sin(state.rot.pitch);
|
|
237
|
+
return [x1, py * cp - z1 * sp, py * sp + z1 * cp];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function orbRadius() {
|
|
241
|
+
var s = size();
|
|
242
|
+
return (Math.min(s.w, s.h) / 2 - 48) * state.view.scale;
|
|
243
|
+
}
|
|
244
|
+
|
|
205
245
|
// Visible nodes for the current level.
|
|
206
246
|
function currentNodes() {
|
|
207
247
|
var out = [];
|
|
@@ -231,12 +271,58 @@
|
|
|
231
271
|
ctx.clearRect(0, 0, s.w, s.h);
|
|
232
272
|
var nodes = currentNodes();
|
|
233
273
|
var pos = Object.create(null);
|
|
234
|
-
|
|
235
|
-
var
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
274
|
+
if (state.orb) {
|
|
275
|
+
var byReach = nodes.slice().sort(function (a, b) {
|
|
276
|
+
return (a.x * a.x + a.y * a.y) - (b.x * b.x + b.y * b.y) || a.idx - b.idx;
|
|
277
|
+
});
|
|
278
|
+
var share = Object.create(null);
|
|
279
|
+
byReach.forEach(function (n, i) { share[n.kind + n.idx] = (i + 0.5) / byReach.length; });
|
|
280
|
+
var turn = Object.create(null);
|
|
281
|
+
nodes.slice().sort(function (a, b) { return Math.atan2(a.y, a.x) - Math.atan2(b.y, b.x) || a.idx - b.idx; })
|
|
282
|
+
.forEach(function (n, i) { turn[n.kind + n.idx] = i / nodes.length; });
|
|
283
|
+
var k = orbRadius();
|
|
284
|
+
// Bubble sizes are tuned for a 300 px orb; a phone's smaller orb shrinks them with it.
|
|
285
|
+
var fit = Math.min(1, k / 300);
|
|
286
|
+
var cx = s.w / 2 + state.view.x;
|
|
287
|
+
var cyy = s.h / 2 + state.view.y;
|
|
288
|
+
nodes.forEach(function (n) {
|
|
289
|
+
var p = orbPoint(share[n.kind + n.idx], turn[n.kind + n.idx]);
|
|
290
|
+
n.u = p;
|
|
291
|
+
n.sx = cx + p[0] * k;
|
|
292
|
+
n.sy = cyy + p[1] * k;
|
|
293
|
+
n.depth = p[2];
|
|
294
|
+
// Nearer is larger and opaque; the far side recedes.
|
|
295
|
+
n.r = n.r * fit * (0.6 + 0.4 * (p[2] + 1) / 2);
|
|
296
|
+
pos[n.kind + n.idx] = n;
|
|
297
|
+
});
|
|
298
|
+
// No two near-side bubbles may cover each other: each is capped at a
|
|
299
|
+
// share of the gap to its nearest near-side neighbour.
|
|
300
|
+
var nearNodes = nodes.filter(function (n) { return n.depth > 0; });
|
|
301
|
+
if (nearNodes.length <= 400) {
|
|
302
|
+
nearNodes.forEach(function (n) {
|
|
303
|
+
var gap = Infinity;
|
|
304
|
+
nearNodes.forEach(function (o) { if (o !== n) gap = Math.min(gap, Math.hypot(o.sx - n.sx, o.sy - n.sy)); });
|
|
305
|
+
if (gap < Infinity) n.r = Math.max(2, Math.min(n.r, gap * 0.45));
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
nodes.sort(function (a, b) { return a.depth - b.depth || a.idx - b.idx; });
|
|
309
|
+
var glow = ctx.createRadialGradient(cx - k * 0.35, cyy - k * 0.35, k * 0.1, cx, cyy, k);
|
|
310
|
+
glow.addColorStop(0, cssVar('--orb-light'));
|
|
311
|
+
glow.addColorStop(1, cssVar('--orb-dark'));
|
|
312
|
+
ctx.fillStyle = glow;
|
|
313
|
+
ctx.beginPath();
|
|
314
|
+
ctx.arc(cx, cyy, k, 0, Math.PI * 2);
|
|
315
|
+
ctx.fill();
|
|
316
|
+
} else {
|
|
317
|
+
nodes.forEach(function (n) {
|
|
318
|
+
var p = toScreen(n.x, n.y);
|
|
319
|
+
n.sx = p[0];
|
|
320
|
+
n.sy = p[1];
|
|
321
|
+
pos[n.kind + n.idx] = n;
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
// 1 on the near side and in 2D, fading to 0.15 on the far side of the orb.
|
|
325
|
+
function nearness(a) { return state.orb ? 0.15 + 0.85 * (a.depth + 1) / 2 : 1; }
|
|
240
326
|
|
|
241
327
|
// Edges first.
|
|
242
328
|
var edge = cssVar('--edge');
|
|
@@ -252,11 +338,13 @@
|
|
|
252
338
|
var hot = state.area === e[0] || state.area === e[1];
|
|
253
339
|
ctx.strokeStyle = hot ? strong : edge;
|
|
254
340
|
ctx.lineWidth = 0.5 + 4 * Math.sqrt(e[2] / maxW);
|
|
341
|
+
ctx.globalAlpha = Math.min(nearness(a), nearness(b));
|
|
255
342
|
ctx.beginPath();
|
|
256
343
|
ctx.moveTo(a.sx, a.sy);
|
|
257
344
|
ctx.lineTo(b.sx, b.sy);
|
|
258
345
|
ctx.stroke();
|
|
259
346
|
});
|
|
347
|
+
ctx.globalAlpha = 1;
|
|
260
348
|
} else {
|
|
261
349
|
var drawEdges = function (list, dashed) {
|
|
262
350
|
ctx.setLineDash(dashed ? [4, 4] : []);
|
|
@@ -267,11 +355,13 @@
|
|
|
267
355
|
var hot = state.file === e[0] || state.file === e[1];
|
|
268
356
|
ctx.strokeStyle = hot ? strong : edge;
|
|
269
357
|
ctx.lineWidth = hot ? 1.8 : 0.8;
|
|
358
|
+
ctx.globalAlpha = Math.min(nearness(a), nearness(b));
|
|
270
359
|
ctx.beginPath();
|
|
271
360
|
ctx.moveTo(a.sx, a.sy);
|
|
272
361
|
ctx.lineTo(b.sx, b.sy);
|
|
273
362
|
ctx.stroke();
|
|
274
363
|
});
|
|
364
|
+
ctx.globalAlpha = 1;
|
|
275
365
|
ctx.setLineDash([]);
|
|
276
366
|
};
|
|
277
367
|
drawEdges(model.edges, false);
|
|
@@ -299,7 +389,7 @@
|
|
|
299
389
|
var f = files[n.idx];
|
|
300
390
|
color = f.role === 'test' ? areaColor(state.area, 0.45) : f.role === 'infra' ? grey : areaColor(state.area, 1);
|
|
301
391
|
}
|
|
302
|
-
ctx.globalAlpha = dim ? 0.35 : 1;
|
|
392
|
+
ctx.globalAlpha = (dim ? 0.35 : 1) * nearness(n);
|
|
303
393
|
ctx.fillStyle = color;
|
|
304
394
|
ctx.beginPath();
|
|
305
395
|
ctx.arc(n.sx, n.sy, r, 0, Math.PI * 2);
|
|
@@ -324,10 +414,15 @@
|
|
|
324
414
|
ctx.setLineDash([]);
|
|
325
415
|
}
|
|
326
416
|
ctx.globalAlpha = 1;
|
|
327
|
-
|
|
417
|
+
// The far side of the orb is seen through the near side: never a click target.
|
|
418
|
+
if (!state.orb || n.depth > 0) drawn.push({ kind: n.kind, idx: n.idx, x: n.sx, y: n.sy, r: Math.max(r, 8) });
|
|
328
419
|
});
|
|
329
420
|
|
|
330
|
-
drawLabels(nodes, s);
|
|
421
|
+
drawLabels(state.orb ? nodes.filter(function (n) { return n.depth > 0.15; }) : nodes, s);
|
|
422
|
+
// For the render check: the bubbles as drawn, with their side of the orb.
|
|
423
|
+
window.__mapOrbNodes = state.orb
|
|
424
|
+
? nodes.map(function (n) { return { x: n.sx, y: n.sy, r: n.r, near: n.depth > 0, u: n.u }; })
|
|
425
|
+
: [];
|
|
331
426
|
}
|
|
332
427
|
|
|
333
428
|
// Labels: biggest first, placed only where they fit, flipped near the right
|
|
@@ -407,6 +502,7 @@
|
|
|
407
502
|
r.views.forEach(function (v) { parts.push(base(files[v].path)); });
|
|
408
503
|
r.models.forEach(function (v) { parts.push(base(files[v].path)); });
|
|
409
504
|
r.tables.forEach(function (tb) { parts.push('⊞ ' + tb); });
|
|
505
|
+
(r.inferredTables || []).forEach(function (tb) { parts.push('⊞ ' + tb + ' ?'); });
|
|
410
506
|
li.appendChild(el('span', { class: 'fdir' }, parts.length ? '→ ' + parts.join(' → ') : '→ ' + t('none')));
|
|
411
507
|
ul.appendChild(li);
|
|
412
508
|
});
|
|
@@ -428,6 +524,7 @@
|
|
|
428
524
|
kv([
|
|
429
525
|
[t('area'), f.area >= 0 ? areas[f.area].name : '—'],
|
|
430
526
|
[t('role'), STR[lang].roles[f.role]],
|
|
527
|
+
[t('linkedBy'), f.linkedBy ? f.linkedBy.join(', ') : '—'],
|
|
431
528
|
[t('guesses'), String(f.guesses)],
|
|
432
529
|
[t('outcome'), outcomeOf[state.file] === undefined ? '—' : String(outcomeOf[state.file])],
|
|
433
530
|
[t('state'), data.meta.state]
|
|
@@ -511,9 +608,13 @@
|
|
|
511
608
|
panel.appendChild(el('h3', null, t('areas') + ' (' + areas.length + ')'));
|
|
512
609
|
var list = el('ul', { class: 'plist' });
|
|
513
610
|
areas.forEach(function (a, i) {
|
|
514
|
-
var b = el('button', { type: 'button' });
|
|
611
|
+
var b = el('button', { type: 'button', class: 'aentry', title: a.name + ' · ' + a.files.length });
|
|
515
612
|
b.appendChild(swatch(i));
|
|
516
|
-
|
|
613
|
+
var label = el('span', { class: 'alabel' });
|
|
614
|
+
var path = a.kind === 'small-parts' ? null : a.name;
|
|
615
|
+
label.appendChild(el('span', { class: 'fname' }, (path ? base(path) : a.name) + ' · ' + a.files.length));
|
|
616
|
+
if (path && dir(path) !== '.') label.appendChild(el('span', { class: 'fdir' }, dir(path)));
|
|
617
|
+
b.appendChild(label);
|
|
517
618
|
b.addEventListener('click', function () { openArea(i); });
|
|
518
619
|
var li = el('li');
|
|
519
620
|
li.appendChild(b);
|
|
@@ -615,7 +716,10 @@
|
|
|
615
716
|
renderPanel();
|
|
616
717
|
draw();
|
|
617
718
|
}
|
|
618
|
-
function resetView() {
|
|
719
|
+
function resetView() {
|
|
720
|
+
state.view = { scale: 1, x: 0, y: 0 };
|
|
721
|
+
state.rot = { yaw: ORB_YAW, pitch: ORB_PITCH };
|
|
722
|
+
}
|
|
619
723
|
function goHome() { state.level = 'overview'; state.area = -1; state.file = -1; resetView(); refresh(); }
|
|
620
724
|
function openArea(i) { state.level = 'area'; state.area = i; state.file = -1; resetView(); refresh(); }
|
|
621
725
|
function openFile(i) {
|
|
@@ -632,7 +736,10 @@
|
|
|
632
736
|
// Input.
|
|
633
737
|
var drag = null;
|
|
634
738
|
canvas.addEventListener('pointerdown', function (e) {
|
|
635
|
-
drag = {
|
|
739
|
+
drag = {
|
|
740
|
+
x: e.clientX, y: e.clientY, vx: state.view.x, vy: state.view.y,
|
|
741
|
+
yaw: state.rot.yaw, pitch: state.rot.pitch, turn: state.orb && !e.shiftKey, moved: false
|
|
742
|
+
};
|
|
636
743
|
canvas.setPointerCapture(e.pointerId);
|
|
637
744
|
});
|
|
638
745
|
canvas.addEventListener('pointermove', function (e) {
|
|
@@ -640,8 +747,16 @@
|
|
|
640
747
|
var dx = e.clientX - drag.x;
|
|
641
748
|
var dy = e.clientY - drag.y;
|
|
642
749
|
if (Math.abs(dx) + Math.abs(dy) > 3) drag.moved = true;
|
|
643
|
-
if (drag.moved)
|
|
750
|
+
if (!drag.moved) return;
|
|
751
|
+
if (drag.turn) turnTo(drag.yaw + dx * 0.008, drag.pitch - dy * 0.008);
|
|
752
|
+
else { state.view.x = drag.vx + dx; state.view.y = drag.vy + dy; draw(); }
|
|
644
753
|
});
|
|
754
|
+
// Pitch stops short of the poles, so "up" never flips under the pointer.
|
|
755
|
+
function turnTo(yaw, pitch) {
|
|
756
|
+
state.rot.yaw = yaw;
|
|
757
|
+
state.rot.pitch = Math.max(-1.4, Math.min(1.4, pitch));
|
|
758
|
+
draw();
|
|
759
|
+
}
|
|
645
760
|
canvas.addEventListener('pointerup', function (e) {
|
|
646
761
|
var wasDrag = drag && drag.moved;
|
|
647
762
|
drag = null;
|
|
@@ -660,7 +775,14 @@
|
|
|
660
775
|
state.view.scale = Math.min(12, Math.max(0.3, state.view.scale * f));
|
|
661
776
|
draw();
|
|
662
777
|
}
|
|
778
|
+
var TURN_KEYS = { ArrowLeft: [-0.15, 0], ArrowRight: [0.15, 0], ArrowUp: [0, 0.15], ArrowDown: [0, -0.15] };
|
|
663
779
|
document.addEventListener('keydown', function (e) {
|
|
780
|
+
var step = TURN_KEYS[e.key];
|
|
781
|
+
if (step && state.orb && document.activeElement !== searchInput) {
|
|
782
|
+
e.preventDefault();
|
|
783
|
+
turnTo(state.rot.yaw + step[0], state.rot.pitch + step[1]);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
664
786
|
if (e.key === 'Escape') {
|
|
665
787
|
if (document.activeElement === searchInput && searchInput.value) { searchInput.value = ''; clear(results); return; }
|
|
666
788
|
if (state.file >= 0) { state.file = -1; refresh(); } else if (state.area >= 0) goHome();
|
|
@@ -710,6 +832,13 @@
|
|
|
710
832
|
document.getElementById('z-in').addEventListener('click', function () { zoom(1.25); });
|
|
711
833
|
document.getElementById('z-out').addEventListener('click', function () { zoom(1 / 1.25); });
|
|
712
834
|
document.getElementById('z-fit').addEventListener('click', function () { resetView(); draw(); });
|
|
835
|
+
var dimButton = document.getElementById('dim');
|
|
836
|
+
dimButton.addEventListener('click', function () {
|
|
837
|
+
state.orb = !state.orb;
|
|
838
|
+
dimButton.setAttribute('aria-pressed', String(state.orb));
|
|
839
|
+
resetView();
|
|
840
|
+
draw();
|
|
841
|
+
});
|
|
713
842
|
|
|
714
843
|
var themes = ['system', 'light', 'dark'];
|
|
715
844
|
var theme = 'system';
|
|
@@ -744,6 +873,8 @@
|
|
|
744
873
|
document.getElementById('z-in').setAttribute('aria-label', t('zoomIn'));
|
|
745
874
|
document.getElementById('z-out').setAttribute('aria-label', t('zoomOut'));
|
|
746
875
|
document.getElementById('z-fit').textContent = t('reset');
|
|
876
|
+
document.getElementById('dim').textContent = t('dim3d');
|
|
877
|
+
document.getElementById('dim').setAttribute('title', t('dimHint'));
|
|
747
878
|
document.getElementById('lang').textContent = t('lang');
|
|
748
879
|
applyTheme();
|
|
749
880
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ordered post-extraction passes (ADR-298). The build runs this list and
|
|
3
|
+
* nothing else after resolution; a pass that throws is reported and skipped,
|
|
4
|
+
* and the build goes on.
|
|
5
|
+
*
|
|
6
|
+
* Order is meaning: convention providers contest base edges they prove false
|
|
7
|
+
* before anything is added on top, docs add their links, and SCIP last
|
|
8
|
+
* supersedes the name guesses it duplicates.
|
|
9
|
+
*/
|
|
10
|
+
import type { SourceFile } from './build.js';
|
|
11
|
+
import { conventionsDefault } from './conventions_default.js';
|
|
12
|
+
import { docsLayer } from './docs_layer.js';
|
|
13
|
+
import { activeProviders, type ProjectProbe } from './providers/index.js';
|
|
14
|
+
import type { ResolutionConfig } from './resolution_tiers.js';
|
|
15
|
+
import { type ScipDocument, scipEdges, supersededByScip } from './scip_reader.js';
|
|
16
|
+
import type { ActiveProviderRecord, CodeEdge, CodeNode } from './types.js';
|
|
17
|
+
|
|
18
|
+
export interface PassContext {
|
|
19
|
+
files: readonly SourceFile[];
|
|
20
|
+
docs: readonly SourceFile[];
|
|
21
|
+
scip: readonly ScipDocument[];
|
|
22
|
+
resolution: ResolutionConfig;
|
|
23
|
+
probe: ProjectProbe;
|
|
24
|
+
/** The graph so far, read-only: a pass proposes, the orchestrator applies. */
|
|
25
|
+
nodes: readonly CodeNode[];
|
|
26
|
+
edges: readonly CodeEdge[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PassFailure {
|
|
30
|
+
pass: string;
|
|
31
|
+
reason: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Contest {
|
|
35
|
+
/** Suppressed-edge counter the removed edges are reported under, if any. */
|
|
36
|
+
key?: string;
|
|
37
|
+
test: (e: CodeEdge) => boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PassResult {
|
|
41
|
+
nodes: CodeNode[];
|
|
42
|
+
edges: CodeEdge[];
|
|
43
|
+
/** Applied to the existing edges, in order, before this pass's own are added. */
|
|
44
|
+
contests?: Contest[];
|
|
45
|
+
/** Failures inside the pass that did not stop it (one provider of several). */
|
|
46
|
+
failures?: PassFailure[];
|
|
47
|
+
/** Convention providers that applied, for the graph's record. */
|
|
48
|
+
providers?: ActiveProviderRecord[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface GraphPass {
|
|
52
|
+
id: string;
|
|
53
|
+
applies(ctx: PassContext): boolean;
|
|
54
|
+
run(ctx: PassContext): PassResult;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const conventionProviders: GraphPass = {
|
|
58
|
+
id: 'convention-providers',
|
|
59
|
+
applies: (ctx) => activeProviders(ctx.probe).length > 0,
|
|
60
|
+
run: (ctx) => {
|
|
61
|
+
const out: PassResult = { nodes: [], edges: [], contests: [], failures: [], providers: [] };
|
|
62
|
+
const known = new Set(ctx.nodes.map((n) => n.id));
|
|
63
|
+
const drawn = new Set<string>();
|
|
64
|
+
for (const { provider, roots } of activeProviders(ctx.probe)) {
|
|
65
|
+
out.providers?.push({ id: provider.id, roots });
|
|
66
|
+
try {
|
|
67
|
+
const under = (p: string): boolean => roots.includes('') || roots.some((r) => p.startsWith(`${r}/`));
|
|
68
|
+
const files = ctx.files.filter((f) => under(f.path));
|
|
69
|
+
const allPaths = ctx.probe.paths.length > 0 ? ctx.probe.paths : ctx.files.map((f) => f.path);
|
|
70
|
+
const r = provider.build({
|
|
71
|
+
files,
|
|
72
|
+
baseNodes: ctx.nodes,
|
|
73
|
+
baseEdges: ctx.edges,
|
|
74
|
+
paths: allPaths.filter(under),
|
|
75
|
+
manifests: new Map([...ctx.probe.manifests].filter(([p]) => under(p))),
|
|
76
|
+
resolution: ctx.resolution,
|
|
77
|
+
roots,
|
|
78
|
+
});
|
|
79
|
+
if (r.nodes.length === 0 && r.edges.length === 0) continue;
|
|
80
|
+
if (r.contest) out.contests?.push({ key: provider.id, test: r.contest });
|
|
81
|
+
// Two providers may draw the same edge; the first keeps it.
|
|
82
|
+
for (const e of r.edges) {
|
|
83
|
+
const key = `${e.source}\0${e.relation}\0${e.target}`;
|
|
84
|
+
if (drawn.has(key)) continue;
|
|
85
|
+
drawn.add(key);
|
|
86
|
+
out.edges.push({ ...e, provider_id: provider.id });
|
|
87
|
+
}
|
|
88
|
+
// Two providers may declare the same table or template node; the first wins.
|
|
89
|
+
for (const n of r.nodes) {
|
|
90
|
+
if (known.has(n.id)) continue;
|
|
91
|
+
known.add(n.id);
|
|
92
|
+
out.nodes.push(n);
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
out.failures?.push({ pass: `convention-providers:${provider.id}`, reason: (e as Error).message });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const defaults: GraphPass = {
|
|
103
|
+
id: 'conventions-default',
|
|
104
|
+
applies: (ctx) => ctx.files.length > 0,
|
|
105
|
+
run: (ctx) => {
|
|
106
|
+
const paths = ctx.probe.paths.length > 0 ? ctx.probe.paths : [...ctx.files, ...ctx.docs].map((f) => f.path).sort();
|
|
107
|
+
return conventionsDefault({ files: ctx.files, paths, nodes: ctx.nodes, edges: ctx.edges });
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const docs: GraphPass = {
|
|
112
|
+
id: 'docs',
|
|
113
|
+
applies: (ctx) => ctx.docs.length > 0,
|
|
114
|
+
run: (ctx) => {
|
|
115
|
+
const allFiles = new Set([...ctx.files.map((f) => f.path), ...ctx.docs.map((d) => d.path)]);
|
|
116
|
+
const layer = docsLayer(ctx.docs, allFiles);
|
|
117
|
+
// An earlier pass may already have drawn a document as a link target.
|
|
118
|
+
const known = new Set(ctx.nodes.map((n) => n.id));
|
|
119
|
+
return { nodes: layer.nodes.filter((n) => !known.has(n.id)), edges: layer.edges };
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const scip: GraphPass = {
|
|
124
|
+
id: 'scip',
|
|
125
|
+
applies: (ctx) => ctx.scip.length > 0,
|
|
126
|
+
run: (ctx) => {
|
|
127
|
+
const stated = scipEdges(ctx.scip, ctx.nodes);
|
|
128
|
+
return { nodes: [], edges: stated, contests: [{ test: supersededByScip(stated, ctx.nodes) }] };
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const PASSES: readonly GraphPass[] = [conventionProviders, defaults, docs, scip];
|
|
133
|
+
|
|
134
|
+
export interface PassReport {
|
|
135
|
+
/** Edges removed by a keyed contest, per key (a provider id); only non-zero keys appear. */
|
|
136
|
+
suppressed: Record<string, number>;
|
|
137
|
+
failures: PassFailure[];
|
|
138
|
+
providers: ActiveProviderRecord[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Run every applicable pass in order, mutating `nodes` / `edges` in place. */
|
|
142
|
+
export function runPasses(
|
|
143
|
+
base: Omit<PassContext, 'nodes' | 'edges'>,
|
|
144
|
+
nodes: CodeNode[],
|
|
145
|
+
edges: CodeEdge[],
|
|
146
|
+
passes: readonly GraphPass[] = PASSES,
|
|
147
|
+
): PassReport {
|
|
148
|
+
const report: PassReport = { suppressed: {}, failures: [], providers: [] };
|
|
149
|
+
for (const pass of passes) {
|
|
150
|
+
const ctx: PassContext = { ...base, nodes, edges };
|
|
151
|
+
let result: PassResult;
|
|
152
|
+
try {
|
|
153
|
+
if (!pass.applies(ctx)) continue;
|
|
154
|
+
result = pass.run(ctx);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
report.failures.push({ pass: pass.id, reason: (e as Error).message });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
report.failures.push(...(result.failures ?? []));
|
|
160
|
+
report.providers.push(...(result.providers ?? []));
|
|
161
|
+
for (const c of result.contests ?? []) {
|
|
162
|
+
const kept = edges.filter((e) => !c.test(e));
|
|
163
|
+
const removed = edges.length - kept.length;
|
|
164
|
+
if (c.key && removed > 0) report.suppressed[c.key] = (report.suppressed[c.key] ?? 0) + removed;
|
|
165
|
+
// Element-wise: a spread of ~200k edges overflows the call stack.
|
|
166
|
+
edges.length = 0;
|
|
167
|
+
for (const e of kept) edges.push(e);
|
|
168
|
+
}
|
|
169
|
+
for (const e of result.edges) edges.push(e);
|
|
170
|
+
for (const n of result.nodes) nodes.push(n);
|
|
171
|
+
}
|
|
172
|
+
return report;
|
|
173
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a `project` argument — a repo id, a registered name or a registered
|
|
3
|
+
* path — into the root a graph verb runs against (ADR-299, step 5.1). Only a
|
|
4
|
+
* registered project resolves: an MCP caller cannot point a tool at an
|
|
5
|
+
* arbitrary directory, only at one the owner already worked in. A name two
|
|
6
|
+
* registered projects share is refused with both candidates, never guessed.
|
|
7
|
+
*/
|
|
8
|
+
import { findProjects, readRegistry } from '../_lib/project_registry.js';
|
|
9
|
+
|
|
10
|
+
export type ProjectRef = { root: string } | { error: string; candidates: string[] };
|
|
11
|
+
|
|
12
|
+
export function resolveProjectRef(ref: string): ProjectRef {
|
|
13
|
+
const reg = readRegistry();
|
|
14
|
+
const hits = findProjects(reg, ref);
|
|
15
|
+
if (hits.length === 1) return { root: (hits[0] as [string, { root: string }])[1].root };
|
|
16
|
+
if (hits.length > 1) {
|
|
17
|
+
return { error: `'${ref}' names ${hits.length} registered projects — pass an id`, candidates: hits.map(([id, e]) => `${id} ${e.root}`) };
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
error: `no registered project matches '${ref}' — \`code-graph projects list\` shows them`,
|
|
21
|
+
candidates: Object.entries(reg.projects)
|
|
22
|
+
.slice(0, 10)
|
|
23
|
+
.map(([id, e]) => `${id} ${e.root}`),
|
|
24
|
+
};
|
|
25
|
+
}
|