@weftlet/toolkit 0.1.2 → 0.2.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.
Files changed (50) hide show
  1. package/README.md +36 -1
  2. package/THIRD_PARTY_NOTICES.md +33 -0
  3. package/dist/application/hydrator.js +18 -5
  4. package/dist/application/overlay.js +46 -9
  5. package/dist/application/saved-evidence.d.ts +1 -0
  6. package/dist/configuration/config.d.ts +4 -0
  7. package/dist/configuration/config.js +12 -0
  8. package/dist/configuration/scip-project.d.ts +9 -0
  9. package/dist/configuration/scip-project.js +40 -0
  10. package/dist/extraction/codex-auth.js +9 -2
  11. package/dist/extraction/codex-command.d.ts +8 -0
  12. package/dist/extraction/codex-command.js +81 -0
  13. package/dist/extraction/document-references.d.ts +18 -0
  14. package/dist/extraction/document-references.js +94 -0
  15. package/dist/extraction/document.d.ts +9 -2
  16. package/dist/extraction/document.js +44 -46
  17. package/dist/extraction/module-request.d.ts +3 -0
  18. package/dist/extraction/module-request.js +25 -0
  19. package/dist/extraction/routing.d.ts +14 -0
  20. package/dist/extraction/routing.js +151 -0
  21. package/dist/extraction/scip-project.d.ts +3 -0
  22. package/dist/extraction/scip-project.js +33 -0
  23. package/dist/extraction/semantic-process.js +17 -5
  24. package/dist/extraction/semantic.js +13 -2
  25. package/dist/extraction/static.d.ts +7 -3
  26. package/dist/extraction/static.js +15 -9
  27. package/dist/graph/query.js +21 -34
  28. package/dist/graph/ranking.d.ts +10 -0
  29. package/dist/graph/ranking.js +85 -0
  30. package/dist/interfaces/doctor.js +12 -3
  31. package/docs/ADR-002-saved-evidence.md +7 -1
  32. package/docs/ADR-003-draft-overlays.md +6 -0
  33. package/docs/ADR-004-rdf-query-path.md +6 -0
  34. package/docs/ADR-006-provider-boundaries.md +5 -0
  35. package/docs/ADR-010-committed-source-indexing.md +77 -0
  36. package/docs/ADR-011-explicit-document-links.md +48 -0
  37. package/docs/ADR-012-impact-ranking.md +50 -0
  38. package/docs/ADR-013-relative-module-requests.md +58 -0
  39. package/docs/ADR-014-routing-declarations.md +108 -0
  40. package/docs/CONSUMER-TRIAL.md +165 -0
  41. package/docs/ENVIRONMENT-VALIDATION.md +9 -0
  42. package/docs/IMPACT-EVALUATION.md +363 -0
  43. package/docs/PROVIDERS.md +30 -2
  44. package/docs/RELEASING.md +3 -1
  45. package/docs/ROADMAP.md +44 -4
  46. package/docs/SOURCE-INVENTORY.md +10 -0
  47. package/docs/VALIDATION.md +366 -0
  48. package/docs/VIEWER.md +5 -0
  49. package/docs/extraction-manifest.json +87 -16
  50. package/package.json +5 -2
@@ -3,6 +3,7 @@ import { contextPackEvidenceAuthority } from "./authority.js";
3
3
  import { RdfEvidenceGraph } from "./rdf.js";
4
4
  import { sha256, stableJson } from "../infrastructure/util.js";
5
5
  import { diagnosticSummary, selectDiagnostics } from "./diagnostics.js";
6
+ import { factSearchText, IMPACT_RANKING_VERSION, rankImpactFacts } from './ranking.js';
6
7
  export const DEFAULT_CONTEXT_PACK_FACTS = 60;
7
8
  export const MAX_CONTEXT_PACK_FACTS = 200;
8
9
  const contextPackLanes = [
@@ -90,21 +91,6 @@ function focusTerms(values) {
90
91
  .filter((value) => value.length >= 4 && !searchStopWords.has(value))),
91
92
  ];
92
93
  }
93
- function factSearchText(fact) {
94
- return [
95
- fact.subject.id,
96
- fact.subject.label,
97
- fact.predicate,
98
- fact.object.id,
99
- fact.object.label,
100
- fact.provenance.path,
101
- ]
102
- .join(' ')
103
- .toLowerCase();
104
- }
105
- function semanticPriority(fact) {
106
- return /codex-cli|document-linker/.test(fact.extractor.id) ? 0 : 1;
107
- }
108
94
  function boundedFactLimit(value) {
109
95
  if (value === undefined)
110
96
  return DEFAULT_CONTEXT_PACK_FACTS;
@@ -121,6 +107,7 @@ export class MarkdownContextPackRenderer {
121
107
  `Snapshot: \`${pack.snapshot.snapshotId}\` (${pack.snapshot.commitSha})`,
122
108
  `Configuration: ${pack.snapshot.configurationDigest ?? 'legacy/unknown'}; scope: ${pack.snapshot.scope?.join(', ') ?? 'legacy/unknown'}`,
123
109
  ...(pack.exploration ? [
110
+ `Ranking: ${pack.exploration.rankingVersion ?? 'legacy/unknown'}`,
124
111
  `Relationships returned: ${pack.exploration.returned}/${pack.exploration.reachableFacts}; truncated: ${pack.exploration.truncated}; depth limit reached: ${pack.exploration.depthLimitReached}`,
125
112
  ...(pack.exploration.continuation ? [`Continuation: ${pack.exploration.continuation}`] : []),
126
113
  ] : []),
@@ -211,7 +198,6 @@ export class SnapshotImpactQueryService {
211
198
  const directTerms = anchorTerms.length > 0 ? anchorTerms : focus;
212
199
  const direct = snapshot.facts.filter((fact) => this.matches(fact, directTerms));
213
200
  const ranked = new Map(direct.map((fact) => [fact.id, fact]));
214
- const distance = new Map(direct.map((fact) => [fact.id, 0]));
215
201
  let frontier = new Set(direct.flatMap((fact) => [fact.subject, fact.object]
216
202
  .filter((node) => node.kind !== 'Project')
217
203
  .map((node) => node.id)));
@@ -222,8 +208,6 @@ export class SnapshotImpactQueryService {
222
208
  if (!frontier.has(fact.subject.id) && !frontier.has(fact.object.id))
223
209
  continue;
224
210
  ranked.set(fact.id, fact);
225
- if (!distance.has(fact.id))
226
- distance.set(fact.id, depth + 1);
227
211
  for (const node of [fact.subject, fact.object]) {
228
212
  if (node.kind !== 'Project' && !visitedNodes.has(node.id))
229
213
  next.add(node.id);
@@ -233,27 +217,29 @@ export class SnapshotImpactQueryService {
233
217
  visitedNodes.add(node);
234
218
  frontier = next;
235
219
  }
236
- const ordered = [...ranked.values()].sort((left, right) => {
237
- const leftText = factSearchText(left);
238
- const rightText = factSearchText(right);
239
- const leftFocus = focus.filter((term) => leftText.includes(term)).length;
240
- const rightFocus = focus.filter((term) => rightText.includes(term)).length;
241
- return (rightFocus - leftFocus ||
242
- semanticPriority(left) - semanticPriority(right) ||
243
- (distance.get(left.id) ?? 4) - (distance.get(right.id) ?? 4) ||
244
- left.id.localeCompare(right.id));
245
- });
246
- const allBalanced = [
247
- ...selectBalancedFacts(ordered, ordered.length).facts,
248
- ...ordered.filter((fact) => contextPackLaneForFact(fact) === undefined),
249
- ];
220
+ const tiers = new Map();
221
+ for (const { fact, score } of rankImpactFacts([...ranked.values()], direct, directTerms, focus)) {
222
+ const tier = tiers.get(score) ?? [];
223
+ tier.push(fact);
224
+ tiers.set(score, tier);
225
+ }
226
+ // Balance evidence lanes among similarly ranked facts, not across unrelated distance tiers.
227
+ const allBalanced = [...tiers.values()].flatMap((tier) => [
228
+ ...selectBalancedFacts(tier, tier.length).facts,
229
+ ...tier.filter((fact) => contextPackLaneForFact(fact) === undefined),
230
+ ]);
250
231
  const { continuation: _continuation, snapshotId: _snapshotId, ...query } = request;
251
- const queryDigest = sha256(stableJson(query));
232
+ const queryDigest = sha256(stableJson({ ranking: IMPACT_RANKING_VERSION, query }));
252
233
  let offset = 0;
253
234
  if (request.continuation) {
254
235
  if (request.continuation.length > 2048)
255
236
  throw new Error('Invalid continuation');
256
237
  const token = JSON.parse(Buffer.from(request.continuation, 'base64url').toString('utf8'));
238
+ if (!token || typeof token !== 'object' || Array.isArray(token))
239
+ throw new Error('Invalid continuation');
240
+ if (token.ranking !== IMPACT_RANKING_VERSION) {
241
+ throw new Error('Continuation uses a different ranking version; restart the scan without continuation');
242
+ }
257
243
  if (token.snapshot !== snapshot.manifest.snapshotId ||
258
244
  token.digest !== snapshot.manifest.contentDigest || token.query !== queryDigest ||
259
245
  !Number.isInteger(token.offset) || token.offset < 0 || token.offset > allBalanced.length) {
@@ -266,7 +252,7 @@ export class SnapshotImpactQueryService {
266
252
  const nextOffset = offset + facts.length;
267
253
  const continuation = nextOffset < allBalanced.length ? Buffer.from(JSON.stringify({
268
254
  snapshot: snapshot.manifest.snapshotId, digest: snapshot.manifest.contentDigest,
269
- query: queryDigest, offset: nextOffset,
255
+ query: queryDigest, ranking: IMPACT_RANKING_VERSION, offset: nextOffset,
270
256
  })).toString('base64url') : undefined;
271
257
  const capabilities = [
272
258
  ...new Map(facts
@@ -357,6 +343,7 @@ export class SnapshotImpactQueryService {
357
343
  },
358
344
  diagnostics: selectDiagnostics(snapshot, request, facts, focus, queryDiagnostics),
359
345
  exploration: {
346
+ rankingVersion: IMPACT_RANKING_VERSION,
360
347
  returned: facts.length, reachableFacts: allBalanced.length, maxDepth,
361
348
  depthLimitReached: frontier.size > 0, truncated: Boolean(continuation) || frontier.size > 0,
362
349
  continuation,
@@ -0,0 +1,10 @@
1
+ import type { GraphFact } from '@weftlet/core';
2
+ /** Binds continuation offsets to the ordering policy; changing this requires restarting a scan. */
3
+ export declare const IMPACT_RANKING_VERSION = "proximity-diversity-v1";
4
+ export declare function factSearchText(fact: GraphFact): string;
5
+ export interface RankedImpactFact {
6
+ readonly fact: GraphFact;
7
+ readonly score: number;
8
+ }
9
+ /** Order an already bounded reachable set. No facts are pruned and no authority is changed. */
10
+ export declare function rankImpactFacts(facts: readonly GraphFact[], direct: readonly GraphFact[], anchorTerms: readonly string[], focus: readonly string[]): readonly RankedImpactFact[];
@@ -0,0 +1,85 @@
1
+ import { stableJson } from '../infrastructure/util.js';
2
+ /** Binds continuation offsets to the ordering policy; changing this requires restarting a scan. */
3
+ export const IMPACT_RANKING_VERSION = 'proximity-diversity-v1';
4
+ export function factSearchText(fact) {
5
+ return [fact.subject.id, fact.subject.label, fact.predicate, fact.object.id,
6
+ fact.object.label, fact.provenance.path].join(' ').toLowerCase();
7
+ }
8
+ function edgeCost(fact, forward) {
9
+ // A shared unresolved import is context, not evidence that its co-importers consume this change.
10
+ if ([fact.subject, fact.object].some((node) => node.id.startsWith('contract:module:')))
11
+ return 8;
12
+ // Follow typed definitions towards consumers cheaply; upstream dependencies remain available.
13
+ if (forward && fact.predicate === 'consumes' && fact.object.kind === 'Contract' &&
14
+ ['Component', 'Evidence'].includes(fact.subject.kind))
15
+ return 4;
16
+ return 1;
17
+ }
18
+ function stableTie(fact) {
19
+ // Unrelated commit hashes and input order must not decide which otherwise-equal file comes first.
20
+ return stableJson({ subject: fact.subject.id, predicate: fact.predicate, object: fact.object.id,
21
+ path: fact.provenance.path, start: fact.provenance.startLine, end: fact.provenance.endLine,
22
+ extractor: fact.extractor, authority: fact.authority, sourceAuthority: fact.sourceAuthority,
23
+ extractionReview: fact.extractionReview, confidence: fact.confidence });
24
+ }
25
+ /** Order an already bounded reachable set. No facts are pruned and no authority is changed. */
26
+ export function rankImpactFacts(facts, direct, anchorTerms, focus) {
27
+ const adjacent = new Map();
28
+ const connect = (from, to, cost) => {
29
+ if (from.kind === 'Project' || to.kind === 'Project')
30
+ return;
31
+ const edges = adjacent.get(from.id) ?? [];
32
+ edges.push({ to: to.id, cost });
33
+ adjacent.set(from.id, edges);
34
+ };
35
+ for (const fact of facts) {
36
+ connect(fact.subject, fact.object, edgeCost(fact, true));
37
+ connect(fact.object, fact.subject, edgeCost(fact, false));
38
+ }
39
+ const distance = new Map();
40
+ // Integer edge costs permit a bucket queue: O(edges + maximum path cost), without a heap dependency.
41
+ const pending = [];
42
+ const enqueue = (id, cost) => {
43
+ if (cost >= (distance.get(id) ?? Infinity))
44
+ return;
45
+ distance.set(id, cost);
46
+ (pending[cost] ??= []).push(id);
47
+ };
48
+ for (const fact of direct) {
49
+ const eligible = [fact.subject, fact.object].filter((node) => node.kind !== 'Project');
50
+ const matching = eligible.filter((node) => anchorTerms.some((term) => `${node.id} ${node.label}`.toLowerCase().includes(term)));
51
+ // Some adapters only identify an anchor through its citation. Keep those queries supported.
52
+ for (const node of matching.length ? matching : eligible)
53
+ enqueue(node.id, 0);
54
+ }
55
+ for (let cost = 0; cost < pending.length; cost++) {
56
+ for (const id of pending[cost] ?? []) {
57
+ if (distance.get(id) !== cost)
58
+ continue;
59
+ for (const edge of adjacent.get(id) ?? [])
60
+ enqueue(edge.to, cost + edge.cost);
61
+ }
62
+ }
63
+ const directIds = new Set(direct.map((fact) => fact.id));
64
+ const rows = facts.map((fact) => {
65
+ const text = factSearchText(fact);
66
+ return {
67
+ fact,
68
+ proximity: directIds.has(fact.id) ? 0 : Math.min((distance.get(fact.subject.id) ?? Infinity) + edgeCost(fact, true), (distance.get(fact.object.id) ?? Infinity) + edgeCost(fact, false)),
69
+ focus: focus.filter((term) => text.includes(term)).length,
70
+ tie: stableTie(fact),
71
+ };
72
+ });
73
+ const lexical = (a, b) => a < b ? -1 : a > b ? 1 : 0;
74
+ const compare = (a, b) => b.focus - a.focus || lexical(a.tie, b.tie) || lexical(a.fact.id, b.fact.id);
75
+ rows.sort((a, b) => a.proximity - b.proximity || compare(a, b));
76
+ const counts = new Map();
77
+ const diverse = rows.map((row) => {
78
+ const seen = counts.get(row.fact.provenance.path) ?? 0;
79
+ counts.set(row.fact.provenance.path, seen + 1);
80
+ // Each additional fact from one source costs one ordinary edge. Details stay on later pages.
81
+ return { ...row, score: row.proximity + seen };
82
+ });
83
+ return diverse.sort((a, b) => a.score - b.score || compare(a, b))
84
+ .map(({ fact, score }) => ({ fact, score }));
85
+ }
@@ -3,14 +3,23 @@ import { promisify } from 'node:util';
3
3
  import { loadConfig } from "../configuration/config.js";
4
4
  import { resolveStorageRoot } from "../configuration/cache.js";
5
5
  import { FileSnapshotStore } from "../storage/storage.js";
6
+ import { resolveCodexInvocation } from '../extraction/codex-command.js';
6
7
  import { readCodexLoginMode, semanticCommandEnvironment } from '../extraction/codex-auth.js';
7
8
  /** Reads readiness only. It never installs tools, prints credentials, or starts a model. */
8
9
  export async function diagnoseSetup(configPath) {
9
10
  const { config, repositoryRoot } = await loadConfig(configPath);
10
11
  const execute = promisify(execFile);
12
+ const environment = semanticCommandEnvironment(process.env);
11
13
  const probe = async (name, args) => {
12
14
  try {
13
- const result = await execute(name, args, { cwd: repositoryRoot, timeout: 5000, maxBuffer: 8192 });
15
+ const started = performance.now();
16
+ const invocation = name === 'codex' ? await resolveCodexInvocation(name, environment) : { command: name, prefixArgs: [] };
17
+ const remaining = Math.ceil(5000 - (performance.now() - started));
18
+ if (remaining <= 0)
19
+ return { available: false, output: '' };
20
+ const result = await execute(invocation.command, [...invocation.prefixArgs, ...args], {
21
+ cwd: repositoryRoot, timeout: remaining, maxBuffer: 8192, windowsHide: true, env: name === 'codex' ? environment : process.env,
22
+ });
14
23
  return { available: true, output: `${result.stdout}${result.stderr}` };
15
24
  }
16
25
  catch {
@@ -18,13 +27,13 @@ export async function diagnoseSetup(configPath) {
18
27
  }
19
28
  };
20
29
  const git = await probe('git', ['--version']);
21
- const codex = await probe(process.platform === 'win32' ? 'codex.exe' : 'codex', ['--version']);
30
+ const codex = await probe('codex', ['--version']);
22
31
  const auth = config.semantic.auth;
23
32
  const authMode = config.semantic.provider === 'codex-cli' ? auth?.mode ?? 'chatgpt' : undefined;
24
33
  const apiKeyEnv = auth?.mode === 'api-key' ? auth.apiKeyEnv ?? 'CODEX_API_KEY' : undefined;
25
34
  const apiKeyPresent = apiKeyEnv ? Boolean(process.env[apiKeyEnv]?.trim()) : undefined;
26
35
  const loginMode = codex.available && authMode === 'chatgpt'
27
- ? await readCodexLoginMode(process.platform === 'win32' ? 'codex.exe' : 'codex', semanticCommandEnvironment(process.env))
36
+ ? await readCodexLoginMode('codex', environment)
28
37
  : undefined;
29
38
  const docker = config.ontology.backend === 'opencaesar' ? await probe('docker', ['info']) : undefined;
30
39
  const saved = await new FileSnapshotStore(resolveStorageRoot(repositoryRoot, config)).readHead('default');
@@ -6,12 +6,18 @@ Status: implemented, with bounded validation.
6
6
  Explicit hydration produces committed evidence in disposable archive directories, without
7
7
  registering worktrees or requiring the active checkout to be clean. Symlinks/submodules and
8
8
  archives larger than 128 MiB are currently rejected; they are not silently partially indexed.
9
- The archive path requires `git` and `tar`; only Linux is validated.
9
+ The archive path requires `git` and `tar`; Linux and focused hosted Windows checks are
10
+ recorded in [validation](VALIDATION.md). [Committed-tree materialization](../src/repository/committed-tree.ts)
11
+ enforces those isolation and archive bounds.
10
12
 
11
13
  Snapshot IDs bind configuration and semantic scope in addition to commit and runtime revisions.
12
14
  User-scope cache heads are isolated by checkout identity. Snapshots publish via an atomic
13
15
  create-if-absent operation; head pointers replace atomically only after a complete snapshot
14
16
  exists. Concurrent conflicting content fails instead of replacing a winner.
17
+ [Snapshot storage](../src/storage/storage.ts) and [atomic replacement](../src/storage/atomic-rename.ts)
18
+ implement publication; [hydration](../src/application/hydrator.ts) validates evidence before
19
+ advancing the head. [Lifecycle tests](../tests/snapshot-lifecycle.test.ts) exercise concurrent
20
+ publication and saved querying during edits; their presence alone is not a passing run.
15
21
 
16
22
  Queries identify source commit, scope, configuration match and excluded working changes.
17
23
  Final checks reject dirty working trees, old commits, configuration mismatch and draft overlays.
@@ -6,6 +6,8 @@ Status: implemented as a bounded submilestone.
6
6
  bytes against checkout HEAD. Identity binds base SHA, base snapshot digest and captured content
7
7
  digest, including deletions. Code is reindexed in an independent temporary Git repository;
8
8
  the active checkout and its index/branch remain unchanged.
9
+ [Working-content capture](../src/repository/working-content.ts) reads the selected bytes;
10
+ [overlay assembly](../src/application/overlay.ts) performs replacement and publication.
9
11
 
10
12
  All implementation relationships are rebuilt, so consumer links cannot retain deleted/renamed
11
13
  definitions. Affected document assertions and links are invalidated. No model is called and no
@@ -15,6 +17,10 @@ are omitted. Code facts are explicitly draft, including in the viewer.
15
17
  Overlay heads are separate from committed heads and isolated by checkout and agent name.
16
18
  Select the returned snapshot ID explicitly for querying. Overlay snapshots never satisfy final
17
19
  committed-evidence checks.
20
+ [Saved-evidence querying](../src/application/saved-evidence.ts) reports overlay freshness,
21
+ and [snapshot checks](../src/application/snapshot-checks.ts) reject draft evidence for final
22
+ validation. [Overlay tests](../src/application/overlay.test.ts) exercise edits, deletion,
23
+ renames and checkout isolation; this link does not assert a current passing test result.
18
24
 
19
25
  Bounds: 500 changed paths, 1 MiB per source file, source/configuration scope must match the base.
20
26
  Changes outside configured source roots are reported as excluded. This first overlay implementation
@@ -7,6 +7,8 @@ an RDF named graph, with reified provenance, authority, confidence, source diges
7
7
  configuration metadata. A structured record preserves compatibility fields. The traversal
8
8
  projection is read through SPARQL and has field-for-field equivalence tests, including order.
9
9
  The viewer consumes the resulting Context Pack, not an independently extracted graph.
10
+ [RDF projection](../src/graph/rdf.ts) implements the dataset;
11
+ [RDF integration tests](../tests/rdf.test.ts) check projection equivalence and cross-source linkage.
10
12
 
11
13
  Comunica executes local SELECT queries. Parsed queries reject remote SERVICE/dataset access
12
14
  and updates. `rdf-validate-shacl` checks structural integrity and identity-kind consistency.
@@ -19,6 +21,10 @@ depth exhaustion, result truncation and snapshot/query-bound continuation tokens
19
21
  results using `--continuation`. Depth exhaustion requires an explicit larger `--max-depth`;
20
22
  the result never represents that partial traversal as complete.
21
23
  The graph projection itself has a fail-closed 100,000-fact bound.
24
+ [Impact querying](../src/graph/query.ts) produces the [core Context Pack contract](../packages/core/src/core.ts).
25
+ The [MCP interface](../src/interfaces/mcp.ts) exposes that result to agents and the
26
+ [viewer renderer](../src/visualization/visualization.ts) consumes it. [Query tests](../src/graph/query.test.ts)
27
+ exercise bounded retrieval; linked tests are evidence locations, not proof of a passing run.
22
28
 
23
29
  Only paths explicitly listed in `approvedIntentSources` receive `sourceAuthority: declared`.
24
30
  Their extracted assertions remain `authority: inferred` and `extractionReview: unreviewed`.
@@ -9,6 +9,11 @@ CLI subprocesses have cancellation, timeouts, bounded output acceptance and narr
9
9
  Errors report safe status metadata instead of private stderr. Hydration limits source scope
10
10
  before invoking a provider, stops new work after failures, and reuses immutable artifacts.
11
11
  Reasoning settings now participate in Codex cache identity.
12
+ [Semantic hydration](../src/extraction/semantic.ts) owns artifact reuse and validation;
13
+ [Codex command discovery](../src/extraction/codex-command.ts) resolves native/npm launchers,
14
+ and [authentication configuration](../src/extraction/codex-auth.ts) keeps login and API-key modes explicit.
15
+ [Launcher tests](../src/extraction/codex-command.test.ts) exercise synthetic executables without
16
+ claiming live model quality or account authentication.
12
17
 
13
18
  These changes provide an extension seam, not a claim of interoperability with arbitrary models.
14
19
  Additional real adapters require their own live and deterministic tests. The current cloud's
@@ -0,0 +1,77 @@
1
+ # ADR 010: explicit committed source projects for TypeScript indexing
2
+
3
+ Status: implemented as an opt-in indexing mode. No product-policy approval or new hard
4
+ architecture rule is implied.
5
+
6
+ Weftlet's self-evaluation found no SCIP test relationships and no linked shared core
7
+ contract. The build tsconfig excludes tests, and a disposable Git archive has no installed
8
+ workspace links or compiled package outputs. Merely listing `evidenceRoots` does not make
9
+ the TypeScript indexer include those files or resolve their package imports.
10
+
11
+ Keep ordinary SCIP invocation unchanged. Add `scip.sourceProject` for explicitly selecting
12
+ committed TypeScript files and mapping exact local module names to committed source entry
13
+ points. Generate a separate compiler configuration only in the disposable archive. This
14
+ allows indexing an older SHA without adding a new tsconfig to that commit, installing
15
+ dependencies, running package scripts, rebuilding the consumer, or borrowing dirty files
16
+ or workspace links from its active checkout.
17
+
18
+ ## Configuration
19
+
20
+ ```json
21
+ {
22
+ "scip": {
23
+ "version": "0.4.0",
24
+ "command": "bundled-scip-typescript",
25
+ "args": ["index", "--no-global-caches"],
26
+ "sourceProject": {
27
+ "include": ["src/**/*.ts", "tests/**/*.ts", "packages/core/src/**/*.ts"],
28
+ "aliases": { "@weftlet/core": "packages/core/src/index.ts" }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ Paths and globs are relative to the configured repository root, using `/`. Globs support
35
+ `*`, `**` and `?`; list files directly when appropriate. `.ts`, `.tsx`, `.mts` and `.cts`
36
+ files are supported by this selector. Add separate globs for each extension needed.
37
+ Aliases are exact module names, including explicit package subpaths, rather than wildcards.
38
+ Alias targets must exist in the selected commit and match `include`.
39
+
40
+ Select the files that **define** the contracts as well as their consumers. The pinned SCIP
41
+ indexer emits documents for explicitly selected files; resolving an import does not
42
+ automatically index every file behind it. For example, if an entry point re-exports a type
43
+ from another file, include that definition file too. There is no inferred mapping from
44
+ package `dist` exports to source; the configuration author supplies the source entry point.
45
+
46
+ This separate program uses ES2022, NodeNext module resolution, preserved JSX, no emit,
47
+ no ambient `types` packages, and the supplied aliases. It does not inherit build tsconfig
48
+ settings. Missing external dependencies and custom build/compiler behavior remain limits;
49
+ this is local symbol analysis, **not** a successful type check or proof of complete dependency
50
+ resolution. Use the existing `scip.args` path with a committed dedicated tsconfig when that
51
+ configuration is more appropriate. Do not combine `sourceProject` with custom commands,
52
+ workspace discovery, inferred configs or additional CLI arguments: this first mode requires
53
+ exactly the command and args above and rejects conflicting choices.
54
+
55
+ Bounds: 1–100 include patterns, at most 100 aliases and 1–5,000 selected committed files.
56
+ Paths cannot be absolute or escape the repository. Empty selections, missing/unselected
57
+ alias targets and the reserved `.weftlet-scip-source-project.json` path fail before SCIP
58
+ starts. Existing archive protections against symlinks, submodules and oversized archives
59
+ remain in force. The generated project is removed with its disposable tree.
60
+
61
+ Configuration participates in snapshot/cache identity. Existing immutable snapshots remain
62
+ queryable. Source-project patterns also participate in change tracking and overlay capture,
63
+ including files outside `codeRoots`/`evidenceRoots`. Changed code stays draft; rename/delete
64
+ reindexing replaces relationships. Neither test-file inclusion nor a typed test reference
65
+ establishes that a test ran or passed.
66
+
67
+ ## Evidence and remaining work
68
+
69
+ Real SCIP regressions cover a workspace entry point, re-exported contract, a test excluded
70
+ by the build config, dirty and ignored files, repeat digest stability, committed renames
71
+ and deletions, and draft capture outside ordinary code roots. A fresh packed consumer
72
+ checks the new runtime assets and an excluded test resolved through a source alias.
73
+
74
+ The [fixed self-repository evaluation](IMPACT-EVALUATION.md) adds this profile without
75
+ changing its source SHA, expected files, query budgets or ranking. It improves candidate
76
+ retrieval and supplies typed test/core-contract relationships. It does not resolve prose
77
+ intent linkage, external dependency installation, live model quality or query-ranking gaps.
@@ -0,0 +1,48 @@
1
+ # ADR 011: explicit document links with committed targets
2
+
3
+ Status: implemented; source approval and live semantic review remain separate.
4
+
5
+ Intent documents can name implementation and test locations using ordinary Markdown links.
6
+ Weftlet parses CommonMark with `mdast-util-from-markdown`, rather than treating every slash
7
+ in prose, code examples and URLs as a repository relationship. Inline and reference-style
8
+ links resolve relative to the source document; a leading `/` denotes the repository root.
9
+ Fragments select the file identity only, not a validated target heading or line. Percent-encoded
10
+ paths are decoded. External URLs, local anchors, images, HTML, fenced code and unused link
11
+ reference definitions do not create mappings. MDX extensions are not interpreted.
12
+
13
+ For compatibility, literal slash-separated paths in prose and single paths in inline code
14
+ resolve from the repository root. `./` and `../` make a literal path document-relative.
15
+ Use Markdown links for spaces or punctuation in paths. There is no basename search, implicit
16
+ root fallback, extension guessing, directory expansion, remote fetch or active-checkout lookup.
17
+ A stale Markdown link, linked directory or path escaping the repository produces a cited
18
+ `implementationGaps` diagnostic, not a relationship to a guessed target. Unresolved literal
19
+ mentions are ignored: slash-separated prose, package names and examples are not missing-file
20
+ declarations. Use Markdown links when targets need validation. Diagnostics describe extraction gaps;
21
+ this increment does not turn them into hard policy gates. Links to a document verify only
22
+ that its file exists, not that it was selected for semantic extraction.
23
+
24
+ `document-linker@2.0.0` emits `references` facts using exact committed file identities and the
25
+ source link's line span. Reference-style links cite the definition containing the destination.
26
+ Test filenames use the same Evidence kind as SCIP and repository-file extraction. Distinct
27
+ passages remain distinct facts; repeated identical links within one passage deduplicate.
28
+ Containment of model assertions now cites the assertion's passage, rather than the entire doc.
29
+ Missing targets are rechecked on every committed refresh even when semantic artifacts are reused.
30
+
31
+ A reference means that the document points to a file. It does not prove that the file fulfills
32
+ intent, that a test passed, or that its prose is correct. Facts stay `authority: inferred`;
33
+ explicit links are `extractionReview: deterministic`. Model containment stays `unreviewed`.
34
+ Only explicit `approvedIntentSources` configuration sets `sourceAuthority: declared`, including
35
+ for links; source approval never approves a model assertion. The Weftlet evaluation continues
36
+ to configure zero approved sources and zero model assertions.
37
+
38
+ Snapshot assembly advances from `graph-v2` to `graph-v3-links-2.0.0` and binds the linker version
39
+ in both identity and manifest. Refreshes create a different immutable identity; saved v2 snapshots remain readable
40
+ without a provider and are not rewritten. Semantic cache keys are unchanged because model
41
+ extraction is unchanged. Existing overlays conservatively invalidate document references to
42
+ changed/deleted targets and changed prose; they do not invent new approved mappings.
43
+
44
+ The six evaluation documents now have contributor-facing links attached to descriptions of
45
+ actual responsibilities. These are reviewable navigation evidence, not newly approved policy.
46
+ The original frozen baseline remains intact. A measurement using the new documents changes
47
+ its evidence input and must not be presented as a model-quality or ranking improvement.
48
+ Query ranking, automatic scope expansion and live provider extraction are separate work.
@@ -0,0 +1,50 @@
1
+ # ADR 012: order impact evidence by proximity and source diversity
2
+
3
+ Status: implemented as a query-only heuristic; not product policy or an evidence gate.
4
+
5
+ The self-repository evaluation found distractions connected through common imports such as
6
+ `node:fs/promises`. The previous ordering prioritized keyword counts, then Codex/document-link
7
+ extractor names, before hop distance. Global lane balancing could also bring a distant fact
8
+ forward ahead of close evidence. A large file could contribute many symbol facts before a
9
+ useful downstream consumer appeared.
10
+
11
+ Keep snapshot extraction, RDF projection, anchor matching, the reachable set, max-depth rules
12
+ and continuation coverage unchanged. Rank the existing bounded set in `graph/ranking.ts`:
13
+
14
+ - Direct matches start at zero. Other facts use shortest weighted paths from matching anchor
15
+ nodes. Adapters that identify an anchor only through citations retain endpoint fallback.
16
+ - Ordinary relationships cost one. Following a Component/Evidence `consumes` edge upstream
17
+ to a typed Contract costs four; following it towards a consumer costs one. This favors
18
+ downstream consumers while retaining shared contracts and their definitions.
19
+ - Edges touching a generic `contract:module:` import node cost eight in both directions.
20
+ Sharing a module specifier is weaker impact context than a resolved typed consumer or
21
+ explicit product relationship. No module name or evaluation target is special-cased.
22
+ - Each additional fact from one provenance path adds one to its ranking score. This trades
23
+ some repeated detail for earlier evidence from other sources. It is not a per-file cap.
24
+ - Within equal scores, task terms break ties and evidence lanes are balanced. Remaining
25
+ ties use structural/citation fields with code-point ordering, not commit-derived IDs or
26
+ runtime locale. Fact IDs only disambiguate otherwise indistinguishable records.
27
+
28
+ These ordinal weights are reviewable heuristics, not calibrated dependency probabilities.
29
+ They neither change confidence/authority nor claim that proximity proves a product dependency.
30
+ There is no special preference for a model provider, automatic promotion of approved intent,
31
+ new traversal cutoff, or removal of a weak fact. Page through to inspect lower-ranked evidence;
32
+ existing depth exhaustion remains explicit. Snapshot facts, provenance and identities are
33
+ unchanged, so saved evidence can be rescored without an indexer, compiler, provider or refresh.
34
+
35
+ Responses include `exploration.rankingVersion: proximity-diversity-v1`; the Markdown renderer
36
+ also identifies it. Continuations bind this version as well as snapshot identity/content and
37
+ request. Old or different ranking tokens fail with an instruction to restart the scan, rather
38
+ than reusing an offset against a changed order. Changing this policy requires a version bump.
39
+ The evaluator records the version and rejects changes between pages.
40
+
41
+ Tests cover many typed consumers of a large source, deep asynchronous relationships, common
42
+ imports with attractive keywords, citation-only adapters, stable order across input shuffles
43
+ and changed commit-derived IDs, full pagination, and old-token rejection. Real TypeScript/RDF
44
+ pipeline integration and packed-consumer queries exercise the same ordering path.
45
+
46
+ Compare against the exact saved document-link snapshot and unchanged four cases, 60 facts per
47
+ page, three pages, depth 32. Record candidate retrieval and labeled distractions together.
48
+ Even a successful comparison cannot establish exhaustive precision, prove a safe change, or
49
+ replace a broader semantic-quality evaluation. Large-graph performance and interactive browser
50
+ validation remain separate work.
@@ -0,0 +1,58 @@
1
+ # ADR 013: scope relative module request identities
2
+
3
+ Status: implemented as a correction to literal import evidence. Query ranking is unchanged.
4
+
5
+ The frozen dispatch evaluation found that two planners importing `./policy.js` shared the
6
+ same generic contract node, although they were in different directories and SCIP correctly
7
+ identified separate policy symbols. This false connection brought refund and audit files
8
+ into dispatch scans. Adjusting ranking cannot correct that underlying identity.
9
+
10
+ `repository-file-extractor@2.0.0` keeps a literal module request separate from a resolved
11
+ implementation. For ordinary `./` and `../` requests (including `.` and `..`), combine the
12
+ importer's directory with the specifier and normalize POSIX path segments. Git paths use
13
+ the same separators on Windows and Linux. The node ID is `contract:module:./` followed by
14
+ the URI-encoded requested location. Its canonical label and attributes describe a
15
+ `repository-relative-request` with `resolution: unresolved`. Imports from different places
16
+ that request the same location share a node; identical spellings naming different locations
17
+ do not. Labels and attributes stay identical when a node has multiple referring spellings.
18
+
19
+ Requests escaping the repository, containing backslashes, percent escapes, loader query/hash
20
+ suffixes or control characters remain opaque. Their identity hashes the importer and original
21
+ specifier under the reserved `contract:module:./?` prefix. No path decoding, filesystem lookup,
22
+ basename search, source-suffix substitution, directory-index expansion or module execution occurs.
23
+ The prefixes distinguish these identities from bare imports, whose existing identities remain
24
+ unchanged. Case is preserved. Facts cite the importing line and committed content digest;
25
+ their IDs bind the extractor version and module identity.
26
+
27
+ This is lexical request identity, not TypeScript/Node resolution. `./policy.js`, `./policy.ts`
28
+ and `./policy/index.js` remain distinct requests even if a consumer's compiler could map them
29
+ to one source. A remaining import after target deletion is still a cited unresolved request;
30
+ it does not assert that the target exists. Importer renames change the relative context,
31
+ and deleted importers contribute no new facts. SCIP remains the source of resolved local
32
+ typed-symbol relationships. Bare aliases, package exports and environment-dependent resolution
33
+ need their own evidence; the simple line-based import recognizer is not a complete parser.
34
+
35
+ Generic request nodes retain the `contract:module:` prefix and existing ranking treatment.
36
+ No provider preference, confidence/authority promotion, new traversal cutoff or evaluation
37
+ label change accompanies this fix. Typed symbols, explicit document links and queue-routing
38
+ evidence are separate. In particular, matching topic strings still does not establish an
39
+ operational route to the archival consumer in the dispatch evaluation.
40
+
41
+ Committed refreshes use `graph-v4-links-2.0.0-files-2.0.0` and report both assembly and file
42
+ extractor versions. Draft overlay publication IDs also bind the current file extractor, while
43
+ their capture-v2 digest continues identifying the same working bytes. Overlay manifests report
44
+ the new extraction version even when built on a version-1 base. The committed base and prior
45
+ overlay remain immutable and independently queryable; neither is rewritten or advanced by
46
+ building a draft. Semantic artifact keys are unchanged and unchanged prose remains reusable.
47
+
48
+ To obtain corrected relationships, explicitly hydrate the desired committed ref or rebuild
49
+ the draft overlay. Querying saved evidence continues to use that evidence's original facts,
50
+ without requiring an indexer, provider, Docker or refresh. A newer extractor does not silently
51
+ repair or reinterpret historical evidence.
52
+
53
+ Validation covers distinct domains, equivalent requested locations, opaque paths, URI-safe
54
+ identity, preserved bare imports, committed/dirty isolation, importer rename/delete behavior,
55
+ old/new snapshot coexistence, real TypeScript and RDF/SHACL, and installed runtime assets.
56
+ Compare newly extracted facts with the frozen dispatch and Weftlet inputs at the existing
57
+ ranking, case labels and page budgets. Keep missing operational links visible separately
58
+ from retrieval improvements.