@harness-engineering/core 0.10.1 → 0.11.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/dist/architecture/matchers.d.mts +2 -0
- package/dist/architecture/matchers.d.ts +2 -0
- package/dist/architecture/matchers.js +1784 -0
- package/dist/architecture/matchers.mjs +10 -0
- package/dist/chunk-ZHGBWFYD.mjs +1827 -0
- package/dist/index.d.mts +892 -23
- package/dist/index.d.ts +892 -23
- package/dist/index.js +2241 -320
- package/dist/index.mjs +1161 -1026
- package/dist/matchers-D20x48U9.d.mts +353 -0
- package/dist/matchers-D20x48U9.d.ts +353 -0
- package/package.json +11 -3
package/dist/index.mjs
CHANGED
|
@@ -1,43 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArchBaselineManager,
|
|
3
|
+
ArchBaselineSchema,
|
|
4
|
+
ArchConfigSchema,
|
|
5
|
+
ArchDiffResultSchema,
|
|
6
|
+
ArchMetricCategorySchema,
|
|
7
|
+
CategoryBaselineSchema,
|
|
8
|
+
CategoryRegressionSchema,
|
|
9
|
+
CircularDepsCollector,
|
|
10
|
+
ComplexityCollector,
|
|
11
|
+
ConstraintRuleSchema,
|
|
12
|
+
CouplingCollector,
|
|
13
|
+
DepDepthCollector,
|
|
14
|
+
Err,
|
|
15
|
+
ForbiddenImportCollector,
|
|
16
|
+
LayerViolationCollector,
|
|
17
|
+
MetricResultSchema,
|
|
18
|
+
ModuleSizeCollector,
|
|
19
|
+
Ok,
|
|
20
|
+
ThresholdConfigSchema,
|
|
21
|
+
ViolationSchema,
|
|
22
|
+
archMatchers,
|
|
23
|
+
archModule,
|
|
24
|
+
architecture,
|
|
25
|
+
buildDependencyGraph,
|
|
26
|
+
constraintRuleId,
|
|
27
|
+
createEntropyError,
|
|
28
|
+
createError,
|
|
29
|
+
defaultCollectors,
|
|
30
|
+
defineLayer,
|
|
31
|
+
detectCircularDeps,
|
|
32
|
+
detectCircularDepsInFiles,
|
|
33
|
+
detectComplexityViolations,
|
|
34
|
+
detectCouplingViolations,
|
|
35
|
+
diff,
|
|
36
|
+
fileExists,
|
|
37
|
+
findFiles,
|
|
38
|
+
readFileContent,
|
|
39
|
+
resolveFileToLayer,
|
|
40
|
+
runAll,
|
|
41
|
+
validateDependencies,
|
|
42
|
+
violationId
|
|
43
|
+
} from "./chunk-ZHGBWFYD.mjs";
|
|
44
|
+
|
|
1
45
|
// src/index.ts
|
|
2
46
|
export * from "@harness-engineering/types";
|
|
3
47
|
|
|
4
|
-
// src/shared/errors.ts
|
|
5
|
-
function createError(code, message, details = {}, suggestions = []) {
|
|
6
|
-
return { code, message, details, suggestions };
|
|
7
|
-
}
|
|
8
|
-
function createEntropyError(code, message, details = {}, suggestions = []) {
|
|
9
|
-
return { code, message, details, suggestions };
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// src/shared/result.ts
|
|
13
|
-
import { Ok, Err, isOk, isErr } from "@harness-engineering/types";
|
|
14
|
-
|
|
15
|
-
// src/shared/fs-utils.ts
|
|
16
|
-
import { access, constants, readFile } from "fs";
|
|
17
|
-
import { promisify } from "util";
|
|
18
|
-
import { glob } from "glob";
|
|
19
|
-
var accessAsync = promisify(access);
|
|
20
|
-
var readFileAsync = promisify(readFile);
|
|
21
|
-
async function fileExists(path11) {
|
|
22
|
-
try {
|
|
23
|
-
await accessAsync(path11, constants.F_OK);
|
|
24
|
-
return true;
|
|
25
|
-
} catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
async function readFileContent(path11) {
|
|
30
|
-
try {
|
|
31
|
-
const content = await readFileAsync(path11, "utf-8");
|
|
32
|
-
return Ok(content);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
return Err(error);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async function findFiles(pattern, cwd = process.cwd()) {
|
|
38
|
-
return glob(pattern, { cwd, absolute: true });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
48
|
// src/validation/file-structure.ts
|
|
42
49
|
async function validateFileStructure(projectPath, conventions) {
|
|
43
50
|
const missing = [];
|
|
@@ -76,15 +83,15 @@ function validateConfig(data, schema) {
|
|
|
76
83
|
let message = "Configuration validation failed";
|
|
77
84
|
const suggestions = [];
|
|
78
85
|
if (firstError) {
|
|
79
|
-
const
|
|
80
|
-
const pathDisplay =
|
|
86
|
+
const path13 = firstError.path.join(".");
|
|
87
|
+
const pathDisplay = path13 ? ` at "${path13}"` : "";
|
|
81
88
|
if (firstError.code === "invalid_type") {
|
|
82
89
|
const received = firstError.received;
|
|
83
90
|
const expected = firstError.expected;
|
|
84
91
|
if (received === "undefined") {
|
|
85
92
|
code = "MISSING_FIELD";
|
|
86
93
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
87
|
-
suggestions.push(`Field "${
|
|
94
|
+
suggestions.push(`Field "${path13}" is required and must be of type "${expected}"`);
|
|
88
95
|
} else {
|
|
89
96
|
code = "INVALID_TYPE";
|
|
90
97
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -297,30 +304,30 @@ function extractSections(content) {
|
|
|
297
304
|
return result;
|
|
298
305
|
});
|
|
299
306
|
}
|
|
300
|
-
function isExternalLink(
|
|
301
|
-
return
|
|
307
|
+
function isExternalLink(path13) {
|
|
308
|
+
return path13.startsWith("http://") || path13.startsWith("https://") || path13.startsWith("#") || path13.startsWith("mailto:");
|
|
302
309
|
}
|
|
303
310
|
function resolveLinkPath(linkPath, baseDir) {
|
|
304
311
|
return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
|
|
305
312
|
}
|
|
306
|
-
async function validateAgentsMap(
|
|
313
|
+
async function validateAgentsMap(path13 = "./AGENTS.md") {
|
|
307
314
|
console.warn(
|
|
308
315
|
"[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
309
316
|
);
|
|
310
|
-
const contentResult = await readFileContent(
|
|
317
|
+
const contentResult = await readFileContent(path13);
|
|
311
318
|
if (!contentResult.ok) {
|
|
312
319
|
return Err(
|
|
313
320
|
createError(
|
|
314
321
|
"PARSE_ERROR",
|
|
315
322
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
316
|
-
{ path:
|
|
323
|
+
{ path: path13 },
|
|
317
324
|
["Ensure the file exists", "Check file permissions"]
|
|
318
325
|
)
|
|
319
326
|
);
|
|
320
327
|
}
|
|
321
328
|
const content = contentResult.value;
|
|
322
329
|
const sections = extractSections(content);
|
|
323
|
-
const baseDir = dirname(
|
|
330
|
+
const baseDir = dirname(path13);
|
|
324
331
|
const sectionTitles = sections.map((s) => s.title);
|
|
325
332
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
326
333
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -360,6 +367,7 @@ async function validateAgentsMap(path11 = "./AGENTS.md") {
|
|
|
360
367
|
}
|
|
361
368
|
|
|
362
369
|
// src/context/doc-coverage.ts
|
|
370
|
+
import { minimatch } from "minimatch";
|
|
363
371
|
import { basename, relative } from "path";
|
|
364
372
|
function determineImportance(filePath) {
|
|
365
373
|
const name = basename(filePath).toLowerCase();
|
|
@@ -382,7 +390,7 @@ function suggestSection(filePath, domain) {
|
|
|
382
390
|
return `${domain} Reference`;
|
|
383
391
|
}
|
|
384
392
|
async function checkDocCoverage(domain, options = {}) {
|
|
385
|
-
const { docsDir = "./docs", sourceDir = "
|
|
393
|
+
const { docsDir = "./docs", sourceDir = ".", excludePatterns = [], graphCoverage } = options;
|
|
386
394
|
if (graphCoverage) {
|
|
387
395
|
const gaps = graphCoverage.undocumented.map((file) => ({
|
|
388
396
|
file,
|
|
@@ -402,9 +410,7 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
402
410
|
const filteredSourceFiles = sourceFiles.filter((file) => {
|
|
403
411
|
const relativePath = relative(sourceDir, file);
|
|
404
412
|
return !excludePatterns.some((pattern) => {
|
|
405
|
-
|
|
406
|
-
const regex = new RegExp("^" + escaped + "$");
|
|
407
|
-
return regex.test(relativePath) || regex.test(file);
|
|
413
|
+
return minimatch(relativePath, pattern, { dot: true }) || minimatch(file, pattern, { dot: true });
|
|
408
414
|
});
|
|
409
415
|
});
|
|
410
416
|
const docFiles = await findFiles("**/*.md", docsDir);
|
|
@@ -462,8 +468,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
462
468
|
|
|
463
469
|
// src/context/knowledge-map.ts
|
|
464
470
|
import { join as join2, basename as basename2, relative as relative2 } from "path";
|
|
465
|
-
function suggestFix(
|
|
466
|
-
const targetName = basename2(
|
|
471
|
+
function suggestFix(path13, existingFiles) {
|
|
472
|
+
const targetName = basename2(path13).toLowerCase();
|
|
467
473
|
const similar = existingFiles.find((file) => {
|
|
468
474
|
const fileName = basename2(file).toLowerCase();
|
|
469
475
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -471,7 +477,7 @@ function suggestFix(path11, existingFiles) {
|
|
|
471
477
|
if (similar) {
|
|
472
478
|
return `Did you mean "${similar}"?`;
|
|
473
479
|
}
|
|
474
|
-
return `Create the file "${
|
|
480
|
+
return `Create the file "${path13}" or remove the link`;
|
|
475
481
|
}
|
|
476
482
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
477
483
|
console.warn(
|
|
@@ -815,254 +821,6 @@ function getPhaseCategories(phase) {
|
|
|
815
821
|
return [...PHASE_PRIORITIES[phase]];
|
|
816
822
|
}
|
|
817
823
|
|
|
818
|
-
// src/constraints/layers.ts
|
|
819
|
-
import { minimatch } from "minimatch";
|
|
820
|
-
function defineLayer(name, patterns, allowedDependencies) {
|
|
821
|
-
return {
|
|
822
|
-
name,
|
|
823
|
-
patterns,
|
|
824
|
-
allowedDependencies
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
function resolveFileToLayer(file, layers) {
|
|
828
|
-
for (const layer of layers) {
|
|
829
|
-
for (const pattern of layer.patterns) {
|
|
830
|
-
if (minimatch(file, pattern)) {
|
|
831
|
-
return layer;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
return void 0;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// src/constraints/dependencies.ts
|
|
839
|
-
import { dirname as dirname3, resolve, relative as relative4 } from "path";
|
|
840
|
-
function resolveImportPath(importSource, fromFile, _rootDir) {
|
|
841
|
-
if (!importSource.startsWith(".") && !importSource.startsWith("/")) {
|
|
842
|
-
return null;
|
|
843
|
-
}
|
|
844
|
-
const fromDir = dirname3(fromFile);
|
|
845
|
-
let resolved = resolve(fromDir, importSource);
|
|
846
|
-
if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
|
|
847
|
-
resolved = resolved + ".ts";
|
|
848
|
-
}
|
|
849
|
-
return resolved.replace(/\\/g, "/");
|
|
850
|
-
}
|
|
851
|
-
function getImportType(imp) {
|
|
852
|
-
if (imp.kind === "type") return "type-only";
|
|
853
|
-
return "static";
|
|
854
|
-
}
|
|
855
|
-
async function buildDependencyGraph(files, parser, graphDependencyData) {
|
|
856
|
-
if (graphDependencyData) {
|
|
857
|
-
return Ok({
|
|
858
|
-
nodes: graphDependencyData.nodes,
|
|
859
|
-
edges: graphDependencyData.edges
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
const nodes = files.map((f) => f.replace(/\\/g, "/"));
|
|
863
|
-
const edges = [];
|
|
864
|
-
for (const file of files) {
|
|
865
|
-
const normalizedFile = file.replace(/\\/g, "/");
|
|
866
|
-
const parseResult = await parser.parseFile(file);
|
|
867
|
-
if (!parseResult.ok) {
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
const importsResult = parser.extractImports(parseResult.value);
|
|
871
|
-
if (!importsResult.ok) {
|
|
872
|
-
continue;
|
|
873
|
-
}
|
|
874
|
-
for (const imp of importsResult.value) {
|
|
875
|
-
const resolvedPath = resolveImportPath(imp.source, file, "");
|
|
876
|
-
if (resolvedPath) {
|
|
877
|
-
edges.push({
|
|
878
|
-
from: normalizedFile,
|
|
879
|
-
to: resolvedPath,
|
|
880
|
-
importType: getImportType(imp),
|
|
881
|
-
line: imp.location.line
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
return Ok({ nodes, edges });
|
|
887
|
-
}
|
|
888
|
-
function checkLayerViolations(graph, layers, rootDir) {
|
|
889
|
-
const violations = [];
|
|
890
|
-
for (const edge of graph.edges) {
|
|
891
|
-
const fromRelative = relative4(rootDir, edge.from);
|
|
892
|
-
const toRelative = relative4(rootDir, edge.to);
|
|
893
|
-
const fromLayer = resolveFileToLayer(fromRelative, layers);
|
|
894
|
-
const toLayer = resolveFileToLayer(toRelative, layers);
|
|
895
|
-
if (!fromLayer || !toLayer) continue;
|
|
896
|
-
if (fromLayer.name === toLayer.name) continue;
|
|
897
|
-
if (!fromLayer.allowedDependencies.includes(toLayer.name)) {
|
|
898
|
-
violations.push({
|
|
899
|
-
file: edge.from,
|
|
900
|
-
imports: edge.to,
|
|
901
|
-
fromLayer: fromLayer.name,
|
|
902
|
-
toLayer: toLayer.name,
|
|
903
|
-
reason: "WRONG_LAYER",
|
|
904
|
-
line: edge.line,
|
|
905
|
-
suggestion: `Move the dependency to an allowed layer (${fromLayer.allowedDependencies.join(", ") || "none"}) or update layer rules`
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
return violations;
|
|
910
|
-
}
|
|
911
|
-
async function validateDependencies(config) {
|
|
912
|
-
const { layers, rootDir, parser, fallbackBehavior = "error", graphDependencyData } = config;
|
|
913
|
-
if (graphDependencyData) {
|
|
914
|
-
const graphResult2 = await buildDependencyGraph([], parser, graphDependencyData);
|
|
915
|
-
if (!graphResult2.ok) {
|
|
916
|
-
return Err(graphResult2.error);
|
|
917
|
-
}
|
|
918
|
-
const violations2 = checkLayerViolations(graphResult2.value, layers, rootDir);
|
|
919
|
-
return Ok({
|
|
920
|
-
valid: violations2.length === 0,
|
|
921
|
-
violations: violations2,
|
|
922
|
-
graph: graphResult2.value
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
const healthResult = await parser.health();
|
|
926
|
-
if (!healthResult.ok || !healthResult.value.available) {
|
|
927
|
-
if (fallbackBehavior === "skip") {
|
|
928
|
-
return Ok({
|
|
929
|
-
valid: true,
|
|
930
|
-
violations: [],
|
|
931
|
-
graph: { nodes: [], edges: [] },
|
|
932
|
-
skipped: true,
|
|
933
|
-
reason: "Parser unavailable"
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
if (fallbackBehavior === "warn") {
|
|
937
|
-
console.warn(`Parser ${parser.name} unavailable, skipping validation`);
|
|
938
|
-
return Ok({
|
|
939
|
-
valid: true,
|
|
940
|
-
violations: [],
|
|
941
|
-
graph: { nodes: [], edges: [] },
|
|
942
|
-
skipped: true,
|
|
943
|
-
reason: "Parser unavailable"
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
return Err(
|
|
947
|
-
createError(
|
|
948
|
-
"PARSER_UNAVAILABLE",
|
|
949
|
-
`Parser ${parser.name} is not available`,
|
|
950
|
-
{ parser: parser.name },
|
|
951
|
-
["Install required runtime", "Use different parser", 'Set fallbackBehavior: "skip"']
|
|
952
|
-
)
|
|
953
|
-
);
|
|
954
|
-
}
|
|
955
|
-
const allFiles = [];
|
|
956
|
-
for (const layer of layers) {
|
|
957
|
-
for (const pattern of layer.patterns) {
|
|
958
|
-
const files = await findFiles(pattern, rootDir);
|
|
959
|
-
allFiles.push(...files);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
const uniqueFiles = [...new Set(allFiles)];
|
|
963
|
-
const graphResult = await buildDependencyGraph(uniqueFiles, parser);
|
|
964
|
-
if (!graphResult.ok) {
|
|
965
|
-
return Err(graphResult.error);
|
|
966
|
-
}
|
|
967
|
-
const violations = checkLayerViolations(graphResult.value, layers, rootDir);
|
|
968
|
-
return Ok({
|
|
969
|
-
valid: violations.length === 0,
|
|
970
|
-
violations,
|
|
971
|
-
graph: graphResult.value
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
// src/constraints/circular-deps.ts
|
|
976
|
-
function tarjanSCC(graph) {
|
|
977
|
-
const nodeMap = /* @__PURE__ */ new Map();
|
|
978
|
-
const stack = [];
|
|
979
|
-
const sccs = [];
|
|
980
|
-
let index = 0;
|
|
981
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
982
|
-
for (const node of graph.nodes) {
|
|
983
|
-
adjacency.set(node, []);
|
|
984
|
-
}
|
|
985
|
-
for (const edge of graph.edges) {
|
|
986
|
-
const neighbors = adjacency.get(edge.from);
|
|
987
|
-
if (neighbors && graph.nodes.includes(edge.to)) {
|
|
988
|
-
neighbors.push(edge.to);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
function strongConnect(node) {
|
|
992
|
-
nodeMap.set(node, {
|
|
993
|
-
index,
|
|
994
|
-
lowlink: index,
|
|
995
|
-
onStack: true
|
|
996
|
-
});
|
|
997
|
-
index++;
|
|
998
|
-
stack.push(node);
|
|
999
|
-
const neighbors = adjacency.get(node) ?? [];
|
|
1000
|
-
for (const neighbor of neighbors) {
|
|
1001
|
-
const neighborData = nodeMap.get(neighbor);
|
|
1002
|
-
if (!neighborData) {
|
|
1003
|
-
strongConnect(neighbor);
|
|
1004
|
-
const nodeData2 = nodeMap.get(node);
|
|
1005
|
-
const updatedNeighborData = nodeMap.get(neighbor);
|
|
1006
|
-
nodeData2.lowlink = Math.min(nodeData2.lowlink, updatedNeighborData.lowlink);
|
|
1007
|
-
} else if (neighborData.onStack) {
|
|
1008
|
-
const nodeData2 = nodeMap.get(node);
|
|
1009
|
-
nodeData2.lowlink = Math.min(nodeData2.lowlink, neighborData.index);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
const nodeData = nodeMap.get(node);
|
|
1013
|
-
if (nodeData.lowlink === nodeData.index) {
|
|
1014
|
-
const scc = [];
|
|
1015
|
-
let w;
|
|
1016
|
-
do {
|
|
1017
|
-
w = stack.pop();
|
|
1018
|
-
nodeMap.get(w).onStack = false;
|
|
1019
|
-
scc.push(w);
|
|
1020
|
-
} while (w !== node);
|
|
1021
|
-
if (scc.length > 1) {
|
|
1022
|
-
sccs.push(scc);
|
|
1023
|
-
} else if (scc.length === 1) {
|
|
1024
|
-
const selfNode = scc[0];
|
|
1025
|
-
const selfNeighbors = adjacency.get(selfNode) ?? [];
|
|
1026
|
-
if (selfNeighbors.includes(selfNode)) {
|
|
1027
|
-
sccs.push(scc);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
for (const node of graph.nodes) {
|
|
1033
|
-
if (!nodeMap.has(node)) {
|
|
1034
|
-
strongConnect(node);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
return sccs;
|
|
1038
|
-
}
|
|
1039
|
-
function detectCircularDeps(graph) {
|
|
1040
|
-
const sccs = tarjanSCC(graph);
|
|
1041
|
-
const cycles = sccs.map((scc) => {
|
|
1042
|
-
const reversed = scc.reverse();
|
|
1043
|
-
const firstNode = reversed[reversed.length - 1];
|
|
1044
|
-
const cycle = [...reversed, firstNode];
|
|
1045
|
-
return {
|
|
1046
|
-
cycle,
|
|
1047
|
-
severity: "error",
|
|
1048
|
-
size: scc.length
|
|
1049
|
-
};
|
|
1050
|
-
});
|
|
1051
|
-
const largestCycle = cycles.reduce((max, c) => Math.max(max, c.size), 0);
|
|
1052
|
-
return Ok({
|
|
1053
|
-
hasCycles: cycles.length > 0,
|
|
1054
|
-
cycles,
|
|
1055
|
-
largestCycle
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
async function detectCircularDepsInFiles(files, parser, graphDependencyData) {
|
|
1059
|
-
const graphResult = await buildDependencyGraph(files, parser, graphDependencyData);
|
|
1060
|
-
if (!graphResult.ok) {
|
|
1061
|
-
return graphResult;
|
|
1062
|
-
}
|
|
1063
|
-
return detectCircularDeps(graphResult.value);
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
824
|
// src/constraints/boundary.ts
|
|
1067
825
|
function createBoundaryValidator(schema, name) {
|
|
1068
826
|
return {
|
|
@@ -1074,8 +832,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
1074
832
|
return Ok(result.data);
|
|
1075
833
|
}
|
|
1076
834
|
const suggestions = result.error.issues.map((issue) => {
|
|
1077
|
-
const
|
|
1078
|
-
return
|
|
835
|
+
const path13 = issue.path.join(".");
|
|
836
|
+
return path13 ? `${path13}: ${issue.message}` : issue.message;
|
|
1079
837
|
});
|
|
1080
838
|
return Err(
|
|
1081
839
|
createError(
|
|
@@ -1119,6 +877,422 @@ function validateBoundaries(boundaries, data) {
|
|
|
1119
877
|
});
|
|
1120
878
|
}
|
|
1121
879
|
|
|
880
|
+
// src/constraints/sharing/types.ts
|
|
881
|
+
import { z } from "zod";
|
|
882
|
+
var ManifestSchema = z.object({
|
|
883
|
+
name: z.string(),
|
|
884
|
+
version: z.string(),
|
|
885
|
+
description: z.string().optional(),
|
|
886
|
+
include: z.array(z.string()).min(1),
|
|
887
|
+
minHarnessVersion: z.string().optional(),
|
|
888
|
+
keywords: z.array(z.string()).optional().default([]),
|
|
889
|
+
layers: z.record(z.array(z.string())).optional(),
|
|
890
|
+
boundaries: z.array(
|
|
891
|
+
z.object({
|
|
892
|
+
name: z.string(),
|
|
893
|
+
layer: z.string(),
|
|
894
|
+
direction: z.enum(["input", "output"]),
|
|
895
|
+
schema: z.string()
|
|
896
|
+
})
|
|
897
|
+
).optional()
|
|
898
|
+
});
|
|
899
|
+
var BundleConstraintsSchema = z.object({
|
|
900
|
+
layers: z.array(
|
|
901
|
+
z.object({
|
|
902
|
+
name: z.string(),
|
|
903
|
+
pattern: z.string(),
|
|
904
|
+
allowedDependencies: z.array(z.string())
|
|
905
|
+
})
|
|
906
|
+
).optional(),
|
|
907
|
+
forbiddenImports: z.array(
|
|
908
|
+
z.object({
|
|
909
|
+
from: z.string(),
|
|
910
|
+
disallow: z.array(z.string()),
|
|
911
|
+
message: z.string().optional()
|
|
912
|
+
})
|
|
913
|
+
).optional(),
|
|
914
|
+
boundaries: z.object({
|
|
915
|
+
requireSchema: z.array(z.string()).optional()
|
|
916
|
+
}).optional(),
|
|
917
|
+
architecture: z.object({
|
|
918
|
+
thresholds: z.record(z.unknown()).optional(),
|
|
919
|
+
modules: z.record(z.record(z.unknown())).optional()
|
|
920
|
+
}).optional(),
|
|
921
|
+
security: z.object({
|
|
922
|
+
rules: z.record(z.string()).optional()
|
|
923
|
+
}).optional()
|
|
924
|
+
});
|
|
925
|
+
var BundleSchema = z.object({
|
|
926
|
+
name: z.string(),
|
|
927
|
+
version: z.string(),
|
|
928
|
+
description: z.string().optional(),
|
|
929
|
+
minHarnessVersion: z.string().optional(),
|
|
930
|
+
manifest: ManifestSchema,
|
|
931
|
+
constraints: BundleConstraintsSchema,
|
|
932
|
+
contributions: z.record(z.unknown()).optional()
|
|
933
|
+
});
|
|
934
|
+
var ContributionsSchema = z.record(z.unknown());
|
|
935
|
+
var LockfilePackageSchema = z.object({
|
|
936
|
+
version: z.string(),
|
|
937
|
+
source: z.string(),
|
|
938
|
+
installedAt: z.string(),
|
|
939
|
+
contributions: z.record(z.unknown()).optional().nullable(),
|
|
940
|
+
resolved: z.string().optional(),
|
|
941
|
+
integrity: z.string().optional(),
|
|
942
|
+
provenance: z.array(z.string()).optional()
|
|
943
|
+
});
|
|
944
|
+
var LockfileSchema = z.object({
|
|
945
|
+
version: z.literal(1).optional(),
|
|
946
|
+
// Used by some tests
|
|
947
|
+
lockfileVersion: z.literal(1).optional(),
|
|
948
|
+
// Standard field
|
|
949
|
+
packages: z.record(LockfilePackageSchema)
|
|
950
|
+
}).refine((data) => data.version !== void 0 || data.lockfileVersion !== void 0, {
|
|
951
|
+
message: "Either 'version' or 'lockfileVersion' must be present and equal to 1"
|
|
952
|
+
});
|
|
953
|
+
var SharableLayerSchema = z.unknown();
|
|
954
|
+
var SharableForbiddenImportSchema = z.unknown();
|
|
955
|
+
var SharableBoundaryConfigSchema = z.unknown();
|
|
956
|
+
var SharableSecurityRulesSchema = z.unknown();
|
|
957
|
+
|
|
958
|
+
// src/constraints/sharing/write-config.ts
|
|
959
|
+
import * as fs from "fs/promises";
|
|
960
|
+
async function writeConfig(filePath, content) {
|
|
961
|
+
try {
|
|
962
|
+
const json = JSON.stringify(content, null, 2) + "\n";
|
|
963
|
+
await fs.writeFile(filePath, json, "utf-8");
|
|
964
|
+
return Ok(void 0);
|
|
965
|
+
} catch (error) {
|
|
966
|
+
return Err(error instanceof Error ? error : new Error(String(error)));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/constraints/sharing/manifest.ts
|
|
971
|
+
function parseManifest(parsed) {
|
|
972
|
+
const result = ManifestSchema.safeParse(parsed);
|
|
973
|
+
if (!result.success) {
|
|
974
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
975
|
+
return { ok: false, error: `Invalid manifest: ${issues}` };
|
|
976
|
+
}
|
|
977
|
+
return { ok: true, value: result.data };
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/constraints/sharing/bundle.ts
|
|
981
|
+
function resolveDotPath(obj, dotPath) {
|
|
982
|
+
const segments = dotPath.split(".");
|
|
983
|
+
let current = obj;
|
|
984
|
+
for (const segment of segments) {
|
|
985
|
+
if (current === null || typeof current !== "object") {
|
|
986
|
+
return void 0;
|
|
987
|
+
}
|
|
988
|
+
current = current[segment];
|
|
989
|
+
}
|
|
990
|
+
return current;
|
|
991
|
+
}
|
|
992
|
+
function setDotPath(obj, dotPath, value) {
|
|
993
|
+
const segments = dotPath.split(".");
|
|
994
|
+
const lastSegment = segments[segments.length - 1];
|
|
995
|
+
const parentSegments = segments.slice(0, -1);
|
|
996
|
+
let current = obj;
|
|
997
|
+
for (const segment of parentSegments) {
|
|
998
|
+
if (current[segment] === void 0 || current[segment] === null || typeof current[segment] !== "object") {
|
|
999
|
+
current[segment] = {};
|
|
1000
|
+
}
|
|
1001
|
+
current = current[segment];
|
|
1002
|
+
}
|
|
1003
|
+
if (lastSegment !== void 0) {
|
|
1004
|
+
current[lastSegment] = value;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function extractBundle(manifest, config) {
|
|
1008
|
+
const constraints = {};
|
|
1009
|
+
for (const includePath of manifest.include) {
|
|
1010
|
+
const value = resolveDotPath(config, includePath);
|
|
1011
|
+
if (value !== void 0) {
|
|
1012
|
+
setDotPath(constraints, includePath, value);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const bundle = {
|
|
1016
|
+
name: manifest.name,
|
|
1017
|
+
version: manifest.version,
|
|
1018
|
+
...manifest.minHarnessVersion !== void 0 && {
|
|
1019
|
+
minHarnessVersion: manifest.minHarnessVersion
|
|
1020
|
+
},
|
|
1021
|
+
...manifest.description !== void 0 && {
|
|
1022
|
+
description: manifest.description
|
|
1023
|
+
},
|
|
1024
|
+
manifest,
|
|
1025
|
+
constraints
|
|
1026
|
+
};
|
|
1027
|
+
const parsed = BundleSchema.safeParse(bundle);
|
|
1028
|
+
if (!parsed.success) {
|
|
1029
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
1030
|
+
return { ok: false, error: `Invalid bundle: ${issues}` };
|
|
1031
|
+
}
|
|
1032
|
+
return { ok: true, value: parsed.data };
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/constraints/sharing/merge.ts
|
|
1036
|
+
function deepEqual(a, b) {
|
|
1037
|
+
if (a === b) return true;
|
|
1038
|
+
if (typeof a !== typeof b) return false;
|
|
1039
|
+
if (typeof a !== "object" || a === null || b === null) return false;
|
|
1040
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1041
|
+
if (a.length !== b.length) return false;
|
|
1042
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
1043
|
+
}
|
|
1044
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
1045
|
+
const keysA = Object.keys(a);
|
|
1046
|
+
const keysB = Object.keys(b);
|
|
1047
|
+
if (keysA.length !== keysB.length) return false;
|
|
1048
|
+
return keysA.every(
|
|
1049
|
+
(key) => deepEqual(a[key], b[key])
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
function stringArraysEqual(a, b) {
|
|
1053
|
+
if (a.length !== b.length) return false;
|
|
1054
|
+
const sortedA = [...a].sort();
|
|
1055
|
+
const sortedB = [...b].sort();
|
|
1056
|
+
return sortedA.every((val, i) => val === sortedB[i]);
|
|
1057
|
+
}
|
|
1058
|
+
function deepMergeConstraints(localConfig, bundleConstraints, _existingContributions) {
|
|
1059
|
+
const config = { ...localConfig };
|
|
1060
|
+
const contributions = {};
|
|
1061
|
+
const conflicts = [];
|
|
1062
|
+
if (bundleConstraints.layers && bundleConstraints.layers.length > 0) {
|
|
1063
|
+
const localLayers = Array.isArray(localConfig.layers) ? localConfig.layers : [];
|
|
1064
|
+
const mergedLayers = [...localLayers];
|
|
1065
|
+
const contributedLayerNames = [];
|
|
1066
|
+
for (const bundleLayer of bundleConstraints.layers) {
|
|
1067
|
+
const existing = localLayers.find((l) => l.name === bundleLayer.name);
|
|
1068
|
+
if (!existing) {
|
|
1069
|
+
mergedLayers.push(bundleLayer);
|
|
1070
|
+
contributedLayerNames.push(bundleLayer.name);
|
|
1071
|
+
} else {
|
|
1072
|
+
const same = existing.pattern === bundleLayer.pattern && stringArraysEqual(existing.allowedDependencies, bundleLayer.allowedDependencies);
|
|
1073
|
+
if (!same) {
|
|
1074
|
+
conflicts.push({
|
|
1075
|
+
section: "layers",
|
|
1076
|
+
key: bundleLayer.name,
|
|
1077
|
+
localValue: existing,
|
|
1078
|
+
packageValue: bundleLayer,
|
|
1079
|
+
description: `Layer '${bundleLayer.name}' already exists locally with different configuration`
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
config.layers = mergedLayers;
|
|
1085
|
+
if (contributedLayerNames.length > 0) {
|
|
1086
|
+
contributions.layers = contributedLayerNames;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (bundleConstraints.forbiddenImports && bundleConstraints.forbiddenImports.length > 0) {
|
|
1090
|
+
const localFI = Array.isArray(localConfig.forbiddenImports) ? localConfig.forbiddenImports : [];
|
|
1091
|
+
const mergedFI = [...localFI];
|
|
1092
|
+
const contributedFromKeys = [];
|
|
1093
|
+
for (const bundleRule of bundleConstraints.forbiddenImports) {
|
|
1094
|
+
const existing = localFI.find((r) => r.from === bundleRule.from);
|
|
1095
|
+
if (!existing) {
|
|
1096
|
+
const entry = {
|
|
1097
|
+
from: bundleRule.from,
|
|
1098
|
+
disallow: bundleRule.disallow
|
|
1099
|
+
};
|
|
1100
|
+
if (bundleRule.message !== void 0) {
|
|
1101
|
+
entry.message = bundleRule.message;
|
|
1102
|
+
}
|
|
1103
|
+
mergedFI.push(entry);
|
|
1104
|
+
contributedFromKeys.push(bundleRule.from);
|
|
1105
|
+
} else {
|
|
1106
|
+
const same = stringArraysEqual(existing.disallow, bundleRule.disallow);
|
|
1107
|
+
if (!same) {
|
|
1108
|
+
conflicts.push({
|
|
1109
|
+
section: "forbiddenImports",
|
|
1110
|
+
key: bundleRule.from,
|
|
1111
|
+
localValue: existing,
|
|
1112
|
+
packageValue: bundleRule,
|
|
1113
|
+
description: `Forbidden import rule for '${bundleRule.from}' already exists locally with different disallow list`
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
config.forbiddenImports = mergedFI;
|
|
1119
|
+
if (contributedFromKeys.length > 0) {
|
|
1120
|
+
contributions.forbiddenImports = contributedFromKeys;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
if (bundleConstraints.boundaries) {
|
|
1124
|
+
const localBoundaries = localConfig.boundaries ?? { requireSchema: [] };
|
|
1125
|
+
const localSchemas = new Set(localBoundaries.requireSchema ?? []);
|
|
1126
|
+
const bundleSchemas = bundleConstraints.boundaries.requireSchema ?? [];
|
|
1127
|
+
const newSchemas = [];
|
|
1128
|
+
for (const schema of bundleSchemas) {
|
|
1129
|
+
if (!localSchemas.has(schema)) {
|
|
1130
|
+
newSchemas.push(schema);
|
|
1131
|
+
localSchemas.add(schema);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
config.boundaries = {
|
|
1135
|
+
requireSchema: [...localBoundaries.requireSchema ?? [], ...newSchemas]
|
|
1136
|
+
};
|
|
1137
|
+
if (newSchemas.length > 0) {
|
|
1138
|
+
contributions.boundaries = newSchemas;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (bundleConstraints.architecture) {
|
|
1142
|
+
const localArch = localConfig.architecture ?? {
|
|
1143
|
+
enabled: true,
|
|
1144
|
+
baselinePath: ".harness/arch/baselines.json",
|
|
1145
|
+
thresholds: {},
|
|
1146
|
+
modules: {}
|
|
1147
|
+
};
|
|
1148
|
+
const mergedThresholds = { ...localArch.thresholds };
|
|
1149
|
+
const contributedThresholdKeys = [];
|
|
1150
|
+
const bundleThresholds = bundleConstraints.architecture.thresholds ?? {};
|
|
1151
|
+
for (const [category, value] of Object.entries(bundleThresholds)) {
|
|
1152
|
+
if (!(category in mergedThresholds)) {
|
|
1153
|
+
mergedThresholds[category] = value;
|
|
1154
|
+
contributedThresholdKeys.push(category);
|
|
1155
|
+
} else if (!deepEqual(mergedThresholds[category], value)) {
|
|
1156
|
+
conflicts.push({
|
|
1157
|
+
section: "architecture.thresholds",
|
|
1158
|
+
key: category,
|
|
1159
|
+
localValue: mergedThresholds[category],
|
|
1160
|
+
packageValue: value,
|
|
1161
|
+
description: `Architecture threshold '${category}' already exists locally with a different value`
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
const mergedModules = { ...localArch.modules };
|
|
1166
|
+
const contributedModuleKeys = [];
|
|
1167
|
+
const bundleModules = bundleConstraints.architecture.modules ?? {};
|
|
1168
|
+
for (const [modulePath, bundleCategoryMap] of Object.entries(bundleModules)) {
|
|
1169
|
+
if (!(modulePath in mergedModules)) {
|
|
1170
|
+
mergedModules[modulePath] = bundleCategoryMap;
|
|
1171
|
+
for (const cat of Object.keys(bundleCategoryMap)) {
|
|
1172
|
+
contributedModuleKeys.push(`${modulePath}:${cat}`);
|
|
1173
|
+
}
|
|
1174
|
+
} else {
|
|
1175
|
+
const localCategoryMap = mergedModules[modulePath];
|
|
1176
|
+
const mergedCategoryMap = { ...localCategoryMap };
|
|
1177
|
+
for (const [category, value] of Object.entries(bundleCategoryMap)) {
|
|
1178
|
+
if (!(category in mergedCategoryMap)) {
|
|
1179
|
+
mergedCategoryMap[category] = value;
|
|
1180
|
+
contributedModuleKeys.push(`${modulePath}:${category}`);
|
|
1181
|
+
} else if (!deepEqual(mergedCategoryMap[category], value)) {
|
|
1182
|
+
conflicts.push({
|
|
1183
|
+
section: "architecture.modules",
|
|
1184
|
+
key: `${modulePath}:${category}`,
|
|
1185
|
+
localValue: mergedCategoryMap[category],
|
|
1186
|
+
packageValue: value,
|
|
1187
|
+
description: `Architecture module override '${modulePath}' category '${category}' already exists locally with a different value`
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
mergedModules[modulePath] = mergedCategoryMap;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
config.architecture = {
|
|
1195
|
+
...localArch,
|
|
1196
|
+
thresholds: mergedThresholds,
|
|
1197
|
+
modules: mergedModules
|
|
1198
|
+
};
|
|
1199
|
+
if (contributedThresholdKeys.length > 0) {
|
|
1200
|
+
contributions["architecture.thresholds"] = contributedThresholdKeys;
|
|
1201
|
+
}
|
|
1202
|
+
if (contributedModuleKeys.length > 0) {
|
|
1203
|
+
contributions["architecture.modules"] = contributedModuleKeys;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (bundleConstraints.security?.rules) {
|
|
1207
|
+
const localSecurity = localConfig.security ?? { rules: {} };
|
|
1208
|
+
const localRules = localSecurity.rules ?? {};
|
|
1209
|
+
const mergedRules = { ...localRules };
|
|
1210
|
+
const contributedRuleIds = [];
|
|
1211
|
+
for (const [ruleId, severity] of Object.entries(bundleConstraints.security.rules)) {
|
|
1212
|
+
if (!(ruleId in mergedRules)) {
|
|
1213
|
+
mergedRules[ruleId] = severity;
|
|
1214
|
+
contributedRuleIds.push(ruleId);
|
|
1215
|
+
} else if (mergedRules[ruleId] !== severity) {
|
|
1216
|
+
conflicts.push({
|
|
1217
|
+
section: "security.rules",
|
|
1218
|
+
key: ruleId,
|
|
1219
|
+
localValue: mergedRules[ruleId],
|
|
1220
|
+
packageValue: severity,
|
|
1221
|
+
description: `Security rule '${ruleId}' already exists locally with severity '${mergedRules[ruleId]}', bundle has '${severity}'`
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
config.security = { ...localSecurity, rules: mergedRules };
|
|
1226
|
+
if (contributedRuleIds.length > 0) {
|
|
1227
|
+
contributions["security.rules"] = contributedRuleIds;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return { config, contributions, conflicts };
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// src/constraints/sharing/lockfile.ts
|
|
1234
|
+
import * as fs2 from "fs/promises";
|
|
1235
|
+
async function readLockfile(lockfilePath) {
|
|
1236
|
+
let raw;
|
|
1237
|
+
try {
|
|
1238
|
+
raw = await fs2.readFile(lockfilePath, "utf-8");
|
|
1239
|
+
} catch (err) {
|
|
1240
|
+
if (isNodeError(err) && err.code === "ENOENT") {
|
|
1241
|
+
return { ok: true, value: null };
|
|
1242
|
+
}
|
|
1243
|
+
return {
|
|
1244
|
+
ok: false,
|
|
1245
|
+
error: `Failed to read lockfile: ${err instanceof Error ? err.message : String(err)}`
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
let parsed;
|
|
1249
|
+
try {
|
|
1250
|
+
parsed = JSON.parse(raw);
|
|
1251
|
+
} catch {
|
|
1252
|
+
return {
|
|
1253
|
+
ok: false,
|
|
1254
|
+
error: `Failed to parse lockfile as JSON: file contains invalid JSON`
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
const result = LockfileSchema.safeParse(parsed);
|
|
1258
|
+
if (!result.success) {
|
|
1259
|
+
return {
|
|
1260
|
+
ok: false,
|
|
1261
|
+
error: `Lockfile schema validation failed: ${result.error.issues.map((i) => i.message).join(", ")}`
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
return { ok: true, value: result.data };
|
|
1265
|
+
}
|
|
1266
|
+
async function writeLockfile(lockfilePath, lockfile) {
|
|
1267
|
+
await writeConfig(lockfilePath, lockfile);
|
|
1268
|
+
}
|
|
1269
|
+
function addProvenance(lockfile, packageName, entry) {
|
|
1270
|
+
return {
|
|
1271
|
+
...lockfile,
|
|
1272
|
+
packages: {
|
|
1273
|
+
...lockfile.packages,
|
|
1274
|
+
[packageName]: entry
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function removeProvenance(lockfile, packageName) {
|
|
1279
|
+
const existing = lockfile.packages[packageName];
|
|
1280
|
+
if (!existing) {
|
|
1281
|
+
return { lockfile, contributions: null };
|
|
1282
|
+
}
|
|
1283
|
+
const { [packageName]: _removed, ...remaining } = lockfile.packages;
|
|
1284
|
+
return {
|
|
1285
|
+
lockfile: {
|
|
1286
|
+
...lockfile,
|
|
1287
|
+
packages: remaining
|
|
1288
|
+
},
|
|
1289
|
+
contributions: existing.contributions ?? null
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function isNodeError(err) {
|
|
1293
|
+
return err instanceof Error && "code" in err;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1122
1296
|
// src/shared/parsers/typescript.ts
|
|
1123
1297
|
import { parse } from "@typescript-eslint/typescript-estree";
|
|
1124
1298
|
|
|
@@ -1144,11 +1318,11 @@ function walk(node, visitor) {
|
|
|
1144
1318
|
var TypeScriptParser = class {
|
|
1145
1319
|
name = "typescript";
|
|
1146
1320
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1147
|
-
async parseFile(
|
|
1148
|
-
const contentResult = await readFileContent(
|
|
1321
|
+
async parseFile(path13) {
|
|
1322
|
+
const contentResult = await readFileContent(path13);
|
|
1149
1323
|
if (!contentResult.ok) {
|
|
1150
1324
|
return Err(
|
|
1151
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1325
|
+
createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
|
|
1152
1326
|
"Check that the file exists",
|
|
1153
1327
|
"Verify the path is correct"
|
|
1154
1328
|
])
|
|
@@ -1158,7 +1332,7 @@ var TypeScriptParser = class {
|
|
|
1158
1332
|
const ast = parse(contentResult.value, {
|
|
1159
1333
|
loc: true,
|
|
1160
1334
|
range: true,
|
|
1161
|
-
jsx:
|
|
1335
|
+
jsx: path13.endsWith(".tsx"),
|
|
1162
1336
|
errorOnUnknownASTType: false
|
|
1163
1337
|
});
|
|
1164
1338
|
return Ok({
|
|
@@ -1169,7 +1343,7 @@ var TypeScriptParser = class {
|
|
|
1169
1343
|
} catch (e) {
|
|
1170
1344
|
const error = e;
|
|
1171
1345
|
return Err(
|
|
1172
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1346
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
|
|
1173
1347
|
"Check for syntax errors in the file",
|
|
1174
1348
|
"Ensure valid TypeScript syntax"
|
|
1175
1349
|
])
|
|
@@ -1335,11 +1509,11 @@ var TypeScriptParser = class {
|
|
|
1335
1509
|
};
|
|
1336
1510
|
|
|
1337
1511
|
// src/entropy/snapshot.ts
|
|
1338
|
-
import { join as join3, resolve
|
|
1512
|
+
import { join as join3, resolve, relative as relative4 } from "path";
|
|
1339
1513
|
import { minimatch as minimatch2 } from "minimatch";
|
|
1340
1514
|
async function resolveEntryPoints(rootDir, explicitEntries) {
|
|
1341
1515
|
if (explicitEntries && explicitEntries.length > 0) {
|
|
1342
|
-
const resolved = explicitEntries.map((e) =>
|
|
1516
|
+
const resolved = explicitEntries.map((e) => resolve(rootDir, e));
|
|
1343
1517
|
return Ok(resolved);
|
|
1344
1518
|
}
|
|
1345
1519
|
const pkgPath = join3(rootDir, "package.json");
|
|
@@ -1352,27 +1526,27 @@ async function resolveEntryPoints(rootDir, explicitEntries) {
|
|
|
1352
1526
|
if (pkg["exports"]) {
|
|
1353
1527
|
const exports = pkg["exports"];
|
|
1354
1528
|
if (typeof exports === "string") {
|
|
1355
|
-
entries.push(
|
|
1529
|
+
entries.push(resolve(rootDir, exports));
|
|
1356
1530
|
} else if (typeof exports === "object" && exports !== null) {
|
|
1357
1531
|
for (const value of Object.values(exports)) {
|
|
1358
1532
|
if (typeof value === "string") {
|
|
1359
|
-
entries.push(
|
|
1533
|
+
entries.push(resolve(rootDir, value));
|
|
1360
1534
|
}
|
|
1361
1535
|
}
|
|
1362
1536
|
}
|
|
1363
1537
|
}
|
|
1364
1538
|
const main = pkg["main"];
|
|
1365
1539
|
if (typeof main === "string" && entries.length === 0) {
|
|
1366
|
-
entries.push(
|
|
1540
|
+
entries.push(resolve(rootDir, main));
|
|
1367
1541
|
}
|
|
1368
1542
|
const bin = pkg["bin"];
|
|
1369
1543
|
if (bin) {
|
|
1370
1544
|
if (typeof bin === "string") {
|
|
1371
|
-
entries.push(
|
|
1545
|
+
entries.push(resolve(rootDir, bin));
|
|
1372
1546
|
} else if (typeof bin === "object") {
|
|
1373
1547
|
for (const value of Object.values(bin)) {
|
|
1374
1548
|
if (typeof value === "string") {
|
|
1375
|
-
entries.push(
|
|
1549
|
+
entries.push(resolve(rootDir, value));
|
|
1376
1550
|
}
|
|
1377
1551
|
}
|
|
1378
1552
|
}
|
|
@@ -1453,22 +1627,22 @@ function extractInlineRefs(content) {
|
|
|
1453
1627
|
}
|
|
1454
1628
|
return refs;
|
|
1455
1629
|
}
|
|
1456
|
-
async function parseDocumentationFile(
|
|
1457
|
-
const contentResult = await readFileContent(
|
|
1630
|
+
async function parseDocumentationFile(path13) {
|
|
1631
|
+
const contentResult = await readFileContent(path13);
|
|
1458
1632
|
if (!contentResult.ok) {
|
|
1459
1633
|
return Err(
|
|
1460
1634
|
createEntropyError(
|
|
1461
1635
|
"PARSE_ERROR",
|
|
1462
|
-
`Failed to read documentation file: ${
|
|
1463
|
-
{ file:
|
|
1636
|
+
`Failed to read documentation file: ${path13}`,
|
|
1637
|
+
{ file: path13 },
|
|
1464
1638
|
["Check that the file exists"]
|
|
1465
1639
|
)
|
|
1466
1640
|
);
|
|
1467
1641
|
}
|
|
1468
1642
|
const content = contentResult.value;
|
|
1469
|
-
const type =
|
|
1643
|
+
const type = path13.endsWith(".md") ? "markdown" : "text";
|
|
1470
1644
|
return Ok({
|
|
1471
|
-
path:
|
|
1645
|
+
path: path13,
|
|
1472
1646
|
type,
|
|
1473
1647
|
content,
|
|
1474
1648
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -1581,7 +1755,7 @@ function extractAllCodeReferences(docs) {
|
|
|
1581
1755
|
async function buildSnapshot(config) {
|
|
1582
1756
|
const startTime = Date.now();
|
|
1583
1757
|
const parser = config.parser || new TypeScriptParser();
|
|
1584
|
-
const rootDir =
|
|
1758
|
+
const rootDir = resolve(config.rootDir);
|
|
1585
1759
|
const entryPointsResult = await resolveEntryPoints(rootDir, config.entryPoints);
|
|
1586
1760
|
if (!entryPointsResult.ok) {
|
|
1587
1761
|
return Err(entryPointsResult.error);
|
|
@@ -1599,7 +1773,7 @@ async function buildSnapshot(config) {
|
|
|
1599
1773
|
sourceFilePaths.push(...files2);
|
|
1600
1774
|
}
|
|
1601
1775
|
sourceFilePaths = sourceFilePaths.filter((f) => {
|
|
1602
|
-
const rel =
|
|
1776
|
+
const rel = relative4(rootDir, f);
|
|
1603
1777
|
return !excludePatterns.some((p) => minimatch2(rel, p));
|
|
1604
1778
|
});
|
|
1605
1779
|
const files = [];
|
|
@@ -1652,7 +1826,7 @@ async function buildSnapshot(config) {
|
|
|
1652
1826
|
}
|
|
1653
1827
|
|
|
1654
1828
|
// src/entropy/detectors/drift.ts
|
|
1655
|
-
import { dirname as
|
|
1829
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
1656
1830
|
function levenshteinDistance(a, b) {
|
|
1657
1831
|
const matrix = [];
|
|
1658
1832
|
for (let i = 0; i <= b.length; i++) {
|
|
@@ -1757,7 +1931,7 @@ async function checkStructureDrift(snapshot, _config) {
|
|
|
1757
1931
|
for (const doc of snapshot.docs) {
|
|
1758
1932
|
const fileLinks = extractFileLinks(doc.content);
|
|
1759
1933
|
for (const { link, line } of fileLinks) {
|
|
1760
|
-
const resolvedPath =
|
|
1934
|
+
const resolvedPath = resolve2(dirname3(doc.path), link);
|
|
1761
1935
|
const exists = await fileExists(resolvedPath);
|
|
1762
1936
|
if (!exists) {
|
|
1763
1937
|
drifts.push({
|
|
@@ -1840,19 +2014,19 @@ async function detectDocDrift(snapshot, config, graphDriftData) {
|
|
|
1840
2014
|
}
|
|
1841
2015
|
|
|
1842
2016
|
// src/entropy/detectors/dead-code.ts
|
|
1843
|
-
import { dirname as
|
|
2017
|
+
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
1844
2018
|
function resolveImportToFile(importSource, fromFile, snapshot) {
|
|
1845
2019
|
if (!importSource.startsWith(".")) {
|
|
1846
2020
|
return null;
|
|
1847
2021
|
}
|
|
1848
|
-
const fromDir =
|
|
1849
|
-
let resolved =
|
|
2022
|
+
const fromDir = dirname4(fromFile);
|
|
2023
|
+
let resolved = resolve3(fromDir, importSource);
|
|
1850
2024
|
if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
|
|
1851
2025
|
const withTs = resolved + ".ts";
|
|
1852
2026
|
if (snapshot.files.some((f) => f.path === withTs)) {
|
|
1853
2027
|
return withTs;
|
|
1854
2028
|
}
|
|
1855
|
-
const withIndex =
|
|
2029
|
+
const withIndex = resolve3(resolved, "index.ts");
|
|
1856
2030
|
if (snapshot.files.some((f) => f.path === withIndex)) {
|
|
1857
2031
|
return withIndex;
|
|
1858
2032
|
}
|
|
@@ -2131,14 +2305,14 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
|
|
|
2131
2305
|
|
|
2132
2306
|
// src/entropy/detectors/patterns.ts
|
|
2133
2307
|
import { minimatch as minimatch3 } from "minimatch";
|
|
2134
|
-
import { relative as
|
|
2308
|
+
import { relative as relative5 } from "path";
|
|
2135
2309
|
function fileMatchesPattern(filePath, pattern, rootDir) {
|
|
2136
|
-
const relativePath =
|
|
2310
|
+
const relativePath = relative5(rootDir, filePath);
|
|
2137
2311
|
return minimatch3(relativePath, pattern);
|
|
2138
2312
|
}
|
|
2139
2313
|
function checkConfigPattern(pattern, file, rootDir) {
|
|
2140
2314
|
const matches = [];
|
|
2141
|
-
const fileMatches = pattern.files.some((
|
|
2315
|
+
const fileMatches = pattern.files.some((glob) => fileMatchesPattern(file.path, glob, rootDir));
|
|
2142
2316
|
if (!fileMatches) {
|
|
2143
2317
|
return matches;
|
|
2144
2318
|
}
|
|
@@ -2296,418 +2470,6 @@ async function detectPatternViolations(snapshot, config) {
|
|
|
2296
2470
|
});
|
|
2297
2471
|
}
|
|
2298
2472
|
|
|
2299
|
-
// src/entropy/detectors/complexity.ts
|
|
2300
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
2301
|
-
var DEFAULT_THRESHOLDS = {
|
|
2302
|
-
cyclomaticComplexity: { error: 15, warn: 10 },
|
|
2303
|
-
nestingDepth: { warn: 4 },
|
|
2304
|
-
functionLength: { warn: 50 },
|
|
2305
|
-
parameterCount: { warn: 5 },
|
|
2306
|
-
fileLength: { info: 300 },
|
|
2307
|
-
hotspotPercentile: { error: 95 }
|
|
2308
|
-
};
|
|
2309
|
-
function extractFunctions(content) {
|
|
2310
|
-
const functions = [];
|
|
2311
|
-
const lines = content.split("\n");
|
|
2312
|
-
const patterns = [
|
|
2313
|
-
// function declarations: function name(params) {
|
|
2314
|
-
/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/,
|
|
2315
|
-
// method declarations: name(params) {
|
|
2316
|
-
/^\s*(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*[^{]+)?\s*\{/,
|
|
2317
|
-
// arrow functions assigned to const/let/var: const name = (params) =>
|
|
2318
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*[^=]+)?\s*=>/,
|
|
2319
|
-
// arrow functions assigned to const/let/var with single param: const name = param =>
|
|
2320
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(\w+)\s*=>/
|
|
2321
|
-
];
|
|
2322
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2323
|
-
const line = lines[i];
|
|
2324
|
-
for (const pattern of patterns) {
|
|
2325
|
-
const match = line.match(pattern);
|
|
2326
|
-
if (match) {
|
|
2327
|
-
const name = match[1] ?? "anonymous";
|
|
2328
|
-
const paramsStr = match[2] || "";
|
|
2329
|
-
const params = paramsStr.trim() === "" ? 0 : paramsStr.split(",").length;
|
|
2330
|
-
const endLine = findFunctionEnd(lines, i);
|
|
2331
|
-
const body = lines.slice(i, endLine + 1).join("\n");
|
|
2332
|
-
functions.push({
|
|
2333
|
-
name,
|
|
2334
|
-
line: i + 1,
|
|
2335
|
-
params,
|
|
2336
|
-
startLine: i + 1,
|
|
2337
|
-
endLine: endLine + 1,
|
|
2338
|
-
body
|
|
2339
|
-
});
|
|
2340
|
-
break;
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
return functions;
|
|
2345
|
-
}
|
|
2346
|
-
function findFunctionEnd(lines, startIdx) {
|
|
2347
|
-
let depth = 0;
|
|
2348
|
-
let foundOpen = false;
|
|
2349
|
-
for (let i = startIdx; i < lines.length; i++) {
|
|
2350
|
-
const line = lines[i];
|
|
2351
|
-
for (const ch of line) {
|
|
2352
|
-
if (ch === "{") {
|
|
2353
|
-
depth++;
|
|
2354
|
-
foundOpen = true;
|
|
2355
|
-
} else if (ch === "}") {
|
|
2356
|
-
depth--;
|
|
2357
|
-
if (foundOpen && depth === 0) {
|
|
2358
|
-
return i;
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
return lines.length - 1;
|
|
2364
|
-
}
|
|
2365
|
-
function computeCyclomaticComplexity(body) {
|
|
2366
|
-
let complexity = 1;
|
|
2367
|
-
const decisionPatterns = [
|
|
2368
|
-
/\bif\s*\(/g,
|
|
2369
|
-
/\belse\s+if\s*\(/g,
|
|
2370
|
-
/\bwhile\s*\(/g,
|
|
2371
|
-
/\bfor\s*\(/g,
|
|
2372
|
-
/\bcase\s+/g,
|
|
2373
|
-
/&&/g,
|
|
2374
|
-
/\|\|/g,
|
|
2375
|
-
/\?(?!=)/g,
|
|
2376
|
-
// Ternary ? but not ?. or ??
|
|
2377
|
-
/\bcatch\s*\(/g
|
|
2378
|
-
];
|
|
2379
|
-
for (const pattern of decisionPatterns) {
|
|
2380
|
-
const matches = body.match(pattern);
|
|
2381
|
-
if (matches) {
|
|
2382
|
-
complexity += matches.length;
|
|
2383
|
-
}
|
|
2384
|
-
}
|
|
2385
|
-
const elseIfMatches = body.match(/\belse\s+if\s*\(/g);
|
|
2386
|
-
if (elseIfMatches) {
|
|
2387
|
-
complexity -= elseIfMatches.length;
|
|
2388
|
-
}
|
|
2389
|
-
return complexity;
|
|
2390
|
-
}
|
|
2391
|
-
function computeNestingDepth(body) {
|
|
2392
|
-
let maxDepth = 0;
|
|
2393
|
-
let currentDepth = 0;
|
|
2394
|
-
let functionBodyStarted = false;
|
|
2395
|
-
for (const ch of body) {
|
|
2396
|
-
if (ch === "{") {
|
|
2397
|
-
if (!functionBodyStarted) {
|
|
2398
|
-
functionBodyStarted = true;
|
|
2399
|
-
continue;
|
|
2400
|
-
}
|
|
2401
|
-
currentDepth++;
|
|
2402
|
-
if (currentDepth > maxDepth) {
|
|
2403
|
-
maxDepth = currentDepth;
|
|
2404
|
-
}
|
|
2405
|
-
} else if (ch === "}") {
|
|
2406
|
-
if (currentDepth > 0) {
|
|
2407
|
-
currentDepth--;
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
return maxDepth;
|
|
2412
|
-
}
|
|
2413
|
-
async function detectComplexityViolations(snapshot, config, graphData) {
|
|
2414
|
-
const violations = [];
|
|
2415
|
-
const thresholds = {
|
|
2416
|
-
cyclomaticComplexity: {
|
|
2417
|
-
error: config?.thresholds?.cyclomaticComplexity?.error ?? DEFAULT_THRESHOLDS.cyclomaticComplexity.error,
|
|
2418
|
-
warn: config?.thresholds?.cyclomaticComplexity?.warn ?? DEFAULT_THRESHOLDS.cyclomaticComplexity.warn
|
|
2419
|
-
},
|
|
2420
|
-
nestingDepth: {
|
|
2421
|
-
warn: config?.thresholds?.nestingDepth?.warn ?? DEFAULT_THRESHOLDS.nestingDepth.warn
|
|
2422
|
-
},
|
|
2423
|
-
functionLength: {
|
|
2424
|
-
warn: config?.thresholds?.functionLength?.warn ?? DEFAULT_THRESHOLDS.functionLength.warn
|
|
2425
|
-
},
|
|
2426
|
-
parameterCount: {
|
|
2427
|
-
warn: config?.thresholds?.parameterCount?.warn ?? DEFAULT_THRESHOLDS.parameterCount.warn
|
|
2428
|
-
},
|
|
2429
|
-
fileLength: {
|
|
2430
|
-
info: config?.thresholds?.fileLength?.info ?? DEFAULT_THRESHOLDS.fileLength.info
|
|
2431
|
-
}
|
|
2432
|
-
};
|
|
2433
|
-
let totalFunctions = 0;
|
|
2434
|
-
for (const file of snapshot.files) {
|
|
2435
|
-
let content;
|
|
2436
|
-
try {
|
|
2437
|
-
content = await readFile2(file.path, "utf-8");
|
|
2438
|
-
} catch {
|
|
2439
|
-
continue;
|
|
2440
|
-
}
|
|
2441
|
-
const lines = content.split("\n");
|
|
2442
|
-
if (lines.length > thresholds.fileLength.info) {
|
|
2443
|
-
violations.push({
|
|
2444
|
-
file: file.path,
|
|
2445
|
-
function: "<file>",
|
|
2446
|
-
line: 1,
|
|
2447
|
-
metric: "fileLength",
|
|
2448
|
-
value: lines.length,
|
|
2449
|
-
threshold: thresholds.fileLength.info,
|
|
2450
|
-
tier: 3,
|
|
2451
|
-
severity: "info",
|
|
2452
|
-
message: `File has ${lines.length} lines (threshold: ${thresholds.fileLength.info})`
|
|
2453
|
-
});
|
|
2454
|
-
}
|
|
2455
|
-
const functions = extractFunctions(content);
|
|
2456
|
-
totalFunctions += functions.length;
|
|
2457
|
-
for (const fn of functions) {
|
|
2458
|
-
const complexity = computeCyclomaticComplexity(fn.body);
|
|
2459
|
-
if (complexity > thresholds.cyclomaticComplexity.error) {
|
|
2460
|
-
violations.push({
|
|
2461
|
-
file: file.path,
|
|
2462
|
-
function: fn.name,
|
|
2463
|
-
line: fn.line,
|
|
2464
|
-
metric: "cyclomaticComplexity",
|
|
2465
|
-
value: complexity,
|
|
2466
|
-
threshold: thresholds.cyclomaticComplexity.error,
|
|
2467
|
-
tier: 1,
|
|
2468
|
-
severity: "error",
|
|
2469
|
-
message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (error threshold: ${thresholds.cyclomaticComplexity.error})`
|
|
2470
|
-
});
|
|
2471
|
-
} else if (complexity > thresholds.cyclomaticComplexity.warn) {
|
|
2472
|
-
violations.push({
|
|
2473
|
-
file: file.path,
|
|
2474
|
-
function: fn.name,
|
|
2475
|
-
line: fn.line,
|
|
2476
|
-
metric: "cyclomaticComplexity",
|
|
2477
|
-
value: complexity,
|
|
2478
|
-
threshold: thresholds.cyclomaticComplexity.warn,
|
|
2479
|
-
tier: 2,
|
|
2480
|
-
severity: "warning",
|
|
2481
|
-
message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (warning threshold: ${thresholds.cyclomaticComplexity.warn})`
|
|
2482
|
-
});
|
|
2483
|
-
}
|
|
2484
|
-
const nestingDepth = computeNestingDepth(fn.body);
|
|
2485
|
-
if (nestingDepth > thresholds.nestingDepth.warn) {
|
|
2486
|
-
violations.push({
|
|
2487
|
-
file: file.path,
|
|
2488
|
-
function: fn.name,
|
|
2489
|
-
line: fn.line,
|
|
2490
|
-
metric: "nestingDepth",
|
|
2491
|
-
value: nestingDepth,
|
|
2492
|
-
threshold: thresholds.nestingDepth.warn,
|
|
2493
|
-
tier: 2,
|
|
2494
|
-
severity: "warning",
|
|
2495
|
-
message: `Function "${fn.name}" has nesting depth of ${nestingDepth} (threshold: ${thresholds.nestingDepth.warn})`
|
|
2496
|
-
});
|
|
2497
|
-
}
|
|
2498
|
-
const fnLength = fn.endLine - fn.startLine + 1;
|
|
2499
|
-
if (fnLength > thresholds.functionLength.warn) {
|
|
2500
|
-
violations.push({
|
|
2501
|
-
file: file.path,
|
|
2502
|
-
function: fn.name,
|
|
2503
|
-
line: fn.line,
|
|
2504
|
-
metric: "functionLength",
|
|
2505
|
-
value: fnLength,
|
|
2506
|
-
threshold: thresholds.functionLength.warn,
|
|
2507
|
-
tier: 2,
|
|
2508
|
-
severity: "warning",
|
|
2509
|
-
message: `Function "${fn.name}" is ${fnLength} lines long (threshold: ${thresholds.functionLength.warn})`
|
|
2510
|
-
});
|
|
2511
|
-
}
|
|
2512
|
-
if (fn.params > thresholds.parameterCount.warn) {
|
|
2513
|
-
violations.push({
|
|
2514
|
-
file: file.path,
|
|
2515
|
-
function: fn.name,
|
|
2516
|
-
line: fn.line,
|
|
2517
|
-
metric: "parameterCount",
|
|
2518
|
-
value: fn.params,
|
|
2519
|
-
threshold: thresholds.parameterCount.warn,
|
|
2520
|
-
tier: 2,
|
|
2521
|
-
severity: "warning",
|
|
2522
|
-
message: `Function "${fn.name}" has ${fn.params} parameters (threshold: ${thresholds.parameterCount.warn})`
|
|
2523
|
-
});
|
|
2524
|
-
}
|
|
2525
|
-
if (graphData) {
|
|
2526
|
-
const hotspot = graphData.hotspots.find(
|
|
2527
|
-
(h) => h.file === file.path && h.function === fn.name
|
|
2528
|
-
);
|
|
2529
|
-
if (hotspot && hotspot.hotspotScore > graphData.percentile95Score) {
|
|
2530
|
-
violations.push({
|
|
2531
|
-
file: file.path,
|
|
2532
|
-
function: fn.name,
|
|
2533
|
-
line: fn.line,
|
|
2534
|
-
metric: "hotspotScore",
|
|
2535
|
-
value: hotspot.hotspotScore,
|
|
2536
|
-
threshold: graphData.percentile95Score,
|
|
2537
|
-
tier: 1,
|
|
2538
|
-
severity: "error",
|
|
2539
|
-
message: `Function "${fn.name}" is a complexity hotspot (score: ${hotspot.hotspotScore}, p95: ${graphData.percentile95Score})`
|
|
2540
|
-
});
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
2546
|
-
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
2547
|
-
const infoCount = violations.filter((v) => v.severity === "info").length;
|
|
2548
|
-
return Ok({
|
|
2549
|
-
violations,
|
|
2550
|
-
stats: {
|
|
2551
|
-
filesAnalyzed: snapshot.files.length,
|
|
2552
|
-
functionsAnalyzed: totalFunctions,
|
|
2553
|
-
violationCount: violations.length,
|
|
2554
|
-
errorCount,
|
|
2555
|
-
warningCount,
|
|
2556
|
-
infoCount
|
|
2557
|
-
}
|
|
2558
|
-
});
|
|
2559
|
-
}
|
|
2560
|
-
|
|
2561
|
-
// src/entropy/detectors/coupling.ts
|
|
2562
|
-
var DEFAULT_THRESHOLDS2 = {
|
|
2563
|
-
fanOut: { warn: 15 },
|
|
2564
|
-
fanIn: { info: 20 },
|
|
2565
|
-
couplingRatio: { warn: 0.7 },
|
|
2566
|
-
transitiveDependencyDepth: { info: 30 }
|
|
2567
|
-
};
|
|
2568
|
-
function computeMetricsFromSnapshot(snapshot) {
|
|
2569
|
-
const fanInMap = /* @__PURE__ */ new Map();
|
|
2570
|
-
for (const file of snapshot.files) {
|
|
2571
|
-
for (const imp of file.imports) {
|
|
2572
|
-
const resolved = resolveImportSource(imp.source, file.path, snapshot);
|
|
2573
|
-
if (resolved) {
|
|
2574
|
-
fanInMap.set(resolved, (fanInMap.get(resolved) || 0) + 1);
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
return snapshot.files.map((file) => {
|
|
2579
|
-
const fanOut = file.imports.length;
|
|
2580
|
-
const fanIn = fanInMap.get(file.path) || 0;
|
|
2581
|
-
const total = fanIn + fanOut;
|
|
2582
|
-
const couplingRatio = total > 0 ? fanOut / total : 0;
|
|
2583
|
-
return {
|
|
2584
|
-
file: file.path,
|
|
2585
|
-
fanIn,
|
|
2586
|
-
fanOut,
|
|
2587
|
-
couplingRatio,
|
|
2588
|
-
transitiveDepth: 0
|
|
2589
|
-
};
|
|
2590
|
-
});
|
|
2591
|
-
}
|
|
2592
|
-
function resolveRelativePath(from, source) {
|
|
2593
|
-
const dir = from.includes("/") ? from.substring(0, from.lastIndexOf("/")) : ".";
|
|
2594
|
-
const parts = dir.split("/");
|
|
2595
|
-
for (const segment of source.split("/")) {
|
|
2596
|
-
if (segment === ".") continue;
|
|
2597
|
-
if (segment === "..") {
|
|
2598
|
-
parts.pop();
|
|
2599
|
-
} else {
|
|
2600
|
-
parts.push(segment);
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
return parts.join("/");
|
|
2604
|
-
}
|
|
2605
|
-
function resolveImportSource(source, fromFile, snapshot) {
|
|
2606
|
-
if (!source.startsWith(".") && !source.startsWith("/")) {
|
|
2607
|
-
return void 0;
|
|
2608
|
-
}
|
|
2609
|
-
const resolved = resolveRelativePath(fromFile, source);
|
|
2610
|
-
const filePaths = snapshot.files.map((f) => f.path);
|
|
2611
|
-
const candidates = [
|
|
2612
|
-
resolved,
|
|
2613
|
-
`${resolved}.ts`,
|
|
2614
|
-
`${resolved}.tsx`,
|
|
2615
|
-
`${resolved}/index.ts`,
|
|
2616
|
-
`${resolved}/index.tsx`
|
|
2617
|
-
];
|
|
2618
|
-
for (const candidate of candidates) {
|
|
2619
|
-
const match = filePaths.find((fp) => fp === candidate);
|
|
2620
|
-
if (match) return match;
|
|
2621
|
-
}
|
|
2622
|
-
return void 0;
|
|
2623
|
-
}
|
|
2624
|
-
function checkViolations(metrics, config) {
|
|
2625
|
-
const thresholds = {
|
|
2626
|
-
fanOut: { ...DEFAULT_THRESHOLDS2.fanOut, ...config?.thresholds?.fanOut },
|
|
2627
|
-
fanIn: { ...DEFAULT_THRESHOLDS2.fanIn, ...config?.thresholds?.fanIn },
|
|
2628
|
-
couplingRatio: { ...DEFAULT_THRESHOLDS2.couplingRatio, ...config?.thresholds?.couplingRatio },
|
|
2629
|
-
transitiveDependencyDepth: {
|
|
2630
|
-
...DEFAULT_THRESHOLDS2.transitiveDependencyDepth,
|
|
2631
|
-
...config?.thresholds?.transitiveDependencyDepth
|
|
2632
|
-
}
|
|
2633
|
-
};
|
|
2634
|
-
const violations = [];
|
|
2635
|
-
for (const m of metrics) {
|
|
2636
|
-
if (thresholds.fanOut.warn !== void 0 && m.fanOut > thresholds.fanOut.warn) {
|
|
2637
|
-
violations.push({
|
|
2638
|
-
file: m.file,
|
|
2639
|
-
metric: "fanOut",
|
|
2640
|
-
value: m.fanOut,
|
|
2641
|
-
threshold: thresholds.fanOut.warn,
|
|
2642
|
-
tier: 2,
|
|
2643
|
-
severity: "warning",
|
|
2644
|
-
message: `File has ${m.fanOut} imports (threshold: ${thresholds.fanOut.warn})`
|
|
2645
|
-
});
|
|
2646
|
-
}
|
|
2647
|
-
if (thresholds.fanIn.info !== void 0 && m.fanIn > thresholds.fanIn.info) {
|
|
2648
|
-
violations.push({
|
|
2649
|
-
file: m.file,
|
|
2650
|
-
metric: "fanIn",
|
|
2651
|
-
value: m.fanIn,
|
|
2652
|
-
threshold: thresholds.fanIn.info,
|
|
2653
|
-
tier: 3,
|
|
2654
|
-
severity: "info",
|
|
2655
|
-
message: `File is imported by ${m.fanIn} files (threshold: ${thresholds.fanIn.info})`
|
|
2656
|
-
});
|
|
2657
|
-
}
|
|
2658
|
-
const totalConnections = m.fanIn + m.fanOut;
|
|
2659
|
-
if (totalConnections > 5 && thresholds.couplingRatio.warn !== void 0 && m.couplingRatio > thresholds.couplingRatio.warn) {
|
|
2660
|
-
violations.push({
|
|
2661
|
-
file: m.file,
|
|
2662
|
-
metric: "couplingRatio",
|
|
2663
|
-
value: m.couplingRatio,
|
|
2664
|
-
threshold: thresholds.couplingRatio.warn,
|
|
2665
|
-
tier: 2,
|
|
2666
|
-
severity: "warning",
|
|
2667
|
-
message: `Coupling ratio is ${m.couplingRatio.toFixed(2)} (threshold: ${thresholds.couplingRatio.warn})`
|
|
2668
|
-
});
|
|
2669
|
-
}
|
|
2670
|
-
if (thresholds.transitiveDependencyDepth.info !== void 0 && m.transitiveDepth > thresholds.transitiveDependencyDepth.info) {
|
|
2671
|
-
violations.push({
|
|
2672
|
-
file: m.file,
|
|
2673
|
-
metric: "transitiveDependencyDepth",
|
|
2674
|
-
value: m.transitiveDepth,
|
|
2675
|
-
threshold: thresholds.transitiveDependencyDepth.info,
|
|
2676
|
-
tier: 3,
|
|
2677
|
-
severity: "info",
|
|
2678
|
-
message: `Transitive dependency depth is ${m.transitiveDepth} (threshold: ${thresholds.transitiveDependencyDepth.info})`
|
|
2679
|
-
});
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
|
-
return violations;
|
|
2683
|
-
}
|
|
2684
|
-
async function detectCouplingViolations(snapshot, config, graphData) {
|
|
2685
|
-
let metrics;
|
|
2686
|
-
if (graphData) {
|
|
2687
|
-
metrics = graphData.files.map((f) => ({
|
|
2688
|
-
file: f.file,
|
|
2689
|
-
fanIn: f.fanIn,
|
|
2690
|
-
fanOut: f.fanOut,
|
|
2691
|
-
couplingRatio: f.couplingRatio,
|
|
2692
|
-
transitiveDepth: f.transitiveDepth
|
|
2693
|
-
}));
|
|
2694
|
-
} else {
|
|
2695
|
-
metrics = computeMetricsFromSnapshot(snapshot);
|
|
2696
|
-
}
|
|
2697
|
-
const violations = checkViolations(metrics, config);
|
|
2698
|
-
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
2699
|
-
const infoCount = violations.filter((v) => v.severity === "info").length;
|
|
2700
|
-
return Ok({
|
|
2701
|
-
violations,
|
|
2702
|
-
stats: {
|
|
2703
|
-
filesAnalyzed: metrics.length,
|
|
2704
|
-
violationCount: violations.length,
|
|
2705
|
-
warningCount,
|
|
2706
|
-
infoCount
|
|
2707
|
-
}
|
|
2708
|
-
});
|
|
2709
|
-
}
|
|
2710
|
-
|
|
2711
2473
|
// src/entropy/detectors/size-budget.ts
|
|
2712
2474
|
import { readdirSync, statSync } from "fs";
|
|
2713
2475
|
import { join as join4 } from "path";
|
|
@@ -3136,14 +2898,14 @@ var EntropyAnalyzer = class {
|
|
|
3136
2898
|
};
|
|
3137
2899
|
|
|
3138
2900
|
// src/entropy/fixers/safe-fixes.ts
|
|
3139
|
-
import * as
|
|
3140
|
-
import { promisify
|
|
3141
|
-
import { dirname as
|
|
3142
|
-
var
|
|
3143
|
-
var
|
|
3144
|
-
var unlink2 =
|
|
3145
|
-
var mkdir2 =
|
|
3146
|
-
var copyFile2 =
|
|
2901
|
+
import * as fs3 from "fs";
|
|
2902
|
+
import { promisify } from "util";
|
|
2903
|
+
import { dirname as dirname5, basename as basename4, join as join5 } from "path";
|
|
2904
|
+
var readFile3 = promisify(fs3.readFile);
|
|
2905
|
+
var writeFile3 = promisify(fs3.writeFile);
|
|
2906
|
+
var unlink2 = promisify(fs3.unlink);
|
|
2907
|
+
var mkdir2 = promisify(fs3.mkdir);
|
|
2908
|
+
var copyFile2 = promisify(fs3.copyFile);
|
|
3147
2909
|
var DEFAULT_FIX_CONFIG = {
|
|
3148
2910
|
dryRun: false,
|
|
3149
2911
|
fixTypes: ["unused-imports", "dead-files"],
|
|
@@ -3239,7 +3001,7 @@ function previewFix(fix) {
|
|
|
3239
3001
|
async function createBackup(filePath, backupDir) {
|
|
3240
3002
|
const backupPath = join5(backupDir, `${Date.now()}-${basename4(filePath)}`);
|
|
3241
3003
|
try {
|
|
3242
|
-
await mkdir2(
|
|
3004
|
+
await mkdir2(dirname5(backupPath), { recursive: true });
|
|
3243
3005
|
await copyFile2(filePath, backupPath);
|
|
3244
3006
|
return Ok(backupPath);
|
|
3245
3007
|
} catch (e) {
|
|
@@ -3270,25 +3032,25 @@ async function applySingleFix(fix, config) {
|
|
|
3270
3032
|
break;
|
|
3271
3033
|
case "delete-lines":
|
|
3272
3034
|
if (fix.line !== void 0) {
|
|
3273
|
-
const content = await
|
|
3035
|
+
const content = await readFile3(fix.file, "utf-8");
|
|
3274
3036
|
const lines = content.split("\n");
|
|
3275
3037
|
lines.splice(fix.line - 1, 1);
|
|
3276
|
-
await
|
|
3038
|
+
await writeFile3(fix.file, lines.join("\n"));
|
|
3277
3039
|
}
|
|
3278
3040
|
break;
|
|
3279
3041
|
case "replace":
|
|
3280
3042
|
if (fix.oldContent && fix.newContent !== void 0) {
|
|
3281
|
-
const content = await
|
|
3043
|
+
const content = await readFile3(fix.file, "utf-8");
|
|
3282
3044
|
const newContent = content.replace(fix.oldContent, fix.newContent);
|
|
3283
|
-
await
|
|
3045
|
+
await writeFile3(fix.file, newContent);
|
|
3284
3046
|
}
|
|
3285
3047
|
break;
|
|
3286
3048
|
case "insert":
|
|
3287
3049
|
if (fix.line !== void 0 && fix.newContent) {
|
|
3288
|
-
const content = await
|
|
3050
|
+
const content = await readFile3(fix.file, "utf-8");
|
|
3289
3051
|
const lines = content.split("\n");
|
|
3290
3052
|
lines.splice(fix.line - 1, 0, fix.newContent);
|
|
3291
|
-
await
|
|
3053
|
+
await writeFile3(fix.file, lines.join("\n"));
|
|
3292
3054
|
}
|
|
3293
3055
|
break;
|
|
3294
3056
|
}
|
|
@@ -3465,46 +3227,46 @@ function deduplicateCleanupFindings(findings) {
|
|
|
3465
3227
|
}
|
|
3466
3228
|
|
|
3467
3229
|
// src/entropy/config/schema.ts
|
|
3468
|
-
import { z } from "zod";
|
|
3469
|
-
var MustExportRuleSchema =
|
|
3470
|
-
type:
|
|
3471
|
-
names:
|
|
3230
|
+
import { z as z2 } from "zod";
|
|
3231
|
+
var MustExportRuleSchema = z2.object({
|
|
3232
|
+
type: z2.literal("must-export"),
|
|
3233
|
+
names: z2.array(z2.string())
|
|
3472
3234
|
});
|
|
3473
|
-
var MustExportDefaultRuleSchema =
|
|
3474
|
-
type:
|
|
3475
|
-
kind:
|
|
3235
|
+
var MustExportDefaultRuleSchema = z2.object({
|
|
3236
|
+
type: z2.literal("must-export-default"),
|
|
3237
|
+
kind: z2.enum(["class", "function", "object"]).optional()
|
|
3476
3238
|
});
|
|
3477
|
-
var NoExportRuleSchema =
|
|
3478
|
-
type:
|
|
3479
|
-
names:
|
|
3239
|
+
var NoExportRuleSchema = z2.object({
|
|
3240
|
+
type: z2.literal("no-export"),
|
|
3241
|
+
names: z2.array(z2.string())
|
|
3480
3242
|
});
|
|
3481
|
-
var MustImportRuleSchema =
|
|
3482
|
-
type:
|
|
3483
|
-
from:
|
|
3484
|
-
names:
|
|
3243
|
+
var MustImportRuleSchema = z2.object({
|
|
3244
|
+
type: z2.literal("must-import"),
|
|
3245
|
+
from: z2.string(),
|
|
3246
|
+
names: z2.array(z2.string()).optional()
|
|
3485
3247
|
});
|
|
3486
|
-
var NoImportRuleSchema =
|
|
3487
|
-
type:
|
|
3488
|
-
from:
|
|
3248
|
+
var NoImportRuleSchema = z2.object({
|
|
3249
|
+
type: z2.literal("no-import"),
|
|
3250
|
+
from: z2.string()
|
|
3489
3251
|
});
|
|
3490
|
-
var NamingRuleSchema =
|
|
3491
|
-
type:
|
|
3492
|
-
match:
|
|
3493
|
-
convention:
|
|
3252
|
+
var NamingRuleSchema = z2.object({
|
|
3253
|
+
type: z2.literal("naming"),
|
|
3254
|
+
match: z2.string(),
|
|
3255
|
+
convention: z2.enum(["camelCase", "PascalCase", "UPPER_SNAKE", "kebab-case"])
|
|
3494
3256
|
});
|
|
3495
|
-
var MaxExportsRuleSchema =
|
|
3496
|
-
type:
|
|
3497
|
-
count:
|
|
3257
|
+
var MaxExportsRuleSchema = z2.object({
|
|
3258
|
+
type: z2.literal("max-exports"),
|
|
3259
|
+
count: z2.number().positive()
|
|
3498
3260
|
});
|
|
3499
|
-
var MaxLinesRuleSchema =
|
|
3500
|
-
type:
|
|
3501
|
-
count:
|
|
3261
|
+
var MaxLinesRuleSchema = z2.object({
|
|
3262
|
+
type: z2.literal("max-lines"),
|
|
3263
|
+
count: z2.number().positive()
|
|
3502
3264
|
});
|
|
3503
|
-
var RequireJSDocRuleSchema =
|
|
3504
|
-
type:
|
|
3505
|
-
for:
|
|
3265
|
+
var RequireJSDocRuleSchema = z2.object({
|
|
3266
|
+
type: z2.literal("require-jsdoc"),
|
|
3267
|
+
for: z2.array(z2.enum(["function", "class", "export"]))
|
|
3506
3268
|
});
|
|
3507
|
-
var RuleSchema =
|
|
3269
|
+
var RuleSchema = z2.discriminatedUnion("type", [
|
|
3508
3270
|
MustExportRuleSchema,
|
|
3509
3271
|
MustExportDefaultRuleSchema,
|
|
3510
3272
|
NoExportRuleSchema,
|
|
@@ -3515,47 +3277,47 @@ var RuleSchema = z.discriminatedUnion("type", [
|
|
|
3515
3277
|
MaxLinesRuleSchema,
|
|
3516
3278
|
RequireJSDocRuleSchema
|
|
3517
3279
|
]);
|
|
3518
|
-
var ConfigPatternSchema =
|
|
3519
|
-
name:
|
|
3520
|
-
description:
|
|
3521
|
-
severity:
|
|
3522
|
-
files:
|
|
3280
|
+
var ConfigPatternSchema = z2.object({
|
|
3281
|
+
name: z2.string().min(1),
|
|
3282
|
+
description: z2.string(),
|
|
3283
|
+
severity: z2.enum(["error", "warning"]),
|
|
3284
|
+
files: z2.array(z2.string()),
|
|
3523
3285
|
rule: RuleSchema,
|
|
3524
|
-
message:
|
|
3286
|
+
message: z2.string().optional()
|
|
3525
3287
|
});
|
|
3526
|
-
var PatternConfigSchema =
|
|
3527
|
-
patterns:
|
|
3528
|
-
customPatterns:
|
|
3288
|
+
var PatternConfigSchema = z2.object({
|
|
3289
|
+
patterns: z2.array(ConfigPatternSchema),
|
|
3290
|
+
customPatterns: z2.array(z2.any()).optional(),
|
|
3529
3291
|
// Code patterns are functions, can't validate
|
|
3530
|
-
ignoreFiles:
|
|
3292
|
+
ignoreFiles: z2.array(z2.string()).optional()
|
|
3531
3293
|
});
|
|
3532
|
-
var DriftConfigSchema =
|
|
3533
|
-
docPaths:
|
|
3534
|
-
checkApiSignatures:
|
|
3535
|
-
checkExamples:
|
|
3536
|
-
checkStructure:
|
|
3537
|
-
ignorePatterns:
|
|
3294
|
+
var DriftConfigSchema = z2.object({
|
|
3295
|
+
docPaths: z2.array(z2.string()).optional(),
|
|
3296
|
+
checkApiSignatures: z2.boolean().optional(),
|
|
3297
|
+
checkExamples: z2.boolean().optional(),
|
|
3298
|
+
checkStructure: z2.boolean().optional(),
|
|
3299
|
+
ignorePatterns: z2.array(z2.string()).optional()
|
|
3538
3300
|
});
|
|
3539
|
-
var DeadCodeConfigSchema =
|
|
3540
|
-
entryPoints:
|
|
3541
|
-
includeTypes:
|
|
3542
|
-
includeInternals:
|
|
3543
|
-
ignorePatterns:
|
|
3544
|
-
treatDynamicImportsAs:
|
|
3301
|
+
var DeadCodeConfigSchema = z2.object({
|
|
3302
|
+
entryPoints: z2.array(z2.string()).optional(),
|
|
3303
|
+
includeTypes: z2.boolean().optional(),
|
|
3304
|
+
includeInternals: z2.boolean().optional(),
|
|
3305
|
+
ignorePatterns: z2.array(z2.string()).optional(),
|
|
3306
|
+
treatDynamicImportsAs: z2.enum(["used", "unknown"]).optional()
|
|
3545
3307
|
});
|
|
3546
|
-
var EntropyConfigSchema =
|
|
3547
|
-
rootDir:
|
|
3548
|
-
parser:
|
|
3308
|
+
var EntropyConfigSchema = z2.object({
|
|
3309
|
+
rootDir: z2.string(),
|
|
3310
|
+
parser: z2.any().optional(),
|
|
3549
3311
|
// LanguageParser instance, can't validate
|
|
3550
|
-
entryPoints:
|
|
3551
|
-
analyze:
|
|
3552
|
-
drift:
|
|
3553
|
-
deadCode:
|
|
3554
|
-
patterns:
|
|
3312
|
+
entryPoints: z2.array(z2.string()).optional(),
|
|
3313
|
+
analyze: z2.object({
|
|
3314
|
+
drift: z2.union([z2.boolean(), DriftConfigSchema]).optional(),
|
|
3315
|
+
deadCode: z2.union([z2.boolean(), DeadCodeConfigSchema]).optional(),
|
|
3316
|
+
patterns: z2.union([z2.boolean(), PatternConfigSchema]).optional()
|
|
3555
3317
|
}),
|
|
3556
|
-
include:
|
|
3557
|
-
exclude:
|
|
3558
|
-
docPaths:
|
|
3318
|
+
include: z2.array(z2.string()).optional(),
|
|
3319
|
+
exclude: z2.array(z2.string()).optional(),
|
|
3320
|
+
docPaths: z2.array(z2.string()).optional()
|
|
3559
3321
|
});
|
|
3560
3322
|
function validatePatternConfig(config) {
|
|
3561
3323
|
const result = PatternConfigSchema.safeParse(config);
|
|
@@ -3575,7 +3337,7 @@ function validatePatternConfig(config) {
|
|
|
3575
3337
|
|
|
3576
3338
|
// src/performance/baseline-manager.ts
|
|
3577
3339
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3578
|
-
import { join as join6, dirname as
|
|
3340
|
+
import { join as join6, dirname as dirname6 } from "path";
|
|
3579
3341
|
var BaselineManager = class {
|
|
3580
3342
|
baselinesPath;
|
|
3581
3343
|
constructor(projectRoot) {
|
|
@@ -3619,7 +3381,7 @@ var BaselineManager = class {
|
|
|
3619
3381
|
updatedFrom: commitHash,
|
|
3620
3382
|
benchmarks
|
|
3621
3383
|
};
|
|
3622
|
-
const dir =
|
|
3384
|
+
const dir = dirname6(this.baselinesPath);
|
|
3623
3385
|
if (!existsSync(dir)) {
|
|
3624
3386
|
mkdirSync(dir, { recursive: true });
|
|
3625
3387
|
}
|
|
@@ -3653,7 +3415,7 @@ var BenchmarkRunner = class {
|
|
|
3653
3415
|
/**
|
|
3654
3416
|
* Discover .bench.ts files matching the glob pattern.
|
|
3655
3417
|
*/
|
|
3656
|
-
discover(cwd,
|
|
3418
|
+
discover(cwd, glob) {
|
|
3657
3419
|
try {
|
|
3658
3420
|
const result = execFileSync(
|
|
3659
3421
|
"find",
|
|
@@ -3672,8 +3434,8 @@ var BenchmarkRunner = class {
|
|
|
3672
3434
|
).trim();
|
|
3673
3435
|
if (!result) return [];
|
|
3674
3436
|
const files = result.split("\n").filter(Boolean);
|
|
3675
|
-
if (
|
|
3676
|
-
return files.filter((f) => f.includes(
|
|
3437
|
+
if (glob && glob !== "**/*.bench.ts") {
|
|
3438
|
+
return files.filter((f) => f.includes(glob.replace(/\*/g, "")));
|
|
3677
3439
|
}
|
|
3678
3440
|
return files;
|
|
3679
3441
|
} catch {
|
|
@@ -3687,10 +3449,10 @@ var BenchmarkRunner = class {
|
|
|
3687
3449
|
async run(options = {}) {
|
|
3688
3450
|
const cwd = options.cwd ?? process.cwd();
|
|
3689
3451
|
const timeout = options.timeout ?? 12e4;
|
|
3690
|
-
const
|
|
3452
|
+
const glob = options.glob;
|
|
3691
3453
|
const args = ["vitest", "bench", "--run"];
|
|
3692
|
-
if (
|
|
3693
|
-
args.push(
|
|
3454
|
+
if (glob) {
|
|
3455
|
+
args.push(glob);
|
|
3694
3456
|
}
|
|
3695
3457
|
args.push("--reporter=json");
|
|
3696
3458
|
try {
|
|
@@ -3812,7 +3574,7 @@ var RegressionDetector = class {
|
|
|
3812
3574
|
};
|
|
3813
3575
|
|
|
3814
3576
|
// src/performance/critical-path.ts
|
|
3815
|
-
import * as
|
|
3577
|
+
import * as fs4 from "fs";
|
|
3816
3578
|
import * as path from "path";
|
|
3817
3579
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
|
|
3818
3580
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
@@ -3864,7 +3626,7 @@ var CriticalPathResolver = class {
|
|
|
3864
3626
|
walkDir(dir, entries) {
|
|
3865
3627
|
let items;
|
|
3866
3628
|
try {
|
|
3867
|
-
items =
|
|
3629
|
+
items = fs4.readdirSync(dir, { withFileTypes: true });
|
|
3868
3630
|
} catch {
|
|
3869
3631
|
return;
|
|
3870
3632
|
}
|
|
@@ -3880,7 +3642,7 @@ var CriticalPathResolver = class {
|
|
|
3880
3642
|
scanFile(filePath, entries) {
|
|
3881
3643
|
let content;
|
|
3882
3644
|
try {
|
|
3883
|
-
content =
|
|
3645
|
+
content = fs4.readFileSync(filePath, "utf-8");
|
|
3884
3646
|
} catch {
|
|
3885
3647
|
return;
|
|
3886
3648
|
}
|
|
@@ -4059,17 +3821,17 @@ function resetFeedbackConfig() {
|
|
|
4059
3821
|
}
|
|
4060
3822
|
|
|
4061
3823
|
// src/feedback/review/diff-analyzer.ts
|
|
4062
|
-
function parseDiff(
|
|
3824
|
+
function parseDiff(diff2) {
|
|
4063
3825
|
try {
|
|
4064
|
-
if (!
|
|
4065
|
-
return Ok({ diff, files: [] });
|
|
3826
|
+
if (!diff2.trim()) {
|
|
3827
|
+
return Ok({ diff: diff2, files: [] });
|
|
4066
3828
|
}
|
|
4067
3829
|
const files = [];
|
|
4068
3830
|
const newFileRegex = /new file mode/;
|
|
4069
3831
|
const deletedFileRegex = /deleted file mode/;
|
|
4070
3832
|
const additionRegex = /^\+(?!\+\+)/gm;
|
|
4071
3833
|
const deletionRegex = /^-(?!--)/gm;
|
|
4072
|
-
const diffParts =
|
|
3834
|
+
const diffParts = diff2.split(/(?=diff --git)/);
|
|
4073
3835
|
for (const part of diffParts) {
|
|
4074
3836
|
if (!part.trim()) continue;
|
|
4075
3837
|
const headerMatch = /diff --git a\/(.+?) b\/(.+?)(?:\n|$)/.exec(part);
|
|
@@ -4092,7 +3854,7 @@ function parseDiff(diff) {
|
|
|
4092
3854
|
deletions
|
|
4093
3855
|
});
|
|
4094
3856
|
}
|
|
4095
|
-
return Ok({ diff, files });
|
|
3857
|
+
return Ok({ diff: diff2, files });
|
|
4096
3858
|
} catch (error) {
|
|
4097
3859
|
return Err({
|
|
4098
3860
|
code: "DIFF_PARSE_ERROR",
|
|
@@ -4610,7 +4372,7 @@ async function requestMultiplePeerReviews(requests) {
|
|
|
4610
4372
|
|
|
4611
4373
|
// src/feedback/logging/file-sink.ts
|
|
4612
4374
|
import { appendFileSync, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
4613
|
-
import { dirname as
|
|
4375
|
+
import { dirname as dirname7 } from "path";
|
|
4614
4376
|
var FileSink = class {
|
|
4615
4377
|
name = "file";
|
|
4616
4378
|
filePath;
|
|
@@ -4633,7 +4395,7 @@ var FileSink = class {
|
|
|
4633
4395
|
}
|
|
4634
4396
|
ensureDirectory() {
|
|
4635
4397
|
if (!this.initialized) {
|
|
4636
|
-
const dir =
|
|
4398
|
+
const dir = dirname7(this.filePath);
|
|
4637
4399
|
if (!existsSync2(dir)) {
|
|
4638
4400
|
mkdirSync2(dir, { recursive: true });
|
|
4639
4401
|
}
|
|
@@ -4696,77 +4458,182 @@ var NoOpSink = class {
|
|
|
4696
4458
|
}
|
|
4697
4459
|
};
|
|
4698
4460
|
|
|
4461
|
+
// src/architecture/sync-constraints.ts
|
|
4462
|
+
function syncConstraintNodes(store, rules, violations) {
|
|
4463
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4464
|
+
const ruleIds = new Set(rules.map((r) => r.id));
|
|
4465
|
+
const violationsByCategory = /* @__PURE__ */ new Map();
|
|
4466
|
+
for (const result of violations) {
|
|
4467
|
+
const files = result.violations.map((v) => v.file);
|
|
4468
|
+
const existing = violationsByCategory.get(result.category) ?? [];
|
|
4469
|
+
violationsByCategory.set(result.category, [...existing, ...files]);
|
|
4470
|
+
}
|
|
4471
|
+
const existingNodesById = /* @__PURE__ */ new Map();
|
|
4472
|
+
for (const node of store.findNodes({ type: "constraint" })) {
|
|
4473
|
+
existingNodesById.set(node.id, node);
|
|
4474
|
+
}
|
|
4475
|
+
for (const rule of rules) {
|
|
4476
|
+
const existing = existingNodesById.get(rule.id);
|
|
4477
|
+
const createdAt = existing?.createdAt ?? now;
|
|
4478
|
+
const previousLastViolatedAt = existing?.lastViolatedAt ?? null;
|
|
4479
|
+
const hasViolation = hasMatchingViolation(rule, violationsByCategory);
|
|
4480
|
+
const lastViolatedAt = hasViolation ? now : previousLastViolatedAt;
|
|
4481
|
+
store.upsertNode({
|
|
4482
|
+
id: rule.id,
|
|
4483
|
+
type: "constraint",
|
|
4484
|
+
name: rule.description,
|
|
4485
|
+
category: rule.category,
|
|
4486
|
+
scope: rule.scope,
|
|
4487
|
+
createdAt,
|
|
4488
|
+
lastViolatedAt
|
|
4489
|
+
});
|
|
4490
|
+
}
|
|
4491
|
+
const existingConstraints = store.findNodes({ type: "constraint" });
|
|
4492
|
+
for (const node of existingConstraints) {
|
|
4493
|
+
if (!ruleIds.has(node.id)) {
|
|
4494
|
+
store.removeNode(node.id);
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
function hasMatchingViolation(rule, violationsByCategory) {
|
|
4499
|
+
const files = violationsByCategory.get(rule.category);
|
|
4500
|
+
if (!files || files.length === 0) return false;
|
|
4501
|
+
if (rule.scope === "project") return true;
|
|
4502
|
+
return files.some((file) => file.startsWith(rule.scope));
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
// src/architecture/detect-stale.ts
|
|
4506
|
+
function detectStaleConstraints(store, windowDays = 30, category) {
|
|
4507
|
+
const now = Date.now();
|
|
4508
|
+
const windowMs = windowDays * 24 * 60 * 60 * 1e3;
|
|
4509
|
+
const cutoff = now - windowMs;
|
|
4510
|
+
let constraints = store.findNodes({ type: "constraint" });
|
|
4511
|
+
if (category) {
|
|
4512
|
+
constraints = constraints.filter((n) => n.category === category);
|
|
4513
|
+
}
|
|
4514
|
+
const totalConstraints = constraints.length;
|
|
4515
|
+
const staleConstraints = [];
|
|
4516
|
+
for (const node of constraints) {
|
|
4517
|
+
const lastViolatedAt = node.lastViolatedAt ?? null;
|
|
4518
|
+
const createdAt = node.createdAt;
|
|
4519
|
+
const comparisonTimestamp = lastViolatedAt ?? createdAt;
|
|
4520
|
+
if (!comparisonTimestamp) continue;
|
|
4521
|
+
const timestampMs = new Date(comparisonTimestamp).getTime();
|
|
4522
|
+
if (timestampMs < cutoff) {
|
|
4523
|
+
const daysSince = Math.floor((now - timestampMs) / (24 * 60 * 60 * 1e3));
|
|
4524
|
+
staleConstraints.push({
|
|
4525
|
+
id: node.id,
|
|
4526
|
+
category: node.category,
|
|
4527
|
+
description: node.name ?? "",
|
|
4528
|
+
scope: node.scope ?? "project",
|
|
4529
|
+
lastViolatedAt,
|
|
4530
|
+
daysSinceLastViolation: daysSince
|
|
4531
|
+
});
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
staleConstraints.sort((a, b) => b.daysSinceLastViolation - a.daysSinceLastViolation);
|
|
4535
|
+
return { staleConstraints, totalConstraints, windowDays };
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
// src/architecture/config.ts
|
|
4539
|
+
function resolveThresholds(scope, config) {
|
|
4540
|
+
const projectThresholds = {};
|
|
4541
|
+
for (const [key, val] of Object.entries(config.thresholds)) {
|
|
4542
|
+
projectThresholds[key] = typeof val === "object" && val !== null && !Array.isArray(val) ? { ...val } : val;
|
|
4543
|
+
}
|
|
4544
|
+
if (scope === "project") {
|
|
4545
|
+
return projectThresholds;
|
|
4546
|
+
}
|
|
4547
|
+
const moduleOverrides = config.modules[scope];
|
|
4548
|
+
if (!moduleOverrides) {
|
|
4549
|
+
return projectThresholds;
|
|
4550
|
+
}
|
|
4551
|
+
const merged = { ...projectThresholds };
|
|
4552
|
+
for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
|
|
4553
|
+
const projectValue = projectThresholds[category];
|
|
4554
|
+
if (projectValue !== void 0 && typeof projectValue === "object" && !Array.isArray(projectValue) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
|
|
4555
|
+
merged[category] = {
|
|
4556
|
+
...projectValue,
|
|
4557
|
+
...moduleValue
|
|
4558
|
+
};
|
|
4559
|
+
} else {
|
|
4560
|
+
merged[category] = moduleValue;
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
return merged;
|
|
4564
|
+
}
|
|
4565
|
+
|
|
4699
4566
|
// src/state/types.ts
|
|
4700
|
-
import { z as
|
|
4701
|
-
var FailureEntrySchema =
|
|
4702
|
-
date:
|
|
4703
|
-
skill:
|
|
4704
|
-
type:
|
|
4705
|
-
description:
|
|
4567
|
+
import { z as z3 } from "zod";
|
|
4568
|
+
var FailureEntrySchema = z3.object({
|
|
4569
|
+
date: z3.string(),
|
|
4570
|
+
skill: z3.string(),
|
|
4571
|
+
type: z3.string(),
|
|
4572
|
+
description: z3.string()
|
|
4706
4573
|
});
|
|
4707
|
-
var HandoffSchema =
|
|
4708
|
-
timestamp:
|
|
4709
|
-
fromSkill:
|
|
4710
|
-
phase:
|
|
4711
|
-
summary:
|
|
4712
|
-
completed:
|
|
4713
|
-
pending:
|
|
4714
|
-
concerns:
|
|
4715
|
-
decisions:
|
|
4716
|
-
|
|
4717
|
-
what:
|
|
4718
|
-
why:
|
|
4574
|
+
var HandoffSchema = z3.object({
|
|
4575
|
+
timestamp: z3.string(),
|
|
4576
|
+
fromSkill: z3.string(),
|
|
4577
|
+
phase: z3.string(),
|
|
4578
|
+
summary: z3.string(),
|
|
4579
|
+
completed: z3.array(z3.string()).default([]),
|
|
4580
|
+
pending: z3.array(z3.string()).default([]),
|
|
4581
|
+
concerns: z3.array(z3.string()).default([]),
|
|
4582
|
+
decisions: z3.array(
|
|
4583
|
+
z3.object({
|
|
4584
|
+
what: z3.string(),
|
|
4585
|
+
why: z3.string()
|
|
4719
4586
|
})
|
|
4720
4587
|
).default([]),
|
|
4721
|
-
blockers:
|
|
4722
|
-
contextKeywords:
|
|
4588
|
+
blockers: z3.array(z3.string()).default([]),
|
|
4589
|
+
contextKeywords: z3.array(z3.string()).default([])
|
|
4723
4590
|
});
|
|
4724
|
-
var GateCheckSchema =
|
|
4725
|
-
name:
|
|
4726
|
-
passed:
|
|
4727
|
-
command:
|
|
4728
|
-
output:
|
|
4729
|
-
duration:
|
|
4591
|
+
var GateCheckSchema = z3.object({
|
|
4592
|
+
name: z3.string(),
|
|
4593
|
+
passed: z3.boolean(),
|
|
4594
|
+
command: z3.string(),
|
|
4595
|
+
output: z3.string().optional(),
|
|
4596
|
+
duration: z3.number().optional()
|
|
4730
4597
|
});
|
|
4731
|
-
var GateResultSchema =
|
|
4732
|
-
passed:
|
|
4733
|
-
checks:
|
|
4598
|
+
var GateResultSchema = z3.object({
|
|
4599
|
+
passed: z3.boolean(),
|
|
4600
|
+
checks: z3.array(GateCheckSchema)
|
|
4734
4601
|
});
|
|
4735
|
-
var GateConfigSchema =
|
|
4736
|
-
checks:
|
|
4737
|
-
|
|
4738
|
-
name:
|
|
4739
|
-
command:
|
|
4602
|
+
var GateConfigSchema = z3.object({
|
|
4603
|
+
checks: z3.array(
|
|
4604
|
+
z3.object({
|
|
4605
|
+
name: z3.string(),
|
|
4606
|
+
command: z3.string()
|
|
4740
4607
|
})
|
|
4741
4608
|
).optional(),
|
|
4742
|
-
trace:
|
|
4609
|
+
trace: z3.boolean().optional()
|
|
4743
4610
|
});
|
|
4744
|
-
var HarnessStateSchema =
|
|
4745
|
-
schemaVersion:
|
|
4746
|
-
position:
|
|
4747
|
-
phase:
|
|
4748
|
-
task:
|
|
4611
|
+
var HarnessStateSchema = z3.object({
|
|
4612
|
+
schemaVersion: z3.literal(1),
|
|
4613
|
+
position: z3.object({
|
|
4614
|
+
phase: z3.string().optional(),
|
|
4615
|
+
task: z3.string().optional()
|
|
4749
4616
|
}).default({}),
|
|
4750
|
-
decisions:
|
|
4751
|
-
|
|
4752
|
-
date:
|
|
4753
|
-
decision:
|
|
4754
|
-
context:
|
|
4617
|
+
decisions: z3.array(
|
|
4618
|
+
z3.object({
|
|
4619
|
+
date: z3.string(),
|
|
4620
|
+
decision: z3.string(),
|
|
4621
|
+
context: z3.string()
|
|
4755
4622
|
})
|
|
4756
4623
|
).default([]),
|
|
4757
|
-
blockers:
|
|
4758
|
-
|
|
4759
|
-
id:
|
|
4760
|
-
description:
|
|
4761
|
-
status:
|
|
4624
|
+
blockers: z3.array(
|
|
4625
|
+
z3.object({
|
|
4626
|
+
id: z3.string(),
|
|
4627
|
+
description: z3.string(),
|
|
4628
|
+
status: z3.enum(["open", "resolved"])
|
|
4762
4629
|
})
|
|
4763
4630
|
).default([]),
|
|
4764
|
-
progress:
|
|
4765
|
-
lastSession:
|
|
4766
|
-
date:
|
|
4767
|
-
summary:
|
|
4768
|
-
lastSkill:
|
|
4769
|
-
pendingTasks:
|
|
4631
|
+
progress: z3.record(z3.enum(["pending", "in_progress", "complete"])).default({}),
|
|
4632
|
+
lastSession: z3.object({
|
|
4633
|
+
date: z3.string(),
|
|
4634
|
+
summary: z3.string(),
|
|
4635
|
+
lastSkill: z3.string().optional(),
|
|
4636
|
+
pendingTasks: z3.array(z3.string()).optional()
|
|
4770
4637
|
}).optional()
|
|
4771
4638
|
});
|
|
4772
4639
|
var DEFAULT_STATE = {
|
|
@@ -4778,27 +4645,27 @@ var DEFAULT_STATE = {
|
|
|
4778
4645
|
};
|
|
4779
4646
|
|
|
4780
4647
|
// src/state/state-manager.ts
|
|
4781
|
-
import * as
|
|
4648
|
+
import * as fs6 from "fs";
|
|
4782
4649
|
import * as path3 from "path";
|
|
4783
4650
|
import { execSync as execSync2 } from "child_process";
|
|
4784
4651
|
|
|
4785
4652
|
// src/state/stream-resolver.ts
|
|
4786
|
-
import * as
|
|
4653
|
+
import * as fs5 from "fs";
|
|
4787
4654
|
import * as path2 from "path";
|
|
4788
4655
|
import { execSync } from "child_process";
|
|
4789
4656
|
|
|
4790
4657
|
// src/state/stream-types.ts
|
|
4791
|
-
import { z as
|
|
4792
|
-
var StreamInfoSchema =
|
|
4793
|
-
name:
|
|
4794
|
-
branch:
|
|
4795
|
-
createdAt:
|
|
4796
|
-
lastActiveAt:
|
|
4658
|
+
import { z as z4 } from "zod";
|
|
4659
|
+
var StreamInfoSchema = z4.object({
|
|
4660
|
+
name: z4.string(),
|
|
4661
|
+
branch: z4.string().optional(),
|
|
4662
|
+
createdAt: z4.string(),
|
|
4663
|
+
lastActiveAt: z4.string()
|
|
4797
4664
|
});
|
|
4798
|
-
var StreamIndexSchema =
|
|
4799
|
-
schemaVersion:
|
|
4800
|
-
activeStream:
|
|
4801
|
-
streams:
|
|
4665
|
+
var StreamIndexSchema = z4.object({
|
|
4666
|
+
schemaVersion: z4.literal(1),
|
|
4667
|
+
activeStream: z4.string().nullable(),
|
|
4668
|
+
streams: z4.record(StreamInfoSchema)
|
|
4802
4669
|
});
|
|
4803
4670
|
var DEFAULT_STREAM_INDEX = {
|
|
4804
4671
|
schemaVersion: 1,
|
|
@@ -4829,11 +4696,11 @@ function validateStreamName(name) {
|
|
|
4829
4696
|
}
|
|
4830
4697
|
async function loadStreamIndex(projectPath) {
|
|
4831
4698
|
const idxPath = indexPath(projectPath);
|
|
4832
|
-
if (!
|
|
4699
|
+
if (!fs5.existsSync(idxPath)) {
|
|
4833
4700
|
return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
4834
4701
|
}
|
|
4835
4702
|
try {
|
|
4836
|
-
const raw =
|
|
4703
|
+
const raw = fs5.readFileSync(idxPath, "utf-8");
|
|
4837
4704
|
const parsed = JSON.parse(raw);
|
|
4838
4705
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
4839
4706
|
if (!result.success) {
|
|
@@ -4851,8 +4718,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
4851
4718
|
async function saveStreamIndex(projectPath, index) {
|
|
4852
4719
|
const dir = streamsDir(projectPath);
|
|
4853
4720
|
try {
|
|
4854
|
-
|
|
4855
|
-
|
|
4721
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
4722
|
+
fs5.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
4856
4723
|
return Ok(void 0);
|
|
4857
4724
|
} catch (error) {
|
|
4858
4725
|
return Err(
|
|
@@ -4934,7 +4801,7 @@ async function createStream(projectPath, name, branch) {
|
|
|
4934
4801
|
}
|
|
4935
4802
|
const streamPath = path2.join(streamsDir(projectPath), name);
|
|
4936
4803
|
try {
|
|
4937
|
-
|
|
4804
|
+
fs5.mkdirSync(streamPath, { recursive: true });
|
|
4938
4805
|
} catch (error) {
|
|
4939
4806
|
return Err(
|
|
4940
4807
|
new Error(
|
|
@@ -4978,9 +4845,9 @@ async function archiveStream(projectPath, name) {
|
|
|
4978
4845
|
const streamPath = path2.join(streamsDir(projectPath), name);
|
|
4979
4846
|
const archiveDir = path2.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
4980
4847
|
try {
|
|
4981
|
-
|
|
4848
|
+
fs5.mkdirSync(archiveDir, { recursive: true });
|
|
4982
4849
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4983
|
-
|
|
4850
|
+
fs5.renameSync(streamPath, path2.join(archiveDir, `${name}-${date}`));
|
|
4984
4851
|
} catch (error) {
|
|
4985
4852
|
return Err(
|
|
4986
4853
|
new Error(
|
|
@@ -5003,18 +4870,18 @@ function getStreamForBranch(index, branch) {
|
|
|
5003
4870
|
var STATE_FILES = ["state.json", "handoff.json", "learnings.md", "failures.md"];
|
|
5004
4871
|
async function migrateToStreams(projectPath) {
|
|
5005
4872
|
const harnessDir = path2.join(projectPath, HARNESS_DIR);
|
|
5006
|
-
if (
|
|
4873
|
+
if (fs5.existsSync(indexPath(projectPath))) {
|
|
5007
4874
|
return Ok(void 0);
|
|
5008
4875
|
}
|
|
5009
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
4876
|
+
const filesToMove = STATE_FILES.filter((f) => fs5.existsSync(path2.join(harnessDir, f)));
|
|
5010
4877
|
if (filesToMove.length === 0) {
|
|
5011
4878
|
return Ok(void 0);
|
|
5012
4879
|
}
|
|
5013
4880
|
const defaultDir = path2.join(streamsDir(projectPath), "default");
|
|
5014
4881
|
try {
|
|
5015
|
-
|
|
4882
|
+
fs5.mkdirSync(defaultDir, { recursive: true });
|
|
5016
4883
|
for (const file of filesToMove) {
|
|
5017
|
-
|
|
4884
|
+
fs5.renameSync(path2.join(harnessDir, file), path2.join(defaultDir, file));
|
|
5018
4885
|
}
|
|
5019
4886
|
} catch (error) {
|
|
5020
4887
|
return Err(
|
|
@@ -5055,7 +4922,7 @@ function evictIfNeeded(map) {
|
|
|
5055
4922
|
}
|
|
5056
4923
|
async function getStateDir(projectPath, stream) {
|
|
5057
4924
|
const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
|
|
5058
|
-
const hasStreams =
|
|
4925
|
+
const hasStreams = fs6.existsSync(streamsIndexPath);
|
|
5059
4926
|
if (stream || hasStreams) {
|
|
5060
4927
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
5061
4928
|
if (result.ok) {
|
|
@@ -5073,10 +4940,10 @@ async function loadState(projectPath, stream) {
|
|
|
5073
4940
|
if (!dirResult.ok) return dirResult;
|
|
5074
4941
|
const stateDir = dirResult.value;
|
|
5075
4942
|
const statePath = path3.join(stateDir, STATE_FILE);
|
|
5076
|
-
if (!
|
|
4943
|
+
if (!fs6.existsSync(statePath)) {
|
|
5077
4944
|
return Ok({ ...DEFAULT_STATE });
|
|
5078
4945
|
}
|
|
5079
|
-
const raw =
|
|
4946
|
+
const raw = fs6.readFileSync(statePath, "utf-8");
|
|
5080
4947
|
const parsed = JSON.parse(raw);
|
|
5081
4948
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
5082
4949
|
if (!result.success) {
|
|
@@ -5095,8 +4962,8 @@ async function saveState(projectPath, state, stream) {
|
|
|
5095
4962
|
if (!dirResult.ok) return dirResult;
|
|
5096
4963
|
const stateDir = dirResult.value;
|
|
5097
4964
|
const statePath = path3.join(stateDir, STATE_FILE);
|
|
5098
|
-
|
|
5099
|
-
|
|
4965
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
4966
|
+
fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
5100
4967
|
return Ok(void 0);
|
|
5101
4968
|
} catch (error) {
|
|
5102
4969
|
return Err(
|
|
@@ -5110,7 +4977,7 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5110
4977
|
if (!dirResult.ok) return dirResult;
|
|
5111
4978
|
const stateDir = dirResult.value;
|
|
5112
4979
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5113
|
-
|
|
4980
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5114
4981
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5115
4982
|
let entry;
|
|
5116
4983
|
if (skillName && outcome) {
|
|
@@ -5126,11 +4993,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5126
4993
|
- **${timestamp}:** ${learning}
|
|
5127
4994
|
`;
|
|
5128
4995
|
}
|
|
5129
|
-
if (!
|
|
5130
|
-
|
|
4996
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
4997
|
+
fs6.writeFileSync(learningsPath, `# Learnings
|
|
5131
4998
|
${entry}`);
|
|
5132
4999
|
} else {
|
|
5133
|
-
|
|
5000
|
+
fs6.appendFileSync(learningsPath, entry);
|
|
5134
5001
|
}
|
|
5135
5002
|
learningsCacheMap.delete(learningsPath);
|
|
5136
5003
|
return Ok(void 0);
|
|
@@ -5148,17 +5015,17 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
5148
5015
|
if (!dirResult.ok) return dirResult;
|
|
5149
5016
|
const stateDir = dirResult.value;
|
|
5150
5017
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5151
|
-
if (!
|
|
5018
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
5152
5019
|
return Ok([]);
|
|
5153
5020
|
}
|
|
5154
|
-
const stats =
|
|
5021
|
+
const stats = fs6.statSync(learningsPath);
|
|
5155
5022
|
const cacheKey = learningsPath;
|
|
5156
5023
|
const cached = learningsCacheMap.get(cacheKey);
|
|
5157
5024
|
let entries;
|
|
5158
5025
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5159
5026
|
entries = cached.entries;
|
|
5160
5027
|
} else {
|
|
5161
|
-
const content =
|
|
5028
|
+
const content = fs6.readFileSync(learningsPath, "utf-8");
|
|
5162
5029
|
const lines = content.split("\n");
|
|
5163
5030
|
entries = [];
|
|
5164
5031
|
let currentBlock = [];
|
|
@@ -5201,16 +5068,16 @@ async function appendFailure(projectPath, description, skillName, type, stream)
|
|
|
5201
5068
|
if (!dirResult.ok) return dirResult;
|
|
5202
5069
|
const stateDir = dirResult.value;
|
|
5203
5070
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5204
|
-
|
|
5071
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5205
5072
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5206
5073
|
const entry = `
|
|
5207
5074
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
5208
5075
|
`;
|
|
5209
|
-
if (!
|
|
5210
|
-
|
|
5076
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5077
|
+
fs6.writeFileSync(failuresPath, `# Failures
|
|
5211
5078
|
${entry}`);
|
|
5212
5079
|
} else {
|
|
5213
|
-
|
|
5080
|
+
fs6.appendFileSync(failuresPath, entry);
|
|
5214
5081
|
}
|
|
5215
5082
|
failuresCacheMap.delete(failuresPath);
|
|
5216
5083
|
return Ok(void 0);
|
|
@@ -5228,16 +5095,16 @@ async function loadFailures(projectPath, stream) {
|
|
|
5228
5095
|
if (!dirResult.ok) return dirResult;
|
|
5229
5096
|
const stateDir = dirResult.value;
|
|
5230
5097
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5231
|
-
if (!
|
|
5098
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5232
5099
|
return Ok([]);
|
|
5233
5100
|
}
|
|
5234
|
-
const stats =
|
|
5101
|
+
const stats = fs6.statSync(failuresPath);
|
|
5235
5102
|
const cacheKey = failuresPath;
|
|
5236
5103
|
const cached = failuresCacheMap.get(cacheKey);
|
|
5237
5104
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5238
5105
|
return Ok(cached.entries);
|
|
5239
5106
|
}
|
|
5240
|
-
const content =
|
|
5107
|
+
const content = fs6.readFileSync(failuresPath, "utf-8");
|
|
5241
5108
|
const entries = [];
|
|
5242
5109
|
for (const line of content.split("\n")) {
|
|
5243
5110
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -5267,19 +5134,19 @@ async function archiveFailures(projectPath, stream) {
|
|
|
5267
5134
|
if (!dirResult.ok) return dirResult;
|
|
5268
5135
|
const stateDir = dirResult.value;
|
|
5269
5136
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5270
|
-
if (!
|
|
5137
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5271
5138
|
return Ok(void 0);
|
|
5272
5139
|
}
|
|
5273
5140
|
const archiveDir = path3.join(stateDir, "archive");
|
|
5274
|
-
|
|
5141
|
+
fs6.mkdirSync(archiveDir, { recursive: true });
|
|
5275
5142
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5276
5143
|
let archiveName = `failures-${date}.md`;
|
|
5277
5144
|
let counter = 2;
|
|
5278
|
-
while (
|
|
5145
|
+
while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
|
|
5279
5146
|
archiveName = `failures-${date}-${counter}.md`;
|
|
5280
5147
|
counter++;
|
|
5281
5148
|
}
|
|
5282
|
-
|
|
5149
|
+
fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
|
|
5283
5150
|
failuresCacheMap.delete(failuresPath);
|
|
5284
5151
|
return Ok(void 0);
|
|
5285
5152
|
} catch (error) {
|
|
@@ -5296,8 +5163,8 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
5296
5163
|
if (!dirResult.ok) return dirResult;
|
|
5297
5164
|
const stateDir = dirResult.value;
|
|
5298
5165
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5299
|
-
|
|
5300
|
-
|
|
5166
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5167
|
+
fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
5301
5168
|
return Ok(void 0);
|
|
5302
5169
|
} catch (error) {
|
|
5303
5170
|
return Err(
|
|
@@ -5311,10 +5178,10 @@ async function loadHandoff(projectPath, stream) {
|
|
|
5311
5178
|
if (!dirResult.ok) return dirResult;
|
|
5312
5179
|
const stateDir = dirResult.value;
|
|
5313
5180
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5314
|
-
if (!
|
|
5181
|
+
if (!fs6.existsSync(handoffPath)) {
|
|
5315
5182
|
return Ok(null);
|
|
5316
5183
|
}
|
|
5317
|
-
const raw =
|
|
5184
|
+
const raw = fs6.readFileSync(handoffPath, "utf-8");
|
|
5318
5185
|
const parsed = JSON.parse(raw);
|
|
5319
5186
|
const result = HandoffSchema.safeParse(parsed);
|
|
5320
5187
|
if (!result.success) {
|
|
@@ -5332,8 +5199,8 @@ async function runMechanicalGate(projectPath) {
|
|
|
5332
5199
|
const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
|
|
5333
5200
|
try {
|
|
5334
5201
|
let checks = [];
|
|
5335
|
-
if (
|
|
5336
|
-
const raw = JSON.parse(
|
|
5202
|
+
if (fs6.existsSync(gateConfigPath)) {
|
|
5203
|
+
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
5337
5204
|
const config = GateConfigSchema.safeParse(raw);
|
|
5338
5205
|
if (config.success && config.data.checks) {
|
|
5339
5206
|
checks = config.data.checks;
|
|
@@ -5341,19 +5208,19 @@ async function runMechanicalGate(projectPath) {
|
|
|
5341
5208
|
}
|
|
5342
5209
|
if (checks.length === 0) {
|
|
5343
5210
|
const packageJsonPath = path3.join(projectPath, "package.json");
|
|
5344
|
-
if (
|
|
5345
|
-
const pkg = JSON.parse(
|
|
5211
|
+
if (fs6.existsSync(packageJsonPath)) {
|
|
5212
|
+
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
5346
5213
|
const scripts = pkg.scripts || {};
|
|
5347
5214
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
5348
5215
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
5349
5216
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
5350
5217
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
5351
5218
|
}
|
|
5352
|
-
if (
|
|
5219
|
+
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
5353
5220
|
checks.push({ name: "test", command: "go test ./..." });
|
|
5354
5221
|
checks.push({ name: "build", command: "go build ./..." });
|
|
5355
5222
|
}
|
|
5356
|
-
if (
|
|
5223
|
+
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
5357
5224
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
5358
5225
|
}
|
|
5359
5226
|
}
|
|
@@ -5556,7 +5423,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
5556
5423
|
}
|
|
5557
5424
|
|
|
5558
5425
|
// src/security/scanner.ts
|
|
5559
|
-
import * as
|
|
5426
|
+
import * as fs8 from "fs/promises";
|
|
5560
5427
|
|
|
5561
5428
|
// src/security/rules/registry.ts
|
|
5562
5429
|
var RuleRegistry = class {
|
|
@@ -5587,7 +5454,7 @@ var RuleRegistry = class {
|
|
|
5587
5454
|
};
|
|
5588
5455
|
|
|
5589
5456
|
// src/security/config.ts
|
|
5590
|
-
import { z as
|
|
5457
|
+
import { z as z5 } from "zod";
|
|
5591
5458
|
|
|
5592
5459
|
// src/security/types.ts
|
|
5593
5460
|
var DEFAULT_SECURITY_CONFIG = {
|
|
@@ -5598,19 +5465,19 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
5598
5465
|
};
|
|
5599
5466
|
|
|
5600
5467
|
// src/security/config.ts
|
|
5601
|
-
var RuleOverrideSchema =
|
|
5602
|
-
var SecurityConfigSchema =
|
|
5603
|
-
enabled:
|
|
5604
|
-
strict:
|
|
5605
|
-
rules:
|
|
5606
|
-
exclude:
|
|
5607
|
-
external:
|
|
5608
|
-
semgrep:
|
|
5609
|
-
enabled:
|
|
5610
|
-
rulesets:
|
|
5468
|
+
var RuleOverrideSchema = z5.enum(["off", "error", "warning", "info"]);
|
|
5469
|
+
var SecurityConfigSchema = z5.object({
|
|
5470
|
+
enabled: z5.boolean().default(true),
|
|
5471
|
+
strict: z5.boolean().default(false),
|
|
5472
|
+
rules: z5.record(z5.string(), RuleOverrideSchema).optional().default({}),
|
|
5473
|
+
exclude: z5.array(z5.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
|
|
5474
|
+
external: z5.object({
|
|
5475
|
+
semgrep: z5.object({
|
|
5476
|
+
enabled: z5.union([z5.literal("auto"), z5.boolean()]).default("auto"),
|
|
5477
|
+
rulesets: z5.array(z5.string()).optional()
|
|
5611
5478
|
}).optional(),
|
|
5612
|
-
gitleaks:
|
|
5613
|
-
enabled:
|
|
5479
|
+
gitleaks: z5.object({
|
|
5480
|
+
enabled: z5.union([z5.literal("auto"), z5.boolean()]).default("auto")
|
|
5614
5481
|
}).optional()
|
|
5615
5482
|
}).optional()
|
|
5616
5483
|
});
|
|
@@ -5643,15 +5510,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
5643
5510
|
}
|
|
5644
5511
|
|
|
5645
5512
|
// src/security/stack-detector.ts
|
|
5646
|
-
import * as
|
|
5513
|
+
import * as fs7 from "fs";
|
|
5647
5514
|
import * as path4 from "path";
|
|
5648
5515
|
function detectStack(projectRoot) {
|
|
5649
5516
|
const stacks = [];
|
|
5650
5517
|
const pkgJsonPath = path4.join(projectRoot, "package.json");
|
|
5651
|
-
if (
|
|
5518
|
+
if (fs7.existsSync(pkgJsonPath)) {
|
|
5652
5519
|
stacks.push("node");
|
|
5653
5520
|
try {
|
|
5654
|
-
const pkgJson = JSON.parse(
|
|
5521
|
+
const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
|
|
5655
5522
|
const allDeps = {
|
|
5656
5523
|
...pkgJson.dependencies,
|
|
5657
5524
|
...pkgJson.devDependencies
|
|
@@ -5667,12 +5534,12 @@ function detectStack(projectRoot) {
|
|
|
5667
5534
|
}
|
|
5668
5535
|
}
|
|
5669
5536
|
const goModPath = path4.join(projectRoot, "go.mod");
|
|
5670
|
-
if (
|
|
5537
|
+
if (fs7.existsSync(goModPath)) {
|
|
5671
5538
|
stacks.push("go");
|
|
5672
5539
|
}
|
|
5673
5540
|
const requirementsPath = path4.join(projectRoot, "requirements.txt");
|
|
5674
5541
|
const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
|
|
5675
|
-
if (
|
|
5542
|
+
if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
|
|
5676
5543
|
stacks.push("python");
|
|
5677
5544
|
}
|
|
5678
5545
|
return stacks;
|
|
@@ -6099,7 +5966,7 @@ var SecurityScanner = class {
|
|
|
6099
5966
|
}
|
|
6100
5967
|
async scanFile(filePath) {
|
|
6101
5968
|
if (!this.config.enabled) return [];
|
|
6102
|
-
const content = await
|
|
5969
|
+
const content = await fs8.readFile(filePath, "utf-8");
|
|
6103
5970
|
return this.scanContent(content, filePath, 1);
|
|
6104
5971
|
}
|
|
6105
5972
|
async scanFiles(filePaths) {
|
|
@@ -6132,7 +5999,8 @@ var ALL_CHECKS = [
|
|
|
6132
5999
|
"entropy",
|
|
6133
6000
|
"security",
|
|
6134
6001
|
"perf",
|
|
6135
|
-
"phase-gate"
|
|
6002
|
+
"phase-gate",
|
|
6003
|
+
"arch"
|
|
6136
6004
|
];
|
|
6137
6005
|
async function runSingleCheck(name, projectRoot, config) {
|
|
6138
6006
|
const start = Date.now();
|
|
@@ -6196,7 +6064,17 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6196
6064
|
}
|
|
6197
6065
|
case "docs": {
|
|
6198
6066
|
const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
|
|
6199
|
-
const
|
|
6067
|
+
const entropyConfig = config.entropy || {};
|
|
6068
|
+
const result = await checkDocCoverage("project", {
|
|
6069
|
+
docsDir,
|
|
6070
|
+
sourceDir: projectRoot,
|
|
6071
|
+
excludePatterns: entropyConfig.excludePatterns || [
|
|
6072
|
+
"**/node_modules/**",
|
|
6073
|
+
"**/dist/**",
|
|
6074
|
+
"**/*.test.ts",
|
|
6075
|
+
"**/fixtures/**"
|
|
6076
|
+
]
|
|
6077
|
+
});
|
|
6200
6078
|
if (!result.ok) {
|
|
6201
6079
|
issues.push({ severity: "warning", message: result.error.message });
|
|
6202
6080
|
} else if (result.value.gaps.length > 0) {
|
|
@@ -6271,11 +6149,13 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6271
6149
|
break;
|
|
6272
6150
|
}
|
|
6273
6151
|
case "perf": {
|
|
6152
|
+
const perfConfig = config.performance || {};
|
|
6274
6153
|
const perfAnalyzer = new EntropyAnalyzer({
|
|
6275
6154
|
rootDir: projectRoot,
|
|
6276
6155
|
analyze: {
|
|
6277
|
-
complexity: true,
|
|
6278
|
-
coupling: true
|
|
6156
|
+
complexity: perfConfig.complexity || true,
|
|
6157
|
+
coupling: perfConfig.coupling || true,
|
|
6158
|
+
sizeBudget: perfConfig.sizeBudget || false
|
|
6279
6159
|
}
|
|
6280
6160
|
});
|
|
6281
6161
|
const perfResult = await perfAnalyzer.analyze();
|
|
@@ -6316,6 +6196,43 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6316
6196
|
});
|
|
6317
6197
|
break;
|
|
6318
6198
|
}
|
|
6199
|
+
case "arch": {
|
|
6200
|
+
const rawArchConfig = config.architecture;
|
|
6201
|
+
const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
|
|
6202
|
+
if (!archConfig.enabled) break;
|
|
6203
|
+
const results = await runAll(archConfig, projectRoot);
|
|
6204
|
+
const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
|
|
6205
|
+
const baseline = baselineManager.load();
|
|
6206
|
+
if (baseline) {
|
|
6207
|
+
const diffResult = diff(results, baseline);
|
|
6208
|
+
if (!diffResult.passed) {
|
|
6209
|
+
for (const v of diffResult.newViolations) {
|
|
6210
|
+
issues.push({
|
|
6211
|
+
severity: v.severity,
|
|
6212
|
+
message: `[${v.category || "arch"}] NEW: ${v.detail}`,
|
|
6213
|
+
file: v.file
|
|
6214
|
+
});
|
|
6215
|
+
}
|
|
6216
|
+
for (const r of diffResult.regressions) {
|
|
6217
|
+
issues.push({
|
|
6218
|
+
severity: "error",
|
|
6219
|
+
message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
|
|
6220
|
+
});
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
} else {
|
|
6224
|
+
for (const result of results) {
|
|
6225
|
+
for (const v of result.violations) {
|
|
6226
|
+
issues.push({
|
|
6227
|
+
severity: v.severity,
|
|
6228
|
+
message: `[${result.category}] ${v.detail}`,
|
|
6229
|
+
file: v.file
|
|
6230
|
+
});
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
break;
|
|
6235
|
+
}
|
|
6319
6236
|
}
|
|
6320
6237
|
} catch (error) {
|
|
6321
6238
|
issues.push({
|
|
@@ -6650,22 +6567,22 @@ var PREFIX_PATTERNS = [
|
|
|
6650
6567
|
];
|
|
6651
6568
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
6652
6569
|
var MD_FILE_PATTERN = /\.md$/;
|
|
6653
|
-
function detectChangeType(commitMessage,
|
|
6570
|
+
function detectChangeType(commitMessage, diff2) {
|
|
6654
6571
|
const trimmed = commitMessage.trim();
|
|
6655
6572
|
for (const { pattern, type } of PREFIX_PATTERNS) {
|
|
6656
6573
|
if (pattern.test(trimmed)) {
|
|
6657
6574
|
return type;
|
|
6658
6575
|
}
|
|
6659
6576
|
}
|
|
6660
|
-
if (
|
|
6577
|
+
if (diff2.changedFiles.length > 0 && diff2.changedFiles.every((f) => MD_FILE_PATTERN.test(f))) {
|
|
6661
6578
|
return "docs";
|
|
6662
6579
|
}
|
|
6663
|
-
const newNonTestFiles =
|
|
6580
|
+
const newNonTestFiles = diff2.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
|
|
6664
6581
|
if (newNonTestFiles.length > 0) {
|
|
6665
6582
|
return "feature";
|
|
6666
6583
|
}
|
|
6667
|
-
const hasNewTestFile =
|
|
6668
|
-
if (
|
|
6584
|
+
const hasNewTestFile = diff2.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
|
|
6585
|
+
if (diff2.totalDiffLines < 20 && hasNewTestFile) {
|
|
6669
6586
|
return "bugfix";
|
|
6670
6587
|
}
|
|
6671
6588
|
return "feature";
|
|
@@ -6704,7 +6621,7 @@ function extractImportSources(content) {
|
|
|
6704
6621
|
}
|
|
6705
6622
|
return sources;
|
|
6706
6623
|
}
|
|
6707
|
-
async function
|
|
6624
|
+
async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
6708
6625
|
if (!importSource.startsWith(".")) return null;
|
|
6709
6626
|
const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
|
|
6710
6627
|
const basePath = path7.resolve(fromDir, importSource);
|
|
@@ -6739,7 +6656,7 @@ async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
|
6739
6656
|
const sources = extractImportSources(cf.content);
|
|
6740
6657
|
for (const source of sources) {
|
|
6741
6658
|
if (linesGathered >= budget) break;
|
|
6742
|
-
const resolved = await
|
|
6659
|
+
const resolved = await resolveImportPath(projectRoot, cf.path, source);
|
|
6743
6660
|
if (resolved && !seen.has(resolved)) {
|
|
6744
6661
|
seen.add(resolved);
|
|
6745
6662
|
const contextFile = await readContextFile(projectRoot, resolved, "import");
|
|
@@ -6903,11 +6820,11 @@ async function scopeArchitectureContext(projectRoot, changedFiles, budget, optio
|
|
|
6903
6820
|
return contextFiles;
|
|
6904
6821
|
}
|
|
6905
6822
|
async function scopeContext(options) {
|
|
6906
|
-
const { projectRoot, diff, commitMessage } = options;
|
|
6907
|
-
const changeType = detectChangeType(commitMessage,
|
|
6908
|
-
const budget = computeContextBudget(
|
|
6823
|
+
const { projectRoot, diff: diff2, commitMessage } = options;
|
|
6824
|
+
const changeType = detectChangeType(commitMessage, diff2);
|
|
6825
|
+
const budget = computeContextBudget(diff2.totalDiffLines);
|
|
6909
6826
|
const changedFiles = [];
|
|
6910
|
-
for (const filePath of
|
|
6827
|
+
for (const filePath of diff2.changedFiles) {
|
|
6911
6828
|
const cf = await readContextFile(projectRoot, filePath, "changed");
|
|
6912
6829
|
if (cf) changedFiles.push(cf);
|
|
6913
6830
|
}
|
|
@@ -6927,7 +6844,7 @@ async function scopeContext(options) {
|
|
|
6927
6844
|
changedFiles: [...changedFiles],
|
|
6928
6845
|
contextFiles,
|
|
6929
6846
|
commitHistory: options.commitHistory ?? [],
|
|
6930
|
-
diffLines:
|
|
6847
|
+
diffLines: diff2.totalDiffLines,
|
|
6931
6848
|
contextLines
|
|
6932
6849
|
});
|
|
6933
6850
|
}
|
|
@@ -7937,7 +7854,7 @@ function formatGitHubSummary(options) {
|
|
|
7937
7854
|
async function runReviewPipeline(options) {
|
|
7938
7855
|
const {
|
|
7939
7856
|
projectRoot,
|
|
7940
|
-
diff,
|
|
7857
|
+
diff: diff2,
|
|
7941
7858
|
commitMessage,
|
|
7942
7859
|
flags,
|
|
7943
7860
|
graph,
|
|
@@ -7971,7 +7888,7 @@ async function runReviewPipeline(options) {
|
|
|
7971
7888
|
const mechResult = await runMechanicalChecks({
|
|
7972
7889
|
projectRoot,
|
|
7973
7890
|
config,
|
|
7974
|
-
changedFiles:
|
|
7891
|
+
changedFiles: diff2.changedFiles
|
|
7975
7892
|
});
|
|
7976
7893
|
if (mechResult.ok) {
|
|
7977
7894
|
mechanicalResult = mechResult.value;
|
|
@@ -8010,7 +7927,7 @@ async function runReviewPipeline(options) {
|
|
|
8010
7927
|
try {
|
|
8011
7928
|
contextBundles = await scopeContext({
|
|
8012
7929
|
projectRoot,
|
|
8013
|
-
diff,
|
|
7930
|
+
diff: diff2,
|
|
8014
7931
|
commitMessage,
|
|
8015
7932
|
...graph != null ? { graph } : {},
|
|
8016
7933
|
...conventionFiles != null ? { conventionFiles } : {},
|
|
@@ -8024,14 +7941,14 @@ async function runReviewPipeline(options) {
|
|
|
8024
7941
|
changedFiles: [],
|
|
8025
7942
|
contextFiles: [],
|
|
8026
7943
|
commitHistory: [],
|
|
8027
|
-
diffLines:
|
|
7944
|
+
diffLines: diff2.totalDiffLines,
|
|
8028
7945
|
contextLines: 0
|
|
8029
7946
|
}));
|
|
8030
7947
|
}
|
|
8031
7948
|
const agentResults = await fanOutReview({ bundles: contextBundles });
|
|
8032
7949
|
const rawFindings = agentResults.flatMap((r) => r.findings);
|
|
8033
7950
|
const fileContents = /* @__PURE__ */ new Map();
|
|
8034
|
-
for (const [file, content] of
|
|
7951
|
+
for (const [file, content] of diff2.fileDiffs) {
|
|
8035
7952
|
fileContents.set(file, content);
|
|
8036
7953
|
}
|
|
8037
7954
|
const validatedFindings = await validateFindings({
|
|
@@ -8230,7 +8147,7 @@ function serializeFeature(feature) {
|
|
|
8230
8147
|
}
|
|
8231
8148
|
|
|
8232
8149
|
// src/roadmap/sync.ts
|
|
8233
|
-
import * as
|
|
8150
|
+
import * as fs9 from "fs";
|
|
8234
8151
|
import * as path9 from "path";
|
|
8235
8152
|
import { Ok as Ok3 } from "@harness-engineering/types";
|
|
8236
8153
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
@@ -8247,9 +8164,9 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8247
8164
|
const useRootState = featuresWithPlans.length <= 1;
|
|
8248
8165
|
if (useRootState) {
|
|
8249
8166
|
const rootStatePath = path9.join(projectPath, ".harness", "state.json");
|
|
8250
|
-
if (
|
|
8167
|
+
if (fs9.existsSync(rootStatePath)) {
|
|
8251
8168
|
try {
|
|
8252
|
-
const raw =
|
|
8169
|
+
const raw = fs9.readFileSync(rootStatePath, "utf-8");
|
|
8253
8170
|
const state = JSON.parse(raw);
|
|
8254
8171
|
if (state.progress) {
|
|
8255
8172
|
for (const status of Object.values(state.progress)) {
|
|
@@ -8261,15 +8178,15 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8261
8178
|
}
|
|
8262
8179
|
}
|
|
8263
8180
|
const sessionsDir = path9.join(projectPath, ".harness", "sessions");
|
|
8264
|
-
if (
|
|
8181
|
+
if (fs9.existsSync(sessionsDir)) {
|
|
8265
8182
|
try {
|
|
8266
|
-
const sessionDirs =
|
|
8183
|
+
const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
|
|
8267
8184
|
for (const entry of sessionDirs) {
|
|
8268
8185
|
if (!entry.isDirectory()) continue;
|
|
8269
8186
|
const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
8270
|
-
if (!
|
|
8187
|
+
if (!fs9.existsSync(autopilotPath)) continue;
|
|
8271
8188
|
try {
|
|
8272
|
-
const raw =
|
|
8189
|
+
const raw = fs9.readFileSync(autopilotPath, "utf-8");
|
|
8273
8190
|
const autopilot = JSON.parse(raw);
|
|
8274
8191
|
if (!autopilot.phases) continue;
|
|
8275
8192
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -8320,42 +8237,210 @@ function syncRoadmap(options) {
|
|
|
8320
8237
|
}
|
|
8321
8238
|
|
|
8322
8239
|
// src/interaction/types.ts
|
|
8323
|
-
import { z as
|
|
8324
|
-
var InteractionTypeSchema =
|
|
8325
|
-
var QuestionSchema =
|
|
8326
|
-
text:
|
|
8327
|
-
options:
|
|
8328
|
-
default:
|
|
8240
|
+
import { z as z6 } from "zod";
|
|
8241
|
+
var InteractionTypeSchema = z6.enum(["question", "confirmation", "transition"]);
|
|
8242
|
+
var QuestionSchema = z6.object({
|
|
8243
|
+
text: z6.string(),
|
|
8244
|
+
options: z6.array(z6.string()).optional(),
|
|
8245
|
+
default: z6.string().optional()
|
|
8329
8246
|
});
|
|
8330
|
-
var ConfirmationSchema =
|
|
8331
|
-
text:
|
|
8332
|
-
context:
|
|
8247
|
+
var ConfirmationSchema = z6.object({
|
|
8248
|
+
text: z6.string(),
|
|
8249
|
+
context: z6.string()
|
|
8333
8250
|
});
|
|
8334
|
-
var TransitionSchema =
|
|
8335
|
-
completedPhase:
|
|
8336
|
-
suggestedNext:
|
|
8337
|
-
reason:
|
|
8338
|
-
artifacts:
|
|
8339
|
-
requiresConfirmation:
|
|
8340
|
-
summary:
|
|
8251
|
+
var TransitionSchema = z6.object({
|
|
8252
|
+
completedPhase: z6.string(),
|
|
8253
|
+
suggestedNext: z6.string(),
|
|
8254
|
+
reason: z6.string(),
|
|
8255
|
+
artifacts: z6.array(z6.string()),
|
|
8256
|
+
requiresConfirmation: z6.boolean(),
|
|
8257
|
+
summary: z6.string()
|
|
8341
8258
|
});
|
|
8342
|
-
var EmitInteractionInputSchema =
|
|
8343
|
-
path:
|
|
8259
|
+
var EmitInteractionInputSchema = z6.object({
|
|
8260
|
+
path: z6.string(),
|
|
8344
8261
|
type: InteractionTypeSchema,
|
|
8345
|
-
stream:
|
|
8262
|
+
stream: z6.string().optional(),
|
|
8346
8263
|
question: QuestionSchema.optional(),
|
|
8347
8264
|
confirmation: ConfirmationSchema.optional(),
|
|
8348
8265
|
transition: TransitionSchema.optional()
|
|
8349
8266
|
});
|
|
8350
8267
|
|
|
8351
|
-
// src/
|
|
8352
|
-
import * as
|
|
8268
|
+
// src/blueprint/scanner.ts
|
|
8269
|
+
import * as fs10 from "fs/promises";
|
|
8353
8270
|
import * as path10 from "path";
|
|
8271
|
+
var ProjectScanner = class {
|
|
8272
|
+
constructor(rootDir) {
|
|
8273
|
+
this.rootDir = rootDir;
|
|
8274
|
+
}
|
|
8275
|
+
async scan() {
|
|
8276
|
+
let projectName = path10.basename(this.rootDir);
|
|
8277
|
+
try {
|
|
8278
|
+
const pkgPath = path10.join(this.rootDir, "package.json");
|
|
8279
|
+
const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
|
|
8280
|
+
const pkg = JSON.parse(pkgRaw);
|
|
8281
|
+
if (pkg.name) projectName = pkg.name;
|
|
8282
|
+
} catch {
|
|
8283
|
+
}
|
|
8284
|
+
return {
|
|
8285
|
+
projectName,
|
|
8286
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8287
|
+
modules: [
|
|
8288
|
+
{
|
|
8289
|
+
id: "foundations",
|
|
8290
|
+
title: "Foundations",
|
|
8291
|
+
description: "Utility files and basic types.",
|
|
8292
|
+
files: []
|
|
8293
|
+
},
|
|
8294
|
+
{
|
|
8295
|
+
id: "core-logic",
|
|
8296
|
+
title: "Core Logic",
|
|
8297
|
+
description: "Mid-level services and domain logic.",
|
|
8298
|
+
files: []
|
|
8299
|
+
},
|
|
8300
|
+
{
|
|
8301
|
+
id: "interaction-surface",
|
|
8302
|
+
title: "Interaction Surface",
|
|
8303
|
+
description: "APIs, CLIs, and Entry Points.",
|
|
8304
|
+
files: []
|
|
8305
|
+
},
|
|
8306
|
+
{
|
|
8307
|
+
id: "cross-cutting",
|
|
8308
|
+
title: "Cross-Cutting Concerns",
|
|
8309
|
+
description: "Security, Logging, and Observability.",
|
|
8310
|
+
files: []
|
|
8311
|
+
}
|
|
8312
|
+
],
|
|
8313
|
+
hotspots: [],
|
|
8314
|
+
dependencies: []
|
|
8315
|
+
};
|
|
8316
|
+
}
|
|
8317
|
+
};
|
|
8318
|
+
|
|
8319
|
+
// src/blueprint/generator.ts
|
|
8320
|
+
import * as fs11 from "fs/promises";
|
|
8321
|
+
import * as path11 from "path";
|
|
8322
|
+
import * as ejs from "ejs";
|
|
8323
|
+
|
|
8324
|
+
// src/blueprint/templates.ts
|
|
8325
|
+
var SHELL_TEMPLATE = `
|
|
8326
|
+
<!DOCTYPE html>
|
|
8327
|
+
<html lang="en">
|
|
8328
|
+
<head>
|
|
8329
|
+
<meta charset="UTF-8">
|
|
8330
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8331
|
+
<title>Blueprint: <%= projectName %></title>
|
|
8332
|
+
<style><%- styles %></style>
|
|
8333
|
+
</head>
|
|
8334
|
+
<body>
|
|
8335
|
+
<div id="app">
|
|
8336
|
+
<header>
|
|
8337
|
+
<h1>Blueprint: <%= projectName %></h1>
|
|
8338
|
+
<p>Generated at: <%= generatedAt %></p>
|
|
8339
|
+
</header>
|
|
8340
|
+
<main>
|
|
8341
|
+
<section class="modules">
|
|
8342
|
+
<% modules.forEach(module => { %>
|
|
8343
|
+
<article class="module" id="<%= module.id %>">
|
|
8344
|
+
<h2><%= module.title %></h2>
|
|
8345
|
+
<p><%= module.description %></p>
|
|
8346
|
+
<div class="content">
|
|
8347
|
+
<h3>Code Translation</h3>
|
|
8348
|
+
<div class="translation"><%- module.content.codeTranslation %></div>
|
|
8349
|
+
<h3>Knowledge Check</h3>
|
|
8350
|
+
<div class="quiz">
|
|
8351
|
+
<% module.content.quiz.questions.forEach((q, i) => { %>
|
|
8352
|
+
<div class="question">
|
|
8353
|
+
<p><%= q.question %></p>
|
|
8354
|
+
<button onclick="reveal(this)">Reveal Answer</button>
|
|
8355
|
+
<p class="answer" style="display:none;"><%= q.answer %></p>
|
|
8356
|
+
</div>
|
|
8357
|
+
<% }) %>
|
|
8358
|
+
</div>
|
|
8359
|
+
</div>
|
|
8360
|
+
</article>
|
|
8361
|
+
|
|
8362
|
+
<% }) %>
|
|
8363
|
+
</section>
|
|
8364
|
+
</main>
|
|
8365
|
+
</div>
|
|
8366
|
+
<script><%- scripts %></script>
|
|
8367
|
+
</body>
|
|
8368
|
+
</html>
|
|
8369
|
+
`;
|
|
8370
|
+
var STYLES = `
|
|
8371
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; color: #333; }
|
|
8372
|
+
header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10px; }
|
|
8373
|
+
.module { background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-radius: 4px; }
|
|
8374
|
+
.module h2 { margin-top: 0; color: #0066cc; }
|
|
8375
|
+
`;
|
|
8376
|
+
var SCRIPTS = `
|
|
8377
|
+
function reveal(btn) {
|
|
8378
|
+
btn.nextElementSibling.style.display = 'block';
|
|
8379
|
+
btn.style.display = 'none';
|
|
8380
|
+
}
|
|
8381
|
+
console.log('Blueprint player initialized.');
|
|
8382
|
+
`;
|
|
8383
|
+
|
|
8384
|
+
// src/shared/llm.ts
|
|
8385
|
+
var MockLLMService = class {
|
|
8386
|
+
async generate(prompt) {
|
|
8387
|
+
return "This is a mock LLM response for: " + prompt;
|
|
8388
|
+
}
|
|
8389
|
+
};
|
|
8390
|
+
var llmService = new MockLLMService();
|
|
8391
|
+
|
|
8392
|
+
// src/blueprint/content-pipeline.ts
|
|
8393
|
+
var ContentPipeline = class {
|
|
8394
|
+
async generateModuleContent(module) {
|
|
8395
|
+
const codeContext = module.files.join("\n");
|
|
8396
|
+
const translation = await llmService.generate(
|
|
8397
|
+
`You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
|
|
8398
|
+
);
|
|
8399
|
+
const quizJson = await llmService.generate(
|
|
8400
|
+
`Create 3 technical quiz questions for this code. Return ONLY valid JSON in this format: { "questions": [{ "question": "...", "answer": "..." }] }. Code: ${codeContext}`
|
|
8401
|
+
);
|
|
8402
|
+
let quiz;
|
|
8403
|
+
try {
|
|
8404
|
+
const cleanJson = quizJson.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
8405
|
+
quiz = JSON.parse(cleanJson);
|
|
8406
|
+
} catch (e) {
|
|
8407
|
+
console.error("Failed to parse quiz JSON", e);
|
|
8408
|
+
quiz = { questions: [{ question: "Failed to generate quiz", answer: "N/A" }] };
|
|
8409
|
+
}
|
|
8410
|
+
return {
|
|
8411
|
+
codeTranslation: translation,
|
|
8412
|
+
quiz
|
|
8413
|
+
};
|
|
8414
|
+
}
|
|
8415
|
+
};
|
|
8416
|
+
|
|
8417
|
+
// src/blueprint/generator.ts
|
|
8418
|
+
var BlueprintGenerator = class {
|
|
8419
|
+
contentPipeline = new ContentPipeline();
|
|
8420
|
+
async generate(data, options) {
|
|
8421
|
+
await Promise.all(
|
|
8422
|
+
data.modules.map(async (module) => {
|
|
8423
|
+
module.content = await this.contentPipeline.generateModuleContent(module);
|
|
8424
|
+
})
|
|
8425
|
+
);
|
|
8426
|
+
const html = ejs.render(SHELL_TEMPLATE, {
|
|
8427
|
+
...data,
|
|
8428
|
+
styles: STYLES,
|
|
8429
|
+
scripts: SCRIPTS
|
|
8430
|
+
});
|
|
8431
|
+
await fs11.mkdir(options.outputDir, { recursive: true });
|
|
8432
|
+
await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
|
|
8433
|
+
}
|
|
8434
|
+
};
|
|
8435
|
+
|
|
8436
|
+
// src/update-checker.ts
|
|
8437
|
+
import * as fs12 from "fs";
|
|
8438
|
+
import * as path12 from "path";
|
|
8354
8439
|
import * as os from "os";
|
|
8355
8440
|
import { spawn } from "child_process";
|
|
8356
8441
|
function getStatePath() {
|
|
8357
8442
|
const home = process.env["HOME"] || os.homedir();
|
|
8358
|
-
return
|
|
8443
|
+
return path12.join(home, ".harness", "update-check.json");
|
|
8359
8444
|
}
|
|
8360
8445
|
function isUpdateCheckEnabled(configInterval) {
|
|
8361
8446
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -8368,7 +8453,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
8368
8453
|
}
|
|
8369
8454
|
function readCheckState() {
|
|
8370
8455
|
try {
|
|
8371
|
-
const raw =
|
|
8456
|
+
const raw = fs12.readFileSync(getStatePath(), "utf-8");
|
|
8372
8457
|
const parsed = JSON.parse(raw);
|
|
8373
8458
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
8374
8459
|
const state = parsed;
|
|
@@ -8385,7 +8470,7 @@ function readCheckState() {
|
|
|
8385
8470
|
}
|
|
8386
8471
|
function spawnBackgroundCheck(currentVersion) {
|
|
8387
8472
|
const statePath = getStatePath();
|
|
8388
|
-
const stateDir =
|
|
8473
|
+
const stateDir = path12.dirname(statePath);
|
|
8389
8474
|
const script = `
|
|
8390
8475
|
const { execSync } = require('child_process');
|
|
8391
8476
|
const fs = require('fs');
|
|
@@ -8439,38 +8524,63 @@ Run "harness update" to upgrade.`;
|
|
|
8439
8524
|
}
|
|
8440
8525
|
|
|
8441
8526
|
// src/index.ts
|
|
8442
|
-
var VERSION = "
|
|
8527
|
+
var VERSION = "0.11.0";
|
|
8443
8528
|
export {
|
|
8444
8529
|
AGENT_DESCRIPTORS,
|
|
8445
8530
|
ARCHITECTURE_DESCRIPTOR,
|
|
8446
8531
|
AgentActionEmitter,
|
|
8532
|
+
ArchBaselineManager,
|
|
8533
|
+
ArchBaselineSchema,
|
|
8534
|
+
ArchConfigSchema,
|
|
8535
|
+
ArchDiffResultSchema,
|
|
8536
|
+
ArchMetricCategorySchema,
|
|
8447
8537
|
BUG_DETECTION_DESCRIPTOR,
|
|
8448
8538
|
BaselineManager,
|
|
8449
8539
|
BenchmarkRunner,
|
|
8540
|
+
BlueprintGenerator,
|
|
8541
|
+
BundleConstraintsSchema,
|
|
8542
|
+
BundleSchema,
|
|
8450
8543
|
COMPLIANCE_DESCRIPTOR,
|
|
8544
|
+
CategoryBaselineSchema,
|
|
8545
|
+
CategoryRegressionSchema,
|
|
8451
8546
|
ChecklistBuilder,
|
|
8547
|
+
CircularDepsCollector,
|
|
8548
|
+
ComplexityCollector,
|
|
8452
8549
|
ConfirmationSchema,
|
|
8453
8550
|
ConsoleSink,
|
|
8551
|
+
ConstraintRuleSchema,
|
|
8552
|
+
ContentPipeline,
|
|
8553
|
+
ContributionsSchema,
|
|
8554
|
+
CouplingCollector,
|
|
8454
8555
|
CriticalPathResolver,
|
|
8455
8556
|
DEFAULT_PROVIDER_TIERS,
|
|
8456
8557
|
DEFAULT_SECURITY_CONFIG,
|
|
8457
8558
|
DEFAULT_STATE,
|
|
8458
8559
|
DEFAULT_STREAM_INDEX,
|
|
8560
|
+
DepDepthCollector,
|
|
8459
8561
|
EmitInteractionInputSchema,
|
|
8460
8562
|
EntropyAnalyzer,
|
|
8461
8563
|
EntropyConfigSchema,
|
|
8462
8564
|
ExclusionSet,
|
|
8463
8565
|
FailureEntrySchema,
|
|
8464
8566
|
FileSink,
|
|
8567
|
+
ForbiddenImportCollector,
|
|
8465
8568
|
GateConfigSchema,
|
|
8466
8569
|
GateResultSchema,
|
|
8467
8570
|
HandoffSchema,
|
|
8468
8571
|
HarnessStateSchema,
|
|
8469
8572
|
InteractionTypeSchema,
|
|
8573
|
+
LayerViolationCollector,
|
|
8574
|
+
LockfilePackageSchema,
|
|
8575
|
+
LockfileSchema,
|
|
8576
|
+
ManifestSchema,
|
|
8577
|
+
MetricResultSchema,
|
|
8578
|
+
ModuleSizeCollector,
|
|
8470
8579
|
NoOpExecutor,
|
|
8471
8580
|
NoOpSink,
|
|
8472
8581
|
NoOpTelemetryAdapter,
|
|
8473
8582
|
PatternConfigSchema,
|
|
8583
|
+
ProjectScanner,
|
|
8474
8584
|
QuestionSchema,
|
|
8475
8585
|
REQUIRED_SECTIONS,
|
|
8476
8586
|
RegressionDetector,
|
|
@@ -8478,16 +8588,26 @@ export {
|
|
|
8478
8588
|
SECURITY_DESCRIPTOR,
|
|
8479
8589
|
SecurityConfigSchema,
|
|
8480
8590
|
SecurityScanner,
|
|
8591
|
+
SharableBoundaryConfigSchema,
|
|
8592
|
+
SharableForbiddenImportSchema,
|
|
8593
|
+
SharableLayerSchema,
|
|
8594
|
+
SharableSecurityRulesSchema,
|
|
8481
8595
|
StreamIndexSchema,
|
|
8482
8596
|
StreamInfoSchema,
|
|
8597
|
+
ThresholdConfigSchema,
|
|
8483
8598
|
TransitionSchema,
|
|
8484
8599
|
TypeScriptParser,
|
|
8485
8600
|
VERSION,
|
|
8601
|
+
ViolationSchema,
|
|
8602
|
+
addProvenance,
|
|
8486
8603
|
analyzeDiff,
|
|
8487
8604
|
appendFailure,
|
|
8488
8605
|
appendLearning,
|
|
8489
8606
|
applyFixes,
|
|
8490
8607
|
applyHotspotDowngrade,
|
|
8608
|
+
archMatchers,
|
|
8609
|
+
archModule,
|
|
8610
|
+
architecture,
|
|
8491
8611
|
archiveFailures,
|
|
8492
8612
|
archiveStream,
|
|
8493
8613
|
buildDependencyGraph,
|
|
@@ -8497,6 +8617,7 @@ export {
|
|
|
8497
8617
|
checkEligibility,
|
|
8498
8618
|
classifyFinding,
|
|
8499
8619
|
configureFeedback,
|
|
8620
|
+
constraintRuleId,
|
|
8500
8621
|
contextBudget,
|
|
8501
8622
|
contextFilter,
|
|
8502
8623
|
createBoundaryValidator,
|
|
@@ -8511,6 +8632,8 @@ export {
|
|
|
8511
8632
|
cryptoRules,
|
|
8512
8633
|
deduplicateCleanupFindings,
|
|
8513
8634
|
deduplicateFindings,
|
|
8635
|
+
deepMergeConstraints,
|
|
8636
|
+
defaultCollectors,
|
|
8514
8637
|
defineLayer,
|
|
8515
8638
|
deserializationRules,
|
|
8516
8639
|
detectChangeType,
|
|
@@ -8523,9 +8646,12 @@ export {
|
|
|
8523
8646
|
detectPatternViolations,
|
|
8524
8647
|
detectSizeBudgetViolations,
|
|
8525
8648
|
detectStack,
|
|
8649
|
+
detectStaleConstraints,
|
|
8526
8650
|
determineAssessment,
|
|
8651
|
+
diff,
|
|
8527
8652
|
executeWorkflow,
|
|
8528
8653
|
expressRules,
|
|
8654
|
+
extractBundle,
|
|
8529
8655
|
extractMarkdownLinks,
|
|
8530
8656
|
extractSections,
|
|
8531
8657
|
fanOutReview,
|
|
@@ -8556,6 +8682,7 @@ export {
|
|
|
8556
8682
|
networkRules,
|
|
8557
8683
|
nodeRules,
|
|
8558
8684
|
parseDiff,
|
|
8685
|
+
parseManifest,
|
|
8559
8686
|
parseRoadmap,
|
|
8560
8687
|
parseSecurityConfig,
|
|
8561
8688
|
parseSize,
|
|
@@ -8563,6 +8690,8 @@ export {
|
|
|
8563
8690
|
previewFix,
|
|
8564
8691
|
reactRules,
|
|
8565
8692
|
readCheckState,
|
|
8693
|
+
readLockfile,
|
|
8694
|
+
removeProvenance,
|
|
8566
8695
|
requestMultiplePeerReviews,
|
|
8567
8696
|
requestPeerReview,
|
|
8568
8697
|
resetFeedbackConfig,
|
|
@@ -8570,6 +8699,8 @@ export {
|
|
|
8570
8699
|
resolveModelTier,
|
|
8571
8700
|
resolveRuleSeverity,
|
|
8572
8701
|
resolveStreamPath,
|
|
8702
|
+
resolveThresholds,
|
|
8703
|
+
runAll,
|
|
8573
8704
|
runArchitectureAgent,
|
|
8574
8705
|
runBugDetectionAgent,
|
|
8575
8706
|
runCIChecks,
|
|
@@ -8589,6 +8720,7 @@ export {
|
|
|
8589
8720
|
setActiveStream,
|
|
8590
8721
|
shouldRunCheck,
|
|
8591
8722
|
spawnBackgroundCheck,
|
|
8723
|
+
syncConstraintNodes,
|
|
8592
8724
|
syncRoadmap,
|
|
8593
8725
|
touchStream,
|
|
8594
8726
|
trackAction,
|
|
@@ -8601,5 +8733,8 @@ export {
|
|
|
8601
8733
|
validateFindings,
|
|
8602
8734
|
validateKnowledgeMap,
|
|
8603
8735
|
validatePatternConfig,
|
|
8736
|
+
violationId,
|
|
8737
|
+
writeConfig,
|
|
8738
|
+
writeLockfile,
|
|
8604
8739
|
xssRules
|
|
8605
8740
|
};
|