@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,373 @@
|
|
|
1
|
+
// Modified for standalone extraction; see docs/extraction-manifest.json.
|
|
2
|
+
import { contextPackEvidenceAuthority } from "./authority.js";
|
|
3
|
+
import { RdfEvidenceGraph } from "./rdf.js";
|
|
4
|
+
import { sha256, stableJson } from "../infrastructure/util.js";
|
|
5
|
+
import { diagnosticSummary, selectDiagnostics } from "./diagnostics.js";
|
|
6
|
+
export const DEFAULT_CONTEXT_PACK_FACTS = 60;
|
|
7
|
+
export const MAX_CONTEXT_PACK_FACTS = 200;
|
|
8
|
+
const contextPackLanes = [
|
|
9
|
+
'productIntent',
|
|
10
|
+
'contractsAndInvariants',
|
|
11
|
+
'implementationAndConsumers',
|
|
12
|
+
'validationEvidence',
|
|
13
|
+
];
|
|
14
|
+
function isValidationFact(fact) {
|
|
15
|
+
const path = fact.provenance.path.toLowerCase();
|
|
16
|
+
return (fact.subject.kind === 'Evidence' ||
|
|
17
|
+
fact.object.kind === 'Evidence' ||
|
|
18
|
+
fact.predicate === 'verifiedBy' ||
|
|
19
|
+
/(?:^|\/)(?:test|tests|testing|validation|evidence)(?:\/|$)/.test(path) ||
|
|
20
|
+
/\.(?:spec|test)\.[cm]?[jt]sx?$/.test(path));
|
|
21
|
+
}
|
|
22
|
+
function isDeclaredFact(fact) {
|
|
23
|
+
if (fact.authority)
|
|
24
|
+
return fact.authority === 'declared' && !isValidationFact(fact);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
export function contextPackLaneForFact(fact) {
|
|
28
|
+
if (fact.extractor.id === 'otlp-runtime-evidence')
|
|
29
|
+
return undefined;
|
|
30
|
+
const kinds = new Set([fact.subject.kind, fact.object.kind]);
|
|
31
|
+
if (isValidationFact(fact)) {
|
|
32
|
+
return 'validationEvidence';
|
|
33
|
+
}
|
|
34
|
+
if (kinds.has('Contract') ||
|
|
35
|
+
kinds.has('Invariant') ||
|
|
36
|
+
kinds.has('PersistenceModel') ||
|
|
37
|
+
['governedBy', 'definedBy', 'persistsTo', 'authorizedBy'].includes(fact.predicate)) {
|
|
38
|
+
return 'contractsAndInvariants';
|
|
39
|
+
}
|
|
40
|
+
if ((isDeclaredFact(fact) || fact.sourceAuthority === 'declared') &&
|
|
41
|
+
(kinds.has('Document') ||
|
|
42
|
+
kinds.has('Capability') ||
|
|
43
|
+
kinds.has('Workflow') ||
|
|
44
|
+
['decisionAuthority', 'operationallyOwnedBy', 'references'].includes(fact.predicate))) {
|
|
45
|
+
return 'productIntent';
|
|
46
|
+
}
|
|
47
|
+
return 'implementationAndConsumers';
|
|
48
|
+
}
|
|
49
|
+
export { contextPackEvidenceAuthority } from "./authority.js";
|
|
50
|
+
function selectBalancedFacts(ranked, maxFacts) {
|
|
51
|
+
const queues = Object.fromEntries(contextPackLanes.map((lane) => [
|
|
52
|
+
lane,
|
|
53
|
+
ranked.filter((fact) => contextPackLaneForFact(fact) === lane),
|
|
54
|
+
]));
|
|
55
|
+
const facts = [];
|
|
56
|
+
while (facts.length < maxFacts && contextPackLanes.some((lane) => queues[lane].length > 0)) {
|
|
57
|
+
for (const lane of contextPackLanes) {
|
|
58
|
+
const fact = queues[lane].shift();
|
|
59
|
+
if (fact)
|
|
60
|
+
facts.push(fact);
|
|
61
|
+
if (facts.length === maxFacts)
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const counts = Object.fromEntries(contextPackLanes.map((lane) => [
|
|
66
|
+
lane,
|
|
67
|
+
facts.filter((fact) => contextPackLaneForFact(fact) === lane).length,
|
|
68
|
+
]));
|
|
69
|
+
return { facts, counts };
|
|
70
|
+
}
|
|
71
|
+
const searchStopWords = new Set([
|
|
72
|
+
'about',
|
|
73
|
+
'agent',
|
|
74
|
+
'and',
|
|
75
|
+
'from',
|
|
76
|
+
'into',
|
|
77
|
+
'preserving',
|
|
78
|
+
'replacing',
|
|
79
|
+
'rules',
|
|
80
|
+
'the',
|
|
81
|
+
'while',
|
|
82
|
+
'with',
|
|
83
|
+
]);
|
|
84
|
+
function focusTerms(values) {
|
|
85
|
+
return [
|
|
86
|
+
...new Set(values
|
|
87
|
+
.filter((value) => Boolean(value))
|
|
88
|
+
.flatMap((value) => [value, ...value.split(/[^A-Za-z0-9_.:/-]+/)])
|
|
89
|
+
.map((value) => value.toLowerCase())
|
|
90
|
+
.filter((value) => value.length >= 4 && !searchStopWords.has(value))),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
function factSearchText(fact) {
|
|
94
|
+
return [
|
|
95
|
+
fact.subject.id,
|
|
96
|
+
fact.subject.label,
|
|
97
|
+
fact.predicate,
|
|
98
|
+
fact.object.id,
|
|
99
|
+
fact.object.label,
|
|
100
|
+
fact.provenance.path,
|
|
101
|
+
]
|
|
102
|
+
.join(' ')
|
|
103
|
+
.toLowerCase();
|
|
104
|
+
}
|
|
105
|
+
function semanticPriority(fact) {
|
|
106
|
+
return /codex-cli|document-linker/.test(fact.extractor.id) ? 0 : 1;
|
|
107
|
+
}
|
|
108
|
+
function boundedFactLimit(value) {
|
|
109
|
+
if (value === undefined)
|
|
110
|
+
return DEFAULT_CONTEXT_PACK_FACTS;
|
|
111
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
112
|
+
throw new Error('maxFacts must be a positive integer');
|
|
113
|
+
}
|
|
114
|
+
return Math.min(value, MAX_CONTEXT_PACK_FACTS);
|
|
115
|
+
}
|
|
116
|
+
export class MarkdownContextPackRenderer {
|
|
117
|
+
render(pack) {
|
|
118
|
+
const lines = [
|
|
119
|
+
`# Context Pack`,
|
|
120
|
+
'',
|
|
121
|
+
`Snapshot: \`${pack.snapshot.snapshotId}\` (${pack.snapshot.commitSha})`,
|
|
122
|
+
`Configuration: ${pack.snapshot.configurationDigest ?? 'legacy/unknown'}; scope: ${pack.snapshot.scope?.join(', ') ?? 'legacy/unknown'}`,
|
|
123
|
+
...(pack.exploration ? [
|
|
124
|
+
`Relationships returned: ${pack.exploration.returned}/${pack.exploration.reachableFacts}; truncated: ${pack.exploration.truncated}; depth limit reached: ${pack.exploration.depthLimitReached}`,
|
|
125
|
+
...(pack.exploration.continuation ? [`Continuation: ${pack.exploration.continuation}`] : []),
|
|
126
|
+
] : []),
|
|
127
|
+
...(pack.evidenceState ? [
|
|
128
|
+
`Stale relative to checkout: ${pack.evidenceState.stale}; configuration matches: ${pack.evidenceState.configurationMatches}`,
|
|
129
|
+
`Excluded working changes: ${pack.evidenceState.excludedWorkingChanges.join(', ') || '(none)'}`,
|
|
130
|
+
...(pack.evidenceState.overlayFreshness ? [
|
|
131
|
+
`Overlay freshness: ${pack.evidenceState.overlayFreshness}`,
|
|
132
|
+
`Captured working changes: ${pack.evidenceState.capturedWorkingChanges?.join(', ') || '(none)'}`,
|
|
133
|
+
`Changed since capture: ${pack.evidenceState.changedSinceCapture?.join(', ') || '(none)'}`,
|
|
134
|
+
] : []),
|
|
135
|
+
'Saved evidence does not validate the current working diff.',
|
|
136
|
+
] : []),
|
|
137
|
+
'',
|
|
138
|
+
'## Capabilities',
|
|
139
|
+
...pack.capabilities.map((node) => `- ${node.label} (\`${node.id}\`)`),
|
|
140
|
+
'',
|
|
141
|
+
'## Governing invariants',
|
|
142
|
+
...pack.invariants.map((item) => `- ${item.status}: ${item.message}`),
|
|
143
|
+
'',
|
|
144
|
+
'## Coverage',
|
|
145
|
+
...contextPackLanes.map((lane) => `- ${lane}: ${pack.coverage.counts[lane]} relationships`),
|
|
146
|
+
...(pack.coverage.gaps.length
|
|
147
|
+
? pack.coverage.gaps.map((gap) => `- GAP: ${gap}`)
|
|
148
|
+
: ['- No Context Pack lane gaps detected.']),
|
|
149
|
+
...contextPackLanes.flatMap((lane) => [
|
|
150
|
+
'',
|
|
151
|
+
`## ${lane}`,
|
|
152
|
+
...pack.facts
|
|
153
|
+
.filter((fact) => contextPackLaneForFact(fact) === lane)
|
|
154
|
+
.map((fact) => `- [${contextPackEvidenceAuthority(fact)}] ${fact.subject.label} —${fact.predicate}→ ${fact.object.label} ` +
|
|
155
|
+
`(\`${fact.provenance.path}:${fact.provenance.startLine}\`, confidence ${fact.confidence}${fact.sourceAuthority ? `; source ${fact.sourceAuthority}; extraction ${fact.extractionReview ?? 'unknown'}` : ''})`),
|
|
156
|
+
]),
|
|
157
|
+
'',
|
|
158
|
+
'## Runtime evidence',
|
|
159
|
+
...(pack.runtimeEvidence.length
|
|
160
|
+
? pack.runtimeEvidence.map((item) => `- [observed] ${item.accepted ? 'accepted' : 'segregated'} trace \`${item.traceId}\` ` +
|
|
161
|
+
`(${item.serviceName}@${item.serviceVersion}; ` +
|
|
162
|
+
`${item.capability ?? 'unmapped capability'}; ` +
|
|
163
|
+
`${item.operation ?? 'unmapped operation'}; ` +
|
|
164
|
+
`${item.environment ?? 'unknown environment'}; ` +
|
|
165
|
+
`${item.observedAt ?? 'unknown observation time'})`)
|
|
166
|
+
: ['- GAP: No accepted exact-deployment runtime evidence matched this request.']),
|
|
167
|
+
'',
|
|
168
|
+
'## Suggested files',
|
|
169
|
+
...pack.suggestedFiles.map((path) => `- \`${path}\``),
|
|
170
|
+
'',
|
|
171
|
+
'## Scoped diagnostics',
|
|
172
|
+
...diagnosticSummary(pack).map((item) => `- ${item}`),
|
|
173
|
+
];
|
|
174
|
+
return lines.join('\n');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export class SnapshotImpactQueryService {
|
|
178
|
+
store;
|
|
179
|
+
headName;
|
|
180
|
+
renderer;
|
|
181
|
+
constructor(store, headName, renderer = new MarkdownContextPackRenderer()) {
|
|
182
|
+
this.store = store;
|
|
183
|
+
this.headName = headName;
|
|
184
|
+
this.renderer = renderer;
|
|
185
|
+
}
|
|
186
|
+
async scan(request) {
|
|
187
|
+
const maxFacts = boundedFactLimit(request.maxFacts);
|
|
188
|
+
const saved = request.snapshotId
|
|
189
|
+
? await this.store.read(request.snapshotId)
|
|
190
|
+
: await this.store.readHead(this.headName);
|
|
191
|
+
if (!saved)
|
|
192
|
+
throw new Error('No graph snapshot is available; run context-graph hydrate first');
|
|
193
|
+
const snapshot = { ...saved, facts: await new RdfEvidenceGraph(saved).facts() };
|
|
194
|
+
const maxDepth = request.maxDepth ?? 32;
|
|
195
|
+
if (!Number.isInteger(maxDepth) || maxDepth < 1 || maxDepth > 128) {
|
|
196
|
+
throw new Error('maxDepth must be an integer between 1 and 128');
|
|
197
|
+
}
|
|
198
|
+
const anchorTerms = [
|
|
199
|
+
request.ownerDocument,
|
|
200
|
+
...(request.changedFiles ?? []),
|
|
201
|
+
...(request.symbols ?? []),
|
|
202
|
+
]
|
|
203
|
+
.filter((term) => Boolean(term))
|
|
204
|
+
.map((term) => term.toLowerCase());
|
|
205
|
+
const focus = focusTerms([
|
|
206
|
+
request.task,
|
|
207
|
+
...(request.checklistItems ?? []),
|
|
208
|
+
...(request.capabilities ?? []),
|
|
209
|
+
]);
|
|
210
|
+
const terms = [...anchorTerms, ...focus];
|
|
211
|
+
const directTerms = anchorTerms.length > 0 ? anchorTerms : focus;
|
|
212
|
+
const direct = snapshot.facts.filter((fact) => this.matches(fact, directTerms));
|
|
213
|
+
const ranked = new Map(direct.map((fact) => [fact.id, fact]));
|
|
214
|
+
const distance = new Map(direct.map((fact) => [fact.id, 0]));
|
|
215
|
+
let frontier = new Set(direct.flatMap((fact) => [fact.subject, fact.object]
|
|
216
|
+
.filter((node) => node.kind !== 'Project')
|
|
217
|
+
.map((node) => node.id)));
|
|
218
|
+
const visitedNodes = new Set(frontier);
|
|
219
|
+
for (let depth = 0; depth < maxDepth && frontier.size > 0; depth += 1) {
|
|
220
|
+
const next = new Set();
|
|
221
|
+
for (const fact of snapshot.facts) {
|
|
222
|
+
if (!frontier.has(fact.subject.id) && !frontier.has(fact.object.id))
|
|
223
|
+
continue;
|
|
224
|
+
ranked.set(fact.id, fact);
|
|
225
|
+
if (!distance.has(fact.id))
|
|
226
|
+
distance.set(fact.id, depth + 1);
|
|
227
|
+
for (const node of [fact.subject, fact.object]) {
|
|
228
|
+
if (node.kind !== 'Project' && !visitedNodes.has(node.id))
|
|
229
|
+
next.add(node.id);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
for (const node of next)
|
|
233
|
+
visitedNodes.add(node);
|
|
234
|
+
frontier = next;
|
|
235
|
+
}
|
|
236
|
+
const ordered = [...ranked.values()].sort((left, right) => {
|
|
237
|
+
const leftText = factSearchText(left);
|
|
238
|
+
const rightText = factSearchText(right);
|
|
239
|
+
const leftFocus = focus.filter((term) => leftText.includes(term)).length;
|
|
240
|
+
const rightFocus = focus.filter((term) => rightText.includes(term)).length;
|
|
241
|
+
return (rightFocus - leftFocus ||
|
|
242
|
+
semanticPriority(left) - semanticPriority(right) ||
|
|
243
|
+
(distance.get(left.id) ?? 4) - (distance.get(right.id) ?? 4) ||
|
|
244
|
+
left.id.localeCompare(right.id));
|
|
245
|
+
});
|
|
246
|
+
const allBalanced = [
|
|
247
|
+
...selectBalancedFacts(ordered, ordered.length).facts,
|
|
248
|
+
...ordered.filter((fact) => contextPackLaneForFact(fact) === undefined),
|
|
249
|
+
];
|
|
250
|
+
const { continuation: _continuation, snapshotId: _snapshotId, ...query } = request;
|
|
251
|
+
const queryDigest = sha256(stableJson(query));
|
|
252
|
+
let offset = 0;
|
|
253
|
+
if (request.continuation) {
|
|
254
|
+
if (request.continuation.length > 2048)
|
|
255
|
+
throw new Error('Invalid continuation');
|
|
256
|
+
const token = JSON.parse(Buffer.from(request.continuation, 'base64url').toString('utf8'));
|
|
257
|
+
if (token.snapshot !== snapshot.manifest.snapshotId ||
|
|
258
|
+
token.digest !== snapshot.manifest.contentDigest || token.query !== queryDigest ||
|
|
259
|
+
!Number.isInteger(token.offset) || token.offset < 0 || token.offset > allBalanced.length) {
|
|
260
|
+
throw new Error('Continuation does not match this snapshot and query');
|
|
261
|
+
}
|
|
262
|
+
offset = token.offset;
|
|
263
|
+
}
|
|
264
|
+
const facts = allBalanced.slice(offset, offset + maxFacts);
|
|
265
|
+
const balanced = selectBalancedFacts(facts, facts.length);
|
|
266
|
+
const nextOffset = offset + facts.length;
|
|
267
|
+
const continuation = nextOffset < allBalanced.length ? Buffer.from(JSON.stringify({
|
|
268
|
+
snapshot: snapshot.manifest.snapshotId, digest: snapshot.manifest.contentDigest,
|
|
269
|
+
query: queryDigest, offset: nextOffset,
|
|
270
|
+
})).toString('base64url') : undefined;
|
|
271
|
+
const capabilities = [
|
|
272
|
+
...new Map(facts
|
|
273
|
+
.flatMap((fact) => [fact.subject, fact.object])
|
|
274
|
+
.filter((node) => node.kind === 'Capability')
|
|
275
|
+
.map((node) => [node.id, node])).values(),
|
|
276
|
+
];
|
|
277
|
+
const suggestedFiles = [
|
|
278
|
+
...new Set([...direct, ...facts].map((fact) => fact.provenance.path).filter(Boolean)),
|
|
279
|
+
].slice(0, 20);
|
|
280
|
+
const runtimeEvidence = snapshot.runtimeEvidence.filter((item) => item.accepted &&
|
|
281
|
+
(!terms.length ||
|
|
282
|
+
terms.some((term) => item.capability?.includes(term) || item.operation?.includes(term))));
|
|
283
|
+
const declaredCapabilityIds = new Set(snapshot.facts
|
|
284
|
+
.filter(isDeclaredFact)
|
|
285
|
+
.flatMap((fact) => [fact.subject, fact.object])
|
|
286
|
+
.filter((node) => node.kind === 'Capability')
|
|
287
|
+
.map((node) => node.id));
|
|
288
|
+
const queryDiagnostics = runtimeEvidence.flatMap((item) => {
|
|
289
|
+
if (!item.capability)
|
|
290
|
+
return [];
|
|
291
|
+
const capability = `capability:${item.capability.replace(/^(?:[^:]+:)?capability:/, '')}`;
|
|
292
|
+
if (declaredCapabilityIds.has(capability))
|
|
293
|
+
return [];
|
|
294
|
+
return [
|
|
295
|
+
{
|
|
296
|
+
id: `runtime-intent-gap-${item.traceId}-${item.spanId}`,
|
|
297
|
+
kind: 'drift',
|
|
298
|
+
reason: `Observed runtime capability ${capability} has no declared product-intent mapping. Verify the source docs before concluding intent is absent.`,
|
|
299
|
+
// This is a query over runtime references, not a cited passage in the source file.
|
|
300
|
+
// The actual trace/span and source digest remain in pack.runtimeEvidence.
|
|
301
|
+
provenance: [],
|
|
302
|
+
relatedNodeIds: [capability],
|
|
303
|
+
evidenceScope: 'snapshot-query',
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
});
|
|
307
|
+
for (const [kind, missing, reason] of [
|
|
308
|
+
[
|
|
309
|
+
'missingIntent',
|
|
310
|
+
balanced.counts.productIntent === 0,
|
|
311
|
+
'No declared intent matched this query. Inspect the owner and philosophy sources before concluding a requirement is missing.',
|
|
312
|
+
],
|
|
313
|
+
[
|
|
314
|
+
'implementationGaps',
|
|
315
|
+
balanced.counts.implementationAndConsumers === 0,
|
|
316
|
+
'No implementation relationships matched this query. Inspect code before concluding behavior is missing.',
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
'runtimeGaps',
|
|
320
|
+
runtimeEvidence.length === 0,
|
|
321
|
+
'No accepted exact-deployment runtime evidence matched this query; runtime behavior is not observed.',
|
|
322
|
+
],
|
|
323
|
+
]) {
|
|
324
|
+
if (missing)
|
|
325
|
+
queryDiagnostics.push({
|
|
326
|
+
id: `query-${kind}`,
|
|
327
|
+
kind,
|
|
328
|
+
reason,
|
|
329
|
+
provenance: [],
|
|
330
|
+
relatedNodeIds: [],
|
|
331
|
+
evidenceScope: 'snapshot-query',
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const plain = {
|
|
335
|
+
snapshot: {
|
|
336
|
+
snapshotId: snapshot.manifest.snapshotId,
|
|
337
|
+
commitSha: snapshot.manifest.commitSha,
|
|
338
|
+
contentDigest: snapshot.manifest.contentDigest,
|
|
339
|
+
configurationDigest: snapshot.manifest.configurationDigest,
|
|
340
|
+
scope: snapshot.manifest.scope,
|
|
341
|
+
baseSha: snapshot.manifest.baseSha,
|
|
342
|
+
overlayDigest: snapshot.manifest.overlayDigest,
|
|
343
|
+
overlayCapture: snapshot.manifest.overlayCapture,
|
|
344
|
+
},
|
|
345
|
+
capabilities,
|
|
346
|
+
facts,
|
|
347
|
+
invariants: snapshot.invariants.filter((item) => item.provenance.some((span) => suggestedFiles.includes(span.path))),
|
|
348
|
+
runtimeEvidence,
|
|
349
|
+
suggestedFiles,
|
|
350
|
+
coverage: {
|
|
351
|
+
counts: balanced.counts,
|
|
352
|
+
gaps: [...contextPackLanes
|
|
353
|
+
.filter((lane) => balanced.counts[lane] === 0)
|
|
354
|
+
.map((lane) => `No ${lane} evidence matched this request.`),
|
|
355
|
+
...(facts.some((fact) => fact.sourceAuthority === 'declared' && fact.extractionReview === 'unreviewed')
|
|
356
|
+
? ['Approved-source extraction remains unreviewed; verify cited intent before using it as a rule or gate.'] : [])],
|
|
357
|
+
},
|
|
358
|
+
diagnostics: selectDiagnostics(snapshot, request, facts, focus, queryDiagnostics),
|
|
359
|
+
exploration: {
|
|
360
|
+
returned: facts.length, reachableFacts: allBalanced.length, maxDepth,
|
|
361
|
+
depthLimitReached: frontier.size > 0, truncated: Boolean(continuation) || frontier.size > 0,
|
|
362
|
+
continuation,
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
return { ...plain, markdown: this.renderer.render(plain) };
|
|
366
|
+
}
|
|
367
|
+
matches(fact, terms) {
|
|
368
|
+
if (terms.length === 0)
|
|
369
|
+
return true;
|
|
370
|
+
const haystack = factSearchText(fact);
|
|
371
|
+
return terms.some((term) => haystack.includes(term));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { GraphFact, GraphSnapshot } from "@weftlet/core";
|
|
2
|
+
export declare const CG = "https://context-graph.dev/record#";
|
|
3
|
+
export declare const nodeIri: (id: string) => string;
|
|
4
|
+
export declare const factIri: (id: string) => string;
|
|
5
|
+
/** Lossless RDF dataset: one named graph per cited assertion, with provenance in the default graph. */
|
|
6
|
+
export declare class RdfEvidenceGraph {
|
|
7
|
+
readonly snapshot: GraphSnapshot;
|
|
8
|
+
private readonly dataset;
|
|
9
|
+
constructor(snapshot: GraphSnapshot);
|
|
10
|
+
validate(): Promise<{
|
|
11
|
+
conforms: boolean;
|
|
12
|
+
issues: {
|
|
13
|
+
category: "extraction-gap";
|
|
14
|
+
focus: string;
|
|
15
|
+
path: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
19
|
+
select(sparql: string, maxRows?: number): Promise<{
|
|
20
|
+
rows: Record<string, string>[];
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
/** The traversal projection is read through SPARQL, not a separate fact source. */
|
|
24
|
+
facts(): Promise<readonly GraphFact[]>;
|
|
25
|
+
serialize(): Promise<string>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { QueryEngine } from '@comunica/query-sparql';
|
|
2
|
+
import { DataFactory, Parser, Store, Writer } from 'n3';
|
|
3
|
+
import SHACLValidator from 'rdf-validate-shacl';
|
|
4
|
+
import { contextPackEvidenceAuthority } from "./authority.js";
|
|
5
|
+
import { stableJson } from "../infrastructure/util.js";
|
|
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#';
|
|
11
|
+
export const nodeIri = (id) => `urn:context-graph:node:${encodeURIComponent(id)}`;
|
|
12
|
+
export const factIri = (id) => `urn:context-graph:fact:${encodeURIComponent(id)}`;
|
|
13
|
+
const engine = new QueryEngine();
|
|
14
|
+
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
|
+
/** Lossless RDF dataset: one named graph per cited assertion, with provenance in the default graph. */
|
|
38
|
+
export class RdfEvidenceGraph {
|
|
39
|
+
snapshot;
|
|
40
|
+
dataset = new Store();
|
|
41
|
+
constructor(snapshot) {
|
|
42
|
+
this.snapshot = snapshot;
|
|
43
|
+
for (const [ordinal, fact] of snapshot.facts.entries()) {
|
|
44
|
+
const record = iri(factIri(fact.id));
|
|
45
|
+
const subject = iri(nodeIri(fact.subject.id));
|
|
46
|
+
const object = iri(nodeIri(fact.object.id));
|
|
47
|
+
const predicate = iri(CG + fact.predicate);
|
|
48
|
+
this.dataset.addQuad(subject, predicate, object, record);
|
|
49
|
+
this.dataset.addQuad(record, iri(RDF + 'type'), iri(RDF + 'Statement'));
|
|
50
|
+
this.dataset.addQuad(record, iri(CG + 'ordinal'), literal(String(ordinal), iri(XSD + 'integer')));
|
|
51
|
+
for (const [p, value] of [['subject', subject], ['predicate', predicate], ['object', object]]) {
|
|
52
|
+
this.dataset.addQuad(record, iri(RDF + p), value);
|
|
53
|
+
}
|
|
54
|
+
for (const [p, value] of Object.entries({
|
|
55
|
+
authority: contextPackEvidenceAuthority(fact), path: fact.provenance.path,
|
|
56
|
+
commit: fact.provenance.commitSha, sourceDigest: fact.provenance.digest,
|
|
57
|
+
extractor: `${fact.extractor.id}@${fact.extractor.version}`,
|
|
58
|
+
snapshotId: fact.snapshotId, record: stableJson(fact),
|
|
59
|
+
...(fact.sourceAuthority ? { sourceAuthority: fact.sourceAuthority } : {}),
|
|
60
|
+
...(fact.extractionReview ? { extractionReview: fact.extractionReview } : {}),
|
|
61
|
+
}))
|
|
62
|
+
this.dataset.addQuad(record, iri(CG + p), literal(value));
|
|
63
|
+
for (const [p, value] of [['startLine', fact.provenance.startLine], ['endLine', fact.provenance.endLine]]) {
|
|
64
|
+
this.dataset.addQuad(record, iri(CG + p), literal(String(value), iri(XSD + 'integer')));
|
|
65
|
+
}
|
|
66
|
+
this.dataset.addQuad(record, iri(CG + 'confidence'), literal(String(fact.confidence), iri(XSD + 'decimal')));
|
|
67
|
+
for (const node of [fact.subject, fact.object]) {
|
|
68
|
+
const term = iri(nodeIri(node.id));
|
|
69
|
+
this.dataset.addQuad(term, iri(RDF + 'type'), iri(CG + 'Node'));
|
|
70
|
+
this.dataset.addQuad(term, iri(CG + 'identifier'), literal(node.id));
|
|
71
|
+
this.dataset.addQuad(term, iri(CG + 'kind'), literal(node.kind));
|
|
72
|
+
this.dataset.addQuad(term, iri(CG + 'label'), literal(node.label));
|
|
73
|
+
for (const [key, value] of Object.entries(node.attributes ?? {})) {
|
|
74
|
+
const datatype = typeof value === 'boolean' ? 'boolean' : typeof value === 'number' ? 'decimal' : 'string';
|
|
75
|
+
this.dataset.addQuad(term, iri(`${CG}attribute/${encodeURIComponent(key)}`), literal(String(value), iri(XSD + datatype)), record);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
this.dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'manifest'), literal(stableJson(snapshot.manifest)));
|
|
80
|
+
for (const scope of snapshot.manifest.scope ?? []) {
|
|
81
|
+
this.dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'scope'), literal(scope));
|
|
82
|
+
}
|
|
83
|
+
if (snapshot.manifest.configurationDigest) {
|
|
84
|
+
this.dataset.addQuad(iri('urn:context-graph:snapshot'), iri(CG + 'configurationDigest'), literal(snapshot.manifest.configurationDigest));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async validate() {
|
|
88
|
+
const report = await new SHACLValidator(new Store(shapes)).validate(this.dataset);
|
|
89
|
+
const issues = report.results.map((result) => ({
|
|
90
|
+
category: 'extraction-gap',
|
|
91
|
+
focus: result.focusNode?.value,
|
|
92
|
+
path: result.path?.value,
|
|
93
|
+
message: result.message.map((message) => message.value).join('; '),
|
|
94
|
+
}));
|
|
95
|
+
for (const fact of this.snapshot.facts) {
|
|
96
|
+
if (fact.provenance.endLine < fact.provenance.startLine) {
|
|
97
|
+
issues.push({ category: 'extraction-gap', focus: factIri(fact.id), path: CG + 'endLine',
|
|
98
|
+
message: 'Citation ends before it starts' });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { conforms: report.conforms && issues.length === 0, issues };
|
|
102
|
+
}
|
|
103
|
+
async select(sparql, maxRows = 1000) {
|
|
104
|
+
if (!Number.isInteger(maxRows) || maxRows < 1 || maxRows > 100_000)
|
|
105
|
+
throw new Error('Invalid SPARQL row bound');
|
|
106
|
+
const ast = new SparqlParser().parse(sparql);
|
|
107
|
+
const hasService = (value) => {
|
|
108
|
+
if (!value || typeof value !== 'object')
|
|
109
|
+
return false;
|
|
110
|
+
if (value.type === 'service')
|
|
111
|
+
return true;
|
|
112
|
+
return Object.values(value).some(hasService);
|
|
113
|
+
};
|
|
114
|
+
if (ast.type !== 'query' || ast.queryType !== 'SELECT' || ast.from || hasService(ast)) {
|
|
115
|
+
throw new Error('Only local read queries are supported; remote datasets and updates are disabled');
|
|
116
|
+
}
|
|
117
|
+
const stream = await engine.queryBindings(sparql, { sources: [this.dataset], unionDefaultGraph: true });
|
|
118
|
+
const rows = [];
|
|
119
|
+
let truncated = false;
|
|
120
|
+
for await (const binding of stream) {
|
|
121
|
+
if (rows.length === maxRows) {
|
|
122
|
+
truncated = true;
|
|
123
|
+
stream.destroy();
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
rows.push(Object.fromEntries([...binding].map(([key, value]) => [key.value, value.value])));
|
|
127
|
+
}
|
|
128
|
+
return { rows, truncated };
|
|
129
|
+
}
|
|
130
|
+
/** The traversal projection is read through SPARQL, not a separate fact source. */
|
|
131
|
+
async facts() {
|
|
132
|
+
const result = await this.select(`SELECT ?record WHERE {
|
|
133
|
+
?fact <${CG}record> ?record; <${CG}ordinal> ?ordinal
|
|
134
|
+
} ORDER BY ?ordinal`, 100_000);
|
|
135
|
+
if (result.truncated)
|
|
136
|
+
throw new Error('Graph exceeds the 100000-fact projection bound');
|
|
137
|
+
return result.rows.map((row) => JSON.parse(row.record));
|
|
138
|
+
}
|
|
139
|
+
serialize() {
|
|
140
|
+
const writer = new Writer({ format: 'N-Quads' });
|
|
141
|
+
writer.addQuads(this.dataset.getQuads(null, null, null, null));
|
|
142
|
+
return new Promise((resolve, reject) => writer.end((error, text) => error ? reject(error) : resolve(text)));
|
|
143
|
+
}
|
|
144
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "../runtime-check.mjs";
|
|
2
|
+
export * from "@weftlet/core";
|
|
3
|
+
export * from "./repository/git.js";
|
|
4
|
+
export * from "./application/hydrator.js";
|
|
5
|
+
export * from "./graph/query.js";
|
|
6
|
+
export * from "./extraction/semantic.js";
|
|
7
|
+
export * from "./extraction/telemetry.js";
|
|
8
|
+
export * from "./visualization/visualization.js";
|
|
9
|
+
export * from "./application/overlay.js";
|
|
10
|
+
export * from "./graph/rdf.js";
|
|
11
|
+
export * from "./extraction/providers.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Modified for standalone extraction; see docs/extraction-manifest.json.
|
|
2
|
+
import "../runtime-check.mjs";
|
|
3
|
+
export * from "@weftlet/core";
|
|
4
|
+
export * from "./repository/git.js";
|
|
5
|
+
export * from "./application/hydrator.js";
|
|
6
|
+
export * from "./graph/query.js";
|
|
7
|
+
export * from "./extraction/semantic.js";
|
|
8
|
+
export * from "./extraction/telemetry.js";
|
|
9
|
+
export * from "./visualization/visualization.js";
|
|
10
|
+
export * from "./application/overlay.js";
|
|
11
|
+
export * from "./graph/rdf.js";
|
|
12
|
+
export * from "./extraction/providers.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { resolve as resolveImport } from 'import-meta-resolve';
|
|
4
|
+
/** Match Node's import conditions, anchored at the consumer rather than toolkit dist/. */
|
|
5
|
+
export function resolveConsumerModule(specifier, repositoryRoot) {
|
|
6
|
+
if (isAbsolute(specifier))
|
|
7
|
+
return pathToFileURL(specifier).href;
|
|
8
|
+
const parent = pathToFileURL(resolve(repositoryRoot, 'package.json')).href;
|
|
9
|
+
return resolveImport(specifier, parent);
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export function runProcess(command, args, cwd, environment) {
|
|
3
|
+
const executable = process.platform === 'win32' && command === 'pnpm' ? 'pnpm.cmd' : command;
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const child = spawn(executable, [...args], {
|
|
6
|
+
cwd,
|
|
7
|
+
env: environment ?? process.env,
|
|
8
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
9
|
+
windowsHide: true,
|
|
10
|
+
});
|
|
11
|
+
let stdout = '';
|
|
12
|
+
let stderr = '';
|
|
13
|
+
child.stdout.on('data', (chunk) => {
|
|
14
|
+
stdout += chunk.toString('utf8');
|
|
15
|
+
});
|
|
16
|
+
child.stderr.on('data', (chunk) => {
|
|
17
|
+
stderr += chunk.toString('utf8');
|
|
18
|
+
});
|
|
19
|
+
child.once('error', reject);
|
|
20
|
+
child.once('close', (code) => {
|
|
21
|
+
if (code === 0)
|
|
22
|
+
resolve({ stdout, stderr });
|
|
23
|
+
else
|
|
24
|
+
reject(new Error(`${command} exited ${code}: ${stderr.slice(-8000)}`));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@weftlet/core';
|