@weftlet/toolkit 0.0.0-stage → 0.1.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/LICENSE +176 -0
- package/NOTICE +6 -0
- package/README.md +155 -2
- package/THIRD_PARTY_NOTICES.md +610 -0
- package/dist/application/agent-context.d.ts +23 -0
- package/dist/application/agent-context.js +94 -0
- package/dist/application/application.d.ts +23 -0
- package/dist/application/application.js +91 -0
- package/dist/application/hydrator.d.ts +49 -0
- package/dist/application/hydrator.js +275 -0
- package/dist/application/overlay.d.ts +6 -0
- package/dist/application/overlay.js +110 -0
- package/dist/application/saved-evidence.d.ts +38 -0
- package/dist/application/saved-evidence.js +59 -0
- package/dist/application/snapshot-checks.d.ts +16 -0
- package/dist/application/snapshot-checks.js +51 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +4 -0
- package/dist/configuration/cache.d.ts +2 -0
- package/dist/configuration/cache.js +10 -0
- package/dist/configuration/config.d.ts +79 -0
- package/dist/configuration/config.js +165 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +2 -0
- package/dist/extraction/codex-auth.d.ts +12 -0
- package/dist/extraction/codex-auth.js +74 -0
- package/dist/extraction/document.d.ts +5 -0
- package/dist/extraction/document.js +54 -0
- package/dist/extraction/ontology.d.ts +32 -0
- package/dist/extraction/ontology.js +164 -0
- package/dist/extraction/providers.d.ts +8 -0
- package/dist/extraction/providers.js +58 -0
- package/dist/extraction/runtime-evidence.d.ts +2 -0
- package/dist/extraction/runtime-evidence.js +84 -0
- package/dist/extraction/semantic-citations.d.ts +7 -0
- package/dist/extraction/semantic-citations.js +27 -0
- package/dist/extraction/semantic-process.d.ts +11 -0
- package/dist/extraction/semantic-process.js +68 -0
- package/dist/extraction/semantic.d.ts +73 -0
- package/dist/extraction/semantic.js +414 -0
- package/dist/extraction/static.d.ts +52 -0
- package/dist/extraction/static.js +322 -0
- package/dist/extraction/telemetry.d.ts +19 -0
- package/dist/extraction/telemetry.js +259 -0
- package/dist/governance/governance.d.ts +60 -0
- package/dist/governance/governance.js +209 -0
- package/dist/graph/authority.d.ts +2 -0
- package/dist/graph/authority.js +13 -0
- package/dist/graph/diagnostics.d.ts +5 -0
- package/dist/graph/diagnostics.js +61 -0
- package/dist/graph/query.d.ts +16 -0
- package/dist/graph/query.js +373 -0
- package/dist/graph/rdf.d.ts +26 -0
- package/dist/graph/rdf.js +144 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/infrastructure/module-resolution.d.ts +2 -0
- package/dist/infrastructure/module-resolution.js +10 -0
- package/dist/infrastructure/process.d.ts +5 -0
- package/dist/infrastructure/process.js +27 -0
- package/dist/infrastructure/util.d.ts +1 -0
- package/dist/infrastructure/util.js +2 -0
- package/dist/interfaces/cli.d.ts +4 -0
- package/dist/interfaces/cli.js +197 -0
- package/dist/interfaces/doctor.d.ts +25 -0
- package/dist/interfaces/doctor.js +49 -0
- package/dist/interfaces/init.d.ts +24 -0
- package/dist/interfaces/init.js +154 -0
- package/dist/interfaces/mcp-facade.d.ts +3 -0
- package/dist/interfaces/mcp-facade.js +4 -0
- package/dist/interfaces/mcp.d.ts +2 -0
- package/dist/interfaces/mcp.js +81 -0
- package/dist/interfaces/request.d.ts +2 -0
- package/dist/interfaces/request.js +59 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +4 -0
- package/dist/repository/committed-tree.d.ts +2 -0
- package/dist/repository/committed-tree.js +35 -0
- package/dist/repository/git.d.ts +26 -0
- package/dist/repository/git.js +163 -0
- package/dist/repository/glob.d.ts +2 -0
- package/dist/repository/glob.js +46 -0
- package/dist/repository/working-content.d.ts +3 -0
- package/dist/repository/working-content.js +34 -0
- package/dist/storage/atomic-rename.d.ts +2 -0
- package/dist/storage/atomic-rename.js +22 -0
- package/dist/storage/storage.d.ts +15 -0
- package/dist/storage/storage.js +115 -0
- package/dist/telemetry.d.ts +1 -0
- package/dist/telemetry.js +2 -0
- package/dist/visualization/viewer-interaction.d.ts +12 -0
- package/dist/visualization/viewer-interaction.js +123 -0
- package/dist/visualization/visualization.d.ts +26 -0
- package/dist/visualization/visualization.js +491 -0
- package/docs/ADR-001-standalone-baseline.md +16 -0
- package/docs/ADR-002-saved-evidence.md +21 -0
- package/docs/ADR-003-draft-overlays.md +23 -0
- package/docs/ADR-004-rdf-query-path.md +37 -0
- package/docs/ADR-005-governance-and-optional-oml.md +37 -0
- package/docs/ADR-006-provider-boundaries.md +15 -0
- package/docs/ADR-007-toolkit-modules.md +53 -0
- package/docs/ADR-008-weftlet-packages.md +32 -0
- package/docs/CLOUD-ENVIRONMENT.md +56 -0
- package/docs/ENVIRONMENT-VALIDATION.md +78 -0
- package/docs/MIGRATING-TO-WEFTLET.md +54 -0
- package/docs/PROVIDERS.md +121 -0
- package/docs/RELEASING.md +144 -0
- package/docs/ROADMAP.md +53 -0
- package/docs/SOURCE-INVENTORY.md +48 -0
- package/docs/VALIDATION.md +490 -0
- package/docs/VIEWER.md +36 -0
- package/docs/architecture-baseline.json +57 -0
- package/docs/architecture-policy.md +41 -0
- package/docs/extraction-manifest.json +721 -0
- package/docs/licenses/REVIEW.md +16 -0
- package/docs/licenses/lru-cache-11.5.3-BlueOak.md +55 -0
- package/docs/licenses/negotiate-1.0.1-BSD.txt +22 -0
- package/docs/licenses/spark-md5-3.0.2-MIT.txt +7 -0
- package/examples/generic-typescript/context-graph.config.json +47 -0
- package/examples/generic-typescript/context-graph.oml.config.json +49 -0
- package/examples/generic-typescript/docs/search-capability.md +14 -0
- package/examples/generic-typescript/ontology/build.gradle +38 -0
- package/examples/generic-typescript/ontology/catalog.xml +5 -0
- package/examples/generic-typescript/ontology/settings.gradle +1 -0
- package/examples/generic-typescript/ontology/src/oml/context-graph.dev/core.oml +99 -0
- package/examples/generic-typescript/package.json +6 -0
- package/examples/generic-typescript/src/search.ts +11 -0
- package/examples/generic-typescript/test/search.test.ts +8 -0
- package/examples/generic-typescript/tsconfig.json +10 -0
- package/examples/pipeline/context-graph.config.json +49 -0
- package/examples/pipeline/docs/admission.md +6 -0
- package/examples/pipeline/docs/analysis.md +6 -0
- package/examples/pipeline/docs/discovery.md +6 -0
- package/examples/pipeline/docs/execution.md +6 -0
- package/examples/pipeline/docs/outcomes.md +6 -0
- package/examples/pipeline/docs/planning.md +6 -0
- package/examples/pipeline/package.json +6 -0
- package/examples/pipeline/src/admission.ts +9 -0
- package/examples/pipeline/src/analysis.ts +9 -0
- package/examples/pipeline/src/contracts/admission.ts +1 -0
- package/examples/pipeline/src/contracts/analysis.ts +1 -0
- package/examples/pipeline/src/contracts/discovery.ts +1 -0
- package/examples/pipeline/src/contracts/execution.ts +1 -0
- package/examples/pipeline/src/contracts/outcomes.ts +1 -0
- package/examples/pipeline/src/contracts/planning.ts +1 -0
- package/examples/pipeline/src/discovery.ts +9 -0
- package/examples/pipeline/src/execution.ts +9 -0
- package/examples/pipeline/src/outcomes.ts +9 -0
- package/examples/pipeline/src/planning.ts +9 -0
- package/examples/pipeline/src/queue.ts +12 -0
- package/examples/pipeline/test/pipeline.test.ts +17 -0
- package/examples/pipeline/tsconfig.json +12 -0
- package/package.json +116 -3
- package/runtime-check.d.mts +2 -0
- package/runtime-check.mjs +12 -0
- package/templates/context-graph-development/SKILL.md +37 -0
- package/third-party-tools.json +34 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
// Modified for standalone extraction; see docs/extraction-manifest.json.
|
|
2
|
+
import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { prepareCodexAuthentication, readCodexLoginMode } from './codex-auth.js';
|
|
6
|
+
export { semanticCommandEnvironment } from './codex-auth.js';
|
|
7
|
+
import { runSemanticProcess, SemanticProviderError } from "./semantic-process.js";
|
|
8
|
+
export { SemanticProviderError } from "./semantic-process.js";
|
|
9
|
+
import { sha256, stableJson } from "../infrastructure/util.js";
|
|
10
|
+
import { assertSemanticCitation, numberedSemanticSource, semanticCitationSchema, } from "./semantic-citations.js";
|
|
11
|
+
function omlEscape(value) {
|
|
12
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r?\n/g, '\\n');
|
|
13
|
+
}
|
|
14
|
+
function renderOml(key, assertions) {
|
|
15
|
+
const typeByKind = {
|
|
16
|
+
Project: 'cg:Project',
|
|
17
|
+
Document: 'cg:OwnerDocument',
|
|
18
|
+
Capability: 'cg:ProductCapability',
|
|
19
|
+
Workflow: 'cg:Workflow',
|
|
20
|
+
Component: 'cg:RuntimeComponent',
|
|
21
|
+
Contract: 'cg:APIContract',
|
|
22
|
+
Evidence: 'cg:ValidationArtifact',
|
|
23
|
+
Invariant: 'cg:ArchitectureInvariant',
|
|
24
|
+
Snapshot: 'cg:Project',
|
|
25
|
+
Provider: 'cg:Provider',
|
|
26
|
+
PersistenceModel: 'cg:PersistenceModel',
|
|
27
|
+
Scope: 'cg:Scope',
|
|
28
|
+
};
|
|
29
|
+
const relationByPredicate = {
|
|
30
|
+
realizes: 'cg:realizes',
|
|
31
|
+
implementedBy: 'cg:implementedBy',
|
|
32
|
+
consumes: 'cg:consumes',
|
|
33
|
+
emits: 'cg:emits',
|
|
34
|
+
persistsTo: 'cg:persistsTo',
|
|
35
|
+
governedBy: 'cg:governedBy',
|
|
36
|
+
definedBy: 'cg:definedBy',
|
|
37
|
+
verifiedBy: 'cg:verifiedBy',
|
|
38
|
+
operationallyOwnedBy: 'cg:operationallyOwnedBy',
|
|
39
|
+
decisionAuthority: 'cg:hasDecisionAuthority',
|
|
40
|
+
routesThrough: 'cg:routesThrough',
|
|
41
|
+
authorizedBy: 'cg:authorizedBy',
|
|
42
|
+
sourcedFrom: 'cg:sourcedFrom',
|
|
43
|
+
references: 'cg:references',
|
|
44
|
+
contains: 'cg:contains',
|
|
45
|
+
};
|
|
46
|
+
const nodes = new Map();
|
|
47
|
+
for (const assertion of assertions) {
|
|
48
|
+
nodes.set(assertion.subject.id, assertion.subject);
|
|
49
|
+
nodes.set(assertion.object.id, assertion.object);
|
|
50
|
+
}
|
|
51
|
+
const localNames = new Map([...nodes.keys()].sort().map((id) => [id, `n_${sha256(id).slice(0, 16)}`]));
|
|
52
|
+
const relations = new Map();
|
|
53
|
+
for (const assertion of assertions) {
|
|
54
|
+
const subject = localNames.get(assertion.subject.id);
|
|
55
|
+
const object = localNames.get(assertion.object.id);
|
|
56
|
+
const current = relations.get(subject) ?? [];
|
|
57
|
+
current.push(` ${relationByPredicate[assertion.predicate]} ${object}`);
|
|
58
|
+
relations.set(subject, current);
|
|
59
|
+
}
|
|
60
|
+
const instances = [...nodes.entries()]
|
|
61
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
62
|
+
.map(([id, node]) => {
|
|
63
|
+
const local = localNames.get(id);
|
|
64
|
+
const properties = [
|
|
65
|
+
` cg:hasIdentifier "${omlEscape(node.id)}"`,
|
|
66
|
+
...(relations.get(local) ?? []),
|
|
67
|
+
];
|
|
68
|
+
return ` instance ${local} : ${typeByKind[node.kind]} [\n${properties.join('\n')}\n ]`;
|
|
69
|
+
});
|
|
70
|
+
return [
|
|
71
|
+
`description <https://context-graph.dev/generated/${key}#> as generated {`,
|
|
72
|
+
' uses <https://context-graph.dev/core#> as cg',
|
|
73
|
+
'',
|
|
74
|
+
...instances.flatMap((instance) => [instance, '']),
|
|
75
|
+
'}',
|
|
76
|
+
].join('\n');
|
|
77
|
+
}
|
|
78
|
+
const spawnSemanticCommand = { run: runSemanticProcess };
|
|
79
|
+
const kinds = [
|
|
80
|
+
'Project',
|
|
81
|
+
'Document',
|
|
82
|
+
'Capability',
|
|
83
|
+
'Workflow',
|
|
84
|
+
'Component',
|
|
85
|
+
'Contract',
|
|
86
|
+
'Evidence',
|
|
87
|
+
'Invariant',
|
|
88
|
+
'Snapshot',
|
|
89
|
+
'Provider',
|
|
90
|
+
'PersistenceModel',
|
|
91
|
+
'Scope',
|
|
92
|
+
];
|
|
93
|
+
const predicates = [
|
|
94
|
+
'realizes',
|
|
95
|
+
'implementedBy',
|
|
96
|
+
'consumes',
|
|
97
|
+
'emits',
|
|
98
|
+
'persistsTo',
|
|
99
|
+
'governedBy',
|
|
100
|
+
'definedBy',
|
|
101
|
+
'verifiedBy',
|
|
102
|
+
'operationallyOwnedBy',
|
|
103
|
+
'decisionAuthority',
|
|
104
|
+
'routesThrough',
|
|
105
|
+
'authorizedBy',
|
|
106
|
+
'sourcedFrom',
|
|
107
|
+
'references',
|
|
108
|
+
'contains',
|
|
109
|
+
];
|
|
110
|
+
export function semanticOutputSchema(lineCount) {
|
|
111
|
+
const node = {
|
|
112
|
+
type: 'object',
|
|
113
|
+
additionalProperties: false,
|
|
114
|
+
required: ['id', 'kind', 'label'],
|
|
115
|
+
properties: {
|
|
116
|
+
id: { type: 'string', minLength: 1 },
|
|
117
|
+
kind: { type: 'string', enum: kinds },
|
|
118
|
+
label: { type: 'string', minLength: 1 },
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
type: 'object',
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
required: ['assertions', 'oml', 'diagnostics'],
|
|
125
|
+
properties: {
|
|
126
|
+
assertions: {
|
|
127
|
+
type: 'array',
|
|
128
|
+
items: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
additionalProperties: false,
|
|
131
|
+
required: ['subject', 'predicate', 'object', 'citation', 'confidence'],
|
|
132
|
+
properties: {
|
|
133
|
+
subject: node,
|
|
134
|
+
predicate: { type: 'string', enum: predicates },
|
|
135
|
+
object: node,
|
|
136
|
+
citation: semanticCitationSchema(lineCount),
|
|
137
|
+
confidence: { type: 'number', minimum: 0, maximum: 1 },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
oml: { type: 'string' },
|
|
142
|
+
diagnostics: {
|
|
143
|
+
type: 'array',
|
|
144
|
+
items: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
additionalProperties: false,
|
|
147
|
+
required: ['kind', 'reason', 'citation', 'relatedNodeIds'],
|
|
148
|
+
properties: {
|
|
149
|
+
kind: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
enum: ['missingIntent', 'ambiguousIntent', 'implementationGaps'],
|
|
152
|
+
},
|
|
153
|
+
reason: { type: 'string', minLength: 1, maxLength: 1000 },
|
|
154
|
+
citation: semanticCitationSchema(lineCount),
|
|
155
|
+
relatedNodeIds: { type: 'array', items: { type: 'string', minLength: 1 } },
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** Runtime validation is shared by every provider; schema-constrained output is not trusted blindly. */
|
|
163
|
+
export function assertSemanticOutput(value, source) {
|
|
164
|
+
const record = (item) => typeof item === 'object' && item !== null && !Array.isArray(item);
|
|
165
|
+
const text = (item, max = 1000) => typeof item === 'string' && item.trim().length > 0 && item.length <= max;
|
|
166
|
+
const node = (item) => record(item) && text(item.id) && text(item.label) && kinds.includes(item.kind);
|
|
167
|
+
const fail = (message) => { throw new SemanticProviderError('invalid-output', message); };
|
|
168
|
+
if (!record(value) || !Array.isArray(value.assertions) || value.assertions.length > 1000 || typeof value.oml !== 'string') {
|
|
169
|
+
fail('Invalid semantic output structure');
|
|
170
|
+
}
|
|
171
|
+
const output = value;
|
|
172
|
+
const lineCount = source.content.split('\n').length;
|
|
173
|
+
for (const [index, assertion] of output.assertions.entries()) {
|
|
174
|
+
if (!record(assertion) || !node(assertion.subject) || !node(assertion.object) ||
|
|
175
|
+
!predicates.includes(assertion.predicate) || typeof assertion.confidence !== 'number' ||
|
|
176
|
+
!Number.isFinite(assertion.confidence) || assertion.confidence < 0 || assertion.confidence > 1 || !record(assertion.citation)) {
|
|
177
|
+
fail('Invalid semantic assertion');
|
|
178
|
+
}
|
|
179
|
+
assertSemanticCitation(assertion.citation, source.path, lineCount, `assertion[${index}]`);
|
|
180
|
+
}
|
|
181
|
+
if (output.diagnostics !== undefined) {
|
|
182
|
+
if (!Array.isArray(output.diagnostics) || output.diagnostics.length > 1000)
|
|
183
|
+
fail('Invalid semantic diagnostics');
|
|
184
|
+
for (const [index, item] of output.diagnostics.entries()) {
|
|
185
|
+
if (!record(item) || !['missingIntent', 'ambiguousIntent', 'implementationGaps'].includes(String(item.kind)) ||
|
|
186
|
+
!text(item.reason) || !record(item.citation) || !Array.isArray(item.relatedNodeIds) ||
|
|
187
|
+
item.relatedNodeIds.length > 1000 || !item.relatedNodeIds.every((id) => text(id)))
|
|
188
|
+
fail('Invalid semantic diagnostic');
|
|
189
|
+
assertSemanticCitation(item.citation, source.path, lineCount, `diagnostic[${index}]`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (output.unresolvedMappings !== undefined && (!Array.isArray(output.unresolvedMappings) ||
|
|
193
|
+
output.unresolvedMappings.length > 1000 || !output.unresolvedMappings.every((item) => text(item))))
|
|
194
|
+
fail('Invalid unresolved mappings');
|
|
195
|
+
}
|
|
196
|
+
export class CodexCliSemanticExtractor {
|
|
197
|
+
config;
|
|
198
|
+
runner;
|
|
199
|
+
loginProbe;
|
|
200
|
+
identity;
|
|
201
|
+
constructor(config, runner = spawnSemanticCommand, loginProbe = readCodexLoginMode) {
|
|
202
|
+
this.config = config;
|
|
203
|
+
this.runner = runner;
|
|
204
|
+
this.loginProbe = loginProbe;
|
|
205
|
+
this.identity = `codex-cli:${config.model}:${config.reasoningEffort}:${config.promptVersion}:contract-v2`;
|
|
206
|
+
}
|
|
207
|
+
async extract(source, context, options = {}) {
|
|
208
|
+
options.signal?.throwIfAborted();
|
|
209
|
+
if (Buffer.byteLength(source.content) > (this.config.maxSourceBytes ?? 65_536)) {
|
|
210
|
+
throw new SemanticProviderError('invalid-output', 'Semantic source exceeds configured byte budget');
|
|
211
|
+
}
|
|
212
|
+
const command = process.platform === 'win32' ? 'codex.exe' : 'codex';
|
|
213
|
+
const environment = await prepareCodexAuthentication(this.config.auth, process.env, command, options, this.loginProbe);
|
|
214
|
+
const runRoot = await mkdtemp(join(tmpdir(), 'context-graph-codex-'));
|
|
215
|
+
const schemaPath = join(runRoot, 'schema.json');
|
|
216
|
+
const outputPath = join(runRoot, 'output.json');
|
|
217
|
+
const sourcePath = join(runRoot, 'source.md');
|
|
218
|
+
const lineCount = source.content.split('\n').length;
|
|
219
|
+
const numberedSource = numberedSemanticSource(source.content);
|
|
220
|
+
await writeFile(schemaPath, `${JSON.stringify(semanticOutputSchema(lineCount), null, 2)}\n`, 'utf8');
|
|
221
|
+
await writeFile(sourcePath, numberedSource, 'utf8');
|
|
222
|
+
const prompt = [
|
|
223
|
+
'Extract only semantic architecture assertions explicitly supported by the supplied source.',
|
|
224
|
+
'Use neutral graph kinds and the allowed precise predicates. Never guess a missing mapping.',
|
|
225
|
+
'Use product-wide canonical IDs derived from durable concepts, not IDs scoped to this file.',
|
|
226
|
+
'Reuse capability, policy, workflow, contract, and component IDs that another source would use for the same concept.',
|
|
227
|
+
'When the source names a repository path, use component:file:<path> so semantic and static facts join deterministically.',
|
|
228
|
+
'Capture why a rule exists, its decision authority, and cross-domain dependencies when the source explicitly states them.',
|
|
229
|
+
'Every assertion must cite inclusive 1-based source line numbers and carry calibrated confidence.',
|
|
230
|
+
`Source lines are explicitly prefixed N| below. Cite those original-source numbers, not prompt lines or visual wraps; valid lines are 1-${lineCount}.`,
|
|
231
|
+
'The N| prefix is display metadata, not source text. Preserve blank-line numbering and require startLine <= endLine.',
|
|
232
|
+
'Emit cited diagnostics only for concrete gaps the source exposes; do not generate a wish list of unspecified details.',
|
|
233
|
+
'Use ambiguousIntent for contradictory or unclear declared behavior, and implementationGaps for an explicitly needed but unnamed code mapping.',
|
|
234
|
+
'Use missingIntent only where the source explicitly identifies an undecided requirement. Absence from this one source does not prove absence from the repository.',
|
|
235
|
+
'Owner documents need not name implementation paths. Omit implementation diagnostics unless the missing mapping matters to understanding an explicit contract.',
|
|
236
|
+
'Never infer code drift or runtime gaps from this isolated document. Include only canonical relatedNodeIds supported by assertions in this output.',
|
|
237
|
+
'Every diagnostic needs a specific reason and valid source line citation. Emit an OML description fragment for accepted assertions.',
|
|
238
|
+
'The isolated source.md file is the only repository source available to this run.',
|
|
239
|
+
`Repository: ${context.repositoryId}`,
|
|
240
|
+
`Commit: ${context.commitSha}`,
|
|
241
|
+
`Stable semantic ID: ${source.stableSemanticId}`,
|
|
242
|
+
`Source path: ${source.path}`,
|
|
243
|
+
'',
|
|
244
|
+
numberedSource,
|
|
245
|
+
].join('\n');
|
|
246
|
+
const args = [
|
|
247
|
+
'exec',
|
|
248
|
+
'--ephemeral',
|
|
249
|
+
'--ignore-user-config',
|
|
250
|
+
'--ignore-rules',
|
|
251
|
+
'--skip-git-repo-check',
|
|
252
|
+
'--disable',
|
|
253
|
+
'shell_tool',
|
|
254
|
+
'--cd',
|
|
255
|
+
runRoot,
|
|
256
|
+
'--sandbox',
|
|
257
|
+
'read-only',
|
|
258
|
+
'--model',
|
|
259
|
+
this.config.model,
|
|
260
|
+
'-c',
|
|
261
|
+
`model_reasoning_effort="${this.config.reasoningEffort}"`,
|
|
262
|
+
'--output-schema',
|
|
263
|
+
schemaPath,
|
|
264
|
+
'--output-last-message',
|
|
265
|
+
outputPath,
|
|
266
|
+
'-',
|
|
267
|
+
];
|
|
268
|
+
try {
|
|
269
|
+
const retries = this.config.retries ?? 0;
|
|
270
|
+
for (let attempt = 0;; attempt++) {
|
|
271
|
+
try {
|
|
272
|
+
await this.runner.run(command, args, runRoot, prompt, environment, { ...options, timeoutMs: options.timeoutMs ?? this.config.timeoutMs ?? 120_000 });
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (!(error instanceof SemanticProviderError) || !error.retryable || attempt >= retries || options.signal?.aborted)
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if ((await stat(outputPath)).size > 2 * 1024 * 1024) {
|
|
281
|
+
throw new SemanticProviderError('invalid-output', 'Semantic output exceeds 2 MiB');
|
|
282
|
+
}
|
|
283
|
+
const rawModelOutput = await readFile(outputPath, 'utf8');
|
|
284
|
+
let parsed;
|
|
285
|
+
try {
|
|
286
|
+
parsed = JSON.parse(rawModelOutput);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
throw new SemanticProviderError('invalid-output', 'Semantic output is not valid JSON');
|
|
290
|
+
}
|
|
291
|
+
assertSemanticOutput(parsed, source);
|
|
292
|
+
return { parsed, rawModelOutput };
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
await rm(runRoot, { recursive: true, force: true });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
export class SemanticHydrator {
|
|
300
|
+
config;
|
|
301
|
+
store;
|
|
302
|
+
extractor;
|
|
303
|
+
constructor(config, store, extractor) {
|
|
304
|
+
this.config = config;
|
|
305
|
+
this.store = store;
|
|
306
|
+
this.extractor = extractor;
|
|
307
|
+
}
|
|
308
|
+
async hydrate(source, context, forceRevision, options = {}) {
|
|
309
|
+
options.signal?.throwIfAborted();
|
|
310
|
+
const keyInput = {
|
|
311
|
+
stableSemanticId: source.stableSemanticId,
|
|
312
|
+
sourceDigest: source.digest,
|
|
313
|
+
promptVersion: this.config.promptVersion,
|
|
314
|
+
extractorVersion: this.config.extractorVersion,
|
|
315
|
+
modelIdentity: this.extractor.identity,
|
|
316
|
+
adapterVersion: this.config.adapterVersion,
|
|
317
|
+
ontologyVersion: this.config.ontologyVersion,
|
|
318
|
+
minimumConfidence: this.config.minimumConfidence,
|
|
319
|
+
failOnUnresolved: this.config.failOnUnresolved,
|
|
320
|
+
forceRevision,
|
|
321
|
+
diagnosticContractVersion: 3,
|
|
322
|
+
};
|
|
323
|
+
const key = sha256(stableJson(keyInput));
|
|
324
|
+
const existing = await this.store.read(key);
|
|
325
|
+
if (existing)
|
|
326
|
+
return { artifact: existing, reused: true };
|
|
327
|
+
const extraction = await this.extractor.extract(source, context, options);
|
|
328
|
+
assertSemanticOutput(extraction.parsed, source);
|
|
329
|
+
if (typeof extraction.rawModelOutput !== 'string' || Buffer.byteLength(extraction.rawModelOutput) > 2 * 1024 * 1024) {
|
|
330
|
+
throw new SemanticProviderError('invalid-output', 'Invalid or oversized raw semantic output');
|
|
331
|
+
}
|
|
332
|
+
const output = extraction.parsed;
|
|
333
|
+
const lineCount = source.content.split('\n').length;
|
|
334
|
+
const facts = output.assertions.map((assertion, index) => {
|
|
335
|
+
assertSemanticCitation(assertion.citation, source.path, lineCount, `assertion[${index}]`);
|
|
336
|
+
if (assertion.confidence < this.config.minimumConfidence) {
|
|
337
|
+
throw new Error(`Low-confidence semantic assertion for ${source.path}`);
|
|
338
|
+
}
|
|
339
|
+
const provenance = {
|
|
340
|
+
path: source.path,
|
|
341
|
+
startLine: assertion.citation.startLine,
|
|
342
|
+
endLine: assertion.citation.endLine,
|
|
343
|
+
digest: source.digest,
|
|
344
|
+
commitSha: context.commitSha,
|
|
345
|
+
};
|
|
346
|
+
return {
|
|
347
|
+
id: sha256(stableJson({ ...assertion, provenance })),
|
|
348
|
+
subject: assertion.subject,
|
|
349
|
+
predicate: assertion.predicate,
|
|
350
|
+
object: assertion.object,
|
|
351
|
+
provenance,
|
|
352
|
+
extractor: { id: this.extractor.identity, version: this.config.extractorVersion },
|
|
353
|
+
confidence: assertion.confidence,
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
const nodeIds = new Set(facts.flatMap((fact) => [fact.subject.id, fact.object.id]));
|
|
357
|
+
const diagnostics = (output.diagnostics ?? []).map((item, index) => {
|
|
358
|
+
assertSemanticCitation(item.citation, source.path, lineCount, `diagnostic[${index}]`);
|
|
359
|
+
if (!['missingIntent', 'ambiguousIntent', 'implementationGaps'].includes(item.kind) ||
|
|
360
|
+
typeof item.reason !== 'string' ||
|
|
361
|
+
!item.reason.trim() ||
|
|
362
|
+
item.reason.length > 1000 ||
|
|
363
|
+
item.relatedNodeIds.some((id) => !nodeIds.has(id)))
|
|
364
|
+
throw new Error(`Invalid semantic diagnostic for ${source.path}`);
|
|
365
|
+
const provenance = [
|
|
366
|
+
{
|
|
367
|
+
path: source.path,
|
|
368
|
+
...item.citation,
|
|
369
|
+
digest: source.digest,
|
|
370
|
+
commitSha: context.commitSha,
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
return {
|
|
374
|
+
id: sha256(stableJson({ ...item, provenance })),
|
|
375
|
+
kind: item.kind,
|
|
376
|
+
reason: item.reason,
|
|
377
|
+
relatedNodeIds: item.relatedNodeIds,
|
|
378
|
+
provenance,
|
|
379
|
+
evidenceScope: 'source',
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
const unresolvedMappings = output.unresolvedMappings ?? [];
|
|
383
|
+
if (this.config.failOnUnresolved && (unresolvedMappings.length > 0 || diagnostics.length > 0)) {
|
|
384
|
+
throw new Error(`Unresolved semantic mappings for ${source.path}: ${[...unresolvedMappings, ...diagnostics.map((item) => item.reason)].join('; ')}`);
|
|
385
|
+
}
|
|
386
|
+
const compiledOml = renderOml(key, output.assertions);
|
|
387
|
+
const outputDigest = sha256(stableJson({ facts, diagnostics, compiledOml, rawModelOutput: extraction.rawModelOutput }));
|
|
388
|
+
const artifact = {
|
|
389
|
+
key,
|
|
390
|
+
stableSemanticId: source.stableSemanticId,
|
|
391
|
+
source: {
|
|
392
|
+
path: source.path,
|
|
393
|
+
startLine: 1,
|
|
394
|
+
endLine: lineCount,
|
|
395
|
+
digest: source.digest,
|
|
396
|
+
commitSha: context.commitSha,
|
|
397
|
+
},
|
|
398
|
+
sourceDigest: source.digest,
|
|
399
|
+
promptVersion: this.config.promptVersion,
|
|
400
|
+
extractorVersion: this.config.extractorVersion,
|
|
401
|
+
modelIdentity: this.extractor.identity,
|
|
402
|
+
adapterVersion: this.config.adapterVersion,
|
|
403
|
+
ontologyVersion: this.config.ontologyVersion,
|
|
404
|
+
facts,
|
|
405
|
+
oml: compiledOml,
|
|
406
|
+
unresolvedMappings,
|
|
407
|
+
diagnostics,
|
|
408
|
+
rawModelOutput: extraction.rawModelOutput,
|
|
409
|
+
outputDigest,
|
|
410
|
+
};
|
|
411
|
+
await this.store.write(artifact);
|
|
412
|
+
return { artifact, reused: false };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ContextGraphConfig } from "../configuration/config.js";
|
|
2
|
+
import type { ExtractionContext, FactExtractor, GraphFact } from "@weftlet/core";
|
|
3
|
+
import type { GitRepository } from "../repository/git.js";
|
|
4
|
+
export interface ScipIndexResult {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly digest: string;
|
|
7
|
+
readonly mode: 'full' | 'package';
|
|
8
|
+
readonly version: string;
|
|
9
|
+
}
|
|
10
|
+
export interface StaticIndexer {
|
|
11
|
+
index(context: ExtractionContext): Promise<ScipIndexResult>;
|
|
12
|
+
}
|
|
13
|
+
export declare class ScipTypeScriptIndexer implements StaticIndexer {
|
|
14
|
+
private readonly config;
|
|
15
|
+
private readonly storageRoot;
|
|
16
|
+
constructor(config: ContextGraphConfig['scip'], storageRoot: string);
|
|
17
|
+
index(context: ExtractionContext): Promise<ScipIndexResult>;
|
|
18
|
+
}
|
|
19
|
+
export declare function normalizeScipCheckout(bytes: Uint8Array, checkoutRoot: string): Uint8Array;
|
|
20
|
+
export declare class ScipSymbolExtractor implements FactExtractor {
|
|
21
|
+
private readonly repository;
|
|
22
|
+
private readonly index;
|
|
23
|
+
readonly identity: {
|
|
24
|
+
id: string;
|
|
25
|
+
version: string;
|
|
26
|
+
};
|
|
27
|
+
constructor(repository: GitRepository, index: ScipIndexResult);
|
|
28
|
+
extract(context: ExtractionContext): Promise<readonly GraphFact[]>;
|
|
29
|
+
}
|
|
30
|
+
export declare class RepositoryFileExtractor implements FactExtractor {
|
|
31
|
+
private readonly repository;
|
|
32
|
+
private readonly roots;
|
|
33
|
+
private readonly scipDigest;
|
|
34
|
+
readonly identity: {
|
|
35
|
+
id: string;
|
|
36
|
+
version: string;
|
|
37
|
+
};
|
|
38
|
+
constructor(repository: GitRepository, roots: readonly string[], scipDigest: string);
|
|
39
|
+
extract(context: ExtractionContext): Promise<readonly GraphFact[]>;
|
|
40
|
+
}
|
|
41
|
+
export declare class RepositoryContractExtractor implements FactExtractor {
|
|
42
|
+
private readonly repository;
|
|
43
|
+
private readonly patterns;
|
|
44
|
+
private readonly consumerRoots;
|
|
45
|
+
private readonly aliases;
|
|
46
|
+
readonly identity: {
|
|
47
|
+
id: string;
|
|
48
|
+
version: string;
|
|
49
|
+
};
|
|
50
|
+
constructor(repository: GitRepository, patterns: readonly string[], consumerRoots: readonly string[], aliases?: Readonly<Record<string, readonly string[]>>);
|
|
51
|
+
extract(context: ExtractionContext): Promise<readonly GraphFact[]>;
|
|
52
|
+
}
|