@weftlet/toolkit 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +80 -0
  2. package/dist/application/agent-context.d.ts +3 -1
  3. package/dist/application/agent-context.js +15 -6
  4. package/dist/application/application.js +7 -4
  5. package/dist/application/cache-adoption.d.ts +43 -0
  6. package/dist/application/cache-adoption.js +129 -0
  7. package/dist/application/extraction-identity.d.ts +6 -0
  8. package/dist/application/extraction-identity.js +15 -0
  9. package/dist/application/hydration-failure.d.ts +41 -0
  10. package/dist/application/hydration-failure.js +43 -0
  11. package/dist/application/hydration-plan.d.ts +54 -0
  12. package/dist/application/hydration-plan.js +71 -0
  13. package/dist/application/hydrator.d.ts +3 -5
  14. package/dist/application/hydrator.js +30 -61
  15. package/dist/application/overlay.js +10 -3
  16. package/dist/application/saved-evidence.d.ts +6 -3
  17. package/dist/application/saved-evidence.js +7 -1
  18. package/dist/application/semantic-artifact-store.d.ts +4 -0
  19. package/dist/application/semantic-artifact-store.js +7 -0
  20. package/dist/application/semantic-run.d.ts +29 -0
  21. package/dist/application/semantic-run.js +111 -0
  22. package/dist/application/semantic-scope.d.ts +13 -0
  23. package/dist/application/semantic-scope.js +12 -0
  24. package/dist/application/snapshot-checks.d.ts +2 -1
  25. package/dist/application/snapshot-checks.js +8 -1
  26. package/dist/configuration/cache.d.ts +6 -0
  27. package/dist/configuration/cache.js +18 -0
  28. package/dist/extraction/document.js +1 -1
  29. package/dist/extraction/providers.js +12 -5
  30. package/dist/extraction/repository-nodes.d.ts +2 -3
  31. package/dist/extraction/repository-nodes.js +5 -11
  32. package/dist/extraction/routing.d.ts +1 -1
  33. package/dist/extraction/routing.js +3 -6
  34. package/dist/extraction/semantic-cache.d.ts +13 -0
  35. package/dist/extraction/semantic-cache.js +66 -0
  36. package/dist/extraction/semantic-diagnostics.d.ts +9 -0
  37. package/dist/extraction/semantic-diagnostics.js +15 -0
  38. package/dist/extraction/semantic-identity.d.ts +7 -0
  39. package/dist/extraction/semantic-identity.js +29 -0
  40. package/dist/extraction/semantic-sources.d.ts +27 -0
  41. package/dist/extraction/semantic-sources.js +35 -0
  42. package/dist/extraction/semantic.d.ts +5 -1
  43. package/dist/extraction/semantic.js +47 -24
  44. package/dist/extraction/static.d.ts +13 -5
  45. package/dist/extraction/static.js +8 -15
  46. package/dist/governance/governance.js +11 -7
  47. package/dist/graph/query.js +2 -0
  48. package/dist/graph/rdf.d.ts +1 -1
  49. package/dist/graph/rdf.js +13 -28
  50. package/dist/graph/shacl-shapes.d.ts +5 -0
  51. package/dist/graph/shacl-shapes.js +27 -0
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +4 -0
  54. package/dist/interfaces/cli.js +86 -19
  55. package/dist/repository/git.d.ts +1 -1
  56. package/dist/repository/git.js +5 -1
  57. package/dist/storage/storage.d.ts +7 -1
  58. package/dist/storage/storage.js +15 -3
  59. package/dist/visualization/visualization.js +1 -0
  60. package/docs/ADR-016-hydration-preview.md +91 -0
  61. package/docs/ADR-017-shacl-validation-union.md +102 -0
  62. package/docs/ADR-018-operational-semantic-cache.md +100 -0
  63. package/docs/ADR-019-semantic-failure-diagnostics.md +82 -0
  64. package/docs/ADR-020-canonical-physical-file-producers.md +97 -0
  65. package/docs/PROVIDERS.md +51 -1
  66. package/docs/RELEASE-1.0-PLAN.md +148 -32
  67. package/docs/RELEASING.md +19 -6
  68. package/docs/ROADMAP.md +46 -5
  69. package/docs/VALIDATION.md +341 -3
  70. package/docs/WEFTLET-2.0-PLAN.md +9 -8
  71. package/docs/extraction-manifest.json +55 -23
  72. package/package.json +3 -2
package/README.md CHANGED
@@ -67,6 +67,77 @@ the archive-byte, snapshot replacement and MCP startup regression suite also pas
67
67
  Windows CI. Full native Windows release preparation remains separately unvalidated.
68
68
  See the [validation record](docs/VALIDATION.md).
69
69
 
70
+ ## Preview hydration before model work
71
+
72
+ `hydrate --plan` requires toolkit **0.3.0 or newer**. Releases through 0.2.1 do not support
73
+ this flag. With a supporting version installed, inspect a project's committed inputs:
74
+
75
+ ```sh
76
+ pnpm exec weftlet hydrate --plan --config /path/to/project/context-graph.config.json --ref HEAD
77
+ pnpm exec weftlet hydrate --plan --json --config /path/to/project/context-graph.config.json --ref HEAD
78
+ ```
79
+
80
+ The human summary and JSON identify the resolved commit, current local configuration, selected
81
+ documents and inclusion reasons, UTF-8 byte sizes, source approval, expected cache reuse and
82
+ extraction counts, budget violations, and working changes excluded from the committed input.
83
+ Selection uses configured repository file/directory paths; `approvedIntentSources` uses its
84
+ separate glob matcher. An explicit empty semantic source list selects no documents, fixing the
85
+ earlier accidental expansion to the whole repository.
86
+
87
+ Planning reads Git objects and existing artifacts without loading a model provider or repository
88
+ adapter, indexing code, collecting runtime evidence, validating the graph or writing snapshots.
89
+ It does not check credentials or estimate tokens or dollars. Cache hits depend on unchanged
90
+ inputs; corrupt or unreadable entries are reported separately from misses. `--force-semantic`
91
+ shows planned re-extraction without allocating or writing a forced revision.
92
+
93
+ A plan exits 0 when its source/cache/budget checks are ready. It exits 1 with the full plan when
94
+ errors block those checks or incomplete selection needs review. Missing/no-document selectors
95
+ and existing agent-selection limits are warnings; they do not add a new hydration gate that
96
+ would prevent removing the last document and invalidating its relationships. All selected
97
+ documents remain visible when budgets are exceeded.
98
+
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
102
+ [ADR 016](docs/ADR-016-hydration-preview.md) and the [1.0 task list](docs/RELEASE-1.0-PLAN.md).
103
+
104
+ Starting with **0.4.0**, preview also maps opaque diagnostic source IDs to local paths. Semantic
105
+ failures report completed/remaining work and recovery guidance; `hydrate --json --quiet` emits
106
+ their structured report on stderr. Built-in Codex can reuse completed artifacts after supported
107
+ operational changes such as increasing a timeout. See [provider recovery](docs/PROVIDERS.md#limits-and-reuse)
108
+ for preview, retry and explicit legacy-cache adoption instructions.
109
+
110
+ ## SHACL validation and performance
111
+
112
+ Starting with 0.3.0, the toolkit indexes a temporary union to accelerate cold validation.
113
+ The toolkit uses JavaScript `rdf-validate-shacl` and an in-memory
114
+ N3 store. Each hydration validates the complete graph; semantic artifact reuse does not make
115
+ validation incremental, and there is no validation-result cache, Rust engine or GraphDB service.
116
+ Saved queries do not rerun SHACL.
117
+
118
+ The current shapes use subject/predicate/object values independently of graph names. Validation
119
+ now builds one temporary union graph so repeated property lookups avoid scanning every named
120
+ assertion graph. The same shapes and validator run against that union. The original named dataset,
121
+ assertion records and provenance remain the source for SPARQL, queries and serialization.
122
+
123
+ In bounded, single-run public experiments, 2,000 facts with unique subject/object nodes took
124
+ 31.50 seconds in the original SHACL path, compared with 0.153 seconds to build the union and
125
+ 0.467 seconds for SHACL on that union. A 10,000-fact union case used about 1.3 GiB process RSS;
126
+ the extra projection has a memory cost. These measurements do not reproduce the private consumer's
127
+ full generation run or guarantee a production duration. See [ADR 017](docs/ADR-017-shacl-validation-union.md)
128
+ for methodology, parity boundaries and remaining work. To run the bounded public benchmark from
129
+ a development checkout:
130
+
131
+ ```sh
132
+ pnpm build
133
+ pnpm benchmark:shacl
134
+ ```
135
+
136
+ Current hydration progress reports phase boundaries. The validator performs synchronous work,
137
+ so an ordinary timer cannot provide an honest heartbeat while that work blocks Node's main
138
+ thread. Worker-based progress, early identity checks, validation-result reuse and incremental
139
+ validation remain separate T05 work; they do not delay the focused cold-validation release.
140
+
70
141
  ## Development
71
142
 
72
143
  The development source is organized by responsibility; see
@@ -107,6 +178,15 @@ For tests excluded by the build config or workspace packages whose outputs are a
107
178
  committed archives, configure an [explicit TypeScript source project](docs/ADR-010-committed-source-indexing.md)
108
179
  with selected files and local package aliases.
109
180
 
181
+ Repository adapters using **0.4.0 or newer** should import `repositoryFileNode` from
182
+ `@weftlet/core` for physical-file nodes. It preserves canonical document IDs, spec/test Evidence
183
+ and production Component kinds, including test files containing mocked calls or decorators.
184
+ Keep business-concept nodes separate; see the [core API example](packages/core/README.md) and
185
+ [producer decision](docs/ADR-020-canonical-physical-file-producers.md). After upgrading, rebuild
186
+ the graph with compatible cached artifacts; historical snapshots remain queryable but cannot
187
+ satisfy current validation with old extraction versions. Consumer-owned adapters must be migrated
188
+ as well; the toolkit cannot correct nodes they still classify independently.
189
+
110
190
  In a selected document such as `docs/intent.md`, link a responsibility to its sources:
111
191
 
112
192
  ```md
@@ -7,12 +7,14 @@ export interface AgentSemanticScope {
7
7
  readonly semanticSources: readonly string[];
8
8
  readonly headName: string;
9
9
  readonly snapshotSuffix: string;
10
+ readonly omittedDocuments?: readonly string[];
11
+ readonly ignoredSearchTerms?: number;
10
12
  }
11
13
  export declare class AgentSemanticScopeResolver {
12
14
  private readonly config;
13
15
  private readonly repository;
14
16
  constructor(config: ContextGraphConfig, repository: GitRepository);
15
- resolve(request: ImpactScanRequest): Promise<AgentSemanticScope>;
17
+ resolve(request: ImpactScanRequest, ref?: string): Promise<AgentSemanticScope>;
16
18
  }
17
19
  export declare class PreparedImpactQueryService implements ImpactQueryService {
18
20
  private readonly store;
@@ -16,13 +16,15 @@ export class AgentSemanticScopeResolver {
16
16
  this.config = config;
17
17
  this.repository = repository;
18
18
  }
19
- async resolve(request) {
19
+ async resolve(request, ref = 'HEAD') {
20
20
  const agent = this.config.agentContext;
21
21
  if (!agent)
22
22
  throw new Error('Agent context preparation is not configured');
23
- const commitSha = await this.repository.resolveRef('HEAD');
23
+ const commitSha = await this.repository.resolveRef(ref);
24
24
  const available = new Set((await this.repository.listFilesAt(commitSha, this.config.semanticSources)).filter((path) => /\.(?:md|mdx|txt)$/i.test(path)));
25
25
  const selected = new Set();
26
+ const omitted = new Set();
27
+ let ignoredSearchTerms = 0;
26
28
  for (const path of await this.repository.listFilesAt(commitSha, agent.alwaysSemanticSources)) {
27
29
  if (available.has(path))
28
30
  selected.add(path);
@@ -42,6 +44,8 @@ export class AgentSemanticScopeResolver {
42
44
  .filter((path) => available.has(path));
43
45
  for (const path of references.slice(0, agent.maxReferencedDocuments))
44
46
  selected.add(path);
47
+ for (const path of references.slice(agent.maxReferencedDocuments))
48
+ omitted.add(path);
45
49
  }
46
50
  if (agent.includeMatchingEvidence) {
47
51
  const ownerSlug = basename(ownerDocument).replace(/\.md$/i, '');
@@ -51,16 +55,19 @@ export class AgentSemanticScopeResolver {
51
55
  }
52
56
  }
53
57
  else {
54
- const terms = [
58
+ const requestedTerms = [
55
59
  request.task,
56
60
  ...(request.checklistItems ?? []),
57
61
  ...(request.capabilities ?? []),
58
62
  ]
59
- .filter((term) => Boolean(term))
60
- .slice(0, 8);
61
- const matches = await this.repository.findTextAt(commitSha, terms, this.config.semanticSources);
63
+ .filter((term) => Boolean(term));
64
+ const terms = requestedTerms.slice(0, 8);
65
+ ignoredSearchTerms = requestedTerms.length - terms.length;
66
+ const matches = (await this.repository.findTextAt(commitSha, terms, this.config.semanticSources)).filter((match) => available.has(match.path));
62
67
  for (const match of matches.slice(0, agent.maxReferencedDocuments))
63
68
  selected.add(match.path);
69
+ for (const match of matches.slice(agent.maxReferencedDocuments))
70
+ omitted.add(match.path);
64
71
  }
65
72
  const semanticSources = [...selected].sort();
66
73
  if (semanticSources.length === 0) {
@@ -72,6 +79,8 @@ export class AgentSemanticScopeResolver {
72
79
  semanticSources,
73
80
  headName: `agent-${scopeDigest}`,
74
81
  snapshotSuffix: `agent-${scopeDigest}`,
82
+ omittedDocuments: [...omitted].filter((path) => !selected.has(path)).sort(),
83
+ ignoredSearchTerms,
75
84
  };
76
85
  }
77
86
  }
@@ -10,11 +10,13 @@ import { OpenCaesarOntologyCompiler, DirectRdfOntologyCompiler } from "../extrac
10
10
  import { SnapshotImpactQueryService } from "../graph/query.js";
11
11
  import { SemanticHydrator } from "../extraction/semantic.js";
12
12
  import { createSemanticExtractor } from "../extraction/providers.js";
13
- import { SemanticArtifactStore, FileSnapshotStore } from "../storage/storage.js";
13
+ import { FileSnapshotStore } from "../storage/storage.js";
14
+ import { createSemanticArtifactStore } from './semantic-artifact-store.js';
14
15
  import { RepositoryContractExtractor, RepositoryFileExtractor, ScipSymbolExtractor, ScipTypeScriptIndexer, } from "../extraction/static.js";
15
16
  import { OtlpJsonRuntimeEvidenceProvider } from "../extraction/telemetry.js";
16
17
  import { SnapshotChecks } from "./snapshot-checks.js";
17
18
  import { resolveConsumerModule } from "../infrastructure/module-resolution.js";
19
+ import { requiredSnapshotExtractorVersions } from './extraction-identity.js';
18
20
  const emptyAdapter = {
19
21
  factExtractors: [],
20
22
  invariantEvaluators: [],
@@ -45,7 +47,7 @@ export async function createContextGraphApplication(configPath) {
45
47
  const repository = new GitRepository(repositoryRoot);
46
48
  const adapter = await loadContextGraphAdapterModule(config.adapter, repository, repositoryRoot);
47
49
  const snapshotStore = new FileSnapshotStore(storageRoot);
48
- const artifactStore = new SemanticArtifactStore(storageRoot);
50
+ const artifactStore = createSemanticArtifactStore(repositoryRoot, config);
49
51
  const semanticExtractor = createSemanticExtractor(config.semantic, repositoryRoot);
50
52
  const semanticHydrator = new SemanticHydrator(config.semantic, artifactStore, semanticExtractor);
51
53
  const staticIndexer = new ScipTypeScriptIndexer(config.scip, storageRoot);
@@ -75,7 +77,8 @@ export async function createContextGraphApplication(configPath) {
75
77
  const rawQueryService = scopeResolver
76
78
  ? new PreparedImpactQueryService(snapshotStore, hydrator, scopeResolver)
77
79
  : new SnapshotImpactQueryService(snapshotStore, headName);
78
- const queryService = new SavedEvidenceQueryService(rawQueryService, repository, sha256(stableJson(config)));
80
+ const expectedExtractors = requiredSnapshotExtractorVersions(config);
81
+ const queryService = new SavedEvidenceQueryService(rawQueryService, repository, sha256(stableJson(config)), expectedExtractors);
79
82
  return {
80
83
  ...loaded,
81
84
  storageRoot,
@@ -84,7 +87,7 @@ export async function createContextGraphApplication(configPath) {
84
87
  ontologyCompiler,
85
88
  hydrator,
86
89
  queryService,
87
- snapshotChecks: new SnapshotChecks(snapshotStore, repository, hydrator, scopeResolver, sha256(stableJson(config))),
90
+ snapshotChecks: new SnapshotChecks(snapshotStore, repository, hydrator, scopeResolver, sha256(stableJson(config)), expectedExtractors),
88
91
  scopeResolver,
89
92
  headName,
90
93
  };
@@ -0,0 +1,43 @@
1
+ import type { ImpactScanRequest } from '@weftlet/core';
2
+ import { type SemanticInputIssue, type SemanticSelection } from '../extraction/semantic-sources.js';
3
+ import { type SemanticScope } from './semantic-scope.js';
4
+ export interface SemanticCacheAdoptionOptions {
5
+ readonly fromConfigPath: string;
6
+ readonly ref?: string;
7
+ readonly dryRun?: boolean;
8
+ readonly request?: ImpactScanRequest;
9
+ }
10
+ export type SemanticCacheAdoptionStatus = 'adopted' | 'reusable' | 'planned' | 'missing' | 'corrupt' | 'unavailable' | 'conflict';
11
+ export interface SemanticCacheAdoptionRecord {
12
+ readonly path: string;
13
+ readonly key: string;
14
+ readonly status: SemanticCacheAdoptionStatus;
15
+ readonly reason: string;
16
+ }
17
+ export interface SemanticCacheAdoption {
18
+ readonly schemaVersion: 1;
19
+ readonly kind: 'semantic-cache-adoption';
20
+ readonly repositoryId: string;
21
+ readonly ref: string;
22
+ readonly commitSha: string;
23
+ readonly configurationDigest: string;
24
+ readonly previousConfigurationDigest: string;
25
+ readonly dryRun: boolean;
26
+ readonly ready: boolean;
27
+ readonly scope: SemanticScope & {
28
+ readonly complete: boolean;
29
+ };
30
+ readonly records: readonly SemanticCacheAdoptionRecord[];
31
+ readonly counts: Readonly<Record<SemanticCacheAdoptionStatus, number>>;
32
+ readonly selections: readonly SemanticSelection[];
33
+ readonly budgets: {
34
+ readonly maxDocuments: number;
35
+ readonly maxSourceBytes: number;
36
+ readonly selectedDocuments: number;
37
+ readonly totalSourceBytes: number;
38
+ };
39
+ readonly issues: readonly SemanticInputIssue[];
40
+ readonly assumptions: readonly string[];
41
+ }
42
+ /** Explicitly adopts only committed-source artifacts from a compatible, named legacy profile. */
43
+ export declare function adoptSemanticCache(configPath: string, options: SemanticCacheAdoptionOptions): Promise<SemanticCacheAdoption>;
@@ -0,0 +1,129 @@
1
+ import { loadConfig } from '../configuration/config.js';
2
+ import { resolveSemanticStorageRoots, resolveStorageRoot, semanticCachePartition } from '../configuration/cache.js';
3
+ import { inspectSemanticCache } from '../extraction/semantic-cache.js';
4
+ import { semanticProviderIdentity } from '../extraction/semantic-identity.js';
5
+ import { selectSemanticInputs } from '../extraction/semantic-sources.js';
6
+ import { sha256, stableJson } from '../infrastructure/util.js';
7
+ import { GitRepository } from '../repository/git.js';
8
+ import { SemanticArtifactStore } from '../storage/storage.js';
9
+ import { resolveSemanticScope } from './semantic-scope.js';
10
+ const problemStatuses = new Set(['corrupt', 'unavailable', 'conflict']);
11
+ function problem(inspection, source, location) {
12
+ if (inspection.status !== 'corrupt' && inspection.status !== 'unavailable')
13
+ return undefined;
14
+ return { path: source.path, key: inspection.key, status: inspection.status,
15
+ reason: `${location} cache: ${inspection.reason}` };
16
+ }
17
+ async function inspectCandidate(source, config, identity, previous, current) {
18
+ const [before, existing] = await Promise.all([
19
+ inspectSemanticCache(previous, config, source, identity),
20
+ inspectSemanticCache(current, config, source, identity),
21
+ ]);
22
+ const failure = problem(existing, source, 'Current') ?? problem(before, source, 'Previous');
23
+ if (failure)
24
+ return { source, record: failure };
25
+ const base = { path: source.path, key: existing.key };
26
+ if (existing.status === 'hit') {
27
+ const conflict = before.status === 'hit' && before.artifact.outputDigest !== existing.artifact.outputDigest;
28
+ return { source, record: { ...base, status: conflict ? 'conflict' : 'reusable', reason: conflict
29
+ ? 'Current and previous caches contain different valid artifacts for this key; neither was overwritten.'
30
+ : 'A validated artifact is already available in the current cache.' } };
31
+ }
32
+ if (before.status === 'miss')
33
+ return { source, record: { ...base, status: 'missing',
34
+ reason: 'The explicit previous profile has no complete artifact for this source and extraction contract.' } };
35
+ if (before.status !== 'hit' || existing.status !== 'miss')
36
+ throw new Error('Unexpected cache inspection state');
37
+ return { source, artifact: before.artifact, record: { ...base, status: 'planned',
38
+ reason: 'A validated previous artifact can be copied without extraction or changing its provenance.' } };
39
+ }
40
+ async function publishCandidate(candidate, config, identity, current) {
41
+ let publicationError;
42
+ let published = false;
43
+ try {
44
+ await current.write(candidate.artifact);
45
+ published = true;
46
+ }
47
+ catch (error) {
48
+ publicationError = error;
49
+ }
50
+ // Reinspect even after a successful write: an immutable-key winner can have
51
+ // matching output but corrupt identity metadata. A digest match alone is not
52
+ // permission to report that artifact as reusable.
53
+ const existing = await inspectSemanticCache(current, config, candidate.source, identity);
54
+ const failure = problem(existing, candidate.source, 'Current');
55
+ if (failure)
56
+ return failure;
57
+ if (existing.status === 'hit' && existing.artifact.outputDigest !== candidate.artifact.outputDigest) {
58
+ return { ...candidate.record, status: 'conflict',
59
+ reason: 'A concurrent writer published a different valid artifact; neither artifact was overwritten.' };
60
+ }
61
+ if (!published) {
62
+ throw new Error('Unable to adopt a semantic artifact because storage publication failed.', { cause: publicationError });
63
+ }
64
+ if (existing.status !== 'hit')
65
+ return { ...candidate.record, status: 'unavailable',
66
+ reason: 'Published artifact is no longer readable; no successful adoption is claimed.' };
67
+ return { ...candidate.record, status: 'adopted',
68
+ reason: 'Validated artifact is available in the current cache; the previous artifact is unchanged.' };
69
+ }
70
+ /** Explicitly adopts only committed-source artifacts from a compatible, named legacy profile. */
71
+ export async function adoptSemanticCache(configPath, options) {
72
+ const [loaded, previous] = await Promise.all([loadConfig(configPath), loadConfig(options.fromConfigPath)]);
73
+ const { config, repositoryRoot } = loaded;
74
+ if (repositoryRoot !== previous.repositoryRoot) {
75
+ throw new Error('Cache adoption requires both configurations to resolve to the same checkout.');
76
+ }
77
+ if (semanticCachePartition(repositoryRoot, config) !== semanticCachePartition(repositoryRoot, previous.config)) {
78
+ throw new Error('Cache adoption requires matching non-operational configuration and provider contracts.');
79
+ }
80
+ const repository = new GitRepository(repositoryRoot);
81
+ const ref = options.ref ?? 'HEAD';
82
+ const commitSha = await repository.resolveRef(ref);
83
+ const scope = await resolveSemanticScope(config, repository, commitSha, options.request);
84
+ const selection = await selectSemanticInputs(repository, commitSha, scope.selectors, config.semantic);
85
+ const issues = [...selection.issues];
86
+ const limited = scope.omittedDocuments.length > 0 || scope.ignoredSearchTerms > 0;
87
+ const complete = !limited && selection.selections.every((item) => item.status === 'matched');
88
+ if (limited)
89
+ issues.push({ code: 'selection-limited', severity: 'warning',
90
+ message: `Existing agent selection limits omitted ${scope.omittedDocuments.length} documents and ${scope.ignoredSearchTerms} search terms; adoption describes only the selected scope.`,
91
+ paths: scope.omittedDocuments });
92
+ const dryRun = options.dryRun ?? false;
93
+ const records = [];
94
+ if (!issues.some((issue) => issue.severity === 'error')) {
95
+ const current = new SemanticArtifactStore(resolveSemanticStorageRoots(repositoryRoot, config).primary);
96
+ // Do not scan sibling profiles or infer ownership from an artifact key. The
97
+ // supplied old configuration proves the exact legacy location to inspect.
98
+ const old = new SemanticArtifactStore(resolveStorageRoot(repositoryRoot, previous.config));
99
+ const identity = semanticProviderIdentity(config.semantic);
100
+ for (const source of selection.sources) {
101
+ const candidate = await inspectCandidate(source, config.semantic, identity, old, current);
102
+ const result = candidate.artifact && !dryRun
103
+ ? await publishCandidate(candidate, config.semantic, identity, current) : candidate.record;
104
+ records.push(result);
105
+ if (problemStatuses.has(result.status))
106
+ issues.push({ code: `cache-${result.status}`, severity: 'error',
107
+ message: result.reason, paths: [result.path] });
108
+ }
109
+ }
110
+ const counts = {
111
+ adopted: 0, reusable: 0, planned: 0, missing: 0, corrupt: 0, unavailable: 0, conflict: 0,
112
+ };
113
+ for (const record of records)
114
+ counts[record.status]++;
115
+ return {
116
+ schemaVersion: 1, kind: 'semantic-cache-adoption', repositoryId: config.repositoryId,
117
+ ref, commitSha, configurationDigest: sha256(stableJson(config)),
118
+ previousConfigurationDigest: sha256(stableJson(previous.config)), dryRun,
119
+ ready: complete && !issues.some((issue) => issue.severity === 'error'), scope: { ...scope, complete },
120
+ records, counts, selections: selection.selections, budgets: selection.budgets, issues,
121
+ 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.',
123
+ 'Missing or incomplete previous artifacts need later extraction. Corrupt or conflicting complete artifacts are retained for diagnosis.',
124
+ 'Adoption preserves original artifact identity and provenance; it does not validate or publish a current snapshot.',
125
+ 'Artifacts are adopted independently; earlier valid copies remain available if a later artifact or storage operation fails.',
126
+ 'No provider, authentication, indexing, ontology or repository adapter is initialized. Dry runs do not write cache files.',
127
+ ],
128
+ };
129
+ }
@@ -0,0 +1,6 @@
1
+ import type { ContextGraphConfig } from '../configuration/config.js';
2
+ export declare const graphAssemblyVersion = 7;
3
+ export declare const graphAssemblyIdentity = "standalone-graph-v7";
4
+ /** 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 incompatibleSnapshotExtractors(actual: Readonly<Record<string, string>> | undefined, expected: Readonly<Record<string, string>>): readonly string[];
@@ -0,0 +1,15 @@
1
+ import { repositoryContractExtractorIdentity, scipSymbolExtractorIdentity } from '../extraction/static.js';
2
+ import { routingExtractorIdentity } from '../extraction/routing.js';
3
+ export const graphAssemblyVersion = 7;
4
+ export const graphAssemblyIdentity = `standalone-graph-v${graphAssemblyVersion}`;
5
+ /** Runtime expectations are separate from source/configuration identity and artifact reuse. */
6
+ export function requiredSnapshotExtractorVersions(config) {
7
+ return { assembly: graphAssemblyIdentity,
8
+ [repositoryContractExtractorIdentity.id]: repositoryContractExtractorIdentity.version,
9
+ [scipSymbolExtractorIdentity.id]: scipSymbolExtractorIdentity.version,
10
+ ...(config?.routingSources?.length ? { [routingExtractorIdentity.id]: routingExtractorIdentity.version } : {}),
11
+ };
12
+ }
13
+ export function incompatibleSnapshotExtractors(actual, expected) {
14
+ return Object.keys(expected).filter((id) => actual?.[id] !== expected[id]).sort();
15
+ }
@@ -0,0 +1,41 @@
1
+ import { SemanticProviderError } from '../extraction/semantic-process.js';
2
+ import { SemanticPolicyError, type SemanticHydrationPhase } from '../extraction/semantic-diagnostics.js';
3
+ export type SemanticFailureCode = SemanticProviderError['code'] | SemanticPolicyError['code'] | 'cache-failure' | 'provider-failure';
4
+ export interface SemanticRunProgress {
5
+ readonly total: number;
6
+ readonly completed: number;
7
+ readonly extracted: number;
8
+ readonly reused: number;
9
+ readonly failed: number;
10
+ readonly cancelled: number;
11
+ readonly notStarted: number;
12
+ /** Includes failed, cancelled and not-started sources; it is not an extraction estimate. */
13
+ readonly remaining: number;
14
+ }
15
+ export interface SemanticHydrationFailure {
16
+ readonly schemaVersion: 1;
17
+ readonly kind: 'semantic-hydration-failure';
18
+ readonly code: SemanticFailureCode;
19
+ readonly category: 'timeout' | 'cancellation' | 'provider' | 'infrastructure' | 'extraction-gap';
20
+ readonly phase: SemanticHydrationPhase | 'semantic-scheduling';
21
+ /** Absent when cancellation occurs between sources, without one failing source. */
22
+ readonly sourceId?: string;
23
+ readonly commitSha: string;
24
+ readonly configurationDigest: string;
25
+ /** Processing time through failure, including cache checks/retries for a source. */
26
+ readonly elapsedMs: number;
27
+ readonly elapsedScope: 'source' | 'semantic-run';
28
+ readonly timeoutMs: number;
29
+ readonly timeoutScope: 'provider-attempt' | 'module-execution';
30
+ readonly progress: SemanticRunProgress;
31
+ readonly forced: boolean;
32
+ readonly recovery: readonly string[];
33
+ }
34
+ export declare function semanticFailureCode(error: unknown, phase: SemanticHydrationPhase, signal?: AbortSignal): SemanticFailureCode;
35
+ export declare function semanticFailureCategory(code: SemanticFailureCode): SemanticHydrationFailure['category'];
36
+ /** Public failure envelope deliberately excludes raw errors, causes and provider output. */
37
+ export declare class SemanticHydrationError extends Error {
38
+ readonly report: SemanticHydrationFailure;
39
+ readonly code: SemanticFailureCode;
40
+ constructor(report: SemanticHydrationFailure);
41
+ }
@@ -0,0 +1,43 @@
1
+ import { SemanticProviderError } from '../extraction/semantic-process.js';
2
+ import { SemanticPolicyError } from '../extraction/semantic-diagnostics.js';
3
+ export function semanticFailureCode(error, phase, signal) {
4
+ if (signal?.aborted && (error === signal.reason ||
5
+ (error instanceof Error && error.name === 'AbortError') ||
6
+ (phase === 'semantic-extract' && !(error instanceof SemanticProviderError))))
7
+ return 'cancelled';
8
+ if (error instanceof SemanticProviderError &&
9
+ ['timeout', 'cancelled', 'unavailable', 'exit', 'invalid-output'].includes(error.code))
10
+ return error.code;
11
+ if (error instanceof SemanticPolicyError && ['unresolved', 'low-confidence'].includes(error.code))
12
+ return error.code;
13
+ if (phase === 'semantic-cache-read' || phase === 'semantic-cache-write')
14
+ return 'cache-failure';
15
+ return phase === 'semantic-validate' ? 'invalid-output' : 'provider-failure';
16
+ }
17
+ export function semanticFailureCategory(code) {
18
+ if (code === 'timeout')
19
+ return 'timeout';
20
+ if (code === 'cancelled')
21
+ return 'cancellation';
22
+ if (code === 'unavailable' || code === 'cache-failure')
23
+ return 'infrastructure';
24
+ if (code === 'invalid-output' || code === 'unresolved' || code === 'low-confidence')
25
+ return 'extraction-gap';
26
+ return 'provider';
27
+ }
28
+ /** Public failure envelope deliberately excludes raw errors, causes and provider output. */
29
+ export class SemanticHydrationError extends Error {
30
+ report;
31
+ code;
32
+ constructor(report) {
33
+ const p = report.progress;
34
+ super(`Semantic hydration ${report.code}: ${report.sourceId ?? 'between semantic sources'}; phase ${report.phase}; ` +
35
+ `${report.elapsedScope} elapsed ${report.elapsedMs} ms; configured ${report.timeoutScope} timeout ${report.timeoutMs} ms. ` +
36
+ `${p.completed}/${p.total} completed (${p.extracted} extracted, ${p.reused} reused); ` +
37
+ `${p.remaining} remaining (${p.failed} failed, ${p.cancelled} cancelled, ${p.notStarted} not started).\n` +
38
+ report.recovery.join('\n'));
39
+ this.report = report;
40
+ this.name = 'SemanticHydrationError';
41
+ this.code = report.code;
42
+ }
43
+ }
@@ -0,0 +1,54 @@
1
+ import type { ImpactScanRequest } from '@weftlet/core';
2
+ import { type SemanticCacheInspection } from '../extraction/semantic-cache.js';
3
+ import { type SemanticInputIssue, type SemanticSelection } from '../extraction/semantic-sources.js';
4
+ import { type SemanticScope } from './semantic-scope.js';
5
+ export interface HydrationPlanOptions {
6
+ readonly ref?: string;
7
+ readonly forceSemantic?: boolean;
8
+ readonly request?: ImpactScanRequest;
9
+ }
10
+ export interface HydrationPlanDocument {
11
+ readonly path: string;
12
+ /** Local mapping for opaque IDs in semantic progress and failure reports. */
13
+ readonly sourceId: string;
14
+ readonly stableSemanticId: string;
15
+ readonly sourceDigest: string;
16
+ readonly bytes: number;
17
+ readonly lines: number;
18
+ readonly selectedBy: readonly string[];
19
+ readonly role: 'semantic-document' | 'validation-evidence';
20
+ readonly sourceAuthority: 'declared' | 'inferred';
21
+ readonly approvedBy: readonly string[];
22
+ readonly extractionReview: 'unreviewed';
23
+ readonly cache: Omit<SemanticCacheInspection, 'artifact'>;
24
+ }
25
+ export interface HydrationPlan {
26
+ readonly schemaVersion: 1;
27
+ readonly kind: 'hydration-plan';
28
+ readonly repositoryId: string;
29
+ readonly ref: string;
30
+ readonly commitSha: string;
31
+ readonly configurationDigest: string;
32
+ readonly ready: boolean;
33
+ readonly scope: SemanticScope & {
34
+ readonly complete: boolean;
35
+ };
36
+ readonly documents: readonly HydrationPlanDocument[];
37
+ readonly selections: readonly SemanticSelection[];
38
+ readonly budgets: {
39
+ readonly maxDocuments: number;
40
+ readonly maxSourceBytes: number;
41
+ readonly selectedDocuments: number;
42
+ readonly totalSourceBytes: number;
43
+ };
44
+ readonly estimates: {
45
+ readonly reusableArtifacts: number;
46
+ readonly extractions: number;
47
+ readonly unknownArtifacts: number;
48
+ };
49
+ readonly issues: readonly SemanticInputIssue[];
50
+ readonly excludedWorkingChanges: readonly string[];
51
+ readonly assumptions: readonly string[];
52
+ }
53
+ /** Inspect only committed source objects, config and existing artifacts; never initialize a provider or adapter. */
54
+ export declare function planHydration(configPath: string, options?: HydrationPlanOptions): Promise<HydrationPlan>;
@@ -0,0 +1,71 @@
1
+ import { loadConfig } from '../configuration/config.js';
2
+ import { GitRepository } from '../repository/git.js';
3
+ import { matchesRepositoryGlob } from '../repository/glob.js';
4
+ import { createSemanticArtifactStore } from './semantic-artifact-store.js';
5
+ import { inspectSemanticCache } from '../extraction/semantic-cache.js';
6
+ import { semanticProviderIdentity } from '../extraction/semantic-identity.js';
7
+ import { selectSemanticInputs } from '../extraction/semantic-sources.js';
8
+ import { sha256, stableJson } from '../infrastructure/util.js';
9
+ import { resolveSemanticScope } from './semantic-scope.js';
10
+ import { semanticSourceId } from '../extraction/semantic-diagnostics.js';
11
+ async function describeDocument(source, selections, config, evidencePaths, store, forceSemantic = false) {
12
+ const inspection = forceSemantic
13
+ ? { status: 'forced', reason: 'Explicit force bypasses existing artifacts; a new revision is allocated only during hydration.' }
14
+ : await inspectSemanticCache(store, config.semantic, source, semanticProviderIdentity(config.semantic));
15
+ const { artifact: _artifact, ...cache } = inspection;
16
+ const approvedBy = (config.approvedIntentSources ?? []).filter((pattern) => matchesRepositoryGlob(source.path, pattern));
17
+ return { path: source.path, sourceId: semanticSourceId(config.repositoryId, source),
18
+ stableSemanticId: source.stableSemanticId, sourceDigest: source.digest,
19
+ bytes: Buffer.byteLength(source.content), lines: source.content.split('\n').length,
20
+ selectedBy: selections.filter((item) => item.paths.includes(source.path)).map((item) => item.selector),
21
+ role: evidencePaths.has(source.path) ? 'validation-evidence' : 'semantic-document',
22
+ sourceAuthority: approvedBy.length ? 'declared' : 'inferred', approvedBy,
23
+ extractionReview: 'unreviewed', cache };
24
+ }
25
+ /** Inspect only committed source objects, config and existing artifacts; never initialize a provider or adapter. */
26
+ export async function planHydration(configPath, options = {}) {
27
+ const { config, repositoryRoot } = await loadConfig(configPath);
28
+ const repository = new GitRepository(repositoryRoot);
29
+ const ref = options.ref ?? 'HEAD';
30
+ const commitSha = await repository.resolveRef(ref);
31
+ const scope = await resolveSemanticScope(config, repository, commitSha, options.request);
32
+ const selection = await selectSemanticInputs(repository, commitSha, scope.selectors, config.semantic);
33
+ const issues = [...selection.issues];
34
+ const { omittedDocuments, ignoredSearchTerms } = scope;
35
+ const limited = omittedDocuments.length > 0 || ignoredSearchTerms > 0;
36
+ const complete = !limited && selection.selections.every((item) => item.status === 'matched');
37
+ if (limited)
38
+ issues.push({ code: 'selection-limited', severity: 'warning',
39
+ message: `Existing agent selection limits omitted ${omittedDocuments.length} documents and ${ignoredSearchTerms} search terms; this plan describes only the selected scope.`,
40
+ paths: omittedDocuments });
41
+ const store = createSemanticArtifactStore(repositoryRoot, config);
42
+ const evidencePaths = new Set(await repository.listFilesAt(commitSha, config.evidenceRoots));
43
+ const documents = [];
44
+ for (const source of selection.sources) {
45
+ const document = await describeDocument(source, selection.selections, config, evidencePaths, store, options.forceSemantic);
46
+ const { cache } = document;
47
+ if (cache.status === 'corrupt' || cache.status === 'unavailable')
48
+ issues.push({
49
+ code: `cache-${cache.status}`, severity: 'error', message: cache.reason, paths: [source.path],
50
+ });
51
+ documents.push(document);
52
+ }
53
+ const count = (...states) => documents.filter((doc) => states.includes(doc.cache.status)).length;
54
+ return {
55
+ schemaVersion: 1, kind: 'hydration-plan', repositoryId: config.repositoryId, ref, commitSha,
56
+ configurationDigest: sha256(stableJson(config)), ready: complete && !issues.some((item) => item.severity === 'error'),
57
+ scope: { ...scope, complete },
58
+ documents, selections: selection.selections, budgets: selection.budgets,
59
+ estimates: { reusableArtifacts: count('hit'), extractions: count('miss', 'forced'), unknownArtifacts: count('corrupt', 'unavailable') },
60
+ issues, excludedWorkingChanges: await repository.workingChanges(),
61
+ assumptions: [
62
+ 'Sources come from the resolved commit; configuration and cache are read from the current local environment.',
63
+ 'Estimates require unchanged source, configuration, provider contract and readable artifacts; concurrent changes can invalidate them.',
64
+ 'Document and per-document UTF-8 byte limits apply to all selected documents, including cache hits; no token or monetary estimate is made.',
65
+ 'Ready describes semantic inputs and readable cache only. Providers, credentials, adapters, storage writes, indexing, runtime evidence, routing, ontology and graph validation have not been checked.',
66
+ 'Roles reflect configured evidence roots, not inferred prose meaning. Selected validation documents still undergo semantic extraction today; source approval never approves extracted assertions.',
67
+ 'Selection is the configured scope, optionally narrowed by existing bounded agent selection; recursive SOT traversal and complete product coverage are not claimed.',
68
+ 'Unmatched selectors and bounded scope omissions need review. They do not introduce new hydration gates: removing the last document can still publish a graph with its relationships removed.',
69
+ ],
70
+ };
71
+ }
@@ -4,13 +4,11 @@ import type { GitRepository } from "../repository/git.js";
4
4
  import type { OntologyCompiler } from "../extraction/ontology.js";
5
5
  import type { SemanticHydrator } from "../extraction/semantic.js";
6
6
  import type { ScipIndexResult, StaticIndexer } from "../extraction/static.js";
7
+ import { type HydrationProgressEvent } from './semantic-run.js';
8
+ export type { HydrationProgressEvent } from './semantic-run.js';
7
9
  export interface HydrationOptions {
8
10
  readonly signal?: AbortSignal;
9
- readonly onProgress?: (event: {
10
- phase: string;
11
- completed?: number;
12
- total?: number;
13
- }) => void;
11
+ readonly onProgress?: (event: HydrationProgressEvent) => void;
14
12
  readonly ref: string;
15
13
  readonly headName: string;
16
14
  readonly forceSemantic?: boolean;