@weftlet/toolkit 0.5.0 → 0.6.1

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 (82) hide show
  1. package/README.md +139 -14
  2. package/dist/application/cache-adoption.d.ts +2 -1
  3. package/dist/application/cache-adoption.js +12 -5
  4. package/dist/application/document-coverage.d.ts +28 -0
  5. package/dist/application/document-coverage.js +58 -0
  6. package/dist/application/document-overlay.d.ts +14 -0
  7. package/dist/application/document-overlay.js +31 -0
  8. package/dist/application/extraction-identity.d.ts +2 -1
  9. package/dist/application/extraction-identity.js +7 -0
  10. package/dist/application/hydration-inputs.d.ts +15 -0
  11. package/dist/application/hydration-inputs.js +55 -0
  12. package/dist/application/hydration-plan.d.ts +7 -0
  13. package/dist/application/hydration-plan.js +18 -5
  14. package/dist/application/hydrator.js +26 -14
  15. package/dist/application/overlay.js +26 -8
  16. package/dist/application/saved-evidence.d.ts +2 -2
  17. package/dist/application/semantic-scope.d.ts +1 -1
  18. package/dist/application/semantic-scope.js +2 -2
  19. package/dist/configuration/cache.js +13 -1
  20. package/dist/configuration/config.d.ts +2 -0
  21. package/dist/configuration/config.js +5 -0
  22. package/dist/configuration/document-traversal.d.ts +7 -0
  23. package/dist/configuration/document-traversal.js +13 -0
  24. package/dist/extraction/canonical-document-links.d.ts +28 -0
  25. package/dist/extraction/canonical-document-links.js +95 -0
  26. package/dist/extraction/document-classification.d.ts +44 -0
  27. package/dist/extraction/document-classification.js +161 -0
  28. package/dist/extraction/document-evidence.d.ts +21 -0
  29. package/dist/extraction/document-evidence.js +83 -0
  30. package/dist/extraction/document-traversal.d.ts +76 -0
  31. package/dist/extraction/document-traversal.js +210 -0
  32. package/dist/extraction/document.d.ts +3 -1
  33. package/dist/extraction/document.js +3 -1
  34. package/dist/extraction/ontology.js +2 -2
  35. package/dist/extraction/static.js +25 -20
  36. package/dist/graph/authority.js +3 -0
  37. package/dist/graph/coverage.d.ts +8 -0
  38. package/dist/graph/coverage.js +96 -0
  39. package/dist/graph/document-role-description.d.ts +5 -0
  40. package/dist/graph/document-role-description.js +18 -0
  41. package/dist/graph/query.d.ts +2 -2
  42. package/dist/graph/query.js +22 -62
  43. package/dist/graph/rdf.js +11 -0
  44. package/dist/graph/shacl-shapes.js +26 -0
  45. package/dist/index.d.ts +5 -0
  46. package/dist/index.js +4 -0
  47. package/dist/infrastructure/artifact-paths.d.ts +4 -0
  48. package/dist/infrastructure/artifact-paths.js +14 -0
  49. package/dist/interfaces/cli.js +18 -1
  50. package/dist/interfaces/document-coverage.d.ts +9 -0
  51. package/dist/interfaces/document-coverage.js +117 -0
  52. package/dist/repository/git.d.ts +11 -0
  53. package/dist/repository/git.js +38 -0
  54. package/dist/storage/storage.js +41 -13
  55. package/dist/visualization/visualization.js +21 -6
  56. package/docs/ADR-017-shacl-validation-union.md +4 -0
  57. package/docs/ADR-021-identity-preflight-and-validation-timing.md +5 -2
  58. package/docs/ADR-022-release-documentation.md +44 -0
  59. package/docs/ADR-023-document-coverage-preview.md +64 -0
  60. package/docs/ADR-024-implementation-coverage.md +51 -0
  61. package/docs/ADR-025-document-classification.md +78 -0
  62. package/docs/ADR-026-traversal-hydration.md +94 -0
  63. package/docs/ADR-027-validation-closeout.md +155 -0
  64. package/docs/ADR-028-bounded-artifact-paths.md +58 -0
  65. package/docs/CONSUMER-TRIAL.md +7 -5
  66. package/docs/GRAPHIFY-EVALUATION.md +70 -0
  67. package/docs/MIGRATING-TO-WEFTLET.md +1 -1
  68. package/docs/PRE-1.0-BASELINE.md +94 -0
  69. package/docs/PROVIDERS.md +10 -1
  70. package/docs/RELEASE-1.0-PLAN.md +145 -23
  71. package/docs/RELEASING.md +46 -8
  72. package/docs/ROADMAP.md +48 -16
  73. package/docs/VALIDATION.md +390 -0
  74. package/docs/assessments/pre-1.0-2026-10-28.json +200 -0
  75. package/docs/benchmarks/validation-2026-10-28/README.md +25 -0
  76. package/docs/benchmarks/validation-2026-10-28/large.json +1 -0
  77. package/docs/benchmarks/validation-2026-10-28/lifecycle.json +1 -0
  78. package/docs/benchmarks/validation-2026-10-28/native.json +1 -0
  79. package/docs/benchmarks/validation-2026-10-28/rich.json +1 -0
  80. package/docs/extraction-manifest.json +78 -20
  81. package/examples/generic-typescript/document-policy.json +9 -0
  82. package/package.json +7 -4
package/README.md CHANGED
@@ -13,6 +13,10 @@ Proposed future capabilities are recorded in the [Weftlet 2.0 product plan](docs
13
13
 
14
14
  ## Set up a project
15
15
 
16
+ <!-- weftlet:release:start -->
17
+ Package versions in this checkout/archive: `@weftlet/toolkit@0.6.1` and `@weftlet/core@0.6.1`.
18
+ <!-- weftlet:release:end -->
19
+
16
20
  Weftlet uses `@weftlet/toolkit`; its neutral `@weftlet/core` dependency is installed
17
21
  automatically. Both packages are available on npm.
18
22
  See [release status](docs/RELEASING.md). Existing Tently users should follow
@@ -80,7 +84,7 @@ pnpm exec weftlet hydrate --plan --json --config /path/to/project/context-graph.
80
84
  The human summary and JSON identify the resolved commit, current local configuration, selected
81
85
  documents and inclusion reasons, UTF-8 byte sizes, source approval, expected cache reuse and
82
86
  extraction counts, budget violations, and working changes excluded from the committed input.
83
- Selection uses configured repository file/directory paths; `approvedIntentSources` uses its
87
+ Direct-mode selection uses configured repository file/directory paths; `approvedIntentSources` uses its
84
88
  separate glob matcher. An explicit empty semantic source list selects no documents, fixing the
85
89
  earlier accidental expansion to the whole repository.
86
90
 
@@ -96,9 +100,9 @@ and existing agent-selection limits are warnings; they do not add a new hydratio
96
100
  would prevent removing the last document and invalidating its relationships. All selected
97
101
  documents remain visible when budgets are exceeded.
98
102
 
99
- Source approval does not approve model assertions. Validation-document labels reflect configured
100
- evidence roots; selected validation documents still undergo semantic extraction today. Recursive
101
- SOT traversal and default validation-document separation remain T07 work. See
103
+ Source approval does not approve model assertions. In direct mode, validation-document labels
104
+ reflect configured evidence roots and selected validation documents still undergo semantic
105
+ extraction. The opt-in traversal mode below excludes them by default. See
102
106
  [ADR 016](docs/ADR-016-hydration-preview.md) and the [1.0 task list](docs/RELEASE-1.0-PLAN.md).
103
107
 
104
108
  Starting with **0.4.0**, preview also maps opaque diagnostic source IDs to local paths. Semantic
@@ -107,6 +111,119 @@ their structured report on stderr. Built-in Codex can reuse completed artifacts
107
111
  operational changes such as increasing a timeout. See [provider recovery](docs/PROVIDERS.md#limits-and-reuse)
108
112
  for preview, retry and explicit legacy-cache adoption instructions.
109
113
 
114
+ ## Preview linked document coverage
115
+
116
+ This checkout/archive includes a separate prospective preview. npm 0.5.0 does not include this command:
117
+
118
+ ```sh
119
+ pnpm exec weftlet documents plan --entry docs/sot.md --entry docs/roadmap.md --root docs --ref HEAD
120
+ pnpm exec weftlet documents plan --entry docs/sot.md --root docs --ref HEAD --json
121
+ ```
122
+
123
+ Use your repository's exact committed entry paths. `--root` may repeat and defaults to the
124
+ repository root. The operation follows supported Markdown links with cycle protection and
125
+ deduplication, records citations and exclusions, and reports missing/ambiguous targets. Paths,
126
+ approval matches and validation-evidence labels remain explicit; following a link never approves
127
+ its contents. Git objects supply the input even while the checkout is dirty.
128
+
129
+ This reports prospective coverage and **does not change hydration inputs or estimate cache/model
130
+ work**. Continue using `hydrate --plan` for the current execution plan. This command does not modify configuration.
131
+ Existing semantic document/byte budgets are compared after traversal, including validation
132
+ documents. Independent read limits (`--max-read-documents`, `--max-read-bytes`,
133
+ `--max-document-bytes`) leave an explicit unread frontier if reached. Incomplete coverage or
134
+ exceeded extraction budgets return a report with exit status 1, without model calls or graph writes.
135
+ The preview follows only supported local links; complete coverage is not a claim of complete
136
+ product knowledge. See [ADR 023](docs/ADR-023-document-coverage-preview.md) for limits and the
137
+ prospective-operation boundaries.
138
+
139
+ The preview also accepts an optional JSON source policy:
140
+
141
+ ```sh
142
+ pnpm exec weftlet documents plan --entry docs/sot.md --root docs --document-policy document-policy.json --json
143
+ ```
144
+
145
+ ```json
146
+ {
147
+ "schemaVersion": 1,
148
+ "roles": [
149
+ { "id": "intent", "pattern": "docs/sot.md", "role": "intent" },
150
+ { "id": "plans", "pattern": "docs/plans/**", "role": "plan" },
151
+ { "id": "checks", "pattern": "docs/validation/**", "role": "validation" }
152
+ ],
153
+ "owners": [
154
+ { "id": "sot-owner", "pattern": "docs/**", "ownerDocument": "docs/sot.md" }
155
+ ]
156
+ }
157
+ ```
158
+
159
+ Roles are `intent`, `plan`, `validation` or `reference`; unmatched sources are `unclassified`
160
+ unless configured evidence roots or approved intent sources provide a default. The owner must
161
+ be read within the selected coverage, match `approvedIntentSources`, and have an unambiguous
162
+ intent role. Owner mappings and links never transfer approval to another source or approve an
163
+ assertion. Conflicting mappings remain explicit gaps.
164
+
165
+ This file is an operation input, separate from `context-graph.config.json`. `ready` and the
166
+ exit status still describe traversal and budget checks; inspect `classification.complete` and
167
+ its issues for role/ownership gaps. The policy does not change current hydration or cache inputs.
168
+ The public `extractDocumentEvidence` API separately emits cited document-presence and reference
169
+ facts, including empty validation documents, without interpreting prose or treating a written
170
+ “PASS” as proof that a test ran. Queries, RDF/SHACL and the viewer preserve its source metadata.
171
+ See [ADR 025](docs/ADR-025-document-classification.md) for the contract and limits.
172
+
173
+ ## Hydrate linked SOT and roadmap documents
174
+
175
+ This checkout/archive adds opt-in traversal hydration; npm 0.5.0 does not support it. Merge this
176
+ fragment into your normal configuration, using real committed entry paths and reviewed owner
177
+ mappings. Keep the existing `semanticSources` array for direct-mode compatibility; traversal
178
+ replaces its selection when enabled.
179
+
180
+ ```json
181
+ {
182
+ "documentTraversal": {
183
+ "entryPoints": ["docs/sot.md", "docs/roadmap.md"],
184
+ "roots": ["docs"],
185
+ "maxReadDocuments": 1000,
186
+ "maxReadBytes": 16777216,
187
+ "maxDocumentBytes": 1048576,
188
+ "policy": {
189
+ "schemaVersion": 1,
190
+ "roles": [
191
+ { "id": "intent", "pattern": "docs/sot.md", "role": "intent" },
192
+ { "id": "plans", "pattern": "docs/roadmap.md", "role": "plan" },
193
+ { "id": "checks", "pattern": "docs/validation/**", "role": "validation" }
194
+ ],
195
+ "owners": [
196
+ { "id": "product-owner", "pattern": "docs/**", "ownerDocument": "docs/sot.md" }
197
+ ]
198
+ }
199
+ }
200
+ }
201
+ ```
202
+
203
+ Review `approvedIntentSources` separately: an owner must already be explicitly approved. Preview
204
+ with `weftlet hydrate --plan --json`, then run `weftlet hydrate --ref HEAD` after reviewing scope
205
+ and cost. The plan reports all discovered documents, roles, ownership, exclusions, read limits,
206
+ semantic budgets and expected artifact reuse. Validation documents receive presence/reference
207
+ facts without model calls. `semantic.maxDocuments` and `maxSourceBytes` apply to semantic candidates;
208
+ traversal read limits apply to every read document. Broken/ambiguous links, incomplete reads, role
209
+ ambiguity or exceeded budgets stop new model work rather than silently trimming coverage.
210
+
211
+ Ownership gaps remain diagnostics. Reaching an owner, choosing an intent role or reading an
212
+ approved source never approves a model assertion. Plans/unclassified sources remain explicitly
213
+ labeled, and model facts remain inferred/unreviewed. Complete coverage means the configured
214
+ canonical links were followed, not that every product rule was found.
215
+
216
+ Built-in Codex can reuse unchanged artifacts when enabling traversal or editing its policy/read
217
+ limits. Role/owner metadata is assembled without rewriting those artifacts. Custom module
218
+ providers use a separate full-config cache. Snapshots bind the configuration and selection, while
219
+ old snapshots remain queryable. Draft overlays recompute reachability, invalidate changed sources
220
+ and owners, and label incomplete coverage without making model calls.
221
+
222
+ Traversal cannot be combined with `agentContext` or a direct programmatic source override; saved
223
+ impact queries still accept owner/task filters. `documents plan` remains a prospective operation;
224
+ its sidecar policy is not automatically adopted by hydration. See
225
+ [ADR 026](docs/ADR-026-traversal-hydration.md) for identity, compatibility and limits.
226
+
110
227
  ## SHACL validation and performance
111
228
 
112
229
  Starting with **0.5.0**, identity preflight runs before ontology/RDF work.
@@ -143,10 +260,14 @@ pnpm build
143
260
  pnpm benchmark:shacl
144
261
  ```
145
262
 
146
- Current hydration progress reports phase boundaries. The validator performs synchronous work,
147
- so an ordinary timer cannot provide an honest heartbeat while that work blocks Node's main
148
- thread. Worker-based progress, validation-result reuse and incremental
149
- validation remain separate T05 work; they do not delay the focused cold-validation release.
263
+ The [T05 closeout](docs/ADR-027-validation-closeout.md) measures cold, repeated and changed inputs,
264
+ plus worker, exact-result-key and Rust prototypes. Direct full validation remains the 1.0 choice:
265
+ the 10,000-fact public cases measured about 2.9–5.7 seconds, with material memory use. These are
266
+ fixture measurements, not a private consumer capacity guarantee. The prototypes are development
267
+ tools, not runtime options; every hydration still performs full validation. Current progress reports
268
+ phase boundaries because synchronous work blocks same-thread timers. Reproduce the lifecycle study
269
+ from a development checkout with `pnpm benchmark:validation`; raw results and commands are linked
270
+ from the ADR.
150
271
 
151
272
  ## Development
152
273
 
@@ -214,12 +335,9 @@ Each committed JSON record names a scoped binding and exact producer/consumer fi
214
335
  are cited candidate dependencies; they do not assert an approved policy or observed delivery.
215
336
 
216
337
  Install the locally packed core and toolkit tarballs together. The installed commands are
217
- `weftlet`, `weftlet-mcp`, and the compatible `context-graph` / `context-graph-mcp` aliases. For example, after preparing a release, replace `<version>` with the version in `.release/manifest.json`:
218
-
219
- ```sh
220
- npm install --save-dev "/path/to/weftlet/.release/weftlet-core-<version>.tgz" "/path/to/weftlet/.release/weftlet-toolkit-<version>.tgz"
221
- npx weftlet init
222
- ```
338
+ `weftlet`, `weftlet-mcp`, and the compatible `context-graph` / `context-graph-mcp` aliases.
339
+ Use the [versioned installation examples](docs/RELEASING.md#inspecting-and-consuming-releases)
340
+ generated for this checkout/archive; then run `npx weftlet init`.
223
341
 
224
342
  Pass `--config /absolute/path/context-graph.config.json`
225
343
  to the CLI or set `CONTEXT_GRAPH_CONFIG` for MCP. No launcher rebuild is required for installed
@@ -248,6 +366,13 @@ ahead of repeated detail and shared-import context. Ranking changes ordering, no
248
366
  the reachable scope. Responses identify the ranking version; after a ranking upgrade, restart
249
367
  scans whose old continuation tokens are rejected. No snapshot refresh is needed for new ordering.
250
368
 
369
+ This source includes a [coverage reporting correction](docs/ADR-024-implementation-coverage.md)
370
+ for the next release: code relationships displayed under contracts also count as implementation
371
+ evidence. New reports identify `coverage.scope: "returned-page"` and `countsAreNonExclusive: true`.
372
+ Counts can overlap and describe returned evidence; they are not completeness percentages or proof
373
+ that behavior is correct. Document/model claims and test-only evidence do not establish production
374
+ implementation. The reporting fix works with saved snapshots without another hydration.
375
+
251
376
  [Relative import evidence](docs/ADR-013-relative-module-requests.md) now scopes module requests
252
377
  to the importing directory. An explicit refresh applies this extraction fix; saved snapshots
253
378
  keep their original facts. These requests remain unresolved evidence, while SCIP supplies
@@ -1,4 +1,4 @@
1
- import type { ImpactScanRequest } from '@weftlet/core';
1
+ import type { DocumentSelectionRecord, ImpactScanRequest } from '@weftlet/core';
2
2
  import { type SemanticInputIssue, type SemanticSelection } from '../extraction/semantic-sources.js';
3
3
  import { type SemanticScope } from './semantic-scope.js';
4
4
  export interface SemanticCacheAdoptionOptions {
@@ -28,6 +28,7 @@ export interface SemanticCacheAdoption {
28
28
  readonly complete: boolean;
29
29
  };
30
30
  readonly records: readonly SemanticCacheAdoptionRecord[];
31
+ readonly documentSelection?: DocumentSelectionRecord;
31
32
  readonly counts: Readonly<Record<SemanticCacheAdoptionStatus, number>>;
32
33
  readonly selections: readonly SemanticSelection[];
33
34
  readonly budgets: {
@@ -2,10 +2,10 @@ import { loadConfig } from '../configuration/config.js';
2
2
  import { resolveSemanticStorageRoots, resolveStorageRoot, semanticCachePartition } from '../configuration/cache.js';
3
3
  import { inspectSemanticCache } from '../extraction/semantic-cache.js';
4
4
  import { semanticProviderIdentity } from '../extraction/semantic-identity.js';
5
- import { selectSemanticInputs } from '../extraction/semantic-sources.js';
6
5
  import { sha256, stableJson } from '../infrastructure/util.js';
7
6
  import { GitRepository } from '../repository/git.js';
8
7
  import { SemanticArtifactStore } from '../storage/storage.js';
8
+ import { selectHydrationInputs } from './hydration-inputs.js';
9
9
  import { resolveSemanticScope } from './semantic-scope.js';
10
10
  const problemStatuses = new Set(['corrupt', 'unavailable', 'conflict']);
11
11
  function problem(inspection, source, location) {
@@ -67,6 +67,11 @@ async function publishCandidate(candidate, config, identity, current) {
67
67
  return { ...candidate.record, status: 'adopted',
68
68
  reason: 'Validated artifact is available in the current cache; the previous artifact is unchanged.' };
69
69
  }
70
+ function previousArtifactRoot(repositoryRoot, config) {
71
+ // Traverse only the explicitly named module namespace; direct/Codex recovery retains legacy conflict checks.
72
+ return config.documentTraversal && config.semantic.provider === 'module'
73
+ ? resolveSemanticStorageRoots(repositoryRoot, config).primary : resolveStorageRoot(repositoryRoot, config);
74
+ }
70
75
  /** Explicitly adopts only committed-source artifacts from a compatible, named legacy profile. */
71
76
  export async function adoptSemanticCache(configPath, options) {
72
77
  const [loaded, previous] = await Promise.all([loadConfig(configPath), loadConfig(options.fromConfigPath)]);
@@ -81,10 +86,11 @@ export async function adoptSemanticCache(configPath, options) {
81
86
  const ref = options.ref ?? 'HEAD';
82
87
  const commitSha = await repository.resolveRef(ref);
83
88
  const scope = await resolveSemanticScope(config, repository, commitSha, options.request);
84
- const selection = await selectSemanticInputs(repository, commitSha, scope.selectors, config.semantic);
89
+ const selection = await selectHydrationInputs(repository, commitSha, config, scope.selectors);
85
90
  const issues = [...selection.issues];
86
91
  const limited = scope.omittedDocuments.length > 0 || scope.ignoredSearchTerms > 0;
87
- const complete = !limited && selection.selections.every((item) => item.status === 'matched');
92
+ const complete = !limited && selection.selections.every((item) => item.status === 'matched') &&
93
+ (selection.documentSelection?.complete ?? true);
88
94
  if (limited)
89
95
  issues.push({ code: 'selection-limited', severity: 'warning',
90
96
  message: `Existing agent selection limits omitted ${scope.omittedDocuments.length} documents and ${scope.ignoredSearchTerms} search terms; adoption describes only the selected scope.`,
@@ -95,7 +101,7 @@ export async function adoptSemanticCache(configPath, options) {
95
101
  const current = new SemanticArtifactStore(resolveSemanticStorageRoots(repositoryRoot, config).primary);
96
102
  // Do not scan sibling profiles or infer ownership from an artifact key. The
97
103
  // supplied old configuration proves the exact legacy location to inspect.
98
- const old = new SemanticArtifactStore(resolveStorageRoot(repositoryRoot, previous.config));
104
+ const old = new SemanticArtifactStore(previousArtifactRoot(repositoryRoot, previous.config));
99
105
  const identity = semanticProviderIdentity(config.semantic);
100
106
  for (const source of selection.sources) {
101
107
  const candidate = await inspectCandidate(source, config.semantic, identity, old, current);
@@ -117,9 +123,10 @@ export async function adoptSemanticCache(configPath, options) {
117
123
  ref, commitSha, configurationDigest: sha256(stableJson(config)),
118
124
  previousConfigurationDigest: sha256(stableJson(previous.config)), dryRun,
119
125
  ready: complete && !issues.some((issue) => issue.severity === 'error'), scope: { ...scope, complete },
126
+ ...(selection.documentSelection ? { documentSelection: selection.documentSelection } : {}),
120
127
  records, counts, selections: selection.selections, budgets: selection.budgets, issues,
121
128
  assumptions: [
122
- 'Selection uses the same configured or bounded agent scope as hydration planning at the resolved commit; only expected keys in the explicitly named previous profile are inspected.',
129
+ 'Selection uses the same configured, traversal or bounded agent scope as hydration planning at the resolved commit; only expected keys in the explicitly named previous profile are inspected.',
123
130
  'Missing or incomplete previous artifacts need later extraction. Corrupt or conflicting complete artifacts are retained for diagnosis.',
124
131
  'Adoption preserves original artifact identity and provenance; it does not validate or publish a current snapshot.',
125
132
  'Artifacts are adopted independently; earlier valid copies remain available if a later artifact or storage operation fails.',
@@ -0,0 +1,28 @@
1
+ import { type DocumentTraversalOptions, type DocumentTraversalReport } from '../extraction/document-traversal.js';
2
+ import { type DocumentClassification, type DocumentClassificationPolicy, type DocumentClassificationReport } from '../extraction/document-classification.js';
3
+ export interface DocumentCoverageOptions extends Omit<DocumentTraversalOptions, 'maxDocuments' | 'maxSourceBytes'> {
4
+ readonly ref?: string;
5
+ readonly documentPolicy?: DocumentClassificationPolicy;
6
+ }
7
+ export interface DocumentCoveragePlan extends Omit<DocumentTraversalReport, 'documents'> {
8
+ readonly schemaVersion: 1;
9
+ readonly kind: 'document-coverage-plan';
10
+ readonly mode: 'prospective';
11
+ readonly changesHydrationSelection: false;
12
+ readonly repositoryId: string;
13
+ readonly ref: string;
14
+ readonly commitSha: string;
15
+ readonly configurationDigest: string;
16
+ readonly coverageDigest: string;
17
+ /** Traversal and budget readiness only; classification is reported independently. */
18
+ readonly ready: boolean;
19
+ readonly classification: DocumentClassificationReport;
20
+ readonly documents: readonly (DocumentTraversalReport['documents'][number] & DocumentClassification & {
21
+ readonly role: 'semantic-document' | 'validation-evidence';
22
+ readonly extractionReview: 'unreviewed';
23
+ })[];
24
+ readonly excludedWorkingChanges: readonly string[];
25
+ readonly assumptions: readonly string[];
26
+ }
27
+ /** Read committed links only. This does not initialize execution, inspect caches or change selection. */
28
+ export declare function planDocumentCoverage(configPath: string, options: DocumentCoverageOptions): Promise<DocumentCoveragePlan>;
@@ -0,0 +1,58 @@
1
+ import { loadConfig } from '../configuration/config.js';
2
+ import { GitRepository } from '../repository/git.js';
3
+ import { sha256, stableJson } from '../infrastructure/util.js';
4
+ import { traverseDocuments } from '../extraction/document-traversal.js';
5
+ import { classifyDocumentSources, parseDocumentClassificationPolicy } from '../extraction/document-classification.js';
6
+ function classifyKnownDocuments(paths, context, policy) {
7
+ // Traversal bounds reads, not its discovered frontier. Preserve every known path while each
8
+ // classifier call stays bounded; all batches see the same complete set of read owners.
9
+ const ordered = [...paths].sort();
10
+ const batchSize = 1000;
11
+ const first = classifyDocumentSources(ordered.slice(0, batchSize), context, policy);
12
+ const documents = [...first.documents];
13
+ const issues = [...first.issues];
14
+ for (let offset = batchSize; offset < ordered.length; offset += batchSize) {
15
+ const batch = classifyDocumentSources(ordered.slice(offset, offset + batchSize), context, policy);
16
+ documents.push(...batch.documents);
17
+ issues.push(...batch.issues);
18
+ }
19
+ return { policyDigest: first.policyDigest, documents, issues, complete: !issues.length };
20
+ }
21
+ /** Read committed links only. This does not initialize execution, inspect caches or change selection. */
22
+ export async function planDocumentCoverage(configPath, options) {
23
+ const { config, repositoryRoot } = await loadConfig(configPath);
24
+ const documentPolicy = parseDocumentClassificationPolicy(options.documentPolicy ?? { schemaVersion: 1 });
25
+ const repository = new GitRepository(repositoryRoot);
26
+ const ref = options.ref ?? 'HEAD';
27
+ const commitSha = await repository.resolveRef(ref);
28
+ const traversal = await traverseDocuments(repository, commitSha, {
29
+ entryPoints: options.entryPoints, roots: options.roots,
30
+ maxReadDocuments: options.maxReadDocuments, maxReadBytes: options.maxReadBytes,
31
+ maxDocumentBytes: options.maxDocumentBytes,
32
+ maxDocuments: config.semantic.maxDocuments ?? 8, maxSourceBytes: config.semantic.maxSourceBytes ?? 65_536,
33
+ });
34
+ const classification = classifyKnownDocuments(traversal.documents.map(document => document.path), {
35
+ approvedIntentSources: config.approvedIntentSources ?? [], evidenceRoots: config.evidenceRoots,
36
+ availableDocuments: traversal.documents.filter(document => document.status === 'read').map(document => document.path),
37
+ }, documentPolicy);
38
+ const classified = new Map(classification.documents.map(document => [document.path, document]));
39
+ const documents = traversal.documents.map(document => ({ ...document, ...classified.get(document.path),
40
+ role: classified.get(document.path).sourceRole === 'validation' ? 'validation-evidence' : 'semantic-document',
41
+ extractionReview: 'unreviewed' }));
42
+ return { ...traversal, documents, schemaVersion: 1, kind: 'document-coverage-plan', mode: 'prospective',
43
+ changesHydrationSelection: false, repositoryId: config.repositoryId, ref, commitSha,
44
+ configurationDigest: sha256(stableJson(config)), classification,
45
+ coverageDigest: sha256(stableJson({ commitSha, traversal, documents, classification })),
46
+ ready: traversal.coverage.complete && traversal.budgets.withinLimits,
47
+ excludedWorkingChanges: await repository.workingChanges(),
48
+ assumptions: [
49
+ 'This is prospective document coverage, not the hydration execution plan. It changes no configuration or hydration inputs and estimates no cache reuse, model calls, tokens or cost.',
50
+ 'Documents and link citations come from the resolved commit; configuration, document policy and excluded working changes describe current operation inputs.',
51
+ 'Complete covers supported local document links inside the requested scope. External links, fragments and resolved non-document files are reported but not traversed; this does not prove complete product coverage.',
52
+ 'Extraction budgets compare all discovered documents, including validation evidence. Safety limits can leave an explicit unread frontier; undiscovered descendants then remain unknown.',
53
+ 'Ready describes traversal and budget checks only. Classification completeness is separate; unresolved roles or ownership are evidence gaps, not proven business-rule violations or approved intent.',
54
+ 'Roles use explicit document policy and configured defaults. Ownership maps require a read, approved intent owner. Neither approval nor ownership transfers through links or approves individual assertions.',
55
+ 'No provider, adapter, indexer, runtime source or graph validator was initialized. No cache, snapshot or source document was written.',
56
+ 'Use hydrate --plan for current extraction inputs and cache estimates. The standalone document-evidence API is available; connecting classification and validation separation to hydration remains T07c.',
57
+ ] };
58
+ }
@@ -0,0 +1,14 @@
1
+ import type { ContextGraphConfig } from '../configuration/config.js';
2
+ import type { DocumentSelectionRecord, GraphFact } from '@weftlet/core';
3
+ import type { HydrationInputs } from './hydration-inputs.js';
4
+ /** Retain only unchanged, still-reachable assertions with unchanged source ownership. */
5
+ export declare function retainOverlayDocumentFact(fact: GraphFact, inputs: HydrationInputs | undefined, affected: ReadonlySet<string>): boolean;
6
+ export declare function overlayDocumentRoots(config: Pick<ContextGraphConfig, 'documentTraversal'>): readonly string[];
7
+ export declare function overlayDocumentManifest(selection?: DocumentSelectionRecord): {
8
+ documentSelection: DocumentSelectionRecord;
9
+ scope: readonly string[];
10
+ } | {
11
+ documentSelection?: undefined;
12
+ scope?: undefined;
13
+ };
14
+ export declare function overlayDocumentNotes(config: Pick<ContextGraphConfig, 'documentTraversal'>): readonly string[];
@@ -0,0 +1,31 @@
1
+ import { documentEvidenceExtractorIdentity } from '../extraction/document-evidence.js';
2
+ import { stableJson } from '../infrastructure/util.js';
3
+ /** Retain only unchanged, still-reachable assertions with unchanged source ownership. */
4
+ export function retainOverlayDocumentFact(fact, inputs, affected) {
5
+ if (!inputs?.documentSelection)
6
+ return true;
7
+ if (fact.extractor.id === documentEvidenceExtractorIdentity.id)
8
+ return false;
9
+ if (!/\.(?:md|mdx|txt)$/i.test(fact.provenance.path))
10
+ return true;
11
+ if (!inputs.documentSelection.complete)
12
+ return false;
13
+ const source = inputs.sources.find(source => source.path === fact.provenance.path);
14
+ const classification = inputs.documentEvidence?.classification.documents.find(document => document.path === fact.provenance.path);
15
+ return Boolean(source && classification && source.digest === fact.provenance.digest &&
16
+ fact.sourceRole === classification.sourceRole &&
17
+ fact.sourcePolicyDigest === inputs.documentSelection.policyDigest &&
18
+ stableJson(fact.sourceOwnership) === stableJson(classification.ownership) &&
19
+ !classification.ownership.ownerDocuments.some(path => affected.has(path)));
20
+ }
21
+ export function overlayDocumentRoots(config) {
22
+ return config.documentTraversal ? config.documentTraversal.roots ?? ['.'] : [];
23
+ }
24
+ export function overlayDocumentManifest(selection) {
25
+ return selection ? { documentSelection: selection, scope: selection.documentPaths } : {};
26
+ }
27
+ export function overlayDocumentNotes(config) {
28
+ return config.documentTraversal ? [
29
+ 'Document traversal was recomputed from captured drafts. Changed/unreachable sources and changed owners invalidate prior semantic assertions; incomplete coverage invalidates all retained document assertions.',
30
+ ] : [];
31
+ }
@@ -1,6 +1,7 @@
1
1
  import type { ContextGraphConfig } from '../configuration/config.js';
2
2
  export declare const graphAssemblyVersion = 7;
3
3
  export declare const graphAssemblyIdentity = "standalone-graph-v7";
4
+ export declare function documentExtractionVersions(config?: Pick<ContextGraphConfig, 'documentTraversal'>): Readonly<Record<string, string>>;
4
5
  /** Runtime expectations are separate from source/configuration identity and artifact reuse. */
5
- export declare function requiredSnapshotExtractorVersions(config?: Pick<ContextGraphConfig, 'routingSources'>): Readonly<Record<string, string>>;
6
+ export declare function requiredSnapshotExtractorVersions(config?: Pick<ContextGraphConfig, 'routingSources' | 'documentTraversal'>): Readonly<Record<string, string>>;
6
7
  export declare function incompatibleSnapshotExtractors(actual: Readonly<Record<string, string>> | undefined, expected: Readonly<Record<string, string>>): readonly string[];
@@ -1,10 +1,17 @@
1
1
  import { repositoryContractExtractorIdentity, scipSymbolExtractorIdentity } from '../extraction/static.js';
2
2
  import { routingExtractorIdentity } from '../extraction/routing.js';
3
+ import { documentEvidenceExtractorIdentity } from '../extraction/document-evidence.js';
4
+ import { documentSelectionIdentity } from './hydration-inputs.js';
3
5
  export const graphAssemblyVersion = 7;
4
6
  export const graphAssemblyIdentity = `standalone-graph-v${graphAssemblyVersion}`;
7
+ export function documentExtractionVersions(config) {
8
+ return config?.documentTraversal ? { 'document-selection': documentSelectionIdentity,
9
+ [documentEvidenceExtractorIdentity.id]: documentEvidenceExtractorIdentity.version } : {};
10
+ }
5
11
  /** Runtime expectations are separate from source/configuration identity and artifact reuse. */
6
12
  export function requiredSnapshotExtractorVersions(config) {
7
13
  return { assembly: graphAssemblyIdentity,
14
+ ...documentExtractionVersions(config),
8
15
  [repositoryContractExtractorIdentity.id]: repositoryContractExtractorIdentity.version,
9
16
  [scipSymbolExtractorIdentity.id]: scipSymbolExtractorIdentity.version,
10
17
  ...(config?.routingSources?.length ? { [routingExtractorIdentity.id]: routingExtractorIdentity.version } : {}),
@@ -0,0 +1,15 @@
1
+ import type { DocumentSelectionRecord, GraphFact } from '@weftlet/core';
2
+ import type { ContextGraphConfig } from '../configuration/config.js';
3
+ import type { GitRepository } from '../repository/git.js';
4
+ import { selectSemanticInputs } from '../extraction/semantic-sources.js';
5
+ import { extractDocumentEvidence } from '../extraction/document-evidence.js';
6
+ export declare const documentSelectionIdentity = "document-selection-v1";
7
+ export type DocumentEvidence = Awaited<ReturnType<typeof extractDocumentEvidence>>;
8
+ export type HydrationInputs = Awaited<ReturnType<typeof selectSemanticInputs>> & {
9
+ readonly documentEvidence?: DocumentEvidence;
10
+ readonly documentSelection?: DocumentSelectionRecord;
11
+ };
12
+ /** One committed selection for preview, hydration and explicit cache adoption. No execution dependencies. */
13
+ export declare function selectHydrationInputs(repository: GitRepository, commitSha: string, config: ContextGraphConfig, selectors?: readonly string[]): Promise<HydrationInputs>;
14
+ /** Assembly metadata is deliberately outside immutable provider artifacts. */
15
+ export declare function classifyHydratedFacts(facts: readonly GraphFact[], evidence?: DocumentEvidence): readonly GraphFact[];
@@ -0,0 +1,55 @@
1
+ import { selectSemanticInputs } from '../extraction/semantic-sources.js';
2
+ import { extractDocumentEvidence } from '../extraction/document-evidence.js';
3
+ import { sha256, stableJson } from '../infrastructure/util.js';
4
+ export const documentSelectionIdentity = 'document-selection-v1';
5
+ /** One committed selection for preview, hydration and explicit cache adoption. No execution dependencies. */
6
+ export async function selectHydrationInputs(repository, commitSha, config, selectors = config.semanticSources) {
7
+ if (!config.documentTraversal)
8
+ return selectSemanticInputs(repository, commitSha, selectors, config.semantic);
9
+ const context = { repositoryRoot: repository.root, repositoryId: config.repositoryId,
10
+ ref: commitSha, commitSha, snapshotId: commitSha, changedPaths: [], deletedPaths: [], renamedPaths: {} };
11
+ const evidence = await extractDocumentEvidence(repository, context, {
12
+ ...config.documentTraversal, documentPolicy: config.documentTraversal.policy,
13
+ // Traversal reads have their own limits; semantic budgets are applied after role exclusion below.
14
+ maxDocuments: 10_000, maxSourceBytes: 4 * 1024 * 1024,
15
+ approvedIntentSources: config.approvedIntentSources, evidenceRoots: config.evidenceRoots,
16
+ });
17
+ const classified = new Map(evidence.classification.documents.map(document => [document.path, document]));
18
+ const excluded = [];
19
+ const semanticPaths = [];
20
+ for (const document of evidence.traversal.documents) {
21
+ const classification = classified.get(document.path);
22
+ if (document.status !== 'read')
23
+ excluded.push({ path: document.path, reason: 'unread' });
24
+ else if (classification?.roleStatus === 'ambiguous')
25
+ excluded.push({ path: document.path, reason: 'ambiguous-role' });
26
+ else if (classification?.sourceRole === 'validation')
27
+ excluded.push({ path: document.path, reason: 'validation' });
28
+ else
29
+ semanticPaths.push(document.path);
30
+ }
31
+ const selection = await selectSemanticInputs(repository, commitSha, semanticPaths, config.semantic);
32
+ // Prospective traversal budgets count every document. Execution budgets count only semantic inputs.
33
+ const issues = [...selection.issues, ...evidence.traversal.issues.filter(issue => !['document-budget', 'source-byte-budget'].includes(issue.code)), ...evidence.classification.issues.map(issue => ({ ...issue, severity: issue.code === 'role-ambiguous' ? 'error' : issue.severity }))];
34
+ const record = { version: 1, policyDigest: evidence.classification.policyDigest,
35
+ complete: evidence.traversal.coverage.complete && !issues.some(issue => issue.severity === 'error'),
36
+ entryPoints: evidence.traversal.entryPoints, roots: evidence.traversal.roots,
37
+ documentPaths: evidence.traversal.documents.map(document => document.path), semanticPaths, excluded };
38
+ const digest = sha256(stableJson({ identity: documentSelectionIdentity, commitSha, record,
39
+ traversal: evidence.traversal, classification: evidence.classification }));
40
+ return { ...selection, issues, documentEvidence: evidence, documentSelection: { ...record, digest } };
41
+ }
42
+ /** Assembly metadata is deliberately outside immutable provider artifacts. */
43
+ export function classifyHydratedFacts(facts, evidence) {
44
+ if (!evidence)
45
+ return facts;
46
+ const classified = new Map(evidence.classification.documents.map(document => [document.path, document]));
47
+ return facts.map(fact => {
48
+ const document = classified.get(fact.provenance.path);
49
+ if (!document)
50
+ throw new Error('Document assembly fact has no classified source');
51
+ const metadata = { sourceRole: document.sourceRole, sourceOwnership: document.ownership,
52
+ sourcePolicyDigest: evidence.classification.policyDigest, sourceAuthority: document.sourceAuthority };
53
+ return { ...fact, ...metadata, id: sha256(stableJson({ fact: fact.id, metadata, identity: documentSelectionIdentity })) };
54
+ });
55
+ }
@@ -2,6 +2,8 @@ import type { ImpactScanRequest } from '@weftlet/core';
2
2
  import { type SemanticCacheInspection } from '../extraction/semantic-cache.js';
3
3
  import { type SemanticInputIssue, type SemanticSelection } from '../extraction/semantic-sources.js';
4
4
  import { type SemanticScope } from './semantic-scope.js';
5
+ import { type DocumentEvidence } from './hydration-inputs.js';
6
+ import type { DocumentSelectionRecord, SourceDocumentRole, SourceOwnership } from '@weftlet/core';
5
7
  export interface HydrationPlanOptions {
6
8
  readonly ref?: string;
7
9
  readonly forceSemantic?: boolean;
@@ -21,6 +23,9 @@ export interface HydrationPlanDocument {
21
23
  readonly approvedBy: readonly string[];
22
24
  readonly extractionReview: 'unreviewed';
23
25
  readonly cache: Omit<SemanticCacheInspection, 'artifact'>;
26
+ readonly sourceRole?: SourceDocumentRole;
27
+ readonly sourceOwnership?: SourceOwnership;
28
+ readonly sourcePolicyDigest?: string;
24
29
  }
25
30
  export interface HydrationPlan {
26
31
  readonly schemaVersion: 1;
@@ -34,6 +39,8 @@ export interface HydrationPlan {
34
39
  readonly complete: boolean;
35
40
  };
36
41
  readonly documents: readonly HydrationPlanDocument[];
42
+ readonly documentSelection?: DocumentSelectionRecord;
43
+ readonly documentCoverage?: Pick<DocumentEvidence, 'traversal' | 'classification'>;
37
44
  readonly selections: readonly SemanticSelection[];
38
45
  readonly budgets: {
39
46
  readonly maxDocuments: number;
@@ -4,9 +4,9 @@ import { matchesRepositoryGlob } from '../repository/glob.js';
4
4
  import { createSemanticArtifactStore } from './semantic-artifact-store.js';
5
5
  import { inspectSemanticCache } from '../extraction/semantic-cache.js';
6
6
  import { semanticProviderIdentity } from '../extraction/semantic-identity.js';
7
- import { selectSemanticInputs } from '../extraction/semantic-sources.js';
8
7
  import { sha256, stableJson } from '../infrastructure/util.js';
9
8
  import { resolveSemanticScope } from './semantic-scope.js';
9
+ import { selectHydrationInputs } from './hydration-inputs.js';
10
10
  import { semanticSourceId } from '../extraction/semantic-diagnostics.js';
11
11
  async function describeDocument(source, selections, config, evidencePaths, store, forceSemantic = false) {
12
12
  const inspection = forceSemantic
@@ -29,11 +29,12 @@ export async function planHydration(configPath, options = {}) {
29
29
  const ref = options.ref ?? 'HEAD';
30
30
  const commitSha = await repository.resolveRef(ref);
31
31
  const scope = await resolveSemanticScope(config, repository, commitSha, options.request);
32
- const selection = await selectSemanticInputs(repository, commitSha, scope.selectors, config.semantic);
32
+ const selection = await selectHydrationInputs(repository, commitSha, config, scope.selectors);
33
33
  const issues = [...selection.issues];
34
34
  const { omittedDocuments, ignoredSearchTerms } = scope;
35
35
  const limited = omittedDocuments.length > 0 || ignoredSearchTerms > 0;
36
- const complete = !limited && selection.selections.every((item) => item.status === 'matched');
36
+ const complete = !limited && selection.selections.every((item) => item.status === 'matched') &&
37
+ (selection.documentSelection?.complete ?? true);
37
38
  if (limited)
38
39
  issues.push({ code: 'selection-limited', severity: 'warning',
39
40
  message: `Existing agent selection limits omitted ${omittedDocuments.length} documents and ${ignoredSearchTerms} search terms; this plan describes only the selected scope.`,
@@ -42,7 +43,11 @@ export async function planHydration(configPath, options = {}) {
42
43
  const evidencePaths = new Set(await repository.listFilesAt(commitSha, config.evidenceRoots));
43
44
  const documents = [];
44
45
  for (const source of selection.sources) {
45
- const document = await describeDocument(source, selection.selections, config, evidencePaths, store, options.forceSemantic);
46
+ const described = await describeDocument(source, selection.selections, config, evidencePaths, store, options.forceSemantic);
47
+ const classification = selection.documentEvidence?.classification.documents.find(item => item.path === source.path);
48
+ const document = classification ? { ...described, role: 'semantic-document',
49
+ sourceAuthority: classification.sourceAuthority, sourceRole: classification.sourceRole,
50
+ sourceOwnership: classification.ownership, sourcePolicyDigest: selection.documentEvidence.classification.policyDigest } : described;
46
51
  const { cache } = document;
47
52
  if (cache.status === 'corrupt' || cache.status === 'unavailable')
48
53
  issues.push({
@@ -56,9 +61,17 @@ export async function planHydration(configPath, options = {}) {
56
61
  configurationDigest: sha256(stableJson(config)), ready: complete && !issues.some((item) => item.severity === 'error'),
57
62
  scope: { ...scope, complete },
58
63
  documents, selections: selection.selections, budgets: selection.budgets,
64
+ ...(selection.documentSelection ? { documentSelection: selection.documentSelection,
65
+ documentCoverage: { traversal: selection.documentEvidence.traversal, classification: selection.documentEvidence.classification } } : {}),
59
66
  estimates: { reusableArtifacts: count('hit'), extractions: count('miss', 'forced'), unknownArtifacts: count('corrupt', 'unavailable') },
60
67
  issues, excludedWorkingChanges: await repository.workingChanges(),
61
- assumptions: [
68
+ assumptions: selection.documentSelection ? [
69
+ 'Traversal uses committed entry points and canonical local links. All discovered documents and exclusions are reported; only semantic candidates count against extraction budgets.',
70
+ 'Validation documents retain deterministic presence/references without model extraction. Role ambiguity and incomplete coverage block model work; ownership gaps remain explicit diagnostics.',
71
+ 'Classification and owner mappings never approve assertions. Model facts remain inferred/unreviewed, including from approved sources.',
72
+ 'Readiness checks selection, budgets and readable caches. It does not initialize providers, adapters, credentials, indexing, runtime sources or graph validation.',
73
+ 'Built-in Codex artifacts retain their extraction identity. Traversal module providers use a separate conservative cache; no cache or snapshot was written.',
74
+ ] : [
62
75
  'Sources come from the resolved commit; configuration and cache are read from the current local environment.',
63
76
  'Estimates require unchanged source, configuration, provider contract and readable artifacts; concurrent changes can invalidate them.',
64
77
  'Document and per-document UTF-8 byte limits apply to all selected documents, including cache hits; no token or monetary estimate is made.',