@descryy/core 0.1.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/LICENSE +21 -0
- package/dist/capabilities/index.d.ts +3 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +2 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/capabilities/preconditions.d.ts +99 -0
- package/dist/capabilities/preconditions.d.ts.map +1 -0
- package/dist/capabilities/preconditions.js +94 -0
- package/dist/capabilities/preconditions.js.map +1 -0
- package/dist/contracts/engine.d.ts +90 -0
- package/dist/contracts/engine.d.ts.map +1 -0
- package/dist/contracts/engine.js +291 -0
- package/dist/contracts/engine.js.map +1 -0
- package/dist/contracts/index.d.ts +9 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +5 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/orphans.d.ts +96 -0
- package/dist/contracts/orphans.d.ts.map +1 -0
- package/dist/contracts/orphans.js +241 -0
- package/dist/contracts/orphans.js.map +1 -0
- package/dist/contracts/paths.d.ts +132 -0
- package/dist/contracts/paths.d.ts.map +1 -0
- package/dist/contracts/paths.js +245 -0
- package/dist/contracts/paths.js.map +1 -0
- package/dist/contracts/shapes.d.ts +99 -0
- package/dist/contracts/shapes.d.ts.map +1 -0
- package/dist/contracts/shapes.js +196 -0
- package/dist/contracts/shapes.js.map +1 -0
- package/dist/governance/budget.d.ts +89 -0
- package/dist/governance/budget.d.ts.map +1 -0
- package/dist/governance/budget.js +101 -0
- package/dist/governance/budget.js.map +1 -0
- package/dist/governance/candidate-boundary.d.ts +49 -0
- package/dist/governance/candidate-boundary.d.ts.map +1 -0
- package/dist/governance/candidate-boundary.js +51 -0
- package/dist/governance/candidate-boundary.js.map +1 -0
- package/dist/governance/escalation.d.ts +63 -0
- package/dist/governance/escalation.d.ts.map +1 -0
- package/dist/governance/escalation.js +72 -0
- package/dist/governance/escalation.js.map +1 -0
- package/dist/governance/fact-boundary.d.ts +77 -0
- package/dist/governance/fact-boundary.d.ts.map +1 -0
- package/dist/governance/fact-boundary.js +91 -0
- package/dist/governance/fact-boundary.js.map +1 -0
- package/dist/governance/finding-funnel.d.ts +109 -0
- package/dist/governance/finding-funnel.d.ts.map +1 -0
- package/dist/governance/finding-funnel.js +137 -0
- package/dist/governance/finding-funnel.js.map +1 -0
- package/dist/governance/index.d.ts +11 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +6 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/graph/build.d.ts +127 -0
- package/dist/graph/build.d.ts.map +1 -0
- package/dist/graph/build.js +0 -0
- package/dist/graph/build.js.map +1 -0
- package/dist/graph/cross-language.d.ts +70 -0
- package/dist/graph/cross-language.d.ts.map +1 -0
- package/dist/graph/cross-language.js +104 -0
- package/dist/graph/cross-language.js.map +1 -0
- package/dist/graph/index.d.ts +11 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +6 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/merge.d.ts +95 -0
- package/dist/graph/merge.d.ts.map +1 -0
- package/dist/graph/merge.js +131 -0
- package/dist/graph/merge.js.map +1 -0
- package/dist/graph/persist.d.ts +42 -0
- package/dist/graph/persist.d.ts.map +1 -0
- package/dist/graph/persist.js +52 -0
- package/dist/graph/persist.js.map +1 -0
- package/dist/graph/runtime-confirmation.d.ts +169 -0
- package/dist/graph/runtime-confirmation.d.ts.map +1 -0
- package/dist/graph/runtime-confirmation.js +318 -0
- package/dist/graph/runtime-confirmation.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger/index.d.ts +5 -0
- package/dist/ledger/index.d.ts.map +1 -0
- package/dist/ledger/index.js +3 -0
- package/dist/ledger/index.js.map +1 -0
- package/dist/ledger/mint.d.ts +81 -0
- package/dist/ledger/mint.d.ts.map +1 -0
- package/dist/ledger/mint.js +138 -0
- package/dist/ledger/mint.js.map +1 -0
- package/dist/ledger/resolve.d.ts +124 -0
- package/dist/ledger/resolve.d.ts.map +1 -0
- package/dist/ledger/resolve.js +290 -0
- package/dist/ledger/resolve.js.map +1 -0
- package/dist/multipr/fingerprint.d.ts +56 -0
- package/dist/multipr/fingerprint.d.ts.map +1 -0
- package/dist/multipr/fingerprint.js +169 -0
- package/dist/multipr/fingerprint.js.map +1 -0
- package/dist/multipr/hidden-dependency.d.ts +120 -0
- package/dist/multipr/hidden-dependency.d.ts.map +1 -0
- package/dist/multipr/hidden-dependency.js +185 -0
- package/dist/multipr/hidden-dependency.js.map +1 -0
- package/dist/multipr/index.d.ts +14 -0
- package/dist/multipr/index.d.ts.map +1 -0
- package/dist/multipr/index.js +8 -0
- package/dist/multipr/index.js.map +1 -0
- package/dist/multipr/mechanical.d.ts +239 -0
- package/dist/multipr/mechanical.d.ts.map +1 -0
- package/dist/multipr/mechanical.js +333 -0
- package/dist/multipr/mechanical.js.map +1 -0
- package/dist/multipr/migration-heads.d.ts +160 -0
- package/dist/multipr/migration-heads.d.ts.map +1 -0
- package/dist/multipr/migration-heads.js +212 -0
- package/dist/multipr/migration-heads.js.map +1 -0
- package/dist/multipr/overlap.d.ts +186 -0
- package/dist/multipr/overlap.d.ts.map +1 -0
- package/dist/multipr/overlap.js +254 -0
- package/dist/multipr/overlap.js.map +1 -0
- package/dist/multipr/scope-store.d.ts +105 -0
- package/dist/multipr/scope-store.d.ts.map +1 -0
- package/dist/multipr/scope-store.js +182 -0
- package/dist/multipr/scope-store.js.map +1 -0
- package/dist/multipr/superseded.d.ts +133 -0
- package/dist/multipr/superseded.d.ts.map +1 -0
- package/dist/multipr/superseded.js +128 -0
- package/dist/multipr/superseded.js.map +1 -0
- package/dist/query/confirmed-facts.d.ts +56 -0
- package/dist/query/confirmed-facts.d.ts.map +1 -0
- package/dist/query/confirmed-facts.js +64 -0
- package/dist/query/confirmed-facts.js.map +1 -0
- package/dist/query/declared-value-closure.d.ts +126 -0
- package/dist/query/declared-value-closure.d.ts.map +1 -0
- package/dist/query/declared-value-closure.js +156 -0
- package/dist/query/declared-value-closure.js.map +1 -0
- package/dist/query/index.d.ts +28 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +15 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/memory.d.ts +20 -0
- package/dist/query/memory.d.ts.map +1 -0
- package/dist/query/memory.js +55 -0
- package/dist/query/memory.js.map +1 -0
- package/dist/query/provider.d.ts +127 -0
- package/dist/query/provider.d.ts.map +1 -0
- package/dist/query/provider.js +54 -0
- package/dist/query/provider.js.map +1 -0
- package/dist/query/queries.d.ts +119 -0
- package/dist/query/queries.d.ts.map +1 -0
- package/dist/query/queries.js +267 -0
- package/dist/query/queries.js.map +1 -0
- package/dist/query/refusal-fetch.d.ts +114 -0
- package/dist/query/refusal-fetch.d.ts.map +1 -0
- package/dist/query/refusal-fetch.js +200 -0
- package/dist/query/refusal-fetch.js.map +1 -0
- package/dist/query/refusal-questions.d.ts +123 -0
- package/dist/query/refusal-questions.d.ts.map +1 -0
- package/dist/query/refusal-questions.js +122 -0
- package/dist/query/refusal-questions.js.map +1 -0
- package/dist/query/root-cause-score.d.ts +218 -0
- package/dist/query/root-cause-score.d.ts.map +1 -0
- package/dist/query/root-cause-score.js +251 -0
- package/dist/query/root-cause-score.js.map +1 -0
- package/dist/query/row-closure-picture.d.ts +108 -0
- package/dist/query/row-closure-picture.d.ts.map +1 -0
- package/dist/query/row-closure-picture.js +131 -0
- package/dist/query/row-closure-picture.js.map +1 -0
- package/dist/query/similar-incidents.d.ts +76 -0
- package/dist/query/similar-incidents.d.ts.map +1 -0
- package/dist/query/similar-incidents.js +169 -0
- package/dist/query/similar-incidents.js.map +1 -0
- package/dist/query/sqlite.d.ts +13 -0
- package/dist/query/sqlite.d.ts.map +1 -0
- package/dist/query/sqlite.js +54 -0
- package/dist/query/sqlite.js.map +1 -0
- package/dist/query/traverse.d.ts +110 -0
- package/dist/query/traverse.d.ts.map +1 -0
- package/dist/query/traverse.js +0 -0
- package/dist/query/traverse.js.map +1 -0
- package/dist/query/unresolved.d.ts +195 -0
- package/dist/query/unresolved.d.ts.map +1 -0
- package/dist/query/unresolved.js +225 -0
- package/dist/query/unresolved.js.map +1 -0
- package/dist/query/verification-status.d.ts +134 -0
- package/dist/query/verification-status.d.ts.map +1 -0
- package/dist/query/verification-status.js +163 -0
- package/dist/query/verification-status.js.map +1 -0
- package/dist/recording/index.d.ts +26 -0
- package/dist/recording/index.d.ts.map +1 -0
- package/dist/recording/index.js +30 -0
- package/dist/recording/index.js.map +1 -0
- package/dist/recording/migrate.d.ts +22 -0
- package/dist/recording/migrate.d.ts.map +1 -0
- package/dist/recording/migrate.js +51 -0
- package/dist/recording/migrate.js.map +1 -0
- package/dist/recording/reader.d.ts +27 -0
- package/dist/recording/reader.d.ts.map +1 -0
- package/dist/recording/reader.js +55 -0
- package/dist/recording/reader.js.map +1 -0
- package/dist/recording/redact.d.ts +63 -0
- package/dist/recording/redact.d.ts.map +1 -0
- package/dist/recording/redact.js +98 -0
- package/dist/recording/redact.js.map +1 -0
- package/dist/recording/schema.d.ts +41 -0
- package/dist/recording/schema.d.ts.map +1 -0
- package/dist/recording/schema.js +104 -0
- package/dist/recording/schema.js.map +1 -0
- package/dist/recording/writer.d.ts +120 -0
- package/dist/recording/writer.d.ts.map +1 -0
- package/dist/recording/writer.js +81 -0
- package/dist/recording/writer.js.map +1 -0
- package/dist/scoping/fanout.d.ts +86 -0
- package/dist/scoping/fanout.d.ts.map +1 -0
- package/dist/scoping/fanout.js +95 -0
- package/dist/scoping/fanout.js.map +1 -0
- package/dist/scoping/index.d.ts +10 -0
- package/dist/scoping/index.d.ts.map +1 -0
- package/dist/scoping/index.js +6 -0
- package/dist/scoping/index.js.map +1 -0
- package/dist/scoping/score.d.ts +83 -0
- package/dist/scoping/score.d.ts.map +1 -0
- package/dist/scoping/score.js +125 -0
- package/dist/scoping/score.js.map +1 -0
- package/dist/scoping/tiers.d.ts +32 -0
- package/dist/scoping/tiers.d.ts.map +1 -0
- package/dist/scoping/tiers.js +35 -0
- package/dist/scoping/tiers.js.map +1 -0
- package/dist/scoping/traverse.d.ts +60 -0
- package/dist/scoping/traverse.d.ts.map +1 -0
- package/dist/scoping/traverse.js +172 -0
- package/dist/scoping/traverse.js.map +1 -0
- package/dist/scoping/weights.d.ts +97 -0
- package/dist/scoping/weights.d.ts.map +1 -0
- package/dist/scoping/weights.js +167 -0
- package/dist/scoping/weights.js.map +1 -0
- package/dist/sources/git/diff.d.ts +53 -0
- package/dist/sources/git/diff.d.ts.map +1 -0
- package/dist/sources/git/diff.js +140 -0
- package/dist/sources/git/diff.js.map +1 -0
- package/dist/sources/git/env.d.ts +2 -0
- package/dist/sources/git/env.d.ts.map +1 -0
- package/dist/sources/git/env.js +21 -0
- package/dist/sources/git/env.js.map +1 -0
- package/dist/sources/git/history.d.ts +140 -0
- package/dist/sources/git/history.d.ts.map +1 -0
- package/dist/sources/git/history.js +314 -0
- package/dist/sources/git/history.js.map +1 -0
- package/dist/sources/git/index.d.ts +7 -0
- package/dist/sources/git/index.d.ts.map +1 -0
- package/dist/sources/git/index.js +4 -0
- package/dist/sources/git/index.js.map +1 -0
- package/dist/sources/git/source.d.ts +95 -0
- package/dist/sources/git/source.d.ts.map +1 -0
- package/dist/sources/git/source.js +0 -0
- package/dist/sources/git/source.js.map +1 -0
- package/dist/sources/migrations/dialects.d.ts +59 -0
- package/dist/sources/migrations/dialects.d.ts.map +1 -0
- package/dist/sources/migrations/dialects.js +84 -0
- package/dist/sources/migrations/dialects.js.map +1 -0
- package/dist/sources/migrations/index.d.ts +3 -0
- package/dist/sources/migrations/index.d.ts.map +1 -0
- package/dist/sources/migrations/index.js +2 -0
- package/dist/sources/migrations/index.js.map +1 -0
- package/dist/sources/migrations/read.d.ts +113 -0
- package/dist/sources/migrations/read.d.ts.map +1 -0
- package/dist/sources/migrations/read.js +270 -0
- package/dist/sources/migrations/read.js.map +1 -0
- package/dist/sources/workspace/index.d.ts +3 -0
- package/dist/sources/workspace/index.d.ts.map +1 -0
- package/dist/sources/workspace/index.js +2 -0
- package/dist/sources/workspace/index.js.map +1 -0
- package/dist/sources/workspace/workspace.d.ts +64 -0
- package/dist/sources/workspace/workspace.d.ts.map +1 -0
- package/dist/sources/workspace/workspace.js +294 -0
- package/dist/sources/workspace/workspace.js.map +1 -0
- package/dist/store/driver/driver.d.ts +82 -0
- package/dist/store/driver/driver.d.ts.map +1 -0
- package/dist/store/driver/driver.js +31 -0
- package/dist/store/driver/driver.js.map +1 -0
- package/dist/store/driver/node-sqlite.d.ts +28 -0
- package/dist/store/driver/node-sqlite.d.ts.map +1 -0
- package/dist/store/driver/node-sqlite.js +188 -0
- package/dist/store/driver/node-sqlite.js.map +1 -0
- package/dist/store/index.d.ts +33 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +46 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/migrate.d.ts +68 -0
- package/dist/store/migrate.d.ts.map +1 -0
- package/dist/store/migrate.js +140 -0
- package/dist/store/migrate.js.map +1 -0
- package/dist/store/reader.d.ts +127 -0
- package/dist/store/reader.d.ts.map +1 -0
- package/dist/store/reader.js +269 -0
- package/dist/store/reader.js.map +1 -0
- package/dist/store/schema.d.ts +87 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +322 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/store/writer.d.ts +116 -0
- package/dist/store/writer.d.ts.map +1 -0
- package/dist/store/writer.js +279 -0
- package/dist/store/writer.js.map +1 -0
- package/dist/tiers/certify.d.ts +375 -0
- package/dist/tiers/certify.d.ts.map +1 -0
- package/dist/tiers/certify.js +434 -0
- package/dist/tiers/certify.js.map +1 -0
- package/dist/tiers/index.d.ts +5 -0
- package/dist/tiers/index.d.ts.map +1 -0
- package/dist/tiers/index.js +3 -0
- package/dist/tiers/index.js.map +1 -0
- package/dist/tiers/ladder.d.ts +73 -0
- package/dist/tiers/ladder.d.ts.map +1 -0
- package/dist/tiers/ladder.js +197 -0
- package/dist/tiers/ladder.js.map +1 -0
- package/dist/validation/config-graph.d.ts +104 -0
- package/dist/validation/config-graph.d.ts.map +1 -0
- package/dist/validation/config-graph.js +205 -0
- package/dist/validation/config-graph.js.map +1 -0
- package/dist/validation/env.d.ts +243 -0
- package/dist/validation/env.d.ts.map +1 -0
- package/dist/validation/env.js +447 -0
- package/dist/validation/env.js.map +1 -0
- package/dist/validation/index.d.ts +16 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +9 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/join-substitution.d.ts +186 -0
- package/dist/validation/join-substitution.d.ts.map +1 -0
- package/dist/validation/join-substitution.js +191 -0
- package/dist/validation/join-substitution.js.map +1 -0
- package/dist/validation/migrations.d.ts +133 -0
- package/dist/validation/migrations.d.ts.map +1 -0
- package/dist/validation/migrations.js +315 -0
- package/dist/validation/migrations.js.map +1 -0
- package/dist/validation/nodes.d.ts +32 -0
- package/dist/validation/nodes.d.ts.map +1 -0
- package/dist/validation/nodes.js +45 -0
- package/dist/validation/nodes.js.map +1 -0
- package/dist/validation/rollback.d.ts +124 -0
- package/dist/validation/rollback.d.ts.map +1 -0
- package/dist/validation/rollback.js +302 -0
- package/dist/validation/rollback.js.map +1 -0
- package/dist/validation/route-drift.d.ts +108 -0
- package/dist/validation/route-drift.d.ts.map +1 -0
- package/dist/validation/route-drift.js +103 -0
- package/dist/validation/route-drift.js.map +1 -0
- package/dist/validation/sources.d.ts +153 -0
- package/dist/validation/sources.d.ts.map +1 -0
- package/dist/validation/sources.js +409 -0
- package/dist/validation/sources.js.map +1 -0
- package/dist/worker/index.d.ts +5 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +3 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/pool.d.ts +73 -0
- package/dist/worker/pool.d.ts.map +1 -0
- package/dist/worker/pool.js +203 -0
- package/dist/worker/pool.js.map +1 -0
- package/dist/worker/protocol.d.ts +91 -0
- package/dist/worker/protocol.d.ts.map +1 -0
- package/dist/worker/protocol.js +19 -0
- package/dist/worker/protocol.js.map +1 -0
- package/dist/worker/traversal.worker.d.ts +25 -0
- package/dist/worker/traversal.worker.d.ts.map +1 -0
- package/dist/worker/traversal.worker.js +124 -0
- package/dist/worker/traversal.worker.js.map +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `AdjacencyProvider` — the interface every traversal is written against.
|
|
3
|
+
*
|
|
4
|
+
* Not an abstraction for its own sake. Two consequences follow from it and both
|
|
5
|
+
* are structural:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Most of the engine's tests need no database.** A traversal written
|
|
8
|
+
* against SQLite is a traversal that can only be tested with a file, a
|
|
9
|
+
* schema, a writer and eventually a worker thread. Written against this, the
|
|
10
|
+
* same code runs over an in-memory graph built in four lines.
|
|
11
|
+
*
|
|
12
|
+
* 2. **Traversal stays identical across backends.** §11.8 chose application-code
|
|
13
|
+
* traversal over recursive CTEs precisely so that decay scoring, budgets and
|
|
14
|
+
* per-edge weights work the same on SQLite and Postgres. That only holds if
|
|
15
|
+
* the traversal never sees which one it is talking to.
|
|
16
|
+
*
|
|
17
|
+
* `incoming()` takes a limit and a ranking rather than returning everything.
|
|
18
|
+
* That is not politeness: the prior-art graphs measured for this build have a
|
|
19
|
+
* max in-degree of **1,036** (`append`), and materialising every neighbour of a
|
|
20
|
+
* hub turns a 200-node budget into a 10,000-row read.
|
|
21
|
+
*/
|
|
22
|
+
/** The cap `nodesOfType` applies when a caller names none. */
|
|
23
|
+
export const DEFAULT_NODE_SET_LIMIT = 5_000;
|
|
24
|
+
/**
|
|
25
|
+
* Rank edges the way every caller wants them: best evidence first, stably.
|
|
26
|
+
*
|
|
27
|
+
* Resolution outranks confidence because they are not the same axis — a
|
|
28
|
+
* type-checked edge at 0.8 is a better fact than a name-matched one at 0.95, and
|
|
29
|
+
* §11A.5 exists because v2.0 could not tell those apart. `from`/`to`/`type`
|
|
30
|
+
* break the final tie so the order is identical between runs.
|
|
31
|
+
*/
|
|
32
|
+
export function rankEdges(edges) {
|
|
33
|
+
return [...edges].sort((a, b) => {
|
|
34
|
+
if (a.resolution !== b.resolution)
|
|
35
|
+
return b.resolution - a.resolution;
|
|
36
|
+
if (a.confidence !== b.confidence)
|
|
37
|
+
return b.confidence - a.confidence;
|
|
38
|
+
return `${a.from}${a.to}${a.type}`.localeCompare(`${b.from}${b.to}${b.type}`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Apply type filter, confidence floor, ranking and limit. Shared by both providers. */
|
|
42
|
+
export function applyAdjacencyOptions(edges, options = {}) {
|
|
43
|
+
const types = options.types;
|
|
44
|
+
let filtered = types === undefined || types.length === 0
|
|
45
|
+
? [...edges]
|
|
46
|
+
: edges.filter((e) => types.includes(e.type));
|
|
47
|
+
if (options.minConfidence !== undefined) {
|
|
48
|
+
const floor = options.minConfidence;
|
|
49
|
+
filtered = filtered.filter((e) => e.confidence >= floor);
|
|
50
|
+
}
|
|
51
|
+
const ranked = rankEdges(filtered);
|
|
52
|
+
return options.limit === undefined ? ranked : ranked.slice(0, Math.max(0, options.limit));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/query/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAkGH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,CAAC;AAE5C;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,KAAwB;IAChD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9B,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU;YAAE,OAAO,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;QACtE,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU;YAAE,OAAO,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;QACtE,OAAO,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,qBAAqB,CACnC,KAAwB,EACxB,UAA4B,EAAE;IAE9B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,IAAI,QAAQ,GACV,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QACvC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QACZ,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC;QACpC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IACnC,OAAO,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;AAC5F,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six query types of architecture §11.9, plus one added since: §18's
|
|
3
|
+
* `get_git_history`/`get_git_diff` named a real gap — `CHANGES_WITH` edges
|
|
4
|
+
* existed in the graph, produced by `analyze`'s git source, with no query
|
|
5
|
+
* reading them per node. `changesWith` (below) is that query. It is scoped to
|
|
6
|
+
* the stored co-change graph, never a live `git log`/`git diff` call — a new
|
|
7
|
+
* query over an existing edge type, not a new capability.
|
|
8
|
+
*
|
|
9
|
+
* Every one returns a `QueryResult`, so no caller can take the answer without
|
|
10
|
+
* the disclosure attached to it. That is the point of the envelope: §27.3's
|
|
11
|
+
* staleness rule and principle 3's resolution cap are not reporting concerns
|
|
12
|
+
* bolted on later, they are properties of the answer itself.
|
|
13
|
+
*
|
|
14
|
+
* One of the seven is **deliberately incomplete in this build** and says so —
|
|
15
|
+
* see `rootCauseSearch`. Reporting a partial answer as a whole one is the
|
|
16
|
+
* exact failure the honest-degradation rule exists to prevent.
|
|
17
|
+
*/
|
|
18
|
+
import type { EdgeType, IRNode } from "@descryy/ir";
|
|
19
|
+
import type { AdjacencyProvider, QueryResult } from "./provider.ts";
|
|
20
|
+
import { type TraversalOptions } from "./traverse.ts";
|
|
21
|
+
export interface ImpactOptions extends TraversalOptions {
|
|
22
|
+
/** Score decay per hop. Step 12 replaces this with §12.4's real weights. */
|
|
23
|
+
readonly weightFor?: (edge: {
|
|
24
|
+
confidence: number;
|
|
25
|
+
type: EdgeType;
|
|
26
|
+
}) => number;
|
|
27
|
+
readonly minScore?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function impactOf(provider: AdjacencyProvider, seeds: readonly string[], options?: ImpactOptions): QueryResult<{
|
|
30
|
+
node: IRNode;
|
|
31
|
+
depth: number;
|
|
32
|
+
score: number;
|
|
33
|
+
}[]>;
|
|
34
|
+
export interface RootCauseMatch {
|
|
35
|
+
readonly incident: IRNode;
|
|
36
|
+
readonly via: EdgeType;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Incidents correlated with a node, by graph edges only.
|
|
40
|
+
*
|
|
41
|
+
* **Incomplete by design in this build, and the caller is told so.** §11.9's
|
|
42
|
+
* root-cause query is "incident **plus embedding match**", and this build has no
|
|
43
|
+
* vector store — `embeddings` is a pointer table, since §8.2 keeps vectors in
|
|
44
|
+
* pgvector or a local extension and a column of serialised floats with no index
|
|
45
|
+
* is a scan rather than a search.
|
|
46
|
+
*
|
|
47
|
+
* So this answers the structural half and discloses the other. Returning it
|
|
48
|
+
* silently as "root cause search" would report "we found no similar incident"
|
|
49
|
+
* when the truth is "we did not look the way we said we would" — §20.2 keeps
|
|
50
|
+
* those in different categories for a reason.
|
|
51
|
+
*/
|
|
52
|
+
export declare function rootCauseSearch(provider: AdjacencyProvider, nodeId: string): QueryResult<RootCauseMatch[]>;
|
|
53
|
+
export declare function testsCovering(provider: AdjacencyProvider, nodeId: string, options?: {
|
|
54
|
+
maxDepth?: number;
|
|
55
|
+
budget?: number;
|
|
56
|
+
}): QueryResult<IRNode[]>;
|
|
57
|
+
/**
|
|
58
|
+
* The four edge types that carry data rather than control.
|
|
59
|
+
*
|
|
60
|
+
* Exported because a caller that discloses *what it walked* must name the same
|
|
61
|
+
* list this walk uses. A second copy in the MCP tool would drift, and the note
|
|
62
|
+
* would then describe a walk that did not run — the report-the-run rule applied
|
|
63
|
+
* to a constant instead of to a header.
|
|
64
|
+
*/
|
|
65
|
+
export declare const DATA_FLOW_EDGE_TYPES: readonly ["PROPAGATES_TO", "READS", "WRITES", "RETURNS"];
|
|
66
|
+
export declare function dataFlowFrom(provider: AdjacencyProvider, nodeId: string, options?: TraversalOptions): QueryResult<{
|
|
67
|
+
node: IRNode;
|
|
68
|
+
depth: number;
|
|
69
|
+
score: number;
|
|
70
|
+
}[]>;
|
|
71
|
+
export declare function traceApi(provider: AdjacencyProvider, fromNodeId: string, toNodeId: string, options?: TraversalOptions): QueryResult<{
|
|
72
|
+
path: IRNode[];
|
|
73
|
+
hops: number;
|
|
74
|
+
}>;
|
|
75
|
+
export interface ChangeRisk {
|
|
76
|
+
readonly affected: {
|
|
77
|
+
node: IRNode;
|
|
78
|
+
depth: number;
|
|
79
|
+
score: number;
|
|
80
|
+
}[];
|
|
81
|
+
readonly tests: IRNode[];
|
|
82
|
+
readonly untested: IRNode[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The scoped subgraph, ranked, intersected with `TESTS` edges.
|
|
86
|
+
*
|
|
87
|
+
* The ranking weights are Step 12's; this composes the primitives so that step
|
|
88
|
+
* supplies a weight function rather than a second traversal.
|
|
89
|
+
*/
|
|
90
|
+
export declare function changeRisk(provider: AdjacencyProvider, changed: readonly string[], options?: ImpactOptions): QueryResult<ChangeRisk>;
|
|
91
|
+
export interface CoChange {
|
|
92
|
+
readonly node: IRNode;
|
|
93
|
+
/** Commits touching both files together, within the git source's own read window. */
|
|
94
|
+
readonly support: number;
|
|
95
|
+
/** `support` divided by the more-frequently-changed file's own total — DEC-036/§11.7. */
|
|
96
|
+
readonly confidence: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* `CHANGES_WITH` edges touching one `FILE` node, in either direction.
|
|
100
|
+
*
|
|
101
|
+
* `CHANGES_WITH` is symmetric but stored once, `from` the lexicographically
|
|
102
|
+
* smaller path (DEC-036) — reading only `provider.outgoing` would silently
|
|
103
|
+
* miss every pair where this file sorts second. Both `outgoing` and
|
|
104
|
+
* `incoming` are read here so the caller never has to know which side of the
|
|
105
|
+
* pair this file landed on; `traverse.ts`'s `neighbours()` does the identical
|
|
106
|
+
* thing for `direction: "both"`, but a one-hop, unscored list is what this
|
|
107
|
+
* question actually asks — walking it through `bfs` would return `Reached`
|
|
108
|
+
* entries with no `support`/`confidence` to report, since those live in
|
|
109
|
+
* `attrs`, not in the traversal primitives' own `Reached` shape.
|
|
110
|
+
*
|
|
111
|
+
* Answers only what the stored graph holds: co-change is windowed by however
|
|
112
|
+
* far back the git source read (`GitSourceOptions`, not this function's
|
|
113
|
+
* concern), and a pair below `minSupport`/`minConfidence` at ingest time
|
|
114
|
+
* never became an edge at all — silence here does not mean the files never
|
|
115
|
+
* changed together, only that they did not clear the threshold recorded when
|
|
116
|
+
* the graph was built.
|
|
117
|
+
*/
|
|
118
|
+
export declare function changesWith(provider: AdjacencyProvider, nodeId: string): QueryResult<CoChange[]>;
|
|
119
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/query/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six query types of architecture §11.9, plus one added since: §18's
|
|
3
|
+
* `get_git_history`/`get_git_diff` named a real gap — `CHANGES_WITH` edges
|
|
4
|
+
* existed in the graph, produced by `analyze`'s git source, with no query
|
|
5
|
+
* reading them per node. `changesWith` (below) is that query. It is scoped to
|
|
6
|
+
* the stored co-change graph, never a live `git log`/`git diff` call — a new
|
|
7
|
+
* query over an existing edge type, not a new capability.
|
|
8
|
+
*
|
|
9
|
+
* Every one returns a `QueryResult`, so no caller can take the answer without
|
|
10
|
+
* the disclosure attached to it. That is the point of the envelope: §27.3's
|
|
11
|
+
* staleness rule and principle 3's resolution cap are not reporting concerns
|
|
12
|
+
* bolted on later, they are properties of the answer itself.
|
|
13
|
+
*
|
|
14
|
+
* One of the seven is **deliberately incomplete in this build** and says so —
|
|
15
|
+
* see `rootCauseSearch`. Reporting a partial answer as a whole one is the
|
|
16
|
+
* exact failure the honest-degradation rule exists to prevent.
|
|
17
|
+
*/
|
|
18
|
+
import { bfs, findPath, weightedBfs } from "./traverse.js";
|
|
19
|
+
function stampFor(provider, resolutionFloor) {
|
|
20
|
+
return { ...provider.baseStamp(), resolutionFloor };
|
|
21
|
+
}
|
|
22
|
+
function result(provider, data, resolutionFloor, truncated, notes) {
|
|
23
|
+
if (truncated) {
|
|
24
|
+
notes.push("The node budget was reached, so this answer is partial. Nodes beyond the budget " +
|
|
25
|
+
"were not examined and are not reported as clean.");
|
|
26
|
+
}
|
|
27
|
+
return { data, stamp: stampFor(provider, resolutionFloor), truncated, notes };
|
|
28
|
+
}
|
|
29
|
+
/** Resolves reached ids to nodes, dropping any the provider no longer has. */
|
|
30
|
+
function hydrate(provider, reached) {
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const entry of reached) {
|
|
33
|
+
const node = provider.node(entry.id);
|
|
34
|
+
if (node !== undefined)
|
|
35
|
+
out.push({ node, depth: entry.depth, score: entry.score });
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
export function impactOf(provider, seeds, options = {}) {
|
|
40
|
+
const outcome = weightedBfs(provider, seeds, {
|
|
41
|
+
direction: "in",
|
|
42
|
+
...options,
|
|
43
|
+
...(options.weightFor === undefined ? {} : { weightFor: options.weightFor }),
|
|
44
|
+
});
|
|
45
|
+
// Direction is `in` by default and that is not a typo: "what breaks if this
|
|
46
|
+
// changes" walks from the changed node to its *callers*, not its callees. A
|
|
47
|
+
// function's dependencies are unaffected by it changing; its dependents are
|
|
48
|
+
// the blast radius.
|
|
49
|
+
const notes = [];
|
|
50
|
+
if (outcome.resolutionFloor <= 1) {
|
|
51
|
+
notes.push(`This blast radius rests on edges resolved no better than R${outcome.resolutionFloor}. ` +
|
|
52
|
+
"Cross-file calls are not reliably resolved at that level, so the radius is a " +
|
|
53
|
+
"lower bound rather than a complete answer.");
|
|
54
|
+
}
|
|
55
|
+
return result(provider, hydrate(provider, outcome.reached), outcome.resolutionFloor, outcome.truncated, notes);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Incidents correlated with a node, by graph edges only.
|
|
59
|
+
*
|
|
60
|
+
* **Incomplete by design in this build, and the caller is told so.** §11.9's
|
|
61
|
+
* root-cause query is "incident **plus embedding match**", and this build has no
|
|
62
|
+
* vector store — `embeddings` is a pointer table, since §8.2 keeps vectors in
|
|
63
|
+
* pgvector or a local extension and a column of serialised floats with no index
|
|
64
|
+
* is a scan rather than a search.
|
|
65
|
+
*
|
|
66
|
+
* So this answers the structural half and discloses the other. Returning it
|
|
67
|
+
* silently as "root cause search" would report "we found no similar incident"
|
|
68
|
+
* when the truth is "we did not look the way we said we would" — §20.2 keeps
|
|
69
|
+
* those in different categories for a reason.
|
|
70
|
+
*/
|
|
71
|
+
export function rootCauseSearch(provider, nodeId) {
|
|
72
|
+
const types = ["INCIDENT_CORRELATED", "FIXED_BY"];
|
|
73
|
+
const matches = [];
|
|
74
|
+
let floor = 4;
|
|
75
|
+
for (const edge of [...provider.incoming(nodeId, { types }), ...provider.outgoing(nodeId, { types })]) {
|
|
76
|
+
const otherId = edge.from === nodeId ? edge.to : edge.from;
|
|
77
|
+
const node = provider.node(otherId);
|
|
78
|
+
if (node === undefined)
|
|
79
|
+
continue;
|
|
80
|
+
if (node.type !== "INCIDENT" && node.type !== "FIX_PATTERN")
|
|
81
|
+
continue;
|
|
82
|
+
if (edge.resolution < floor)
|
|
83
|
+
floor = edge.resolution;
|
|
84
|
+
matches.push({ incident: node, via: edge.type });
|
|
85
|
+
}
|
|
86
|
+
return result(provider, matches, floor, false, [
|
|
87
|
+
"Structural incident links only. Embedding similarity was not run — this build has no " +
|
|
88
|
+
"vector store (§8.2), so a similar past incident with no graph edge to this node will " +
|
|
89
|
+
"not appear here. Absence is not evidence.",
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// 3. Test discovery — "what tests cover this?"
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
export function testsCovering(provider, nodeId, options = {}) {
|
|
96
|
+
// `TESTS` edges inward, transitively: a test covering a handler also covers
|
|
97
|
+
// what the handler calls, and stopping at depth 1 would report a changed
|
|
98
|
+
// helper as untested when a test does exercise it.
|
|
99
|
+
const outcome = bfs(provider, [nodeId], {
|
|
100
|
+
direction: "in",
|
|
101
|
+
types: ["TESTS", "CALLS"],
|
|
102
|
+
maxDepth: options.maxDepth ?? 3,
|
|
103
|
+
...(options.budget === undefined ? {} : { budget: options.budget }),
|
|
104
|
+
});
|
|
105
|
+
const tests = hydrate(provider, outcome.reached)
|
|
106
|
+
.map((r) => r.node)
|
|
107
|
+
.filter((n) => n.type === "TEST_CASE");
|
|
108
|
+
const notes = [];
|
|
109
|
+
if (outcome.resolutionFloor <= 1) {
|
|
110
|
+
notes.push(`Test discovery rests on R${outcome.resolutionFloor} edges. At that level cross-file calls ` +
|
|
111
|
+
"are unresolved, so tests reaching this node indirectly may be missing from this list.");
|
|
112
|
+
}
|
|
113
|
+
if (tests.length === 0) {
|
|
114
|
+
notes.push("No covering test was found. That is not the same as this code being untested — it may " +
|
|
115
|
+
"be exercised by a test the adapter could not link.");
|
|
116
|
+
}
|
|
117
|
+
return result(provider, tests, outcome.resolutionFloor, outcome.truncated, notes);
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// 4. Data flow — "where does this field end up?"
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* The four edge types that carry data rather than control.
|
|
124
|
+
*
|
|
125
|
+
* Exported because a caller that discloses *what it walked* must name the same
|
|
126
|
+
* list this walk uses. A second copy in the MCP tool would drift, and the note
|
|
127
|
+
* would then describe a walk that did not run — the report-the-run rule applied
|
|
128
|
+
* to a constant instead of to a header.
|
|
129
|
+
*/
|
|
130
|
+
export const DATA_FLOW_EDGE_TYPES = ["PROPAGATES_TO", "READS", "WRITES", "RETURNS"];
|
|
131
|
+
export function dataFlowFrom(provider, nodeId, options = {}) {
|
|
132
|
+
const outcome = bfs(provider, [nodeId], {
|
|
133
|
+
direction: "out",
|
|
134
|
+
types: [...DATA_FLOW_EDGE_TYPES],
|
|
135
|
+
...options,
|
|
136
|
+
});
|
|
137
|
+
const notes = [];
|
|
138
|
+
if (outcome.resolutionFloor < 3) {
|
|
139
|
+
// §11A.6: data propagation compares *shapes*, not names, and shapes are an
|
|
140
|
+
// R3 capability. Below it the chain is path-level at best.
|
|
141
|
+
notes.push(`Field-level propagation requires R3; the edges here reach only R${outcome.resolutionFloor}. ` +
|
|
142
|
+
"This is a path-level answer — where data can flow, not which field arrives where.");
|
|
143
|
+
}
|
|
144
|
+
return result(provider, hydrate(provider, outcome.reached), outcome.resolutionFloor, outcome.truncated, notes);
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// 5. API tracing — "how does this work end to end?"
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
export function traceApi(provider, fromNodeId, toNodeId, options = {}) {
|
|
150
|
+
const edges = findPath(provider, fromNodeId, toNodeId, { direction: "both", ...options });
|
|
151
|
+
const ids = edges.length === 0 ? [] : [fromNodeId];
|
|
152
|
+
for (const e of edges) {
|
|
153
|
+
// Each hop may be traversed in either direction, so the far end is
|
|
154
|
+
// whichever of the two is not wherever the walk actually is right now —
|
|
155
|
+
// tracked from `ids` itself, not re-derived from the previous edge's own
|
|
156
|
+
// `.to`, which is only correct when every hop happens to go forward.
|
|
157
|
+
// `direction: "both"` exists precisely so a hop can go either way (e.g.
|
|
158
|
+
// an incoming SERVES_API followed by an outgoing CALLS), and the old
|
|
159
|
+
// `edges[i - 1]!.to` comparison silently re-picked the node just left
|
|
160
|
+
// whenever the previous hop was traversed backward, duplicating it in
|
|
161
|
+
// the path instead of advancing.
|
|
162
|
+
const current = ids[ids.length - 1];
|
|
163
|
+
ids.push(e.from === current ? e.to : e.from);
|
|
164
|
+
}
|
|
165
|
+
const path = [];
|
|
166
|
+
for (const id of ids) {
|
|
167
|
+
const node = provider.node(id);
|
|
168
|
+
if (node !== undefined)
|
|
169
|
+
path.push(node);
|
|
170
|
+
}
|
|
171
|
+
let floor = 4;
|
|
172
|
+
for (const edge of edges)
|
|
173
|
+
if (edge.resolution < floor)
|
|
174
|
+
floor = edge.resolution;
|
|
175
|
+
const notes = edges.length === 0
|
|
176
|
+
? ["No path found. The two nodes may be genuinely unconnected, or connected only by " +
|
|
177
|
+
"edges this resolution level cannot resolve."]
|
|
178
|
+
: [];
|
|
179
|
+
return result(provider, { path, hops: edges.length }, floor, false, notes);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The scoped subgraph, ranked, intersected with `TESTS` edges.
|
|
183
|
+
*
|
|
184
|
+
* The ranking weights are Step 12's; this composes the primitives so that step
|
|
185
|
+
* supplies a weight function rather than a second traversal.
|
|
186
|
+
*/
|
|
187
|
+
export function changeRisk(provider, changed, options = {}) {
|
|
188
|
+
const impact = impactOf(provider, changed, options);
|
|
189
|
+
const tests = new Map();
|
|
190
|
+
const untested = [];
|
|
191
|
+
for (const entry of impact.data) {
|
|
192
|
+
if (entry.node.type === "TEST_CASE")
|
|
193
|
+
continue;
|
|
194
|
+
const covering = provider.incoming(entry.node.id, { types: ["TESTS"] });
|
|
195
|
+
if (covering.length === 0) {
|
|
196
|
+
untested.push(entry.node);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
for (const edge of covering) {
|
|
200
|
+
const test = provider.node(edge.from);
|
|
201
|
+
if (test !== undefined)
|
|
202
|
+
tests.set(test.id, test);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const notes = [...impact.notes];
|
|
206
|
+
if (untested.length > 0) {
|
|
207
|
+
// §20.2's "not tested" category, populated rather than implied. This list is
|
|
208
|
+
// the honest half of the answer — the part a passing test run cannot cover.
|
|
209
|
+
notes.push(`${untested.length} affected node(s) have no covering test in the graph and cannot be ` +
|
|
210
|
+
"verified by re-running the suite.");
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
data: { affected: impact.data, tests: [...tests.values()], untested },
|
|
214
|
+
stamp: impact.stamp,
|
|
215
|
+
truncated: impact.truncated,
|
|
216
|
+
notes,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* `CHANGES_WITH` edges touching one `FILE` node, in either direction.
|
|
221
|
+
*
|
|
222
|
+
* `CHANGES_WITH` is symmetric but stored once, `from` the lexicographically
|
|
223
|
+
* smaller path (DEC-036) — reading only `provider.outgoing` would silently
|
|
224
|
+
* miss every pair where this file sorts second. Both `outgoing` and
|
|
225
|
+
* `incoming` are read here so the caller never has to know which side of the
|
|
226
|
+
* pair this file landed on; `traverse.ts`'s `neighbours()` does the identical
|
|
227
|
+
* thing for `direction: "both"`, but a one-hop, unscored list is what this
|
|
228
|
+
* question actually asks — walking it through `bfs` would return `Reached`
|
|
229
|
+
* entries with no `support`/`confidence` to report, since those live in
|
|
230
|
+
* `attrs`, not in the traversal primitives' own `Reached` shape.
|
|
231
|
+
*
|
|
232
|
+
* Answers only what the stored graph holds: co-change is windowed by however
|
|
233
|
+
* far back the git source read (`GitSourceOptions`, not this function's
|
|
234
|
+
* concern), and a pair below `minSupport`/`minConfidence` at ingest time
|
|
235
|
+
* never became an edge at all — silence here does not mean the files never
|
|
236
|
+
* changed together, only that they did not clear the threshold recorded when
|
|
237
|
+
* the graph was built.
|
|
238
|
+
*/
|
|
239
|
+
export function changesWith(provider, nodeId) {
|
|
240
|
+
const edges = [
|
|
241
|
+
...provider.outgoing(nodeId, { types: ["CHANGES_WITH"] }),
|
|
242
|
+
...provider.incoming(nodeId, { types: ["CHANGES_WITH"] }),
|
|
243
|
+
];
|
|
244
|
+
const byOther = new Map();
|
|
245
|
+
let floor = 4;
|
|
246
|
+
for (const edge of edges) {
|
|
247
|
+
const otherId = edge.from === nodeId ? edge.to : edge.from;
|
|
248
|
+
if (otherId === nodeId)
|
|
249
|
+
continue; // Self-pairs cannot occur (source dedupes per commit), guarded anyway.
|
|
250
|
+
const node = provider.node(otherId);
|
|
251
|
+
if (node === undefined)
|
|
252
|
+
continue;
|
|
253
|
+
if (edge.resolution < floor)
|
|
254
|
+
floor = edge.resolution;
|
|
255
|
+
const support = typeof edge.attrs?.["support"] === "number" ? edge.attrs["support"] : 0;
|
|
256
|
+
byOther.set(otherId, { node, support, confidence: edge.confidence });
|
|
257
|
+
}
|
|
258
|
+
const data = [...byOther.values()].sort((a, b) => b.support - a.support || a.node.id.localeCompare(b.node.id));
|
|
259
|
+
const notes = [];
|
|
260
|
+
if (data.length === 0) {
|
|
261
|
+
notes.push("No CHANGES_WITH edge touches this node. That may mean the file has no co-change history " +
|
|
262
|
+
"above the recorded support/confidence thresholds, or that git history was skipped when " +
|
|
263
|
+
"the graph was built — this query cannot tell the two apart from the graph alone.");
|
|
264
|
+
}
|
|
265
|
+
return result(provider, data, edges.length === 0 ? 0 : floor, false, notes);
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/query/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The backing for `refusal_fetch` — `ai-tool-contract.md` §5.4, §8 endpoint 14.
|
|
3
|
+
*
|
|
4
|
+
* ## The premise this corrects
|
|
5
|
+
*
|
|
6
|
+
* `ai-layer-plan.md` §3 originally claimed this tool was cheap because
|
|
7
|
+
* "`askableHandle` exists in core and nothing serves it." Checked against
|
|
8
|
+
* `refusal-questions.ts` rather than taken on the plan's word, both halves
|
|
9
|
+
* were false: `askableHandle` is the *askable predicate* — `blockedBy` only
|
|
10
|
+
* for `refusalClass === "value-unknown"`, `null` for `capability-gap` /
|
|
11
|
+
* `out-of-scope` / `varies-per-call` / unset — and it is already served, by
|
|
12
|
+
* `packages/mcp/src/tools/questions.ts` via `groupRefusalsIntoQuestions`.
|
|
13
|
+
* The contract's own §5.4 records the correction; this module is the new
|
|
14
|
+
* work it points at.
|
|
15
|
+
*
|
|
16
|
+
* ## Why `groupRefusalsIntoQuestions` cannot be reused here
|
|
17
|
+
*
|
|
18
|
+
* That grouper drops every row that is not `value-unknown` **by design** —
|
|
19
|
+
* correct for its own purpose (a question queue only wants rows a human
|
|
20
|
+
* sentence can close), wrong for this one. A `refusal_fetch` that silently
|
|
21
|
+
* returned only the askable subset would be the empty-denominator defect
|
|
22
|
+
* (`ai-tool-contract.md` §1's "count over something that was not there,
|
|
23
|
+
* presented as a count of zero") wearing technique 4's clothes — the same
|
|
24
|
+
* defect class this project has already hit three times elsewhere
|
|
25
|
+
* (shopizer's 0-of-370 route count, airbyte's "not on disk", koa's silent
|
|
26
|
+
* `routers.size === 0`). This module therefore counts and pages **every**
|
|
27
|
+
* `UnresolvedRef`, of every `refusalClass` and unset alike.
|
|
28
|
+
*
|
|
29
|
+
* ## Call-site trap, named so it is not rediscovered the expensive way
|
|
30
|
+
*
|
|
31
|
+
* `store/reader.ts#unresolvedRefs` defaults `classified: true`, which
|
|
32
|
+
* restricts to `attrs IS NOT NULL` — exactly the rows a producer has
|
|
33
|
+
* classified at all. A caller that wires this module up to that reader
|
|
34
|
+
* without passing `{ classified: false }` will silently exclude every
|
|
35
|
+
* *unclassified* row from `count`, reproducing the same defect this module
|
|
36
|
+
* exists to prevent, one call site later. `summariseRefusals`/
|
|
37
|
+
* `fetchRefusalPage` cannot detect that mistake — they only know the
|
|
38
|
+
* population they were handed — so the honesty of `count` here is only as
|
|
39
|
+
* good as the completeness of what a caller passes in.
|
|
40
|
+
*
|
|
41
|
+
* ## The handle
|
|
42
|
+
*
|
|
43
|
+
* Stateless and self-describing (a base64url-encoded cursor plus a content
|
|
44
|
+
* fingerprint of the population it was issued against), not a session-cache
|
|
45
|
+
* lookup — so redemption is correct across process restarts and does not
|
|
46
|
+
* need this module to keep anything in memory between calls. An MCP surface
|
|
47
|
+
* that wants a prettier, shorter token (the contract's own illustrative
|
|
48
|
+
* `ref_9f2c1a`) is free to wrap this in a session cache; that is a
|
|
49
|
+
* presentation choice for P27, not a correctness requirement for this
|
|
50
|
+
* module.
|
|
51
|
+
*
|
|
52
|
+
* `UnresolvedRef` carries no stable id once it has crossed the store
|
|
53
|
+
* boundary (`store/reader.ts#toUnresolvedRef` does not carry the row's
|
|
54
|
+
* content-hash `id` column into the IR shape), so this module derives its
|
|
55
|
+
* own deterministic ordering key from the fields the type does carry. That
|
|
56
|
+
* key is what makes "same handle + same underlying population → same page
|
|
57
|
+
* ordering every time" true regardless of what order the caller's array
|
|
58
|
+
* happens to be in — a concatenation from several producers, or a
|
|
59
|
+
* differently-ordered SQL read, must not change which row lands on which
|
|
60
|
+
* page.
|
|
61
|
+
*
|
|
62
|
+
* Redeeming a handle that cannot be decoded, or whose population fingerprint
|
|
63
|
+
* no longer matches what is being read, is `refused` with a named reason —
|
|
64
|
+
* never a page that happens to be empty, and never a page silently drawn
|
|
65
|
+
* from the wrong cursor position. That is the rule the whole contract rests
|
|
66
|
+
* on (§1: `refused` is a fact about Descry, never a guess dressed as one),
|
|
67
|
+
* and this endpoint is the easiest place in the system to violate it
|
|
68
|
+
* silently.
|
|
69
|
+
*/
|
|
70
|
+
import type { ResultState, UnresolvedRef } from "@descryy/ir";
|
|
71
|
+
/** `refusals` in the envelope (`ai-tool-contract.md` §5.4): `{ count, exemplar, handle }`. */
|
|
72
|
+
export interface RefusalSummary {
|
|
73
|
+
/** The honest denominator — every row in the population handed in, computed before any page cap. */
|
|
74
|
+
readonly count: number;
|
|
75
|
+
/** One row's `reason`, passed through unmodified — never re-worded, templated or truncated (contract §4). `null` only when `count === 0`. */
|
|
76
|
+
readonly exemplar: string | null;
|
|
77
|
+
/** Opaque; redeem with {@link fetchRefusalPage}. `null` only when `count === 0` — there is nothing to page through. */
|
|
78
|
+
readonly handle: string | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Summarise a population of refusal rows into the one-line shape the
|
|
82
|
+
* envelope carries. `refs` must already be the complete population the
|
|
83
|
+
* caller intends to disclose — see the module header's call-site trap for
|
|
84
|
+
* the classic way to under-supply it.
|
|
85
|
+
*/
|
|
86
|
+
export declare function summariseRefusals(refs: readonly UnresolvedRef[], pageSize?: number): RefusalSummary;
|
|
87
|
+
export interface RefusalPageOk {
|
|
88
|
+
readonly state: Extract<ResultState, "ok">;
|
|
89
|
+
readonly items: readonly UnresolvedRef[];
|
|
90
|
+
/** The honest total, restated on every page — the truncation rule pointed the other way (contract §5.5). */
|
|
91
|
+
readonly total: number;
|
|
92
|
+
/** Redeem for the next page. `null` on the last page. */
|
|
93
|
+
readonly nextHandle: string | null;
|
|
94
|
+
}
|
|
95
|
+
export interface RefusalPageRefused {
|
|
96
|
+
readonly state: Extract<ResultState, "refused">;
|
|
97
|
+
/** Named and specific — never a generic "invalid handle" with nothing to act on. */
|
|
98
|
+
readonly reason: string;
|
|
99
|
+
}
|
|
100
|
+
export type RefusalPage = RefusalPageOk | RefusalPageRefused;
|
|
101
|
+
/**
|
|
102
|
+
* Redeem a handle against the current population. `refs` need not be
|
|
103
|
+
* pre-sorted or supplied in the same order as the call that minted the
|
|
104
|
+
* handle — ordering is re-derived deterministically every time.
|
|
105
|
+
*
|
|
106
|
+
* Three distinct ways to be `refused`, each named: the handle cannot be
|
|
107
|
+
* decoded at all; the population's content fingerprint no longer matches
|
|
108
|
+
* (the graph moved since the handle was issued); or the cursor position the
|
|
109
|
+
* handle names does not exist in the current population (only reachable via
|
|
110
|
+
* a handle not issued by this module's own chain, since a well-formed
|
|
111
|
+
* `nextHandle` is never offered past the end).
|
|
112
|
+
*/
|
|
113
|
+
export declare function fetchRefusalPage(refs: readonly UnresolvedRef[], handle: string): RefusalPage;
|
|
114
|
+
//# sourceMappingURL=refusal-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal-fetch.d.ts","sourceRoot":"","sources":["../../src/query/refusal-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA8E9D,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B,oGAAoG;IACpG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6IAA6I;IAC7I,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,uHAAuH;IACvH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,aAAa,EAAE,EAC9B,QAAQ,GAAE,MAA0B,GACnC,cAAc,CAgBhB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,4GAA4G;IAC5G,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAChD,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAE7D;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAoC5F"}
|