@weftlet/toolkit 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -14
- package/dist/application/cache-adoption.d.ts +2 -1
- package/dist/application/cache-adoption.js +12 -5
- package/dist/application/document-coverage.d.ts +28 -0
- package/dist/application/document-coverage.js +58 -0
- package/dist/application/document-overlay.d.ts +14 -0
- package/dist/application/document-overlay.js +31 -0
- package/dist/application/extraction-identity.d.ts +2 -1
- package/dist/application/extraction-identity.js +7 -0
- package/dist/application/hydration-inputs.d.ts +15 -0
- package/dist/application/hydration-inputs.js +55 -0
- package/dist/application/hydration-plan.d.ts +7 -0
- package/dist/application/hydration-plan.js +18 -5
- package/dist/application/hydrator.d.ts +2 -0
- package/dist/application/hydrator.js +40 -18
- package/dist/application/overlay.d.ts +3 -1
- package/dist/application/overlay.js +29 -9
- package/dist/application/saved-evidence.d.ts +2 -2
- package/dist/application/semantic-run.d.ts +3 -0
- package/dist/application/semantic-scope.d.ts +1 -1
- package/dist/application/semantic-scope.js +2 -2
- package/dist/configuration/cache.js +13 -1
- package/dist/configuration/config.d.ts +2 -0
- package/dist/configuration/config.js +6 -1
- package/dist/configuration/document-traversal.d.ts +7 -0
- package/dist/configuration/document-traversal.js +13 -0
- package/dist/extraction/canonical-document-links.d.ts +28 -0
- package/dist/extraction/canonical-document-links.js +95 -0
- package/dist/extraction/document-classification.d.ts +44 -0
- package/dist/extraction/document-classification.js +161 -0
- package/dist/extraction/document-evidence.d.ts +21 -0
- package/dist/extraction/document-evidence.js +83 -0
- package/dist/extraction/document-traversal.d.ts +76 -0
- package/dist/extraction/document-traversal.js +210 -0
- package/dist/extraction/document.d.ts +3 -1
- package/dist/extraction/document.js +3 -1
- package/dist/graph/authority.js +3 -0
- package/dist/graph/coverage.d.ts +8 -0
- package/dist/graph/coverage.js +96 -0
- package/dist/graph/document-role-description.d.ts +5 -0
- package/dist/graph/document-role-description.js +18 -0
- package/dist/graph/identity-preflight.d.ts +57 -0
- package/dist/graph/identity-preflight.js +71 -0
- package/dist/graph/query.d.ts +2 -2
- package/dist/graph/query.js +22 -62
- package/dist/graph/rdf.d.ts +4 -1
- package/dist/graph/rdf.js +21 -6
- package/dist/graph/shacl-shapes.js +26 -0
- package/dist/graph/validation-progress.d.ts +14 -0
- package/dist/graph/validation-progress.js +18 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/interfaces/cli.js +29 -5
- package/dist/interfaces/document-coverage.d.ts +9 -0
- package/dist/interfaces/document-coverage.js +117 -0
- package/dist/repository/git.d.ts +11 -0
- package/dist/repository/git.js +38 -0
- package/dist/visualization/visualization.js +21 -6
- package/docs/ADR-017-shacl-validation-union.md +4 -0
- package/docs/ADR-018-operational-semantic-cache.md +2 -2
- package/docs/ADR-019-semantic-failure-diagnostics.md +2 -2
- package/docs/ADR-020-canonical-physical-file-producers.md +2 -2
- package/docs/ADR-021-identity-preflight-and-validation-timing.md +84 -0
- package/docs/ADR-022-release-documentation.md +44 -0
- package/docs/ADR-023-document-coverage-preview.md +64 -0
- package/docs/ADR-024-implementation-coverage.md +51 -0
- package/docs/ADR-025-document-classification.md +78 -0
- package/docs/ADR-026-traversal-hydration.md +94 -0
- package/docs/ADR-027-validation-closeout.md +155 -0
- package/docs/CONSUMER-TRIAL.md +1 -1
- package/docs/MIGRATING-TO-WEFTLET.md +1 -1
- package/docs/PROVIDERS.md +17 -1
- package/docs/RELEASE-1.0-PLAN.md +177 -48
- package/docs/RELEASING.md +46 -8
- package/docs/ROADMAP.md +59 -28
- package/docs/VALIDATION.md +451 -0
- package/docs/benchmarks/validation-2026-10-28/README.md +25 -0
- package/docs/benchmarks/validation-2026-10-28/large.json +1 -0
- package/docs/benchmarks/validation-2026-10-28/lifecycle.json +1 -0
- package/docs/benchmarks/validation-2026-10-28/native.json +1 -0
- package/docs/benchmarks/validation-2026-10-28/rich.json +1 -0
- package/docs/extraction-manifest.json +73 -17
- package/examples/generic-typescript/document-policy.json +9 -0
- package/package.json +7 -4
package/dist/interfaces/cli.js
CHANGED
|
@@ -18,6 +18,8 @@ import { loadConfig } from "../configuration/config.js";
|
|
|
18
18
|
import { planHydration } from '../application/hydration-plan.js';
|
|
19
19
|
import { adoptSemanticCache } from '../application/cache-adoption.js';
|
|
20
20
|
import { SemanticHydrationError } from '../application/hydration-failure.js';
|
|
21
|
+
import { GraphIdentityConflictError } from '../graph/identity-preflight.js';
|
|
22
|
+
import { runDocumentCoverage } from './document-coverage.js';
|
|
21
23
|
function valueAfter(args, name) {
|
|
22
24
|
const index = args.indexOf(name);
|
|
23
25
|
return index >= 0 ? args[index + 1] : undefined;
|
|
@@ -60,9 +62,23 @@ function formatHydrationPlan(plan) {
|
|
|
60
62
|
`Expected cache reuse: ${plan.estimates.reusableArtifacts}; extractions: ${plan.estimates.extractions}; unknown cache entries: ${plan.estimates.unknownArtifacts}`,
|
|
61
63
|
`Source/cache/budget checks: ${readiness}`,
|
|
62
64
|
];
|
|
65
|
+
if (plan.documentSelection) {
|
|
66
|
+
const selection = plan.documentSelection;
|
|
67
|
+
lines.push(`Document traversal: ${selection.complete ? 'complete' : 'blocked'}; identity ${selection.digest}; policy ${selection.policyDigest}`);
|
|
68
|
+
lines.push(`Discovered: ${selection.documentPaths.length}; semantic candidates: ${selection.semanticPaths.length}`);
|
|
69
|
+
for (const excluded of selection.excluded)
|
|
70
|
+
lines.push(`Excluded from model extraction: ${JSON.stringify(excluded.path)} (${excluded.reason}); retained traversal/evidence reporting.`);
|
|
71
|
+
}
|
|
72
|
+
if (plan.documentCoverage) {
|
|
73
|
+
const { limits, counters } = plan.documentCoverage.traversal;
|
|
74
|
+
lines.push(`Traversal reads: ${counters.readDocuments}/${limits.maxReadDocuments} documents; ${counters.readBytes}/${limits.maxReadBytes} bytes; ${limits.maxDocumentBytes} bytes maximum per document`);
|
|
75
|
+
}
|
|
63
76
|
for (const document of plan.documents) {
|
|
64
77
|
lines.push(`- ${JSON.stringify(document.path)} [${document.sourceId}]: ${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(', ')}`);
|
|
65
78
|
}
|
|
79
|
+
for (const document of plan.documentCoverage?.classification.documents ?? []) {
|
|
80
|
+
lines.push(`Classification ${JSON.stringify(document.path)}: ${document.sourceRole}; owner ${document.ownership.status}; ${document.ownership.ownerDocuments.map(path => JSON.stringify(path)).join(', ')}`);
|
|
81
|
+
}
|
|
66
82
|
for (const selection of plan.selections.filter((item) => item.status !== 'matched')) {
|
|
67
83
|
lines.push(`Selection ${JSON.stringify(selection.selector)}: ${selection.status}`);
|
|
68
84
|
}
|
|
@@ -82,9 +98,11 @@ function formatHydrationPlan(plan) {
|
|
|
82
98
|
async function main() {
|
|
83
99
|
const args = process.argv.slice(2);
|
|
84
100
|
if (args.includes('--help') || !args.length) {
|
|
85
|
-
process.stdout.write('weftlet init|config show|config validate|doctor|hydrate|cache adopt|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.
|
|
101
|
+
process.stdout.write('weftlet documents plan|init|config show|config validate|doctor|hydrate|cache adopt|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.\ndocuments plan --entry <document> [--entry <document>] [--root <path>] [--document-policy <json-file>] [--ref <commit>] [--json] previews prospective committed link coverage; it does not change hydration inputs or estimate model/cache work. Source classification/ownership completeness is separate from traversal readiness. The preview policy is an operation input. Set documentTraversal in configuration to activate traversal hydration; hydrate --plan describes its execution inputs. Safety limits: --max-read-documents, --max-read-bytes, --max-document-bytes.\nhydrate --plan [--json] [--ref <commit>] [--force-semantic] previews scope, cache and budgets without model calls or graph writes. Traversal coverage and role ambiguity block new model work; direct-source selection retains its existing behavior.\nhydrate --json --quiet emits structured semantic failures on stderr with no progress lines; failure stdout stays empty. hydrate --plan maps opaque source IDs to local document paths.\nhydrate|overlay --validation-details explicitly includes local node/fact identities, extractor names and source citations in identity-conflict errors; default reports use opaque references. This flag writes no diagnostic file. Use --json --quiet for structured identity-conflict stderr and empty failure stdout. Validation timings report phase boundaries, not a live heartbeat or percentage.\ncache adopt --from-config <previous.json> [--config <current.json>] [--ref <commit>] [--dry-run] returns JSON and copies only validated compatible artifacts, without model calls or snapshot writes. --dry-run performs no cache writes. Agent configurations accept the same --owner-doc, --task and --capability selection as hydrate.\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');
|
|
86
102
|
return;
|
|
87
103
|
}
|
|
104
|
+
if (await runDocumentCoverage(args, defaultConfigPath))
|
|
105
|
+
return;
|
|
88
106
|
if (args[0] === 'init') {
|
|
89
107
|
await runInit(args.slice(1));
|
|
90
108
|
return;
|
|
@@ -171,7 +189,7 @@ async function main() {
|
|
|
171
189
|
const base = valueAfter(args, '--base-snapshot');
|
|
172
190
|
if (!base)
|
|
173
191
|
throw new Error('overlay requires --base-snapshot <committed snapshot ID>');
|
|
174
|
-
const result = await buildWorkingOverlay(application.repository, application.config, application.snapshotStore, application.storageRoot, base, valueAfter(args, '--agent'));
|
|
192
|
+
const result = await buildWorkingOverlay(application.repository, application.config, application.snapshotStore, application.storageRoot, base, valueAfter(args, '--agent'), { validationDetails: args.includes('--validation-details') });
|
|
175
193
|
process.stdout.write(`${JSON.stringify(result.manifest, null, 2)}\n`);
|
|
176
194
|
return;
|
|
177
195
|
}
|
|
@@ -187,8 +205,11 @@ async function main() {
|
|
|
187
205
|
headName: scope?.headName ?? application.headName,
|
|
188
206
|
semanticSources: scope?.semanticSources,
|
|
189
207
|
forceSemantic: args.includes('--force-semantic'),
|
|
208
|
+
validationDetails: args.includes('--validation-details'),
|
|
190
209
|
signal: controller.signal,
|
|
191
210
|
onProgress: args.includes('--quiet') ? undefined : (event) => process.stderr.write(`[context-graph] ${event.phase}${event.total === undefined ? '' : ` ${event.completed}/${event.total}`}` +
|
|
211
|
+
`${event.status === undefined ? '' : ` ${event.status}`}` +
|
|
212
|
+
`${event.factCount === undefined ? '' : ` facts=${event.factCount}`}${event.nodeCount === undefined ? '' : ` nodes=${event.nodeCount}`}` +
|
|
192
213
|
`${event.sourceId === undefined ? '' : ` ${event.sourceId}`}${event.elapsedMs === undefined ? '' : ` ${event.elapsedMs} ms`}\n`),
|
|
193
214
|
});
|
|
194
215
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -257,8 +278,11 @@ async function main() {
|
|
|
257
278
|
throw new Error('Usage: weftlet hydrate|clean-compare|impact scan|diagnostics show --snapshot <id>|invariant check|ontology compile|visualize --output <file.html|file.dot|file.svg>');
|
|
258
279
|
}
|
|
259
280
|
main().catch((error) => {
|
|
260
|
-
process.stderr.write(`${error instanceof
|
|
261
|
-
? process.argv.includes('--json')
|
|
262
|
-
|
|
281
|
+
process.stderr.write(`${error instanceof GraphIdentityConflictError
|
|
282
|
+
? process.argv.includes('--json') || process.argv.includes('--validation-details')
|
|
283
|
+
? JSON.stringify(error.report, null, 2) : error.message
|
|
284
|
+
: error instanceof SemanticHydrationError
|
|
285
|
+
? process.argv.includes('--json') ? JSON.stringify(error.report, null, 2) : error.message
|
|
286
|
+
: error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
263
287
|
process.exitCode = 1;
|
|
264
288
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DocumentCoverageOptions, type DocumentCoveragePlan } from '../application/document-coverage.js';
|
|
2
|
+
export declare function parseDocumentCoverageArgs(args: readonly string[]): {
|
|
3
|
+
configPath?: string;
|
|
4
|
+
policyPath?: string;
|
|
5
|
+
json: boolean;
|
|
6
|
+
options: DocumentCoverageOptions;
|
|
7
|
+
} | undefined;
|
|
8
|
+
export declare function formatDocumentCoverage(plan: DocumentCoveragePlan): string;
|
|
9
|
+
export declare function runDocumentCoverage(args: readonly string[], defaultConfig: () => string): Promise<boolean>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { planDocumentCoverage } from '../application/document-coverage.js';
|
|
2
|
+
import { open } from 'node:fs/promises';
|
|
3
|
+
import { parseDocumentClassificationPolicy } from '../extraction/document-classification.js';
|
|
4
|
+
const traversalFlags = ['--entry', '--root', '--document-policy', '--max-read-documents', '--max-read-bytes', '--max-document-bytes'];
|
|
5
|
+
const usage = 'Usage: weftlet documents plan --entry <document> [--entry <document>] [--root <path>] [--document-policy <json-file>] [--config <file>] [--ref <commit>] [--json] [--max-read-documents <count>] [--max-read-bytes <bytes>] [--max-document-bytes <bytes>]';
|
|
6
|
+
export function parseDocumentCoverageArgs(args) {
|
|
7
|
+
if (args[0] !== 'documents') {
|
|
8
|
+
if (args.some(arg => traversalFlags.some(flag => arg === flag || arg.startsWith(`${flag}=`)))) {
|
|
9
|
+
throw new Error('Document traversal options are supported only by documents plan; they do not change hydration selection.');
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
if (args[1] !== 'plan')
|
|
14
|
+
throw new Error(usage);
|
|
15
|
+
const values = new Map();
|
|
16
|
+
let json = false;
|
|
17
|
+
for (let index = 2; index < args.length; index++) {
|
|
18
|
+
const flag = args[index];
|
|
19
|
+
if (flag === '--json') {
|
|
20
|
+
if (json)
|
|
21
|
+
throw new Error(usage);
|
|
22
|
+
json = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (![...traversalFlags, '--config', '--ref'].includes(flag))
|
|
26
|
+
throw new Error(usage);
|
|
27
|
+
const value = args[++index];
|
|
28
|
+
if (!value || value.startsWith('--'))
|
|
29
|
+
throw new Error(usage);
|
|
30
|
+
if (values.has(flag) && flag !== '--entry' && flag !== '--root')
|
|
31
|
+
throw new Error(usage);
|
|
32
|
+
values.set(flag, [...(values.get(flag) ?? []), value]);
|
|
33
|
+
}
|
|
34
|
+
if (!values.has('--entry'))
|
|
35
|
+
throw new Error(usage);
|
|
36
|
+
const number = (flag) => {
|
|
37
|
+
const value = values.get(flag)?.[0];
|
|
38
|
+
if (value === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)))
|
|
41
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
42
|
+
return Number(value);
|
|
43
|
+
};
|
|
44
|
+
return { configPath: values.get('--config')?.[0], policyPath: values.get('--document-policy')?.[0], json, options: {
|
|
45
|
+
entryPoints: values.get('--entry'), roots: values.get('--root'), ref: values.get('--ref')?.[0],
|
|
46
|
+
maxReadDocuments: number('--max-read-documents'), maxReadBytes: number('--max-read-bytes'),
|
|
47
|
+
maxDocumentBytes: number('--max-document-bytes'),
|
|
48
|
+
} };
|
|
49
|
+
}
|
|
50
|
+
export function formatDocumentCoverage(plan) {
|
|
51
|
+
const lines = [
|
|
52
|
+
`Prospective document coverage: ${plan.repositoryId}`,
|
|
53
|
+
`Commit: ${plan.commitSha} (requested ${JSON.stringify(plan.ref)})`,
|
|
54
|
+
`Configuration: ${plan.configurationDigest}; coverage: ${plan.coverageDigest}`,
|
|
55
|
+
'Hydration selection unchanged; no extraction/cache estimate.',
|
|
56
|
+
`Entry points: ${plan.entryPoints.map(path => JSON.stringify(path)).join(', ')}`,
|
|
57
|
+
`Roots: ${plan.roots.map(path => JSON.stringify(path)).join(', ')}`,
|
|
58
|
+
`Coverage: ${plan.coverage.complete ? 'complete within supported local scope' : 'incomplete'}; budget comparison: ${plan.budgets.withinLimits ? 'within limits' : 'blocked'}`,
|
|
59
|
+
`Source classification: ${plan.classification.complete ? 'complete' : 'gaps'}; policy: ${plan.classification.policyDigest}. This is separate from traversal/budget readiness.`,
|
|
60
|
+
`Discovered documents: ${plan.budgets.documents}/${plan.budgets.maxDocuments}; bytes: ${plan.budgets.bytes}; maximum per extraction source: ${plan.budgets.maxSourceBytes}`,
|
|
61
|
+
`Read limits: ${plan.limits.maxReadDocuments} documents, ${plan.limits.maxReadBytes} total bytes, ${plan.limits.maxDocumentBytes} bytes per document`,
|
|
62
|
+
];
|
|
63
|
+
for (const document of plan.documents)
|
|
64
|
+
lines.push(`- ${JSON.stringify(document.path)}: ${document.status}, ${document.bytes} bytes; ${document.role}; role ${document.sourceRole} (${document.roleStatus}); source ${document.sourceAuthority}, extraction ${document.extractionReview}; owner ${document.ownership.status} ${document.ownership.ownerDocuments.map(path => JSON.stringify(path)).join(', ')}; ${document.entryPoint ? 'entry point' : 'linked document'}`);
|
|
65
|
+
for (const link of plan.links)
|
|
66
|
+
lines.push(`Link ${JSON.stringify(link.sourcePath)}:${link.startLine}-${link.endLine}: ${link.status}${link.path ? ` ${JSON.stringify(link.path)}` : ''}`);
|
|
67
|
+
for (const issue of plan.issues)
|
|
68
|
+
lines.push(`${issue.severity.toUpperCase()} [${issue.code}]: ${issue.message}`);
|
|
69
|
+
for (const issue of plan.classification.issues)
|
|
70
|
+
lines.push(`CLASSIFICATION ${issue.severity.toUpperCase()} [${issue.code}]: ${issue.message} ${issue.paths.map(path => JSON.stringify(path)).join(', ')}`);
|
|
71
|
+
if (plan.coverage.unvisited.length)
|
|
72
|
+
lines.push(`Unread frontier: ${plan.coverage.unvisited.map(path => JSON.stringify(path)).join(', ')}`);
|
|
73
|
+
if (plan.excludedWorkingChanges.length)
|
|
74
|
+
lines.push(`Working changes excluded: ${plan.excludedWorkingChanges.map(path => JSON.stringify(path)).join(', ')}`);
|
|
75
|
+
lines.push(...plan.assumptions.map(note => `Note: ${note}`));
|
|
76
|
+
return `${lines.join('\n')}\n`;
|
|
77
|
+
}
|
|
78
|
+
async function readDocumentPolicy(path) {
|
|
79
|
+
const handle = await open(path, 'r');
|
|
80
|
+
try {
|
|
81
|
+
const metadata = await handle.stat();
|
|
82
|
+
if (!metadata.isFile() || metadata.size > 65_536)
|
|
83
|
+
throw new Error('Document policy must be a JSON file of at most 65536 bytes');
|
|
84
|
+
const buffer = Buffer.alloc(65_537);
|
|
85
|
+
let length = 0;
|
|
86
|
+
while (length < buffer.length) {
|
|
87
|
+
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, length);
|
|
88
|
+
if (!bytesRead)
|
|
89
|
+
break;
|
|
90
|
+
length += bytesRead;
|
|
91
|
+
}
|
|
92
|
+
if (length > 65_536)
|
|
93
|
+
throw new Error('Document policy exceeds 65536 bytes');
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = JSON.parse(buffer.subarray(0, length).toString('utf8'));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new Error('Document policy must contain valid JSON');
|
|
100
|
+
}
|
|
101
|
+
return parseDocumentClassificationPolicy(parsed);
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
await handle.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export async function runDocumentCoverage(args, defaultConfig) {
|
|
108
|
+
const parsed = parseDocumentCoverageArgs(args);
|
|
109
|
+
if (!parsed)
|
|
110
|
+
return false;
|
|
111
|
+
const options = parsed.policyPath ? { ...parsed.options, documentPolicy: await readDocumentPolicy(parsed.policyPath) } : parsed.options;
|
|
112
|
+
const plan = await planDocumentCoverage(parsed.configPath ?? defaultConfig(), options);
|
|
113
|
+
process.stdout.write(parsed.json ? `${JSON.stringify(plan, null, 2)}\n` : formatDocumentCoverage(plan));
|
|
114
|
+
if (!plan.ready)
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return true;
|
|
117
|
+
}
|
package/dist/repository/git.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface GitTextMatch {
|
|
|
8
8
|
readonly path: string;
|
|
9
9
|
readonly line: number;
|
|
10
10
|
}
|
|
11
|
+
/** Exact committed entries, relative to the configured repository root. */
|
|
12
|
+
export interface GitTreeEntry {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly mode: string;
|
|
15
|
+
readonly type: 'blob' | 'tree' | 'commit';
|
|
16
|
+
readonly objectId: string;
|
|
17
|
+
/** Git blob size in bytes; directories and gitlinks have no size. */
|
|
18
|
+
readonly size?: number;
|
|
19
|
+
}
|
|
11
20
|
export declare class GitRepository {
|
|
12
21
|
readonly root: string;
|
|
13
22
|
constructor(root: string);
|
|
@@ -19,6 +28,8 @@ export declare class GitRepository {
|
|
|
19
28
|
requireExactCleanCommit(commitSha: string): Promise<void>;
|
|
20
29
|
listFilesAt(commitSha: string, roots: readonly string[]): Promise<readonly string[]>;
|
|
21
30
|
listAllFilesAt(commitSha: string): Promise<readonly string[]>;
|
|
31
|
+
/** Read tree metadata without Git's quoted/newline path formatting or checkout access. */
|
|
32
|
+
listEntriesAt(commitSha: string): Promise<readonly GitTreeEntry[]>;
|
|
22
33
|
readFileAt(commitSha: string, path: string): Promise<string>;
|
|
23
34
|
findTextAt(commitSha: string, needles: readonly string[], roots: readonly string[]): Promise<readonly GitTextMatch[]>;
|
|
24
35
|
delta(baseSha: string | undefined, headSha: string, roots: readonly string[]): Promise<GitDelta>;
|
package/dist/repository/git.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
4
|
import { posix } from 'node:path';
|
|
5
|
+
import { isUtf8 } from 'node:buffer';
|
|
5
6
|
import { sha256, slug } from "../infrastructure/util.js";
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
export class GitRepository {
|
|
@@ -73,6 +74,43 @@ export class GitRepository {
|
|
|
73
74
|
listAllFilesAt(commitSha) {
|
|
74
75
|
return this.listFilesAt(commitSha, ['.']);
|
|
75
76
|
}
|
|
77
|
+
/** Read tree metadata without Git's quoted/newline path formatting or checkout access. */
|
|
78
|
+
async listEntriesAt(commitSha) {
|
|
79
|
+
const commit = await this.resolveRef(commitSha);
|
|
80
|
+
// :./ selects the committed subtree at cwd; --full-tree makes output relative to
|
|
81
|
+
// that subtree rather than applying cwd's prefix a second time in a nested project.
|
|
82
|
+
const { stdout } = await execFileAsync('git', ['ls-tree', '-r', '-t', '-l', '-z', '--full-tree', `${commit}:./`], {
|
|
83
|
+
cwd: this.root, encoding: 'buffer', maxBuffer: 64 * 1024 * 1024, windowsHide: true,
|
|
84
|
+
});
|
|
85
|
+
if (!isUtf8(stdout))
|
|
86
|
+
throw new Error('Git tree entry paths are not valid UTF-8');
|
|
87
|
+
const output = stdout.toString('utf8');
|
|
88
|
+
if (!output)
|
|
89
|
+
return [];
|
|
90
|
+
if (!output.endsWith('\0'))
|
|
91
|
+
throw new Error('Malformed NUL-delimited Git tree listing');
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
const entries = output.slice(0, -1).split('\0').map((record) => {
|
|
94
|
+
const match = /^([0-7]{6}) (blob|tree|commit) ([a-f0-9]{40}|[a-f0-9]{64}) +([0-9]+|-)\t([\s\S]+)$/.exec(record);
|
|
95
|
+
if (!match)
|
|
96
|
+
throw new Error('Malformed Git tree entry metadata');
|
|
97
|
+
const mode = match[1];
|
|
98
|
+
const type = match[2];
|
|
99
|
+
const objectId = match[3];
|
|
100
|
+
const rawSize = match[4];
|
|
101
|
+
const path = match[5];
|
|
102
|
+
const size = rawSize === '-' ? undefined : Number(rawSize);
|
|
103
|
+
const validMode = type === 'blob' ? ['100644', '100755', '120000'].includes(mode)
|
|
104
|
+
: mode === (type === 'tree' ? '040000' : '160000');
|
|
105
|
+
if (!validMode || (type === 'blob' ? size === undefined || !Number.isSafeInteger(size) || size < 0 : size !== undefined) ||
|
|
106
|
+
path.startsWith('/') || path.split('/').some(part => !part || part === '.' || part === '..') || seen.has(path)) {
|
|
107
|
+
throw new Error('Malformed Git tree entry mode, size or path');
|
|
108
|
+
}
|
|
109
|
+
seen.add(path);
|
|
110
|
+
return { path, mode, type, objectId, ...(size === undefined ? {} : { size }) };
|
|
111
|
+
});
|
|
112
|
+
return entries.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
113
|
+
}
|
|
76
114
|
readFileAt(commitSha, path) {
|
|
77
115
|
// Git object paths are top-level-relative unless explicitly prefixed with ./.
|
|
78
116
|
return this.execute(['show', `${commitSha}:./${path}`], false);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Modified for standalone extraction; see docs/extraction-manifest.json.
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, extname, resolve } from 'node:path';
|
|
4
|
+
import { repositoryFileNode } from '@weftlet/core';
|
|
4
5
|
import { contextPackEvidenceAuthority, contextPackLaneForFact } from "../graph/query.js";
|
|
6
|
+
import { coverageDescription } from '../graph/coverage.js';
|
|
7
|
+
import { documentSelectionDescription, sourceDocumentDescription } from '../graph/document-role-description.js';
|
|
5
8
|
import { viewerScript } from "./viewer-interaction.js";
|
|
6
9
|
import { diagnosticSummary } from "../graph/diagnostics.js";
|
|
7
10
|
export const MAX_VISUALIZATION_FACTS = 200;
|
|
@@ -67,7 +70,7 @@ function visualColumn(kind, selectedLanes) {
|
|
|
67
70
|
}
|
|
68
71
|
return 'implementation';
|
|
69
72
|
}
|
|
70
|
-
function addNode(nodes, node, selectedLanes) {
|
|
73
|
+
function addNode(nodes, node, selectedLanes, ownValidationSource = false) {
|
|
71
74
|
if (nodes.has(node.id))
|
|
72
75
|
return;
|
|
73
76
|
nodes.set(node.id, {
|
|
@@ -75,7 +78,7 @@ function addNode(nodes, node, selectedLanes) {
|
|
|
75
78
|
label: node.label,
|
|
76
79
|
kind: node.kind,
|
|
77
80
|
cluster: String(node.attributes?.module ?? node.attributes?.layer ?? node.kind),
|
|
78
|
-
column: visualColumn(node.kind, selectedLanes),
|
|
81
|
+
column: ownValidationSource && node.kind === 'Document' ? 'evidence' : visualColumn(node.kind, selectedLanes),
|
|
79
82
|
});
|
|
80
83
|
}
|
|
81
84
|
function assertVisualizationBounds(pack) {
|
|
@@ -104,8 +107,16 @@ function visualModel(pack, sourceUrlTemplate) {
|
|
|
104
107
|
assertVisualizationBounds(pack);
|
|
105
108
|
const nodes = new Map();
|
|
106
109
|
const selectedLanes = new Map();
|
|
110
|
+
const validationDocuments = new Set();
|
|
107
111
|
const edges = [];
|
|
108
112
|
for (const fact of pack.facts) {
|
|
113
|
+
if (fact.sourceRole === 'validation') {
|
|
114
|
+
const citedDocument = repositoryFileNode(fact.provenance.path);
|
|
115
|
+
if (citedDocument.kind === 'Document' &&
|
|
116
|
+
[fact.subject, fact.object].some(node => node.kind === 'Document' && node.id === citedDocument.id)) {
|
|
117
|
+
validationDocuments.add(citedDocument.id);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
109
120
|
const lane = contextPackLaneForFact(fact);
|
|
110
121
|
if (!lane)
|
|
111
122
|
continue;
|
|
@@ -116,8 +127,9 @@ function visualModel(pack, sourceUrlTemplate) {
|
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
for (const fact of pack.facts) {
|
|
119
|
-
addNode(nodes, fact.subject, selectedLanes.get(fact.subject.id));
|
|
120
|
-
addNode(nodes, fact.object, selectedLanes.get(fact.object.id));
|
|
130
|
+
addNode(nodes, fact.subject, selectedLanes.get(fact.subject.id), validationDocuments.has(fact.subject.id));
|
|
131
|
+
addNode(nodes, fact.object, selectedLanes.get(fact.object.id), validationDocuments.has(fact.object.id));
|
|
132
|
+
const sourceDescription = sourceDocumentDescription(fact);
|
|
121
133
|
edges.push({
|
|
122
134
|
id: fact.id,
|
|
123
135
|
from: fact.subject.id,
|
|
@@ -125,7 +137,8 @@ function visualModel(pack, sourceUrlTemplate) {
|
|
|
125
137
|
label: fact.predicate,
|
|
126
138
|
authority: contextPackEvidenceAuthority(fact),
|
|
127
139
|
provenance: `${fact.provenance.path}:${fact.provenance.startLine}`,
|
|
128
|
-
detail: `confidence ${fact.confidence}; ${fact.extractor.id}@${fact.extractor.version}; source ${fact.sourceAuthority ?? 'see citation'}; extraction ${fact.extractionReview ?? 'unspecified'}
|
|
140
|
+
detail: `confidence ${fact.confidence}; ${fact.extractor.id}@${fact.extractor.version}; source ${fact.sourceAuthority ?? 'see citation'}; extraction ${fact.extractionReview ?? 'unspecified'}` +
|
|
141
|
+
(sourceDescription ? `; ${sourceDescription}` : ''),
|
|
129
142
|
source: fact.provenance,
|
|
130
143
|
sourceUrl: sourceLink(fact.provenance, sourceUrlTemplate),
|
|
131
144
|
});
|
|
@@ -398,6 +411,7 @@ export function renderContextPackHtml(pack, sourceUrlTemplate) {
|
|
|
398
411
|
Excluded working paths: ${pack.evidenceState?.excludedWorkingChanges.length ?? 'unknown'}.
|
|
399
412
|
${pack.snapshot.overlayDigest ? `Overlay freshness: ${escapeXml(pack.evidenceState?.overlayFreshness ?? 'unknown')}.` : ''}
|
|
400
413
|
This view does not validate the current diff.</p>
|
|
414
|
+
<p>${escapeXml(documentSelectionDescription(pack.snapshot.documentSelection))}</p>
|
|
401
415
|
<p>${pack.exploration ? `${pack.exploration.returned}/${pack.exploration.reachableFacts} reachable facts on this page; max depth ${pack.exploration.maxDepth}; ${pack.exploration.truncated ? 'TRUNCATED' : 'not truncated within this scope'}${pack.exploration.depthLimitReached ? '; depth limit reached — rerun with greater --max-depth' : ''}` : 'Query completeness metadata unavailable.'}</p>
|
|
402
416
|
${pack.exploration?.continuation ? `<details><summary>Continue agent/CLI query with this token</summary><code>${escapeXml(pack.exploration.continuation)}</code></details>` : ''}
|
|
403
417
|
${pack.evidenceState?.excludedWorkingChanges.length ? `<details><summary>Excluded working changes</summary>${detailList(pack.evidenceState.excludedWorkingChanges, '')}</details>` : ''}
|
|
@@ -410,10 +424,11 @@ export function renderContextPackHtml(pack, sourceUrlTemplate) {
|
|
|
410
424
|
<section class="panel"><h2>Runtime references</h2>${detailList(pack.runtimeEvidence.map(reference => `${reference.serviceName}: ${reference.operation ?? 'observation'} · ${reference.sourcePath} · ${reference.observedAt ?? 'time unknown'}`), 'None in this page.')}<p>Edges are only the facts returned by the agent query. References do not create extra relationships.</p></section>
|
|
411
425
|
<section class="panel">
|
|
412
426
|
<h2>Coverage</h2>
|
|
427
|
+
<p>${escapeXml(coverageDescription(pack.coverage))}</p>
|
|
413
428
|
${Object.entries(pack.coverage.counts)
|
|
414
429
|
.map(([lane, count]) => `<span class="metric">${escapeXml(lane)}: ${count}</span>`)
|
|
415
430
|
.join('')}
|
|
416
|
-
${detailList(pack.coverage.gaps, 'No
|
|
431
|
+
${detailList(pack.coverage.gaps, 'No coverage gaps detected on this returned page.')}
|
|
417
432
|
</section>
|
|
418
433
|
<section class="panel">
|
|
419
434
|
<h2>Governing invariants</h2>
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
Status: accepted and implemented in the focused 0.3.0 release; absent from 0.2.1.
|
|
4
4
|
See [validation](VALIDATION.md) for checks and recorded publication evidence.
|
|
5
5
|
|
|
6
|
+
Follow-up: [ADR 027](ADR-027-validation-closeout.md) records the completed T05 engineering
|
|
7
|
+
evaluation and retains full direct validation. The measurements and remaining-work section below
|
|
8
|
+
describe the original focused release; private consumer acceptance remains separate.
|
|
9
|
+
|
|
6
10
|
## Problem and measured mechanism
|
|
7
11
|
|
|
8
12
|
The canonical RDF dataset has one named relationship graph per assertion, with assertion records,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ADR-018: Preserve Codex artifacts across operational configuration changes
|
|
2
2
|
|
|
3
|
-
Status: accepted and
|
|
4
|
-
|
|
3
|
+
Status: accepted and shipped in 0.4.0; release and validation evidence is recorded in
|
|
4
|
+
[validation](VALIDATION.md).
|
|
5
5
|
|
|
6
6
|
## Problem
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ADR-019: Safe semantic failure diagnostics and settled progress
|
|
2
2
|
|
|
3
|
-
Status:
|
|
4
|
-
|
|
3
|
+
Status: shipped in 0.4.0; validation and publication are recorded in [validation](VALIDATION.md).
|
|
4
|
+
This decision does not change extraction identity.
|
|
5
5
|
|
|
6
6
|
## Problem
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ADR-020: One physical-file policy for deterministic producers and adapters
|
|
2
2
|
|
|
3
|
-
Status:
|
|
4
|
-
separately in [validation](VALIDATION.md).
|
|
3
|
+
Status: upstream fix shipped in 0.4.0; release evidence and pending consumer adapter/hydration
|
|
4
|
+
acceptance are recorded separately in [validation](VALIDATION.md).
|
|
5
5
|
|
|
6
6
|
## Reported defect and ownership
|
|
7
7
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# ADR-021: Early identity conflicts and honest validation phase timing
|
|
2
|
+
|
|
3
|
+
Status: shipped in 0.5.0 after local/hosted checks and independent archive review; release evidence
|
|
4
|
+
is in [validation](VALIDATION.md). Consumer acceptance remains a separate checkpoint.
|
|
5
|
+
This is the bounded T05a increment. Follow-up [ADR 027](ADR-027-validation-closeout.md) records
|
|
6
|
+
the broader T05 engineering evaluation; the remaining-work section below describes this original
|
|
7
|
+
increment. Private consumer performance acceptance remains separate.
|
|
8
|
+
|
|
9
|
+
## Decision
|
|
10
|
+
|
|
11
|
+
Before ontology compilation, invariant evaluation or RDF construction, scan every assembled
|
|
12
|
+
fact's subject and object identity for multiple kinds. The scan covers semantic, document,
|
|
13
|
+
static, adapter, routing and runtime facts, including conceptual identities. Working overlays
|
|
14
|
+
run the same check over retained and newly extracted facts before RDF construction/publication.
|
|
15
|
+
No fact, kind, ID, citation or authority is changed or discarded by the check.
|
|
16
|
+
This runs after semantic/static extraction; it is separate from no-model `hydrate --plan`.
|
|
17
|
+
|
|
18
|
+
`inspectGraphIdentities(facts, { repositoryId, includeDetails? })` returns a report;
|
|
19
|
+
`assertGraphIdentities` throws `GraphIdentityConflictError` on conflicts. Both are public toolkit
|
|
20
|
+
exports. A passing report has `passed: true` and **`shaclValidated: false`**. Consistent kinds
|
|
21
|
+
alone do not establish canonical classification, correct intent, valid citations or conformance.
|
|
22
|
+
The existing full RDF/SHACL path and shapes remain mandatory for successful publication. Direct
|
|
23
|
+
`RdfEvidenceGraph.validate()` and the saved `validate` command still run all checks and return
|
|
24
|
+
their existing verdict format. There is no validation cache, incremental validator or backend change.
|
|
25
|
+
|
|
26
|
+
The two-pass scan is linear in fact occurrences. The common conflict-free path creates no
|
|
27
|
+
per-evidence diagnostic records. On failure it reports every conflicting identity and occurrence,
|
|
28
|
+
including both ends of a self-reference, repeated fact IDs and late conflicts. Counts are complete;
|
|
29
|
+
there is no hidden document, hop or diagnostic cutoff. Reports retain input indices and side so
|
|
30
|
+
duplicate assertion IDs cannot conceal a producer's occurrence.
|
|
31
|
+
|
|
32
|
+
## Privacy and attribution
|
|
33
|
+
|
|
34
|
+
There was no general configurable diagnostic privacy policy. This increment therefore makes
|
|
35
|
+
local detail an explicit invocation option, without adding an extraction/configuration field.
|
|
36
|
+
Default reports contain repository-scoped opaque node, fact, source and extractor references,
|
|
37
|
+
counts and recognized kind names. Unknown kind strings are hashed as well. References correlate
|
|
38
|
+
records; they are not encryption or proof that a low-entropy identifier cannot be guessed.
|
|
39
|
+
|
|
40
|
+
`hydrate --json --quiet` emits a typed identity-failure report on stderr with empty stdout.
|
|
41
|
+
Use `--validation-details` on `hydrate` or `overlay` only when local identities and citations
|
|
42
|
+
may be displayed. That option includes exact node/fact IDs, extractor identity/version, citation
|
|
43
|
+
path/lines/digest/commit, confidence and the original optional authority/review fields. It does
|
|
44
|
+
not include labels, node attributes, source text, prompts or raw provider output. Absent authority
|
|
45
|
+
fields stay absent; source approval is never promoted into assertion approval.
|
|
46
|
+
|
|
47
|
+
The programmatic equivalent is `HydrationOptions.validationDetails` or the overlay's final
|
|
48
|
+
options argument. Detail changes do not enter snapshot, provider or semantic-cache identities.
|
|
49
|
+
The error message stays safe even when its report includes explicitly requested local details.
|
|
50
|
+
No diagnostic file is written automatically. Existing non-identity SHACL errors retain their
|
|
51
|
+
existing detailed behavior; this is not a claim that every command/error has been redacted.
|
|
52
|
+
|
|
53
|
+
## Timing and failure preservation
|
|
54
|
+
|
|
55
|
+
Hydration reports started/completed/failed boundaries and elapsed wall time for identity
|
|
56
|
+
preflight, RDF construction, union construction and full SHACL. Counts describe graph size,
|
|
57
|
+
not validator completion. A completed SHACL phase means the validator returned, including a
|
|
58
|
+
nonconforming verdict. `RdfEvidenceGraph.validate({ onProgress })` exposes its union/SHACL phases.
|
|
59
|
+
|
|
60
|
+
The synchronous JavaScript work can block same-thread timers. These events are not heartbeats,
|
|
61
|
+
percentages, new timeout controls or interruptible worker execution. Worker isolation remains a
|
|
62
|
+
separate decision. The public profiling harness records preflight and validation phase timings
|
|
63
|
+
alongside its existing construction, resource and runtime measurements.
|
|
64
|
+
|
|
65
|
+
Failure preserves completed semantic artifacts and prior snapshots/heads. Correcting and
|
|
66
|
+
versioning an adapter still requires a fresh assembly and full validation, but compatible
|
|
67
|
+
semantic artifacts can be reused. This preflight does not rewrite cached model assertions or
|
|
68
|
+
choose an authoritative business rule. Evidence disagreements must be resolved at their owner.
|
|
69
|
+
|
|
70
|
+
No extraction/assembly version changes are needed: accepted graph facts, cache identities,
|
|
71
|
+
RDF serialization and conformance constraints are unchanged. The check rejects an existing
|
|
72
|
+
single-kind violation earlier; it does not make old snapshots satisfy current validation.
|
|
73
|
+
|
|
74
|
+
## Evidence and remaining work
|
|
75
|
+
|
|
76
|
+
Tests cover full cross-source attribution, conceptual and physical nodes, exact authority,
|
|
77
|
+
default privacy, late conflicts, duplicates/self-references, immutable artifacts/snapshots,
|
|
78
|
+
corrected retries and non-kind failures that still reach full SHACL. CLI and packed-consumer
|
|
79
|
+
cases use actual TypeScript SCIP and public adapters. Existing positive/negative named-dataset
|
|
80
|
+
SHACL equivalence tests remain unchanged except for an additional timing/parity case.
|
|
81
|
+
|
|
82
|
+
Broader T05 work remains: worker-based progress/cancellation, repeated/changed-input performance
|
|
83
|
+
evaluation, and evidence-driven decisions about conformance-result reuse or incremental/native
|
|
84
|
+
backends. This increment does not infer private consumer performance from public fixtures.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# ADR-022: Versioned installation documentation checked against release archives
|
|
2
|
+
|
|
3
|
+
Status: implemented for T06 after 0.5.0; validation is recorded separately in
|
|
4
|
+
[validation](VALIDATION.md). This changes release tooling and packaged documentation, not graph semantics.
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
|
|
8
|
+
The two package manifests own the version of the current checkout/archive. Marked sections in
|
|
9
|
+
the toolkit README, core README and release guide are generated from those manifests. They do
|
|
10
|
+
not claim a version is already published: a Changesets version PR can precede registry availability.
|
|
11
|
+
Publication evidence remains in the validation record and package-specific releases.
|
|
12
|
+
|
|
13
|
+
`scripts/release-documentation.mjs` uses Node built-ins. It is usable by the publish job, which
|
|
14
|
+
installs no project dependencies. Versioning regenerates the marked sections after Changesets
|
|
15
|
+
updates the manifests and before the extraction inventory is refreshed. Missing, duplicate or
|
|
16
|
+
malformed markers fail rather than causing unrelated prose to be overwritten.
|
|
17
|
+
|
|
18
|
+
Source checks compare the complete generated sections and check explicit package pins and
|
|
19
|
+
Weftlet archive names in active installation examples. The maintained scope is the two READMEs,
|
|
20
|
+
`docs/RELEASING.md`, `docs/MIGRATING-TO-WEFTLET.md` and `docs/CONSUMER-TRIAL.md`.
|
|
21
|
+
Feature introductions, earlier release records and historical inspection commands remain intact.
|
|
22
|
+
Historical installation fences require an explicit `weftlet-history` classification; this is a
|
|
23
|
+
reviewed documentation exception, not an automatic exemption based on an old version number.
|
|
24
|
+
|
|
25
|
+
Packed checks read the actual two tarballs. Package names, versions, archive filenames and the
|
|
26
|
+
toolkit's core dependency must match the release group. Required operational documents must be
|
|
27
|
+
present and match the reviewed source, allowing equivalent LF/CRLF checkout representations.
|
|
28
|
+
The same documentation checks run on those packed documents. Both archives are checked before
|
|
29
|
+
any publish operation, including when publication resumes from prepared artifacts.
|
|
30
|
+
|
|
31
|
+
The fresh-consumer smoke uses constrained argument arrays obtained from the checked current
|
|
32
|
+
examples. It installs the documented archive pair and runs the documented pinned setup and
|
|
33
|
+
configuration-validation commands against a disposable registry serving those archives. It
|
|
34
|
+
never evaluates arbitrary Markdown as shell code and makes no model requests.
|
|
35
|
+
|
|
36
|
+
## Boundaries
|
|
37
|
+
|
|
38
|
+
This is a release-documentation check, not a natural-language verifier or general shell parser.
|
|
39
|
+
New current version statements belong in the generated sections. New installation guides must
|
|
40
|
+
be added to the explicit operational-document scope to receive the same checks. Historical
|
|
41
|
+
validation records, changelogs, feature-introduction versions and private consumer documentation
|
|
42
|
+
are not automatically rewritten. Existing immutable npm archives are unchanged.
|
|
43
|
+
|
|
44
|
+
T07 document selection/authority and broader T05 validation progress/reuse remain separate.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ADR-023: Prospective committed document coverage
|
|
2
|
+
|
|
3
|
+
Status: implemented as T07a after T06; source validation and publication are separate in
|
|
4
|
+
[validation](VALIDATION.md). T07b classification/evidence and T07c hydration integration remain planned.
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
|
|
8
|
+
`weftlet documents plan` and `planDocumentCoverage()` inspect the committed Markdown-link closure
|
|
9
|
+
from explicit entry points. The report is `document-coverage-plan`, with `mode: prospective` and
|
|
10
|
+
`changesHydrationSelection: false`. It is separate from `hydrate --plan`, whose inputs and cache
|
|
11
|
+
estimates continue to describe actual hydration. No provider, adapter, indexer, cache, snapshot,
|
|
12
|
+
runtime source or ontology compiler is initialized or written by the new operation.
|
|
13
|
+
|
|
14
|
+
Entry points, exact scope roots and read limits are operation arguments. This increment adds no
|
|
15
|
+
hydration configuration fields and changes no cache partitions, extractor versions or graph
|
|
16
|
+
assembly identities. Existing config supplies repository identity, explicit approval/evidence-root
|
|
17
|
+
labels and semantic budgets for comparison. This avoids making an unused traversal setting
|
|
18
|
+
invalidate current snapshots or cached artifacts.
|
|
19
|
+
|
|
20
|
+
The new NUL-delimited Git tree reader preserves exact committed paths and records type, mode,
|
|
21
|
+
object ID and byte size, including directories, symlinks and gitlinks. It scopes correctly to a
|
|
22
|
+
nested repository root. Existing Git listing/extraction APIs are unchanged. Invalid UTF-8 tree
|
|
23
|
+
paths or malformed metadata fail closed instead of silently disappearing from the index.
|
|
24
|
+
|
|
25
|
+
A separate canonical-link parser follows direct Markdown links and used reference definitions.
|
|
26
|
+
It retains source lines, definition/use positions and conflicting definitions. It does not change
|
|
27
|
+
the graph linker's existing treatment of bare and inline-code paths. Images, code examples, HTML
|
|
28
|
+
and unused definitions do not become traversal edges. CommonMark-compatible links in Markdown,
|
|
29
|
+
MDX and text files are supported; this is not a complete MDX/HTML parser.
|
|
30
|
+
|
|
31
|
+
Relative paths resolve against their source document, leading `/` means repository root, and
|
|
32
|
+
percent encoding and fragment/query removal are explicit. No basename/README guessing or remote
|
|
33
|
+
fetch occurs. Malformed/escaping targets, scope exclusions, missing files, directories, symlinks,
|
|
34
|
+
submodules and ambiguous references are reported distinctly. Scope roots bound document traversal;
|
|
35
|
+
existing regular code/non-document targets outside those roots can still be classified without
|
|
36
|
+
reading their contents. Resolved code links, external URLs
|
|
37
|
+
and same-document fragments remain visible without being traversed. Visited-path deduplication
|
|
38
|
+
terminates cycles while retaining every reaching-link occurrence.
|
|
39
|
+
|
|
40
|
+
## Coverage and limits
|
|
41
|
+
|
|
42
|
+
Traversal computes reachable supported documents before comparing extraction budgets. The
|
|
43
|
+
existing `semantic.maxDocuments`/`maxSourceBytes` values apply only to that comparison; they never
|
|
44
|
+
trim the traversal. Candidates include validation documents at this stage, so totals are not a
|
|
45
|
+
promise of future model work. Missing links and unread descendants make coverage incomplete.
|
|
46
|
+
`ready` requires complete supported local coverage and a passing budget comparison; it does not
|
|
47
|
+
establish approved requirements, semantic quality, complete product coverage or hydration readiness.
|
|
48
|
+
|
|
49
|
+
Separate read-safety limits default to 1,000 documents, 16 MiB total and 1 MiB per document,
|
|
50
|
+
with ceilings of 10,000 documents, 64 MiB and 4 MiB respectively. Committed blob sizes are checked
|
|
51
|
+
before reads; failed attempts count toward read limits. Discovered unread paths remain in the
|
|
52
|
+
frontier and their descendants are explicitly unknown. Git metadata output is bounded to 64 MiB.
|
|
53
|
+
The current string reader conservatively rejects NUL, byte-size mismatches and U+FFFD, including
|
|
54
|
+
a literal replacement character, because it cannot prove lossless decoding in that case.
|
|
55
|
+
|
|
56
|
+
Reports include commit, current configuration digest, coverage digest, paths, sizes, content
|
|
57
|
+
digests, citations, issues and excluded working changes, but no document bodies. They are local
|
|
58
|
+
diagnostic reports and may contain private paths/link destinations. Source approval matches
|
|
59
|
+
remain explicit and do not propagate through links; prospective assertion review stays unreviewed.
|
|
60
|
+
Evidence-root labels do not infer rule ownership or approve source assertions.
|
|
61
|
+
|
|
62
|
+
Before traversal can drive extraction, T07b must preserve validation documents through a separate
|
|
63
|
+
deterministic evidence path. T07c must connect the same selection to hydration, cache recovery and
|
|
64
|
+
freshness checks, with real cross-source positive/negative fixtures and rename/delete behavior.
|