@weftlet/toolkit 0.0.0-stage → 0.1.1
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 +162 -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 +83 -0
- package/docs/MIGRATING-TO-WEFTLET.md +54 -0
- package/docs/PROVIDERS.md +121 -0
- package/docs/RELEASING.md +146 -0
- package/docs/ROADMAP.md +55 -0
- package/docs/SOURCE-INVENTORY.md +48 -0
- package/docs/VALIDATION.md +530 -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,209 @@
|
|
|
1
|
+
import { readFile, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { GitRepository } from "../repository/git.js";
|
|
6
|
+
import { RdfEvidenceGraph, CG, nodeIri } from "../graph/rdf.js";
|
|
7
|
+
import { sha256, stableJson } from "../infrastructure/util.js";
|
|
8
|
+
import { runProcess } from "../infrastructure/process.js";
|
|
9
|
+
export async function analyzeArchitecture(root, policy, policyPath = 'architecture.policy.json') {
|
|
10
|
+
const findings = [];
|
|
11
|
+
const add = (category, ruleId, from, to, message) => {
|
|
12
|
+
const fingerprint = sha256(stableJson({ category, ruleId, from, to }));
|
|
13
|
+
const exception = policy.exceptions?.find((item) => item.fingerprint === fingerprint &&
|
|
14
|
+
item.ruleId === ruleId && item.from === from && item.to === to && item.reason && item.approvalRef &&
|
|
15
|
+
new Date(item.expires).getTime() > Date.now());
|
|
16
|
+
const baseline = policy.baseline?.find((item) => item.fingerprint === fingerprint && item.reason && item.approvalRef);
|
|
17
|
+
findings.push({ category, ruleId, from, to, message, fingerprint,
|
|
18
|
+
disposition: exception ? 'exception' : baseline ? 'baseline' : 'new' });
|
|
19
|
+
};
|
|
20
|
+
if (policy.schemaVersion !== 1 || !Array.isArray(policy.rules) || !policy.roots?.length) {
|
|
21
|
+
throw new Error('Invalid architecture policy');
|
|
22
|
+
}
|
|
23
|
+
for (const rule of policy.rules) {
|
|
24
|
+
if (rule.approved && !rule.approvalRef)
|
|
25
|
+
throw new Error(`Approved rule ${rule.id} requires an approvalRef`);
|
|
26
|
+
if (rule.kind === 'forbidden-import' && (!rule.from || !rule.to))
|
|
27
|
+
throw new Error(`Rule ${rule.id} requires from/to patterns`);
|
|
28
|
+
}
|
|
29
|
+
const repository = new GitRepository(root);
|
|
30
|
+
const commitSha = await repository.resolveRef('HEAD');
|
|
31
|
+
const dirtyPaths = new Set(await repository.workingChanges());
|
|
32
|
+
const facts = [];
|
|
33
|
+
const inputs = {};
|
|
34
|
+
const policyDigest = sha256(stableJson(policy));
|
|
35
|
+
const ruleFor = (name) => policy.rules.find((rule) => rule.id === name);
|
|
36
|
+
let modules = [];
|
|
37
|
+
try {
|
|
38
|
+
const { cruise } = await import('dependency-cruiser');
|
|
39
|
+
const result = await cruise(policy.roots, {
|
|
40
|
+
baseDir: root, outputType: 'json', validate: true, tsPreCompilationDeps: true,
|
|
41
|
+
doNotFollow: { path: 'node_modules' },
|
|
42
|
+
ruleSet: { forbidden: policy.rules.filter((r) => r.kind !== 'contract-owner' && !r.transitive).map((rule) => ({
|
|
43
|
+
name: rule.id, severity: rule.approved ? 'error' : 'warn',
|
|
44
|
+
from: rule.from ? { path: rule.from } : {},
|
|
45
|
+
to: rule.kind === 'no-cycles' ? { circular: true } : { path: rule.to },
|
|
46
|
+
})) },
|
|
47
|
+
}, { conditionNames: ['import', 'node', 'types', 'default'], exportsFields: ['exports'] });
|
|
48
|
+
const parsed = JSON.parse(String(result.output));
|
|
49
|
+
modules = parsed.modules;
|
|
50
|
+
const external = new Set(modules.flatMap((module) => module.dependencies
|
|
51
|
+
.filter((dependency) => dependency.coreModule || dependency.matchesDoNotFollow)
|
|
52
|
+
.map((dependency) => dependency.resolved)));
|
|
53
|
+
for (const violation of parsed.summary.violations) {
|
|
54
|
+
const rule = ruleFor(violation.rule.name);
|
|
55
|
+
add(rule?.approved ? 'rule-violation' : 'advisory', violation.rule.name, violation.from, violation.to, `Dependency rule violated${violation.cycle ? ': cycle detected' : ''}`);
|
|
56
|
+
}
|
|
57
|
+
for (const module of modules) {
|
|
58
|
+
// Roots are entry points, not a filter on the transitive dependency closure.
|
|
59
|
+
if (external.has(module.source) || /(?:^|\/)node_modules(?:\/|$)/.test(module.source))
|
|
60
|
+
continue;
|
|
61
|
+
let content;
|
|
62
|
+
try {
|
|
63
|
+
const path = await realpath(resolve(root, module.source));
|
|
64
|
+
const within = relative(await realpath(root), path);
|
|
65
|
+
if (within === '..' || /^\.\.[\\/]/.test(within) || isAbsolute(within)) {
|
|
66
|
+
add('extraction-gap', 'external-source', module.source, '', 'Required dependency is outside the repository boundary');
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
content = await readFile(path, 'utf8');
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
add('extraction-gap', 'unreadable-source', module.source, '', 'Required source could not be read');
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
inputs[module.source] = sha256(content);
|
|
76
|
+
const membership = policy.modules.filter((m) => new RegExp(m.path).test(module.source));
|
|
77
|
+
if (membership.length !== 1)
|
|
78
|
+
add('extraction-gap', 'module-mapping', module.source, '', 'Source needs exactly one canonical module/layer mapping');
|
|
79
|
+
for (const dependency of module.dependencies) {
|
|
80
|
+
if (dependency.couldNotResolve) {
|
|
81
|
+
add('extraction-gap', 'unresolved-import', module.source, dependency.resolved, 'Import could not be resolved');
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (dependency.coreModule || dependency.matchesDoNotFollow)
|
|
85
|
+
continue;
|
|
86
|
+
const lines = content.split('\n');
|
|
87
|
+
facts.push({
|
|
88
|
+
id: sha256(stableJson({ from: module.source, to: dependency.resolved, digest: inputs[module.source] })),
|
|
89
|
+
subject: { id: `component:file:${module.source}`, kind: 'Component', label: module.source,
|
|
90
|
+
attributes: membership[0] ? { domain: membership[0].domain, layer: membership[0].layer,
|
|
91
|
+
module: membership[0].id, adapter: Boolean(membership[0].adapter) } : {} },
|
|
92
|
+
predicate: 'consumes',
|
|
93
|
+
object: { id: `component:file:${dependency.resolved}`, kind: 'Component', label: dependency.resolved },
|
|
94
|
+
provenance: { path: module.source, startLine: 1, endLine: lines.length, digest: inputs[module.source], commitSha },
|
|
95
|
+
extractor: { id: 'dependency-cruiser', version: '18.7.0' }, confidence: 1,
|
|
96
|
+
authority: dirtyPaths.has(module.source) ? 'draft' : 'implemented', snapshotId: 'pending',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
add('infrastructure-failure', 'dependency-cruiser', '', '', String(error));
|
|
103
|
+
}
|
|
104
|
+
for (const contract of policy.contracts ?? []) {
|
|
105
|
+
const rule = policy.rules.find((rule) => rule.kind === 'contract-owner');
|
|
106
|
+
const owners = policy.modules.filter((module) => module.id === contract.owner);
|
|
107
|
+
if (owners.length !== 1 || !inputs[contract.path]) {
|
|
108
|
+
add('extraction-gap', rule?.id ?? 'contract-owner', contract.path, contract.owner, 'Contract or canonical owner evidence is missing');
|
|
109
|
+
}
|
|
110
|
+
const conflicts = policy.contracts.filter((c) => c.path === contract.path && c.owner !== contract.owner);
|
|
111
|
+
if (conflicts.length)
|
|
112
|
+
add(rule?.approved ? 'rule-violation' : 'advisory', rule?.id ?? 'contract-owner', contract.path, contract.owner, 'Contradictory canonical owners');
|
|
113
|
+
}
|
|
114
|
+
const policySource = await readFile(resolve(root, policyPath), 'utf8');
|
|
115
|
+
if (stableJson(JSON.parse(policySource)) !== stableJson(policy)) {
|
|
116
|
+
throw new Error('Policy differs from its cited source file');
|
|
117
|
+
}
|
|
118
|
+
const policySpan = { path: policyPath, startLine: 1, endLine: policySource.split('\n').length,
|
|
119
|
+
digest: sha256(policySource), commitSha };
|
|
120
|
+
const addPolicyFact = (subject, predicate, object) => {
|
|
121
|
+
facts.push({ id: sha256(stableJson({ subject, predicate, object, policyDigest })),
|
|
122
|
+
subject, predicate, object, provenance: policySpan,
|
|
123
|
+
extractor: { id: 'architecture-policy', version: '1' }, confidence: 1,
|
|
124
|
+
authority: dirtyPaths.has(policyPath) ? 'draft' : 'declared', snapshotId: 'pending' });
|
|
125
|
+
};
|
|
126
|
+
for (const module of policy.modules) {
|
|
127
|
+
const node = { id: `module:${module.id}`, kind: 'Component', label: module.id,
|
|
128
|
+
attributes: { domain: module.domain, layer: module.layer, adapter: Boolean(module.adapter) } };
|
|
129
|
+
addPolicyFact(node, 'governedBy', { id: `layer:${module.layer}`, kind: 'Scope', label: module.layer });
|
|
130
|
+
for (const path of Object.keys(inputs).filter((path) => new RegExp(module.path).test(path))) {
|
|
131
|
+
addPolicyFact(node, 'contains', { id: `component:file:${path}`, kind: 'Component', label: path });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const contract of policy.contracts ?? []) {
|
|
135
|
+
addPolicyFact({ id: `contract:file:${contract.path}`, kind: 'Contract', label: contract.path }, 'operationallyOwnedBy', { id: `module:${contract.owner}`, kind: 'Component', label: contract.owner });
|
|
136
|
+
}
|
|
137
|
+
if (!Object.keys(inputs).length)
|
|
138
|
+
add('extraction-gap', 'source-coverage', '', '', 'No source files were analyzed');
|
|
139
|
+
const contentDigest = sha256(stableJson({ inputs, policyDigest, commitSha, dirtyPaths: [...dirtyPaths].sort(),
|
|
140
|
+
tools: { dependencyCruiser: '18.7.0', eslint: '10.13.0', jscpd: '5.4.1', analyzer: '2' } }));
|
|
141
|
+
const snapshotId = `architecture-${contentDigest}`;
|
|
142
|
+
const snapshot = {
|
|
143
|
+
manifest: {
|
|
144
|
+
schemaVersion: 1, repositoryId: 'architecture-analysis', ref: 'HEAD', commitSha, snapshotId,
|
|
145
|
+
configurationDigest: policyDigest, scope: policy.roots, contentDigest,
|
|
146
|
+
...(dirtyPaths.size ? { baseSha: commitSha, overlayDigest: contentDigest } : {}),
|
|
147
|
+
ontologyVersion: 'architecture-v1', extractorVersions: { 'dependency-cruiser': '18.7.0' },
|
|
148
|
+
semanticModel: 'none', semanticPromptVersion: 'none', generatedAt: new Date().toISOString(),
|
|
149
|
+
semanticArtifactKeys: [], scipIndexDigest: '', rdfDigest: '',
|
|
150
|
+
},
|
|
151
|
+
facts: facts.map((fact) => ({ ...fact, snapshotId })), runtimeEvidence: [], invariants: [], unresolved: [],
|
|
152
|
+
};
|
|
153
|
+
for (const rule of policy.rules.filter((rule) => rule.transitive && rule.kind === 'forbidden-import')) {
|
|
154
|
+
const graph = new RdfEvidenceGraph(snapshot);
|
|
155
|
+
const result = await graph.select(`SELECT DISTINCT ?from ?to WHERE {
|
|
156
|
+
?from <${CG}consumes>+ ?to
|
|
157
|
+
}`, 100_000);
|
|
158
|
+
if (result.truncated)
|
|
159
|
+
add('extraction-gap', rule.id, '', '', 'Transitive dependency query truncated');
|
|
160
|
+
for (const row of result.rows) {
|
|
161
|
+
const from = modules.find((m) => nodeIri(`component:file:${m.source}`) === row.from)?.source;
|
|
162
|
+
const to = modules.find((m) => nodeIri(`component:file:${m.source}`) === row.to)?.source;
|
|
163
|
+
if (from && to && new RegExp(rule.from).test(from) && new RegExp(rule.to).test(to)) {
|
|
164
|
+
add(rule.approved ? 'rule-violation' : 'advisory', rule.id, from, to, 'Transitive dependency inferred by SPARQL property path from source import evidence');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const { ESLint } = await import('eslint');
|
|
170
|
+
const parser = await import('@typescript-eslint/parser');
|
|
171
|
+
const eslint = new ESLint({ cwd: root, overrideConfigFile: true, overrideConfig: [{
|
|
172
|
+
files: ['**/*.ts'], languageOptions: { parser },
|
|
173
|
+
rules: { complexity: ['warn', policy.advisory.maxComplexity],
|
|
174
|
+
'max-lines': ['warn', { max: policy.advisory.maxLines, skipBlankLines: true, skipComments: true }] },
|
|
175
|
+
}] });
|
|
176
|
+
const results = await eslint.lintFiles(policy.roots.map((path) => `${path}/**/*.ts`));
|
|
177
|
+
for (const result of results)
|
|
178
|
+
for (const message of result.messages) {
|
|
179
|
+
add(message.fatal ? 'extraction-gap' : 'advisory', `eslint:${message.ruleId ?? 'parse'}`, result.filePath.slice(root.length + 1), String(message.line), `${message.message} Size/complexity is a review signal, not proof of modularity.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
add('infrastructure-failure', 'eslint', '', '', String(error));
|
|
184
|
+
}
|
|
185
|
+
const temporary = await mkdtemp(join(tmpdir(), 'graph-duplication-'));
|
|
186
|
+
try {
|
|
187
|
+
const require = createRequire(import.meta.url);
|
|
188
|
+
const command = require.resolve('jscpd/run-jscpd.js');
|
|
189
|
+
const config = join(temporary, 'config.json');
|
|
190
|
+
await writeFile(config, JSON.stringify({ semantic: false, reporters: ['json'],
|
|
191
|
+
format: ['typescript'], minTokens: policy.advisory.duplicateMinTokens, minLines: 5,
|
|
192
|
+
threshold: 100, output: temporary, silent: true }));
|
|
193
|
+
await runProcess(process.execPath, [command, '--config', config, '--workers', '2', ...policy.roots], root);
|
|
194
|
+
const report = JSON.parse(await readFile(join(temporary, 'jscpd-report.json'), 'utf8'));
|
|
195
|
+
for (const duplicate of report.duplicates ?? []) {
|
|
196
|
+
add('advisory', 'jscpd:duplication', duplicate.firstFile?.name ?? '', duplicate.secondFile?.name ?? '', 'Text duplication requires semantic review; it does not prove equivalent business logic');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
add('infrastructure-failure', 'jscpd', '', '', String(error));
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
await rm(temporary, { recursive: true, force: true });
|
|
204
|
+
}
|
|
205
|
+
return { policyPath, inputDigest: contentDigest, snapshot, findings,
|
|
206
|
+
passed: !findings.some((finding) => finding.category === 'infrastructure-failure' || finding.category === 'extraction-gap' ||
|
|
207
|
+
(finding.category === 'rule-violation' && finding.disposition === 'new')),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function contextPackEvidenceAuthority(fact) {
|
|
2
|
+
if (fact.authority)
|
|
3
|
+
return fact.authority;
|
|
4
|
+
if (fact.extractor.id === 'otlp-runtime-evidence')
|
|
5
|
+
return 'observed';
|
|
6
|
+
if (fact.extractor.id.startsWith('codex-cli:'))
|
|
7
|
+
return 'inferred';
|
|
8
|
+
if (fact.subject.kind === 'Evidence' || fact.object.kind === 'Evidence' ||
|
|
9
|
+
/(?:^|\/)(?:test|tests|testing|validation|evidence)(?:\/|$)/.test(fact.provenance.path) ||
|
|
10
|
+
/\.(?:spec|test)\.[cm]?[jt]sx?$/.test(fact.provenance.path))
|
|
11
|
+
return 'validated';
|
|
12
|
+
return 'implemented';
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ContextPack, DiagnosticKind, DiagnosticReport, GraphDiagnostic, GraphFact, GraphSnapshot, ImpactScanRequest } from "@weftlet/core";
|
|
2
|
+
export declare const diagnosticKinds: readonly DiagnosticKind[];
|
|
3
|
+
export declare const MAX_DIAGNOSTICS_PER_KIND = 5;
|
|
4
|
+
export declare function selectDiagnostics(snapshot: Pick<GraphSnapshot, 'unresolved' | 'diagnostics'>, request: ImpactScanRequest, facts: readonly GraphFact[], focus: readonly string[], queryDiagnostics: readonly GraphDiagnostic[]): DiagnosticReport;
|
|
5
|
+
export declare function diagnosticSummary(pack: Pick<ContextPack, 'diagnostics'>): readonly string[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const diagnosticKinds = [
|
|
2
|
+
'missingIntent',
|
|
3
|
+
'ambiguousIntent',
|
|
4
|
+
'implementationGaps',
|
|
5
|
+
'runtimeGaps',
|
|
6
|
+
'drift',
|
|
7
|
+
];
|
|
8
|
+
export const MAX_DIAGNOSTICS_PER_KIND = 5;
|
|
9
|
+
function relevantDiagnostic(item, request, facts, focus) {
|
|
10
|
+
const hasSelectors = request.ownerDocument ||
|
|
11
|
+
request.changedFiles?.length ||
|
|
12
|
+
request.symbols?.length ||
|
|
13
|
+
focus.length;
|
|
14
|
+
if (!hasSelectors)
|
|
15
|
+
return true;
|
|
16
|
+
const nodes = new Set(facts
|
|
17
|
+
.flatMap((fact) => [fact.subject, fact.object])
|
|
18
|
+
.filter((node) => !['Project', 'Document', 'Scope'].includes(node.kind))
|
|
19
|
+
.map((node) => node.id));
|
|
20
|
+
if (item.relatedNodeIds.some((id) => nodes.has(id)))
|
|
21
|
+
return true;
|
|
22
|
+
if (item.provenance.some((span) => request.changedFiles?.some((file) => span.path === file)))
|
|
23
|
+
return true;
|
|
24
|
+
// A source document can contain many independent capabilities. Match the cited passage,
|
|
25
|
+
// not every diagnostic from a philosophy document that contributed one selected fact.
|
|
26
|
+
if (item.provenance.some((span) => facts.some((fact) => fact.provenance.path === span.path &&
|
|
27
|
+
fact.provenance.startLine <= span.endLine &&
|
|
28
|
+
fact.provenance.endLine >= span.startLine)))
|
|
29
|
+
return true;
|
|
30
|
+
return (item.provenance.some((span) => span.path === request.ownerDocument) &&
|
|
31
|
+
(focus.length === 0 || focus.some((term) => item.reason.toLowerCase().includes(term))));
|
|
32
|
+
}
|
|
33
|
+
export function selectDiagnostics(snapshot, request, facts, focus, queryDiagnostics) {
|
|
34
|
+
const report = { unclassifiedLegacyCount: snapshot.unresolved.length };
|
|
35
|
+
const unique = [...new Map((snapshot.diagnostics ?? []).map((item) => [item.id, item])).values()];
|
|
36
|
+
for (const kind of diagnosticKinds) {
|
|
37
|
+
const candidates = unique.filter((item) => item.kind === kind);
|
|
38
|
+
const relevant = candidates.filter((item) => relevantDiagnostic(item, request, facts, focus));
|
|
39
|
+
const ordered = [
|
|
40
|
+
...queryDiagnostics.filter((item) => item.kind === kind),
|
|
41
|
+
...relevant.sort((a, b) => a.id.localeCompare(b.id)),
|
|
42
|
+
];
|
|
43
|
+
report[kind] = {
|
|
44
|
+
items: ordered.slice(0, MAX_DIAGNOSTICS_PER_KIND),
|
|
45
|
+
omittedRelevant: Math.max(0, ordered.length - MAX_DIAGNOSTICS_PER_KIND),
|
|
46
|
+
omittedUnrelated: candidates.length - relevant.length,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return report;
|
|
50
|
+
}
|
|
51
|
+
export function diagnosticSummary(pack) {
|
|
52
|
+
return diagnosticKinds
|
|
53
|
+
.flatMap((kind) => {
|
|
54
|
+
const section = pack.diagnostics[kind];
|
|
55
|
+
return [
|
|
56
|
+
`${kind}: ${section.items.length} shown; ${section.omittedRelevant} additional relevant; ${section.omittedUnrelated} unrelated omitted`,
|
|
57
|
+
...section.items.map((item) => `${item.reason} (${item.provenance.map((span) => `${span.path}:${span.startLine}`).join(', ') || 'query of the cited snapshot'})`),
|
|
58
|
+
];
|
|
59
|
+
})
|
|
60
|
+
.concat(`Legacy diagnostics: ${pack.diagnostics.unclassifiedLegacyCount}; inspect with diagnostics show --snapshot <snapshotId>.`);
|
|
61
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ContextPack, ContextPackLane, ContextPackRenderer, GraphFact, ImpactQueryService, ImpactScanRequest, SnapshotStore } from "@weftlet/core";
|
|
2
|
+
export declare const DEFAULT_CONTEXT_PACK_FACTS = 60;
|
|
3
|
+
export declare const MAX_CONTEXT_PACK_FACTS = 200;
|
|
4
|
+
export declare function contextPackLaneForFact(fact: GraphFact): ContextPackLane | undefined;
|
|
5
|
+
export { contextPackEvidenceAuthority } from "./authority.js";
|
|
6
|
+
export declare class MarkdownContextPackRenderer implements ContextPackRenderer {
|
|
7
|
+
render(pack: Omit<ContextPack, 'markdown'>): string;
|
|
8
|
+
}
|
|
9
|
+
export declare class SnapshotImpactQueryService implements ImpactQueryService {
|
|
10
|
+
private readonly store;
|
|
11
|
+
private readonly headName;
|
|
12
|
+
private readonly renderer;
|
|
13
|
+
constructor(store: SnapshotStore, headName: string, renderer?: ContextPackRenderer);
|
|
14
|
+
scan(request: ImpactScanRequest): Promise<ContextPack>;
|
|
15
|
+
private matches;
|
|
16
|
+
}
|