@weftlet/toolkit 0.2.0 → 0.3.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 (37) hide show
  1. package/README.md +69 -3
  2. package/dist/application/agent-context.d.ts +3 -1
  3. package/dist/application/agent-context.js +15 -6
  4. package/dist/application/hydration-plan.d.ts +57 -0
  5. package/dist/application/hydration-plan.js +79 -0
  6. package/dist/application/hydrator.js +12 -16
  7. package/dist/extraction/document.js +1 -6
  8. package/dist/extraction/providers.js +2 -2
  9. package/dist/extraction/repository-nodes.d.ts +7 -0
  10. package/dist/extraction/repository-nodes.js +20 -0
  11. package/dist/extraction/semantic-cache.d.ts +12 -0
  12. package/dist/extraction/semantic-cache.js +59 -0
  13. package/dist/extraction/semantic-identity.d.ts +7 -0
  14. package/dist/extraction/semantic-identity.js +29 -0
  15. package/dist/extraction/semantic-sources.d.ts +27 -0
  16. package/dist/extraction/semantic-sources.js +35 -0
  17. package/dist/extraction/semantic.js +27 -21
  18. package/dist/graph/rdf.d.ts +1 -1
  19. package/dist/graph/rdf.js +13 -28
  20. package/dist/graph/shacl-shapes.d.ts +5 -0
  21. package/dist/graph/shacl-shapes.js +27 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +1 -0
  24. package/dist/interfaces/cli.js +47 -3
  25. package/dist/repository/git.d.ts +1 -1
  26. package/dist/repository/git.js +5 -1
  27. package/docs/ADR-015-repository-document-identity.md +57 -0
  28. package/docs/ADR-016-hydration-preview.md +91 -0
  29. package/docs/ADR-017-shacl-validation-union.md +102 -0
  30. package/docs/CONSUMER-TRIAL.md +6 -5
  31. package/docs/RELEASE-1.0-PLAN.md +288 -0
  32. package/docs/RELEASING.md +27 -8
  33. package/docs/ROADMAP.md +32 -2
  34. package/docs/VALIDATION.md +202 -0
  35. package/docs/WEFTLET-2.0-PLAN.md +283 -0
  36. package/docs/extraction-manifest.json +29 -12
  37. package/package.json +3 -2
@@ -8,7 +8,24 @@ import { resolveCodexInvocation } from './codex-command.js';
8
8
  import { runSemanticProcess, SemanticProviderError } from "./semantic-process.js";
9
9
  export { SemanticProviderError } from "./semantic-process.js";
10
10
  import { sha256, stableJson } from "../infrastructure/util.js";
11
+ import { canonicalizeRepositoryDocumentNode } from './repository-nodes.js';
12
+ import { semanticArtifactKey, semanticArtifactOutputDigest, semanticProviderIdentity } from './semantic-identity.js';
13
+ import { inspectSemanticCache } from './semantic-cache.js';
11
14
  import { assertSemanticCitation, numberedSemanticSource, semanticCitationSchema, } from "./semantic-citations.js";
15
+ function canonicalizeRepositoryDocuments(output) {
16
+ const ids = new Map();
17
+ const assertions = output.assertions.map((assertion) => {
18
+ const subject = canonicalizeRepositoryDocumentNode(assertion.subject);
19
+ const object = canonicalizeRepositoryDocumentNode(assertion.object);
20
+ ids.set(assertion.subject.id, subject.id);
21
+ ids.set(assertion.object.id, object.id);
22
+ return { ...assertion, subject, object };
23
+ });
24
+ return { ...output, assertions, diagnostics: output.diagnostics?.map((diagnostic) => ({
25
+ ...diagnostic,
26
+ relatedNodeIds: diagnostic.relatedNodeIds.map((id) => ids.get(id) ?? id),
27
+ })) };
28
+ }
12
29
  function omlEscape(value) {
13
30
  return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r?\n/g, '\\n');
14
31
  }
@@ -213,7 +230,7 @@ export class CodexCliSemanticExtractor {
213
230
  this.config = config;
214
231
  this.runner = runner;
215
232
  this.loginProbe = loginProbe;
216
- this.identity = `codex-cli:${config.model}:${config.reasoningEffort}:${config.promptVersion}:contract-v2`;
233
+ this.identity = semanticProviderIdentity(config);
217
234
  }
218
235
  async extract(source, context, options = {}) {
219
236
  options.signal?.throwIfAborted();
@@ -235,7 +252,7 @@ export class CodexCliSemanticExtractor {
235
252
  'Use neutral graph kinds and the allowed precise predicates. Never guess a missing mapping.',
236
253
  'Use product-wide canonical IDs derived from durable concepts, not IDs scoped to this file.',
237
254
  'Reuse capability, policy, workflow, contract, and component IDs that another source would use for the same concept.',
238
- 'When the source names a repository path, use component:file:<path> so semantic and static facts join deterministically.',
255
+ 'For a repository document path ending in .md, .mdx or .txt, use document:<path> with kind Document. For other repository files use component:file:<path>; test/spec files have kind Evidence, other files Component. Use exact repository-relative paths; keep business concepts in their own IDs instead of assigning their kind to a file.',
239
256
  'Capture why a rule exists, its decision authority, and cross-domain dependencies when the source explicitly states them.',
240
257
  'Every assertion must cite inclusive 1-based source line numbers and carry calibrated confidence.',
241
258
  `Source lines are explicitly prefixed N| below. Cite those original-source numbers, not prompt lines or visual wraps; valid lines are 1-${lineCount}.`,
@@ -318,29 +335,18 @@ export class SemanticHydrator {
318
335
  }
319
336
  async hydrate(source, context, forceRevision, options = {}) {
320
337
  options.signal?.throwIfAborted();
321
- const keyInput = {
322
- stableSemanticId: source.stableSemanticId,
323
- sourceDigest: source.digest,
324
- promptVersion: this.config.promptVersion,
325
- extractorVersion: this.config.extractorVersion,
326
- modelIdentity: this.extractor.identity,
327
- adapterVersion: this.config.adapterVersion,
328
- ontologyVersion: this.config.ontologyVersion,
329
- minimumConfidence: this.config.minimumConfidence,
330
- failOnUnresolved: this.config.failOnUnresolved,
331
- forceRevision,
332
- diagnosticContractVersion: 3,
333
- };
334
- const key = sha256(stableJson(keyInput));
335
- const existing = await this.store.read(key);
336
- if (existing)
337
- return { artifact: existing, reused: true };
338
+ const key = semanticArtifactKey(this.config, source, this.extractor.identity, forceRevision);
339
+ const cached = await inspectSemanticCache(this.store, this.config, source, this.extractor.identity, forceRevision);
340
+ if (cached.status === 'hit')
341
+ return { artifact: cached.artifact, reused: true };
342
+ if (cached.status !== 'miss')
343
+ throw new Error(cached.reason);
338
344
  const extraction = await this.extractor.extract(source, context, options);
339
345
  assertSemanticOutput(extraction.parsed, source);
340
346
  if (typeof extraction.rawModelOutput !== 'string' || Buffer.byteLength(extraction.rawModelOutput) > 2 * 1024 * 1024) {
341
347
  throw new SemanticProviderError('invalid-output', 'Invalid or oversized raw semantic output');
342
348
  }
343
- const output = extraction.parsed;
349
+ const output = canonicalizeRepositoryDocuments(extraction.parsed);
344
350
  const lineCount = source.content.split('\n').length;
345
351
  const facts = output.assertions.map((assertion, index) => {
346
352
  assertSemanticCitation(assertion.citation, source.path, lineCount, `assertion[${index}]`);
@@ -395,7 +401,7 @@ export class SemanticHydrator {
395
401
  throw new Error(`Unresolved semantic mappings for ${source.path}: ${[...unresolvedMappings, ...diagnostics.map((item) => item.reason)].join('; ')}`);
396
402
  }
397
403
  const compiledOml = renderOml(key, output.assertions);
398
- const outputDigest = sha256(stableJson({ facts, diagnostics, compiledOml, rawModelOutput: extraction.rawModelOutput }));
404
+ const outputDigest = semanticArtifactOutputDigest({ facts, diagnostics, oml: compiledOml, rawModelOutput: extraction.rawModelOutput });
399
405
  const artifact = {
400
406
  key,
401
407
  stableSemanticId: source.stableSemanticId,
@@ -1,5 +1,5 @@
1
1
  import type { GraphFact, GraphSnapshot } from "@weftlet/core";
2
- export declare const CG = "https://context-graph.dev/record#";
2
+ export { CG } from './shacl-shapes.js';
3
3
  export declare const nodeIri: (id: string) => string;
4
4
  export declare const factIri: (id: string) => string;
5
5
  /** Lossless RDF dataset: one named graph per cited assertion, with provenance in the default graph. */
package/dist/graph/rdf.js CHANGED
@@ -1,39 +1,16 @@
1
1
  import { QueryEngine } from '@comunica/query-sparql';
2
- import { DataFactory, Parser, Store, Writer } from 'n3';
2
+ import { DataFactory, Store, Writer } from 'n3';
3
3
  import SHACLValidator from 'rdf-validate-shacl';
4
4
  import { contextPackEvidenceAuthority } from "./authority.js";
5
5
  import { stableJson } from "../infrastructure/util.js";
6
6
  import { createRequire } from 'node:module';
7
- const { namedNode: iri, literal, quad } = DataFactory;
8
- export const CG = 'https://context-graph.dev/record#';
9
- const RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
10
- const XSD = 'http://www.w3.org/2001/XMLSchema#';
7
+ import { CG, RDF, XSD, evidenceShapes } from './shacl-shapes.js';
8
+ const { namedNode: iri, literal } = DataFactory;
9
+ export { CG } from './shacl-shapes.js';
11
10
  export const nodeIri = (id) => `urn:context-graph:node:${encodeURIComponent(id)}`;
12
11
  export const factIri = (id) => `urn:context-graph:fact:${encodeURIComponent(id)}`;
13
12
  const engine = new QueryEngine();
14
13
  const SparqlParser = createRequire(import.meta.url)('sparqljs').Parser;
15
- const shapes = new Parser().parse(`
16
- @prefix cg: <${CG}>.
17
- @prefix rdf: <${RDF}>.
18
- @prefix sh: <http://www.w3.org/ns/shacl#>.
19
- @prefix xsd: <${XSD}>.
20
- cg:FactShape a sh:NodeShape; sh:targetClass rdf:Statement;
21
- sh:property [ sh:path cg:record; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string ];
22
- sh:property [ sh:path rdf:subject; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
23
- sh:property [ sh:path rdf:predicate; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
24
- sh:property [ sh:path rdf:object; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
25
- sh:property [ sh:path cg:authority; sh:minCount 1; sh:maxCount 1;
26
- sh:in ("declared" "implemented" "validated" "observed" "inferred" "draft") ];
27
- sh:property [ sh:path cg:path; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ];
28
- sh:property [ sh:path cg:commit; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ];
29
- sh:property [ sh:path cg:sourceDigest; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ];
30
- sh:property [ sh:path cg:startLine; sh:minCount 1; sh:datatype xsd:integer; sh:minInclusive 1 ];
31
- sh:property [ sh:path cg:endLine; sh:minCount 1; sh:datatype xsd:integer; sh:minInclusive 1 ];
32
- sh:property [ sh:path cg:confidence; sh:minCount 1; sh:datatype xsd:decimal; sh:minInclusive 0; sh:maxInclusive 1 ];
33
- sh:property [ sh:path cg:extractor; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ].
34
- cg:NodeShape a sh:NodeShape; sh:targetClass cg:Node;
35
- sh:property [ sh:path cg:kind; sh:minCount 1; sh:maxCount 1 ].
36
- `);
37
14
  /** Lossless RDF dataset: one named graph per cited assertion, with provenance in the default graph. */
38
15
  export class RdfEvidenceGraph {
39
16
  snapshot;
@@ -85,7 +62,15 @@ export class RdfEvidenceGraph {
85
62
  }
86
63
  }
87
64
  async validate() {
88
- const report = await new SHACLValidator(new Store(shapes)).validate(this.dataset);
65
+ // These graph-independent shapes already read the dataset as a union. Index
66
+ // that union once so every property lookup avoids scanning one graph per fact.
67
+ // Keep every triple; the original named dataset retains assertion provenance
68
+ // and remains the source for queries and serialization.
69
+ const validationGraph = new Store();
70
+ for (const { subject, predicate, object } of this.dataset) {
71
+ validationGraph.addQuad(subject, predicate, object);
72
+ }
73
+ const report = await new SHACLValidator(new Store(evidenceShapes)).validate(validationGraph);
89
74
  const issues = report.results.map((result) => ({
90
75
  category: 'extraction-gap',
91
76
  focus: result.focusNode?.value,
@@ -0,0 +1,5 @@
1
+ export declare const CG = "https://context-graph.dev/record#";
2
+ export declare const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
3
+ export declare const XSD = "http://www.w3.org/2001/XMLSchema#";
4
+ /** Graph-independent evidence constraints, evaluated over the union of all assertion graphs. */
5
+ export declare const evidenceShapes: import("n3").Quad[];
@@ -0,0 +1,27 @@
1
+ import { Parser } from 'n3';
2
+ export const CG = 'https://context-graph.dev/record#';
3
+ export const RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
4
+ export const XSD = 'http://www.w3.org/2001/XMLSchema#';
5
+ /** Graph-independent evidence constraints, evaluated over the union of all assertion graphs. */
6
+ export const evidenceShapes = new Parser().parse(`
7
+ @prefix cg: <${CG}>.
8
+ @prefix rdf: <${RDF}>.
9
+ @prefix sh: <http://www.w3.org/ns/shacl#>.
10
+ @prefix xsd: <${XSD}>.
11
+ cg:FactShape a sh:NodeShape; sh:targetClass rdf:Statement;
12
+ sh:property [ sh:path cg:record; sh:minCount 1; sh:maxCount 1; sh:datatype xsd:string ];
13
+ sh:property [ sh:path rdf:subject; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
14
+ sh:property [ sh:path rdf:predicate; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
15
+ sh:property [ sh:path rdf:object; sh:minCount 1; sh:maxCount 1; sh:nodeKind sh:IRI ];
16
+ sh:property [ sh:path cg:authority; sh:minCount 1; sh:maxCount 1;
17
+ sh:in ("declared" "implemented" "validated" "observed" "inferred" "draft") ];
18
+ sh:property [ sh:path cg:path; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ];
19
+ sh:property [ sh:path cg:commit; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ];
20
+ sh:property [ sh:path cg:sourceDigest; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ];
21
+ sh:property [ sh:path cg:startLine; sh:minCount 1; sh:datatype xsd:integer; sh:minInclusive 1 ];
22
+ sh:property [ sh:path cg:endLine; sh:minCount 1; sh:datatype xsd:integer; sh:minInclusive 1 ];
23
+ sh:property [ sh:path cg:confidence; sh:minCount 1; sh:datatype xsd:decimal; sh:minInclusive 0; sh:maxInclusive 1 ];
24
+ sh:property [ sh:path cg:extractor; sh:minCount 1; sh:datatype xsd:string; sh:minLength 1 ].
25
+ cg:NodeShape a sh:NodeShape; sh:targetClass cg:Node;
26
+ sh:property [ sh:path cg:kind; sh:minCount 1; sh:maxCount 1 ].
27
+ `);
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export * from "./visualization/visualization.js";
9
9
  export * from "./application/overlay.js";
10
10
  export * from "./graph/rdf.js";
11
11
  export * from "./extraction/providers.js";
12
+ export * from './application/hydration-plan.js';
package/dist/index.js CHANGED
@@ -10,3 +10,4 @@ export * from "./visualization/visualization.js";
10
10
  export * from "./application/overlay.js";
11
11
  export * from "./graph/rdf.js";
12
12
  export * from "./extraction/providers.js";
13
+ export * from './application/hydration-plan.js';
@@ -15,6 +15,7 @@ import { FileSnapshotStore } from "../storage/storage.js";
15
15
  import { diagnoseSetup } from "./doctor.js";
16
16
  import { runInit } from "./init.js";
17
17
  import { loadConfig } from "../configuration/config.js";
18
+ import { planHydration } from '../application/hydration-plan.js';
18
19
  function valueAfter(args, name) {
19
20
  const index = args.indexOf(name);
20
21
  return index >= 0 ? args[index + 1] : undefined;
@@ -45,10 +46,41 @@ export function impactRequestFromArgs(args) {
45
46
  continuation: valueAfter(args, '--continuation'),
46
47
  };
47
48
  }
49
+ function formatHydrationPlan(plan) {
50
+ const readiness = plan.issues.some((issue) => issue.severity === 'error')
51
+ ? 'blocked' : plan.ready ? 'ready' : 'needs review';
52
+ const lines = [
53
+ `Hydration plan: ${plan.repositoryId}`,
54
+ `Commit: ${plan.commitSha} (ref ${plan.ref})`,
55
+ `Configuration: ${plan.configurationDigest}`,
56
+ `Scope: ${plan.scope.mode}, ${plan.scope.complete ? 'complete' : 'incomplete'}; head ${plan.scope.headName}`,
57
+ `Documents: ${plan.budgets.selectedDocuments}/${plan.budgets.maxDocuments}; ${plan.budgets.totalSourceBytes} bytes total; ${plan.budgets.maxSourceBytes} bytes maximum per document`,
58
+ `Expected cache reuse: ${plan.estimates.reusableArtifacts}; extractions: ${plan.estimates.extractions}; unknown cache entries: ${plan.estimates.unknownArtifacts}`,
59
+ `Source/cache/budget checks: ${readiness}`,
60
+ ];
61
+ for (const document of plan.documents) {
62
+ lines.push(`- ${JSON.stringify(document.path)}: ${document.bytes} bytes, ${document.lines} lines; ${document.role}; source ${document.sourceAuthority}, extraction ${document.extractionReview}; cache ${document.cache.status}; selected by ${document.selectedBy.map((selector) => JSON.stringify(selector)).join(', ')}`);
63
+ }
64
+ for (const selection of plan.selections.filter((item) => item.status !== 'matched')) {
65
+ lines.push(`Selection ${JSON.stringify(selection.selector)}: ${selection.status}`);
66
+ }
67
+ for (const issue of plan.issues)
68
+ lines.push(`${issue.severity.toUpperCase()} [${issue.code}]: ${issue.message}`);
69
+ if (plan.scope.omittedDocuments.length) {
70
+ lines.push(`Documents omitted by scope limits: ${plan.scope.omittedDocuments.map((path) => JSON.stringify(path)).join(', ')}`);
71
+ }
72
+ if (plan.scope.ignoredSearchTerms)
73
+ lines.push(`Search terms omitted by scope limits: ${plan.scope.ignoredSearchTerms}`);
74
+ if (plan.excludedWorkingChanges.length) {
75
+ lines.push(`Working changes excluded: ${plan.excludedWorkingChanges.map((path) => JSON.stringify(path)).join(', ')}`);
76
+ }
77
+ lines.push(...plan.assumptions.map((assumption) => `Note: ${assumption}`));
78
+ return `${lines.join('\n')}\n`;
79
+ }
48
80
  async function main() {
49
81
  const args = process.argv.slice(2);
50
82
  if (args.includes('--help') || !args.length) {
51
- process.stdout.write('weftlet init|config show|config validate|doctor|hydrate|overlay|impact scan|sparql|validate|govern|visualize|diagnostics show|invariant check|clean-compare|ontology compile\nUse --config <file>; queries accept --snapshot <id>. impact scan accepts --changed-file, --capability, --max-facts, --max-depth, --continuation.\ninit [--yes] [--directory <project>] [--config <file>] [--repository-id <id>] [--code-root <path>] [--semantic-source <path>] [--evidence-root <path>] [--model <name>] [--no-gitignore]\nSource flags may be repeated. init never overwrites an existing config. See README.md for examples.\n');
83
+ process.stdout.write('weftlet init|config show|config validate|doctor|hydrate|overlay|impact scan|sparql|validate|govern|visualize|diagnostics show|invariant check|clean-compare|ontology compile\nUse --config <file>; queries accept --snapshot <id>. impact scan accepts --changed-file, --capability, --max-facts, --max-depth, --continuation.\nhydrate --plan [--json] [--ref <commit>] [--force-semantic] previews scope, cache and budgets without model calls or graph writes. Preview readiness is advisory and does not add a hydration gate.\ninit [--yes] [--directory <project>] [--config <file>] [--repository-id <id>] [--code-root <path>] [--semantic-source <path>] [--evidence-root <path>] [--model <name>] [--no-gitignore]\nSource flags may be repeated. init never overwrites an existing config. See README.md for examples.\n');
52
84
  return;
53
85
  }
54
86
  if (args[0] === 'init') {
@@ -85,6 +117,17 @@ async function main() {
85
117
  process.stdout.write(`${JSON.stringify(await diagnoseSetup(configPath), null, 2)}\n`);
86
118
  return;
87
119
  }
120
+ if (args[0] === 'hydrate' && args.includes('--plan')) {
121
+ const plan = await planHydration(configPath, {
122
+ ref: valueAfter(args, '--ref') ?? 'HEAD',
123
+ forceSemantic: args.includes('--force-semantic'),
124
+ request: impactRequestFromArgs(args),
125
+ });
126
+ process.stdout.write(args.includes('--json') ? `${JSON.stringify(plan, null, 2)}\n` : formatHydrationPlan(plan));
127
+ if (!plan.ready)
128
+ process.exitCode = 1;
129
+ return;
130
+ }
88
131
  const application = await createContextGraphApplication(configPath);
89
132
  const [command, subcommand] = args;
90
133
  if (command === 'sparql' || command === 'validate') {
@@ -120,9 +163,10 @@ async function main() {
120
163
  const controller = new AbortController();
121
164
  const cancel = () => controller.abort();
122
165
  process.once('SIGINT', cancel);
123
- const scope = await application.scopeResolver?.resolve(impactRequestFromArgs(args));
166
+ const ref = valueAfter(args, '--ref') ?? 'HEAD';
167
+ const scope = await application.scopeResolver?.resolve(impactRequestFromArgs(args), ref);
124
168
  const result = await application.hydrator.hydrate({
125
- ref: valueAfter(args, '--ref') ?? 'HEAD',
169
+ ref: scope?.commitSha ?? ref,
126
170
  headName: scope?.headName ?? application.headName,
127
171
  semanticSources: scope?.semanticSources,
128
172
  forceSemantic: args.includes('--force-semantic'),
@@ -22,5 +22,5 @@ export declare class GitRepository {
22
22
  readFileAt(commitSha: string, path: string): Promise<string>;
23
23
  findTextAt(commitSha: string, needles: readonly string[], roots: readonly string[]): Promise<readonly GitTextMatch[]>;
24
24
  delta(baseSha: string | undefined, headSha: string, roots: readonly string[]): Promise<GitDelta>;
25
- semanticSources(context: ExtractionContext, roots: readonly string[]): Promise<readonly SemanticSource[]>;
25
+ semanticSources(context: Pick<ExtractionContext, 'commitSha'>, roots: readonly string[]): Promise<readonly SemanticSource[]>;
26
26
  }
@@ -26,7 +26,7 @@ export class GitRepository {
26
26
  }
27
27
  async workingChanges() {
28
28
  const prefix = await this.run(['rev-parse', '--show-prefix']);
29
- const output = await this.execute(['status', '--porcelain=v1', '-z', '--untracked-files=all'], false);
29
+ const output = await this.execute(['--no-optional-locks', 'status', '--porcelain=v1', '-z', '--untracked-files=all'], false);
30
30
  const records = output.split('\0');
31
31
  const paths = new Set();
32
32
  for (let index = 0; index < records.length; index++) {
@@ -65,6 +65,8 @@ export class GitRepository {
65
65
  }
66
66
  }
67
67
  async listFilesAt(commitSha, roots) {
68
+ if (roots.length === 0)
69
+ return [];
68
70
  const output = await this.run(['ls-tree', '-r', '--name-only', commitSha, '--', ...roots]);
69
71
  return output.split('\n').filter(Boolean).sort();
70
72
  }
@@ -107,6 +109,8 @@ export class GitRepository {
107
109
  return [...matches.values()].sort((left, right) => left.path.localeCompare(right.path));
108
110
  }
109
111
  async delta(baseSha, headSha, roots) {
112
+ if (roots.length === 0)
113
+ return { changedPaths: [], deletedPaths: [], renamedPaths: {} };
110
114
  if (!baseSha) {
111
115
  return {
112
116
  changedPaths: await this.listFilesAt(headSha, roots),
@@ -0,0 +1,57 @@
1
+ # ADR-015: Canonical repository document identities
2
+
3
+ Status: accepted for the document identity patch.
4
+
5
+ ## Problem
6
+
7
+ The 0.2.0 semantic prompt requested `component:file:<path>` for every repository path,
8
+ while deterministic Markdown linking used `document:<path>` for document files.
9
+ Independently valid model assertions could assign Component and Document to the same
10
+ file ID. Combining those facts correctly failed the single-kind SHACL constraint.
11
+ This is an extraction identity defect, not evidence of contradictory product intent.
12
+
13
+ ## Decision
14
+
15
+ Physical repository documents ending in `.md`, `.mdx` or `.txt` use
16
+ `document:<repository-relative-path>` with kind Document. The semantic hydration
17
+ boundary canonicalizes both reserved prefixes (`component:file:` and `document:`)
18
+ for those extensions, using the same file-node policy as deterministic document links.
19
+ This applies to Codex and module providers, before fact IDs, OML and artifact hashes
20
+ are computed. Diagnostic references are remapped with the assertions.
21
+
22
+ Paths retain their exact spelling and case; no basename matching, existence guess,
23
+ ownership inference or concept merging is performed. Distinct business concepts such
24
+ as `contract:retention` remain separate from the document that describes them.
25
+ Every assertion, predicate, citation, confidence and label is retained. Raw provider
26
+ output remains unchanged for inspection. Normalizing physical identity never approves
27
+ an assertion or establishes that a referenced file exists. Snapshot assembly continues
28
+ to mark semantic assertions inferred/unreviewed, independently of source approval.
29
+
30
+ The RDF identity and SHACL shape stay unchanged. Other conflicting kinds still fail
31
+ validation; this policy does not hide genuine conflicts or remove assertions.
32
+
33
+ ## Artifact and snapshot migration
34
+
35
+ An implementation-owned `repositoryNodeIdentityVersion: 1` participates in every
36
+ semantic artifact key, including custom providers with unchanged user configuration.
37
+ Codex's prompt contract advances to v3. Assembly advances to `graph-v5`, records the
38
+ policy version in the manifest and uses a different immutable snapshot ID even for
39
+ the same commit/configuration. Historical snapshots and artifacts remain untouched
40
+ and queryable; they are not relabeled as newly validated evidence.
41
+
42
+ The first semantic refresh after upgrading requires extraction under the new contract.
43
+ Unchanged subsequent refreshes reuse the new artifacts. Do not delete the old cache or
44
+ force a revision as an upgrade workaround. Provider cost for that first refresh remains
45
+ subject to the consumer's configured budgets and authorization.
46
+
47
+ ## Validation and limits
48
+
49
+ The public regression uses two committed documents referencing the same implementation
50
+ document, a deterministic module provider returning the conflicting legacy identities,
51
+ real TypeScript indexing, deterministic links and RDF/SHACL. It checks preserved facts,
52
+ citations and review authority, old-cache isolation and repeat reuse. An unnormalized
53
+ negative control retains the SHACL failure. This is provider-independent deterministic
54
+ coverage, not a claim that a private consumer's full extraction has passed.
55
+
56
+ Operational-config cache reuse, broad identity diagnostics and full-graph SHACL latency
57
+ remain separate work in the [1.0 plan](RELEASE-1.0-PLAN.md).
@@ -0,0 +1,91 @@
1
+ # ADR-016: Read-only hydration planning
2
+
3
+ Status: accepted and implemented for the 0.3.0 release plan; absent from 0.2.1.
4
+ See [validation](VALIDATION.md) for recorded publication evidence.
5
+
6
+ ## Problem
7
+
8
+ Consumers need to inspect document selection, extraction work and configured limits before
9
+ invoking a model. Constructing the execution application is unsuitable for that preview because
10
+ it loads configured repository adapters. Independently calculating source selection or cache keys
11
+ would also allow the preview to disagree with hydration.
12
+
13
+ ## Decision
14
+
15
+ Provide `hydrate --plan` with a human summary and `hydrate --plan --json` with a structured
16
+ `hydration-plan` record. The public `planHydration(configPath, options)` function uses the same
17
+ planner. `--ref` resolves once to a commit SHA, including for agent scope. Configuration and
18
+ artifact storage are read from the current local environment, and the plan reports excluded
19
+ working changes rather than using their bytes as committed evidence.
20
+
21
+ The planner reads configuration, committed Git objects and existing semantic artifacts. It never
22
+ constructs an execution application or loads a configured provider/adapter. It does not probe
23
+ credentials, index TypeScript, collect runtime evidence, compile ontology artifacts, validate RDF,
24
+ publish snapshots or create cache directories. Git status disables optional locks so the preview
25
+ does not refresh the index. Source bodies and raw model output are not included in the result.
26
+
27
+ Committed source selection, duplicate semantic-ID checks and budget assessment are shared with
28
+ hydration. Provider identity and semantic artifact keys are pure functions shared with execution.
29
+ Per-source entries identify exact path/content digest, stable semantic identity, inclusion selectors,
30
+ line and UTF-8 byte counts, configured evidence role and matched source-approval patterns.
31
+
32
+ Classification follows configuration, not an inference about prose meaning. `sourceAuthority`
33
+ can be declared by an approved path while `extractionReview` remains unreviewed. A source matching
34
+ `evidenceRoots` is labeled validation evidence, but selecting it still schedules semantic extraction.
35
+ Recursive SOT traversal and default separation of validation documents belong to T07.
36
+
37
+ ## Cache and readiness semantics
38
+
39
+ Cache inspection distinguishes these states without modifying evidence:
40
+
41
+ | State | Planned meaning |
42
+ | --- | --- |
43
+ | `hit` | Readable artifact matches source identity/content, extraction contract, structure and output digest |
44
+ | `miss` | No artifact exists for the current key; extraction is expected |
45
+ | `forced` | Explicit force requests fresh extraction; no forced key or artifact is created by planning |
46
+ | `corrupt` | Stored JSON, identity, structure or content digest is invalid; reuse is not claimed |
47
+ | `unavailable` | The artifact could not be read; its reuse state is unknown |
48
+
49
+ Corrupt/unavailable entries are not ordinary misses and are never silently repaired or overwritten.
50
+ An artifact can remain reusable after a source rename or a different commit with unchanged content;
51
+ its original path/commit need not equal the current path/commit. Hydration rebinds current provenance.
52
+ The preview's estimate is conditional on unchanged inputs and storage state, not a guarantee against
53
+ concurrent changes or a successful provider run.
54
+
55
+ Limits apply to every selected document, including cache hits: `maxDocuments` bounds the document
56
+ count and `maxSourceBytes` bounds each document's UTF-8 bytes. Total source bytes are reported but
57
+ are not a token or monetary budget. Selection is never trimmed to make these limits pass.
58
+
59
+ Budget, duplicate-ID and cache errors produce a blocked plan. Missing/no-document selectors and
60
+ omissions from existing bounded agent selection produce warnings and an incomplete scope that
61
+ needs review. Both yield `ready: false` and CLI exit 1, while retaining the full plan. `ready: true`
62
+ and exit 0 mean only that these planning checks passed; execution prerequisites and product-intent
63
+ completeness have not been established.
64
+
65
+ Warnings do not create new hard hydration gates. Existing hydration behavior can still publish a
66
+ new graph after deleting the last selected document, removing its old relationships. The preview
67
+ exposes that empty match for review. An explicitly empty source list is complete and selects none.
68
+
69
+ ## Compatibility
70
+
71
+ The shared key/output-digest implementation preserves the released 0.2.1 semantic artifact contract,
72
+ including document identity-policy invalidation. Valid existing artifacts remain reusable; no
73
+ semantic re-extraction is required merely by this refactor. Operational configuration changes can
74
+ still affect cache identity/storage profiles; that is separate T03 work.
75
+
76
+ The empty-source fix changes graph assembly behavior: empty roots previously could enumerate all
77
+ repository documents. Assembly advances to `graph-v6` so corrected snapshots cannot collide with
78
+ immutable older snapshots at the same commit/configuration. Old snapshots remain readable and
79
+ unchanged. The requested ref also controls agent source selection instead of always using HEAD.
80
+
81
+ ## Validation and next work
82
+
83
+ Public regressions cover committed and dirty inputs, unchanged index/files, absent or side-effecting
84
+ provider/adapter modules, empty scope, warm/cold/forced cache, malformed and unreadable artifacts,
85
+ released-key compatibility, budgets, duplicate IDs, missing selectors and historical agent scope
86
+ with explicit omissions. Existing rename/delete lifecycle checks remain applicable. Full validation
87
+ and release evidence are recorded separately in [VALIDATION.md](VALIDATION.md).
88
+
89
+ This increment makes no SHACL performance change. T05 will measure graph construction and full
90
+ validation before selecting caching, incremental validation or backend changes. See the
91
+ [1.0 plan](RELEASE-1.0-PLAN.md) for bounded follow-ups and the separate 2.0 platform direction.
@@ -0,0 +1,102 @@
1
+ # ADR-017: Validate a temporary RDF union without changing canonical evidence
2
+
3
+ Status: accepted and implemented for the focused 0.3.0 release plan; absent from 0.2.1.
4
+ See [validation](VALIDATION.md) for checks and recorded publication evidence.
5
+
6
+ ## Problem and measured mechanism
7
+
8
+ The canonical RDF dataset has one named relationship graph per assertion, with assertion records,
9
+ node metadata and provenance in the default graph. The JavaScript `rdf-validate-shacl` validator
10
+ uses graph-wildcard dataset lookups through clownface. N3 indexes graphs first, so a lookup with
11
+ no graph restriction visits each graph before consulting its subject/predicate/object indexes.
12
+ Repeating those lookups for each focus node and shape property becomes expensive as assertion
13
+ graphs grow.
14
+
15
+ Bounded public experiments measured this cost in the current layout and compared an indexed union
16
+ using the same validator and shapes. They establish a synthetic scaling problem and a useful
17
+ optimization; they do not reproduce the private consumer's complete generation or diagnose its
18
+ reported 30-minute timeout. The consumer's total graph size and other phase costs remain unknown.
19
+
20
+ ## Decision
21
+
22
+ For each `RdfEvidenceGraph.validate()` call, build a temporary single-default-graph N3 store from
23
+ every subject/predicate/object triple in the canonical dataset. Run the unchanged SHACL shapes
24
+ and validator on that store, and retain the existing explicit citation-order check. The canonical
25
+ named dataset remains unchanged and continues to serve SPARQL, fact projection and serialization.
26
+
27
+ This is still full-graph validation on every refresh. It introduces no GraphDB service, native/Rust
28
+ validator, incremental validation or validation-result cache. The short-lived union is not retained
29
+ as another persistent graph or used to replace canonical evidence.
30
+
31
+ RDF graphs are sets: identical SPO triples from multiple named graphs collapse in the validation
32
+ projection. The present shapes already use union lookups and node/value sets, and do not inspect
33
+ graph identity. Separate assertion records and their cited provenance remain intact in the original
34
+ dataset; no assertion, shape or constraint is removed. A future graph-sensitive shape requires an
35
+ explicit compatibility decision and new equivalence tests before using this projection.
36
+
37
+ ## Bounded public measurements
38
+
39
+ Environment: Linux cloud workspace, Node 24.19.0, N3 2.7.2 and rdf-validate-shacl 0.6.5.
40
+ Each case ran once in a fresh child process with a 45-second hard deadline. The prototype copied
41
+ the same original dataset into a single graph only for validation; production-patch verification
42
+ is recorded separately in [VALIDATION.md](VALIDATION.md).
43
+
44
+ Fixtures contain public synthetic Workflow-to-Component assertions, cited metadata and one named
45
+ relationship graph per fact. Shared topology repeats 50 nodes; unique topology uses two distinct
46
+ nodes per fact. Timings below are seconds, with union construction separated from SHACL. RSS is
47
+ the process sample after validation, including Node, libraries and both datasets, not a measurement
48
+ of projection memory alone.
49
+
50
+ | Facts / topology | Original quads | Original SHACL | Union construction | Union SHACL | Union RSS MiB |
51
+ | --- | ---: | ---: | ---: | ---: | ---: |
52
+ | 1,000 shared | 20,203 | 3.889 | 0.061 | 0.239 | 352.3 |
53
+ | 2,000 shared | 40,203 | 19.550 | 0.102 | 0.410 | 440.0 |
54
+ | 1,000 unique | 28,003 | 5.731 | 0.104 | 0.308 | 352.6 |
55
+ | 2,000 unique | 56,003 | 31.502 | 0.153 | 0.467 | 457.9 |
56
+ | 5,000 shared | 100,203 | No verdict before process deadline | 0.251 | 1.131 | 825.5 |
57
+ | 10,000 unique | 280,003 | Not measured | 0.779 | 2.119 | 1,287.3 |
58
+
59
+ The 5,000-fact original-mode process was terminated after 45.051 seconds total elapsed time,
60
+ including startup and construction. It produced no SHACL verdict; this is not an inferred
61
+ completed validation duration. For the 10,000-fact unique union case, initial canonical graph
62
+ construction took 1.058 seconds in addition to the times shown. Its roughly 1.3 GiB RSS is a
63
+ material memory cost and should be measured on larger or different workloads.
64
+
65
+ These single runs are not statistical latency estimates or production guarantees. The experiment
66
+ does not measure semantic extraction, SCIP, SPARQL, serialization, snapshot publication or disk
67
+ storage. No private consumer data or live provider calls were used. The repository benchmark
68
+ `pnpm benchmark:shacl` reproduces bounded public comparisons using the current build; retain its
69
+ configuration, versions and raw output when comparing results.
70
+
71
+ ## Correctness and compatibility
72
+
73
+ The prototype compared sorted conformance issues between the original and union paths across
74
+ valid repeated relationships/attributes, conflicting node kinds, out-of-range confidence, invalid
75
+ citations, empty citation paths, colliding assertion IDs and combined violations. All seven paired
76
+ cases agreed, and before/after serialization hashes preserved the canonical named dataset.
77
+
78
+ Production regressions must compare both paths for all supported constraints, including negative
79
+ cases, and preserve named-graph SPARQL results, fact projection, serialization and repeated calls.
80
+ Those checks establish parity for the current shapes, not arbitrary future SHACL rules. Failed
81
+ validation must continue to prevent publication and preserve the previous valid snapshot.
82
+
83
+ The optimization changes neither facts nor evidence authority and does not require a new semantic
84
+ cache identity. It does not make old evidence eligible for current-diff gates. Normal snapshot
85
+ commit, configuration, scope and provenance checks continue to apply.
86
+
87
+ ## Release boundary and remaining T05 work
88
+
89
+ Ship the demonstrated cold-validation improvement after its independent review and required
90
+ validation, ahead of unrelated 1.0 work. A first graph cannot benefit from reusing a successful
91
+ validation result that does not yet exist. Private consumer acceptance remains pending after release.
92
+
93
+ Early identity checks, detailed progress/elapsed reporting, validation-result caching and safe
94
+ incremental validation remain open. The validator's main validation loop is synchronous; timers
95
+ on the same JavaScript thread cannot show a live heartbeat while it is blocked. Keep phase
96
+ boundaries honest and evaluate bounded worker execution before promising in-phase progress or
97
+ cancellation. Any later cached or incremental result must account for graph content, shapes,
98
+ validator/policy versions, deletions, affected relationships and global constraints, with full
99
+ validation when safe scope cannot be established.
100
+
101
+ Native engines and optional persistent database backends remain measurement-driven alternatives,
102
+ not prerequisites of this release. See the [1.0 task list](RELEASE-1.0-PLAN.md).
@@ -6,11 +6,12 @@ development workspace. Saved evidence queries work without a model login. Hydrat
6
6
  prose uses the configured provider; an OAuth failure may block new extraction but does not
7
7
  remove an already published snapshot.
8
8
 
9
- Use a pinned, reviewed toolkit version and its matching documentation. The current `main`
10
- contains features beyond npm `0.1.2`, including explicit routing declarations. To evaluate
11
- those features before their release, use both locally prepared package archives from the
12
- same reviewed commit; see [release preparation](RELEASING.md). Record that commit separately
13
- from the package version, which does not change with every development commit.
9
+ Use a pinned, reviewed toolkit version and its matching documentation. npm `0.2.0` includes
10
+ explicit routing declarations, committed TypeScript source projects, document linkage and
11
+ the improved impact ranking. To evaluate a newer, unreleased commit, use both locally prepared
12
+ package archives from that same reviewed commit; see [release preparation](RELEASING.md).
13
+ Record the commit separately from the package version, which does not change with every
14
+ development commit.
14
15
 
15
16
  ## Choose tasks before querying
16
17