@harness-engineering/core 0.10.1 → 0.12.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 +885 -23
- package/dist/index.d.ts +885 -23
- package/dist/index.js +2286 -320
- package/dist/index.mjs +1205 -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,491 @@ 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
|
+
thresholds: {},
|
|
1144
|
+
modules: {}
|
|
1145
|
+
};
|
|
1146
|
+
const mergedThresholds = { ...localArch.thresholds };
|
|
1147
|
+
const contributedThresholdKeys = [];
|
|
1148
|
+
const bundleThresholds = bundleConstraints.architecture.thresholds ?? {};
|
|
1149
|
+
for (const [category, value] of Object.entries(bundleThresholds)) {
|
|
1150
|
+
if (!(category in mergedThresholds)) {
|
|
1151
|
+
mergedThresholds[category] = value;
|
|
1152
|
+
contributedThresholdKeys.push(category);
|
|
1153
|
+
} else if (!deepEqual(mergedThresholds[category], value)) {
|
|
1154
|
+
conflicts.push({
|
|
1155
|
+
section: "architecture.thresholds",
|
|
1156
|
+
key: category,
|
|
1157
|
+
localValue: mergedThresholds[category],
|
|
1158
|
+
packageValue: value,
|
|
1159
|
+
description: `Architecture threshold '${category}' already exists locally with a different value`
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const mergedModules = { ...localArch.modules };
|
|
1164
|
+
const contributedModuleKeys = [];
|
|
1165
|
+
const bundleModules = bundleConstraints.architecture.modules ?? {};
|
|
1166
|
+
for (const [modulePath, bundleCategoryMap] of Object.entries(bundleModules)) {
|
|
1167
|
+
if (!(modulePath in mergedModules)) {
|
|
1168
|
+
mergedModules[modulePath] = bundleCategoryMap;
|
|
1169
|
+
for (const cat of Object.keys(bundleCategoryMap)) {
|
|
1170
|
+
contributedModuleKeys.push(`${modulePath}:${cat}`);
|
|
1171
|
+
}
|
|
1172
|
+
} else {
|
|
1173
|
+
const localCategoryMap = mergedModules[modulePath];
|
|
1174
|
+
const mergedCategoryMap = { ...localCategoryMap };
|
|
1175
|
+
for (const [category, value] of Object.entries(bundleCategoryMap)) {
|
|
1176
|
+
if (!(category in mergedCategoryMap)) {
|
|
1177
|
+
mergedCategoryMap[category] = value;
|
|
1178
|
+
contributedModuleKeys.push(`${modulePath}:${category}`);
|
|
1179
|
+
} else if (!deepEqual(mergedCategoryMap[category], value)) {
|
|
1180
|
+
conflicts.push({
|
|
1181
|
+
section: "architecture.modules",
|
|
1182
|
+
key: `${modulePath}:${category}`,
|
|
1183
|
+
localValue: mergedCategoryMap[category],
|
|
1184
|
+
packageValue: value,
|
|
1185
|
+
description: `Architecture module override '${modulePath}' category '${category}' already exists locally with a different value`
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
mergedModules[modulePath] = mergedCategoryMap;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
config.architecture = {
|
|
1193
|
+
...localArch,
|
|
1194
|
+
thresholds: mergedThresholds,
|
|
1195
|
+
modules: mergedModules
|
|
1196
|
+
};
|
|
1197
|
+
if (contributedThresholdKeys.length > 0) {
|
|
1198
|
+
contributions["architecture.thresholds"] = contributedThresholdKeys;
|
|
1199
|
+
}
|
|
1200
|
+
if (contributedModuleKeys.length > 0) {
|
|
1201
|
+
contributions["architecture.modules"] = contributedModuleKeys;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
if (bundleConstraints.security?.rules) {
|
|
1205
|
+
const localSecurity = localConfig.security ?? { rules: {} };
|
|
1206
|
+
const localRules = localSecurity.rules ?? {};
|
|
1207
|
+
const mergedRules = { ...localRules };
|
|
1208
|
+
const contributedRuleIds = [];
|
|
1209
|
+
for (const [ruleId, severity] of Object.entries(bundleConstraints.security.rules)) {
|
|
1210
|
+
if (!(ruleId in mergedRules)) {
|
|
1211
|
+
mergedRules[ruleId] = severity;
|
|
1212
|
+
contributedRuleIds.push(ruleId);
|
|
1213
|
+
} else if (mergedRules[ruleId] !== severity) {
|
|
1214
|
+
conflicts.push({
|
|
1215
|
+
section: "security.rules",
|
|
1216
|
+
key: ruleId,
|
|
1217
|
+
localValue: mergedRules[ruleId],
|
|
1218
|
+
packageValue: severity,
|
|
1219
|
+
description: `Security rule '${ruleId}' already exists locally with severity '${mergedRules[ruleId]}', bundle has '${severity}'`
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
config.security = { ...localSecurity, rules: mergedRules };
|
|
1224
|
+
if (contributedRuleIds.length > 0) {
|
|
1225
|
+
contributions["security.rules"] = contributedRuleIds;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return { config, contributions, conflicts };
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/constraints/sharing/lockfile.ts
|
|
1232
|
+
import * as fs2 from "fs/promises";
|
|
1233
|
+
async function readLockfile(lockfilePath) {
|
|
1234
|
+
let raw;
|
|
1235
|
+
try {
|
|
1236
|
+
raw = await fs2.readFile(lockfilePath, "utf-8");
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
if (isNodeError(err) && err.code === "ENOENT") {
|
|
1239
|
+
return { ok: true, value: null };
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
ok: false,
|
|
1243
|
+
error: `Failed to read lockfile: ${err instanceof Error ? err.message : String(err)}`
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
let parsed;
|
|
1247
|
+
try {
|
|
1248
|
+
parsed = JSON.parse(raw);
|
|
1249
|
+
} catch {
|
|
1250
|
+
return {
|
|
1251
|
+
ok: false,
|
|
1252
|
+
error: `Failed to parse lockfile as JSON: file contains invalid JSON`
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
const result = LockfileSchema.safeParse(parsed);
|
|
1256
|
+
if (!result.success) {
|
|
1257
|
+
return {
|
|
1258
|
+
ok: false,
|
|
1259
|
+
error: `Lockfile schema validation failed: ${result.error.issues.map((i) => i.message).join(", ")}`
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
return { ok: true, value: result.data };
|
|
1263
|
+
}
|
|
1264
|
+
async function writeLockfile(lockfilePath, lockfile) {
|
|
1265
|
+
return writeConfig(lockfilePath, lockfile);
|
|
1266
|
+
}
|
|
1267
|
+
function addProvenance(lockfile, packageName, entry) {
|
|
1268
|
+
return {
|
|
1269
|
+
...lockfile,
|
|
1270
|
+
packages: {
|
|
1271
|
+
...lockfile.packages,
|
|
1272
|
+
[packageName]: entry
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
function removeProvenance(lockfile, packageName) {
|
|
1277
|
+
const existing = lockfile.packages[packageName];
|
|
1278
|
+
if (!existing) {
|
|
1279
|
+
return { lockfile, contributions: null };
|
|
1280
|
+
}
|
|
1281
|
+
const { [packageName]: _removed, ...remaining } = lockfile.packages;
|
|
1282
|
+
return {
|
|
1283
|
+
lockfile: {
|
|
1284
|
+
...lockfile,
|
|
1285
|
+
packages: remaining
|
|
1286
|
+
},
|
|
1287
|
+
contributions: existing.contributions ?? null
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function isNodeError(err) {
|
|
1291
|
+
return err instanceof Error && "code" in err;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// src/constraints/sharing/remove.ts
|
|
1295
|
+
function removeContributions(config, contributions) {
|
|
1296
|
+
const result = { ...config };
|
|
1297
|
+
const layerNames = contributions.layers;
|
|
1298
|
+
if (layerNames && layerNames.length > 0 && Array.isArray(result.layers)) {
|
|
1299
|
+
const nameSet = new Set(layerNames);
|
|
1300
|
+
result.layers = result.layers.filter((l) => !nameSet.has(l.name));
|
|
1301
|
+
}
|
|
1302
|
+
const fromKeys = contributions.forbiddenImports;
|
|
1303
|
+
if (fromKeys && fromKeys.length > 0 && Array.isArray(result.forbiddenImports)) {
|
|
1304
|
+
const fromSet = new Set(fromKeys);
|
|
1305
|
+
result.forbiddenImports = result.forbiddenImports.filter(
|
|
1306
|
+
(r) => !fromSet.has(r.from)
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
const boundarySchemas = contributions.boundaries;
|
|
1310
|
+
if (boundarySchemas && boundarySchemas.length > 0 && result.boundaries) {
|
|
1311
|
+
const boundaries = result.boundaries;
|
|
1312
|
+
if (boundaries.requireSchema) {
|
|
1313
|
+
const schemaSet = new Set(boundarySchemas);
|
|
1314
|
+
result.boundaries = {
|
|
1315
|
+
...boundaries,
|
|
1316
|
+
requireSchema: boundaries.requireSchema.filter((s) => !schemaSet.has(s))
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const thresholdKeys = contributions["architecture.thresholds"];
|
|
1321
|
+
if (thresholdKeys && thresholdKeys.length > 0 && result.architecture) {
|
|
1322
|
+
const arch = { ...result.architecture };
|
|
1323
|
+
const thresholds = { ...arch.thresholds };
|
|
1324
|
+
for (const key of thresholdKeys) {
|
|
1325
|
+
delete thresholds[key];
|
|
1326
|
+
}
|
|
1327
|
+
arch.thresholds = thresholds;
|
|
1328
|
+
result.architecture = arch;
|
|
1329
|
+
}
|
|
1330
|
+
const moduleKeys = contributions["architecture.modules"];
|
|
1331
|
+
if (moduleKeys && moduleKeys.length > 0 && result.architecture) {
|
|
1332
|
+
const arch = { ...result.architecture };
|
|
1333
|
+
const modules = { ...arch.modules };
|
|
1334
|
+
for (const key of moduleKeys) {
|
|
1335
|
+
const colonIdx = key.indexOf(":");
|
|
1336
|
+
if (colonIdx === -1) continue;
|
|
1337
|
+
const modulePath = key.substring(0, colonIdx);
|
|
1338
|
+
const category = key.substring(colonIdx + 1);
|
|
1339
|
+
if (modules[modulePath]) {
|
|
1340
|
+
const moduleCategories = { ...modules[modulePath] };
|
|
1341
|
+
delete moduleCategories[category];
|
|
1342
|
+
if (Object.keys(moduleCategories).length === 0) {
|
|
1343
|
+
delete modules[modulePath];
|
|
1344
|
+
} else {
|
|
1345
|
+
modules[modulePath] = moduleCategories;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
arch.modules = modules;
|
|
1350
|
+
result.architecture = arch;
|
|
1351
|
+
}
|
|
1352
|
+
const ruleIds = contributions["security.rules"];
|
|
1353
|
+
if (ruleIds && ruleIds.length > 0 && result.security) {
|
|
1354
|
+
const security = { ...result.security };
|
|
1355
|
+
const rules = { ...security.rules };
|
|
1356
|
+
for (const id of ruleIds) {
|
|
1357
|
+
delete rules[id];
|
|
1358
|
+
}
|
|
1359
|
+
security.rules = rules;
|
|
1360
|
+
result.security = security;
|
|
1361
|
+
}
|
|
1362
|
+
return result;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1122
1365
|
// src/shared/parsers/typescript.ts
|
|
1123
1366
|
import { parse } from "@typescript-eslint/typescript-estree";
|
|
1124
1367
|
|
|
@@ -1144,11 +1387,11 @@ function walk(node, visitor) {
|
|
|
1144
1387
|
var TypeScriptParser = class {
|
|
1145
1388
|
name = "typescript";
|
|
1146
1389
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1147
|
-
async parseFile(
|
|
1148
|
-
const contentResult = await readFileContent(
|
|
1390
|
+
async parseFile(path13) {
|
|
1391
|
+
const contentResult = await readFileContent(path13);
|
|
1149
1392
|
if (!contentResult.ok) {
|
|
1150
1393
|
return Err(
|
|
1151
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1394
|
+
createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
|
|
1152
1395
|
"Check that the file exists",
|
|
1153
1396
|
"Verify the path is correct"
|
|
1154
1397
|
])
|
|
@@ -1158,7 +1401,7 @@ var TypeScriptParser = class {
|
|
|
1158
1401
|
const ast = parse(contentResult.value, {
|
|
1159
1402
|
loc: true,
|
|
1160
1403
|
range: true,
|
|
1161
|
-
jsx:
|
|
1404
|
+
jsx: path13.endsWith(".tsx"),
|
|
1162
1405
|
errorOnUnknownASTType: false
|
|
1163
1406
|
});
|
|
1164
1407
|
return Ok({
|
|
@@ -1169,7 +1412,7 @@ var TypeScriptParser = class {
|
|
|
1169
1412
|
} catch (e) {
|
|
1170
1413
|
const error = e;
|
|
1171
1414
|
return Err(
|
|
1172
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1415
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
|
|
1173
1416
|
"Check for syntax errors in the file",
|
|
1174
1417
|
"Ensure valid TypeScript syntax"
|
|
1175
1418
|
])
|
|
@@ -1335,11 +1578,11 @@ var TypeScriptParser = class {
|
|
|
1335
1578
|
};
|
|
1336
1579
|
|
|
1337
1580
|
// src/entropy/snapshot.ts
|
|
1338
|
-
import { join as join3, resolve
|
|
1581
|
+
import { join as join3, resolve, relative as relative4 } from "path";
|
|
1339
1582
|
import { minimatch as minimatch2 } from "minimatch";
|
|
1340
1583
|
async function resolveEntryPoints(rootDir, explicitEntries) {
|
|
1341
1584
|
if (explicitEntries && explicitEntries.length > 0) {
|
|
1342
|
-
const resolved = explicitEntries.map((e) =>
|
|
1585
|
+
const resolved = explicitEntries.map((e) => resolve(rootDir, e));
|
|
1343
1586
|
return Ok(resolved);
|
|
1344
1587
|
}
|
|
1345
1588
|
const pkgPath = join3(rootDir, "package.json");
|
|
@@ -1352,27 +1595,27 @@ async function resolveEntryPoints(rootDir, explicitEntries) {
|
|
|
1352
1595
|
if (pkg["exports"]) {
|
|
1353
1596
|
const exports = pkg["exports"];
|
|
1354
1597
|
if (typeof exports === "string") {
|
|
1355
|
-
entries.push(
|
|
1598
|
+
entries.push(resolve(rootDir, exports));
|
|
1356
1599
|
} else if (typeof exports === "object" && exports !== null) {
|
|
1357
1600
|
for (const value of Object.values(exports)) {
|
|
1358
1601
|
if (typeof value === "string") {
|
|
1359
|
-
entries.push(
|
|
1602
|
+
entries.push(resolve(rootDir, value));
|
|
1360
1603
|
}
|
|
1361
1604
|
}
|
|
1362
1605
|
}
|
|
1363
1606
|
}
|
|
1364
1607
|
const main = pkg["main"];
|
|
1365
1608
|
if (typeof main === "string" && entries.length === 0) {
|
|
1366
|
-
entries.push(
|
|
1609
|
+
entries.push(resolve(rootDir, main));
|
|
1367
1610
|
}
|
|
1368
1611
|
const bin = pkg["bin"];
|
|
1369
1612
|
if (bin) {
|
|
1370
1613
|
if (typeof bin === "string") {
|
|
1371
|
-
entries.push(
|
|
1614
|
+
entries.push(resolve(rootDir, bin));
|
|
1372
1615
|
} else if (typeof bin === "object") {
|
|
1373
1616
|
for (const value of Object.values(bin)) {
|
|
1374
1617
|
if (typeof value === "string") {
|
|
1375
|
-
entries.push(
|
|
1618
|
+
entries.push(resolve(rootDir, value));
|
|
1376
1619
|
}
|
|
1377
1620
|
}
|
|
1378
1621
|
}
|
|
@@ -1453,22 +1696,22 @@ function extractInlineRefs(content) {
|
|
|
1453
1696
|
}
|
|
1454
1697
|
return refs;
|
|
1455
1698
|
}
|
|
1456
|
-
async function parseDocumentationFile(
|
|
1457
|
-
const contentResult = await readFileContent(
|
|
1699
|
+
async function parseDocumentationFile(path13) {
|
|
1700
|
+
const contentResult = await readFileContent(path13);
|
|
1458
1701
|
if (!contentResult.ok) {
|
|
1459
1702
|
return Err(
|
|
1460
1703
|
createEntropyError(
|
|
1461
1704
|
"PARSE_ERROR",
|
|
1462
|
-
`Failed to read documentation file: ${
|
|
1463
|
-
{ file:
|
|
1705
|
+
`Failed to read documentation file: ${path13}`,
|
|
1706
|
+
{ file: path13 },
|
|
1464
1707
|
["Check that the file exists"]
|
|
1465
1708
|
)
|
|
1466
1709
|
);
|
|
1467
1710
|
}
|
|
1468
1711
|
const content = contentResult.value;
|
|
1469
|
-
const type =
|
|
1712
|
+
const type = path13.endsWith(".md") ? "markdown" : "text";
|
|
1470
1713
|
return Ok({
|
|
1471
|
-
path:
|
|
1714
|
+
path: path13,
|
|
1472
1715
|
type,
|
|
1473
1716
|
content,
|
|
1474
1717
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -1581,7 +1824,7 @@ function extractAllCodeReferences(docs) {
|
|
|
1581
1824
|
async function buildSnapshot(config) {
|
|
1582
1825
|
const startTime = Date.now();
|
|
1583
1826
|
const parser = config.parser || new TypeScriptParser();
|
|
1584
|
-
const rootDir =
|
|
1827
|
+
const rootDir = resolve(config.rootDir);
|
|
1585
1828
|
const entryPointsResult = await resolveEntryPoints(rootDir, config.entryPoints);
|
|
1586
1829
|
if (!entryPointsResult.ok) {
|
|
1587
1830
|
return Err(entryPointsResult.error);
|
|
@@ -1599,7 +1842,7 @@ async function buildSnapshot(config) {
|
|
|
1599
1842
|
sourceFilePaths.push(...files2);
|
|
1600
1843
|
}
|
|
1601
1844
|
sourceFilePaths = sourceFilePaths.filter((f) => {
|
|
1602
|
-
const rel =
|
|
1845
|
+
const rel = relative4(rootDir, f);
|
|
1603
1846
|
return !excludePatterns.some((p) => minimatch2(rel, p));
|
|
1604
1847
|
});
|
|
1605
1848
|
const files = [];
|
|
@@ -1652,7 +1895,7 @@ async function buildSnapshot(config) {
|
|
|
1652
1895
|
}
|
|
1653
1896
|
|
|
1654
1897
|
// src/entropy/detectors/drift.ts
|
|
1655
|
-
import { dirname as
|
|
1898
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
1656
1899
|
function levenshteinDistance(a, b) {
|
|
1657
1900
|
const matrix = [];
|
|
1658
1901
|
for (let i = 0; i <= b.length; i++) {
|
|
@@ -1757,7 +2000,7 @@ async function checkStructureDrift(snapshot, _config) {
|
|
|
1757
2000
|
for (const doc of snapshot.docs) {
|
|
1758
2001
|
const fileLinks = extractFileLinks(doc.content);
|
|
1759
2002
|
for (const { link, line } of fileLinks) {
|
|
1760
|
-
const resolvedPath =
|
|
2003
|
+
const resolvedPath = resolve2(dirname3(doc.path), link);
|
|
1761
2004
|
const exists = await fileExists(resolvedPath);
|
|
1762
2005
|
if (!exists) {
|
|
1763
2006
|
drifts.push({
|
|
@@ -1840,19 +2083,19 @@ async function detectDocDrift(snapshot, config, graphDriftData) {
|
|
|
1840
2083
|
}
|
|
1841
2084
|
|
|
1842
2085
|
// src/entropy/detectors/dead-code.ts
|
|
1843
|
-
import { dirname as
|
|
2086
|
+
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
1844
2087
|
function resolveImportToFile(importSource, fromFile, snapshot) {
|
|
1845
2088
|
if (!importSource.startsWith(".")) {
|
|
1846
2089
|
return null;
|
|
1847
2090
|
}
|
|
1848
|
-
const fromDir =
|
|
1849
|
-
let resolved =
|
|
2091
|
+
const fromDir = dirname4(fromFile);
|
|
2092
|
+
let resolved = resolve3(fromDir, importSource);
|
|
1850
2093
|
if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
|
|
1851
2094
|
const withTs = resolved + ".ts";
|
|
1852
2095
|
if (snapshot.files.some((f) => f.path === withTs)) {
|
|
1853
2096
|
return withTs;
|
|
1854
2097
|
}
|
|
1855
|
-
const withIndex =
|
|
2098
|
+
const withIndex = resolve3(resolved, "index.ts");
|
|
1856
2099
|
if (snapshot.files.some((f) => f.path === withIndex)) {
|
|
1857
2100
|
return withIndex;
|
|
1858
2101
|
}
|
|
@@ -2131,14 +2374,14 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
|
|
|
2131
2374
|
|
|
2132
2375
|
// src/entropy/detectors/patterns.ts
|
|
2133
2376
|
import { minimatch as minimatch3 } from "minimatch";
|
|
2134
|
-
import { relative as
|
|
2377
|
+
import { relative as relative5 } from "path";
|
|
2135
2378
|
function fileMatchesPattern(filePath, pattern, rootDir) {
|
|
2136
|
-
const relativePath =
|
|
2379
|
+
const relativePath = relative5(rootDir, filePath);
|
|
2137
2380
|
return minimatch3(relativePath, pattern);
|
|
2138
2381
|
}
|
|
2139
2382
|
function checkConfigPattern(pattern, file, rootDir) {
|
|
2140
2383
|
const matches = [];
|
|
2141
|
-
const fileMatches = pattern.files.some((
|
|
2384
|
+
const fileMatches = pattern.files.some((glob) => fileMatchesPattern(file.path, glob, rootDir));
|
|
2142
2385
|
if (!fileMatches) {
|
|
2143
2386
|
return matches;
|
|
2144
2387
|
}
|
|
@@ -2296,418 +2539,6 @@ async function detectPatternViolations(snapshot, config) {
|
|
|
2296
2539
|
});
|
|
2297
2540
|
}
|
|
2298
2541
|
|
|
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
2542
|
// src/entropy/detectors/size-budget.ts
|
|
2712
2543
|
import { readdirSync, statSync } from "fs";
|
|
2713
2544
|
import { join as join4 } from "path";
|
|
@@ -3136,14 +2967,14 @@ var EntropyAnalyzer = class {
|
|
|
3136
2967
|
};
|
|
3137
2968
|
|
|
3138
2969
|
// 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 =
|
|
2970
|
+
import * as fs3 from "fs";
|
|
2971
|
+
import { promisify } from "util";
|
|
2972
|
+
import { dirname as dirname5, basename as basename4, join as join5 } from "path";
|
|
2973
|
+
var readFile3 = promisify(fs3.readFile);
|
|
2974
|
+
var writeFile3 = promisify(fs3.writeFile);
|
|
2975
|
+
var unlink2 = promisify(fs3.unlink);
|
|
2976
|
+
var mkdir2 = promisify(fs3.mkdir);
|
|
2977
|
+
var copyFile2 = promisify(fs3.copyFile);
|
|
3147
2978
|
var DEFAULT_FIX_CONFIG = {
|
|
3148
2979
|
dryRun: false,
|
|
3149
2980
|
fixTypes: ["unused-imports", "dead-files"],
|
|
@@ -3239,7 +3070,7 @@ function previewFix(fix) {
|
|
|
3239
3070
|
async function createBackup(filePath, backupDir) {
|
|
3240
3071
|
const backupPath = join5(backupDir, `${Date.now()}-${basename4(filePath)}`);
|
|
3241
3072
|
try {
|
|
3242
|
-
await mkdir2(
|
|
3073
|
+
await mkdir2(dirname5(backupPath), { recursive: true });
|
|
3243
3074
|
await copyFile2(filePath, backupPath);
|
|
3244
3075
|
return Ok(backupPath);
|
|
3245
3076
|
} catch (e) {
|
|
@@ -3270,25 +3101,25 @@ async function applySingleFix(fix, config) {
|
|
|
3270
3101
|
break;
|
|
3271
3102
|
case "delete-lines":
|
|
3272
3103
|
if (fix.line !== void 0) {
|
|
3273
|
-
const content = await
|
|
3104
|
+
const content = await readFile3(fix.file, "utf-8");
|
|
3274
3105
|
const lines = content.split("\n");
|
|
3275
3106
|
lines.splice(fix.line - 1, 1);
|
|
3276
|
-
await
|
|
3107
|
+
await writeFile3(fix.file, lines.join("\n"));
|
|
3277
3108
|
}
|
|
3278
3109
|
break;
|
|
3279
3110
|
case "replace":
|
|
3280
3111
|
if (fix.oldContent && fix.newContent !== void 0) {
|
|
3281
|
-
const content = await
|
|
3112
|
+
const content = await readFile3(fix.file, "utf-8");
|
|
3282
3113
|
const newContent = content.replace(fix.oldContent, fix.newContent);
|
|
3283
|
-
await
|
|
3114
|
+
await writeFile3(fix.file, newContent);
|
|
3284
3115
|
}
|
|
3285
3116
|
break;
|
|
3286
3117
|
case "insert":
|
|
3287
3118
|
if (fix.line !== void 0 && fix.newContent) {
|
|
3288
|
-
const content = await
|
|
3119
|
+
const content = await readFile3(fix.file, "utf-8");
|
|
3289
3120
|
const lines = content.split("\n");
|
|
3290
3121
|
lines.splice(fix.line - 1, 0, fix.newContent);
|
|
3291
|
-
await
|
|
3122
|
+
await writeFile3(fix.file, lines.join("\n"));
|
|
3292
3123
|
}
|
|
3293
3124
|
break;
|
|
3294
3125
|
}
|
|
@@ -3465,46 +3296,46 @@ function deduplicateCleanupFindings(findings) {
|
|
|
3465
3296
|
}
|
|
3466
3297
|
|
|
3467
3298
|
// src/entropy/config/schema.ts
|
|
3468
|
-
import { z } from "zod";
|
|
3469
|
-
var MustExportRuleSchema =
|
|
3470
|
-
type:
|
|
3471
|
-
names:
|
|
3299
|
+
import { z as z2 } from "zod";
|
|
3300
|
+
var MustExportRuleSchema = z2.object({
|
|
3301
|
+
type: z2.literal("must-export"),
|
|
3302
|
+
names: z2.array(z2.string())
|
|
3472
3303
|
});
|
|
3473
|
-
var MustExportDefaultRuleSchema =
|
|
3474
|
-
type:
|
|
3475
|
-
kind:
|
|
3304
|
+
var MustExportDefaultRuleSchema = z2.object({
|
|
3305
|
+
type: z2.literal("must-export-default"),
|
|
3306
|
+
kind: z2.enum(["class", "function", "object"]).optional()
|
|
3476
3307
|
});
|
|
3477
|
-
var NoExportRuleSchema =
|
|
3478
|
-
type:
|
|
3479
|
-
names:
|
|
3308
|
+
var NoExportRuleSchema = z2.object({
|
|
3309
|
+
type: z2.literal("no-export"),
|
|
3310
|
+
names: z2.array(z2.string())
|
|
3480
3311
|
});
|
|
3481
|
-
var MustImportRuleSchema =
|
|
3482
|
-
type:
|
|
3483
|
-
from:
|
|
3484
|
-
names:
|
|
3312
|
+
var MustImportRuleSchema = z2.object({
|
|
3313
|
+
type: z2.literal("must-import"),
|
|
3314
|
+
from: z2.string(),
|
|
3315
|
+
names: z2.array(z2.string()).optional()
|
|
3485
3316
|
});
|
|
3486
|
-
var NoImportRuleSchema =
|
|
3487
|
-
type:
|
|
3488
|
-
from:
|
|
3317
|
+
var NoImportRuleSchema = z2.object({
|
|
3318
|
+
type: z2.literal("no-import"),
|
|
3319
|
+
from: z2.string()
|
|
3489
3320
|
});
|
|
3490
|
-
var NamingRuleSchema =
|
|
3491
|
-
type:
|
|
3492
|
-
match:
|
|
3493
|
-
convention:
|
|
3321
|
+
var NamingRuleSchema = z2.object({
|
|
3322
|
+
type: z2.literal("naming"),
|
|
3323
|
+
match: z2.string(),
|
|
3324
|
+
convention: z2.enum(["camelCase", "PascalCase", "UPPER_SNAKE", "kebab-case"])
|
|
3494
3325
|
});
|
|
3495
|
-
var MaxExportsRuleSchema =
|
|
3496
|
-
type:
|
|
3497
|
-
count:
|
|
3326
|
+
var MaxExportsRuleSchema = z2.object({
|
|
3327
|
+
type: z2.literal("max-exports"),
|
|
3328
|
+
count: z2.number().positive()
|
|
3498
3329
|
});
|
|
3499
|
-
var MaxLinesRuleSchema =
|
|
3500
|
-
type:
|
|
3501
|
-
count:
|
|
3330
|
+
var MaxLinesRuleSchema = z2.object({
|
|
3331
|
+
type: z2.literal("max-lines"),
|
|
3332
|
+
count: z2.number().positive()
|
|
3502
3333
|
});
|
|
3503
|
-
var RequireJSDocRuleSchema =
|
|
3504
|
-
type:
|
|
3505
|
-
for:
|
|
3334
|
+
var RequireJSDocRuleSchema = z2.object({
|
|
3335
|
+
type: z2.literal("require-jsdoc"),
|
|
3336
|
+
for: z2.array(z2.enum(["function", "class", "export"]))
|
|
3506
3337
|
});
|
|
3507
|
-
var RuleSchema =
|
|
3338
|
+
var RuleSchema = z2.discriminatedUnion("type", [
|
|
3508
3339
|
MustExportRuleSchema,
|
|
3509
3340
|
MustExportDefaultRuleSchema,
|
|
3510
3341
|
NoExportRuleSchema,
|
|
@@ -3515,47 +3346,47 @@ var RuleSchema = z.discriminatedUnion("type", [
|
|
|
3515
3346
|
MaxLinesRuleSchema,
|
|
3516
3347
|
RequireJSDocRuleSchema
|
|
3517
3348
|
]);
|
|
3518
|
-
var ConfigPatternSchema =
|
|
3519
|
-
name:
|
|
3520
|
-
description:
|
|
3521
|
-
severity:
|
|
3522
|
-
files:
|
|
3349
|
+
var ConfigPatternSchema = z2.object({
|
|
3350
|
+
name: z2.string().min(1),
|
|
3351
|
+
description: z2.string(),
|
|
3352
|
+
severity: z2.enum(["error", "warning"]),
|
|
3353
|
+
files: z2.array(z2.string()),
|
|
3523
3354
|
rule: RuleSchema,
|
|
3524
|
-
message:
|
|
3355
|
+
message: z2.string().optional()
|
|
3525
3356
|
});
|
|
3526
|
-
var PatternConfigSchema =
|
|
3527
|
-
patterns:
|
|
3528
|
-
customPatterns:
|
|
3357
|
+
var PatternConfigSchema = z2.object({
|
|
3358
|
+
patterns: z2.array(ConfigPatternSchema),
|
|
3359
|
+
customPatterns: z2.array(z2.any()).optional(),
|
|
3529
3360
|
// Code patterns are functions, can't validate
|
|
3530
|
-
ignoreFiles:
|
|
3361
|
+
ignoreFiles: z2.array(z2.string()).optional()
|
|
3531
3362
|
});
|
|
3532
|
-
var DriftConfigSchema =
|
|
3533
|
-
docPaths:
|
|
3534
|
-
checkApiSignatures:
|
|
3535
|
-
checkExamples:
|
|
3536
|
-
checkStructure:
|
|
3537
|
-
ignorePatterns:
|
|
3363
|
+
var DriftConfigSchema = z2.object({
|
|
3364
|
+
docPaths: z2.array(z2.string()).optional(),
|
|
3365
|
+
checkApiSignatures: z2.boolean().optional(),
|
|
3366
|
+
checkExamples: z2.boolean().optional(),
|
|
3367
|
+
checkStructure: z2.boolean().optional(),
|
|
3368
|
+
ignorePatterns: z2.array(z2.string()).optional()
|
|
3538
3369
|
});
|
|
3539
|
-
var DeadCodeConfigSchema =
|
|
3540
|
-
entryPoints:
|
|
3541
|
-
includeTypes:
|
|
3542
|
-
includeInternals:
|
|
3543
|
-
ignorePatterns:
|
|
3544
|
-
treatDynamicImportsAs:
|
|
3370
|
+
var DeadCodeConfigSchema = z2.object({
|
|
3371
|
+
entryPoints: z2.array(z2.string()).optional(),
|
|
3372
|
+
includeTypes: z2.boolean().optional(),
|
|
3373
|
+
includeInternals: z2.boolean().optional(),
|
|
3374
|
+
ignorePatterns: z2.array(z2.string()).optional(),
|
|
3375
|
+
treatDynamicImportsAs: z2.enum(["used", "unknown"]).optional()
|
|
3545
3376
|
});
|
|
3546
|
-
var EntropyConfigSchema =
|
|
3547
|
-
rootDir:
|
|
3548
|
-
parser:
|
|
3377
|
+
var EntropyConfigSchema = z2.object({
|
|
3378
|
+
rootDir: z2.string(),
|
|
3379
|
+
parser: z2.any().optional(),
|
|
3549
3380
|
// LanguageParser instance, can't validate
|
|
3550
|
-
entryPoints:
|
|
3551
|
-
analyze:
|
|
3552
|
-
drift:
|
|
3553
|
-
deadCode:
|
|
3554
|
-
patterns:
|
|
3381
|
+
entryPoints: z2.array(z2.string()).optional(),
|
|
3382
|
+
analyze: z2.object({
|
|
3383
|
+
drift: z2.union([z2.boolean(), DriftConfigSchema]).optional(),
|
|
3384
|
+
deadCode: z2.union([z2.boolean(), DeadCodeConfigSchema]).optional(),
|
|
3385
|
+
patterns: z2.union([z2.boolean(), PatternConfigSchema]).optional()
|
|
3555
3386
|
}),
|
|
3556
|
-
include:
|
|
3557
|
-
exclude:
|
|
3558
|
-
docPaths:
|
|
3387
|
+
include: z2.array(z2.string()).optional(),
|
|
3388
|
+
exclude: z2.array(z2.string()).optional(),
|
|
3389
|
+
docPaths: z2.array(z2.string()).optional()
|
|
3559
3390
|
});
|
|
3560
3391
|
function validatePatternConfig(config) {
|
|
3561
3392
|
const result = PatternConfigSchema.safeParse(config);
|
|
@@ -3575,7 +3406,7 @@ function validatePatternConfig(config) {
|
|
|
3575
3406
|
|
|
3576
3407
|
// src/performance/baseline-manager.ts
|
|
3577
3408
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3578
|
-
import { join as join6, dirname as
|
|
3409
|
+
import { join as join6, dirname as dirname6 } from "path";
|
|
3579
3410
|
var BaselineManager = class {
|
|
3580
3411
|
baselinesPath;
|
|
3581
3412
|
constructor(projectRoot) {
|
|
@@ -3619,7 +3450,7 @@ var BaselineManager = class {
|
|
|
3619
3450
|
updatedFrom: commitHash,
|
|
3620
3451
|
benchmarks
|
|
3621
3452
|
};
|
|
3622
|
-
const dir =
|
|
3453
|
+
const dir = dirname6(this.baselinesPath);
|
|
3623
3454
|
if (!existsSync(dir)) {
|
|
3624
3455
|
mkdirSync(dir, { recursive: true });
|
|
3625
3456
|
}
|
|
@@ -3653,7 +3484,7 @@ var BenchmarkRunner = class {
|
|
|
3653
3484
|
/**
|
|
3654
3485
|
* Discover .bench.ts files matching the glob pattern.
|
|
3655
3486
|
*/
|
|
3656
|
-
discover(cwd,
|
|
3487
|
+
discover(cwd, glob) {
|
|
3657
3488
|
try {
|
|
3658
3489
|
const result = execFileSync(
|
|
3659
3490
|
"find",
|
|
@@ -3672,8 +3503,8 @@ var BenchmarkRunner = class {
|
|
|
3672
3503
|
).trim();
|
|
3673
3504
|
if (!result) return [];
|
|
3674
3505
|
const files = result.split("\n").filter(Boolean);
|
|
3675
|
-
if (
|
|
3676
|
-
return files.filter((f) => f.includes(
|
|
3506
|
+
if (glob && glob !== "**/*.bench.ts") {
|
|
3507
|
+
return files.filter((f) => f.includes(glob.replace(/\*/g, "")));
|
|
3677
3508
|
}
|
|
3678
3509
|
return files;
|
|
3679
3510
|
} catch {
|
|
@@ -3687,10 +3518,10 @@ var BenchmarkRunner = class {
|
|
|
3687
3518
|
async run(options = {}) {
|
|
3688
3519
|
const cwd = options.cwd ?? process.cwd();
|
|
3689
3520
|
const timeout = options.timeout ?? 12e4;
|
|
3690
|
-
const
|
|
3521
|
+
const glob = options.glob;
|
|
3691
3522
|
const args = ["vitest", "bench", "--run"];
|
|
3692
|
-
if (
|
|
3693
|
-
args.push(
|
|
3523
|
+
if (glob) {
|
|
3524
|
+
args.push(glob);
|
|
3694
3525
|
}
|
|
3695
3526
|
args.push("--reporter=json");
|
|
3696
3527
|
try {
|
|
@@ -3812,7 +3643,7 @@ var RegressionDetector = class {
|
|
|
3812
3643
|
};
|
|
3813
3644
|
|
|
3814
3645
|
// src/performance/critical-path.ts
|
|
3815
|
-
import * as
|
|
3646
|
+
import * as fs4 from "fs";
|
|
3816
3647
|
import * as path from "path";
|
|
3817
3648
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
|
|
3818
3649
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
@@ -3864,7 +3695,7 @@ var CriticalPathResolver = class {
|
|
|
3864
3695
|
walkDir(dir, entries) {
|
|
3865
3696
|
let items;
|
|
3866
3697
|
try {
|
|
3867
|
-
items =
|
|
3698
|
+
items = fs4.readdirSync(dir, { withFileTypes: true });
|
|
3868
3699
|
} catch {
|
|
3869
3700
|
return;
|
|
3870
3701
|
}
|
|
@@ -3880,7 +3711,7 @@ var CriticalPathResolver = class {
|
|
|
3880
3711
|
scanFile(filePath, entries) {
|
|
3881
3712
|
let content;
|
|
3882
3713
|
try {
|
|
3883
|
-
content =
|
|
3714
|
+
content = fs4.readFileSync(filePath, "utf-8");
|
|
3884
3715
|
} catch {
|
|
3885
3716
|
return;
|
|
3886
3717
|
}
|
|
@@ -4059,17 +3890,17 @@ function resetFeedbackConfig() {
|
|
|
4059
3890
|
}
|
|
4060
3891
|
|
|
4061
3892
|
// src/feedback/review/diff-analyzer.ts
|
|
4062
|
-
function parseDiff(
|
|
3893
|
+
function parseDiff(diff2) {
|
|
4063
3894
|
try {
|
|
4064
|
-
if (!
|
|
4065
|
-
return Ok({ diff, files: [] });
|
|
3895
|
+
if (!diff2.trim()) {
|
|
3896
|
+
return Ok({ diff: diff2, files: [] });
|
|
4066
3897
|
}
|
|
4067
3898
|
const files = [];
|
|
4068
3899
|
const newFileRegex = /new file mode/;
|
|
4069
3900
|
const deletedFileRegex = /deleted file mode/;
|
|
4070
3901
|
const additionRegex = /^\+(?!\+\+)/gm;
|
|
4071
3902
|
const deletionRegex = /^-(?!--)/gm;
|
|
4072
|
-
const diffParts =
|
|
3903
|
+
const diffParts = diff2.split(/(?=diff --git)/);
|
|
4073
3904
|
for (const part of diffParts) {
|
|
4074
3905
|
if (!part.trim()) continue;
|
|
4075
3906
|
const headerMatch = /diff --git a\/(.+?) b\/(.+?)(?:\n|$)/.exec(part);
|
|
@@ -4092,7 +3923,7 @@ function parseDiff(diff) {
|
|
|
4092
3923
|
deletions
|
|
4093
3924
|
});
|
|
4094
3925
|
}
|
|
4095
|
-
return Ok({ diff, files });
|
|
3926
|
+
return Ok({ diff: diff2, files });
|
|
4096
3927
|
} catch (error) {
|
|
4097
3928
|
return Err({
|
|
4098
3929
|
code: "DIFF_PARSE_ERROR",
|
|
@@ -4610,7 +4441,7 @@ async function requestMultiplePeerReviews(requests) {
|
|
|
4610
4441
|
|
|
4611
4442
|
// src/feedback/logging/file-sink.ts
|
|
4612
4443
|
import { appendFileSync, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
4613
|
-
import { dirname as
|
|
4444
|
+
import { dirname as dirname7 } from "path";
|
|
4614
4445
|
var FileSink = class {
|
|
4615
4446
|
name = "file";
|
|
4616
4447
|
filePath;
|
|
@@ -4633,7 +4464,7 @@ var FileSink = class {
|
|
|
4633
4464
|
}
|
|
4634
4465
|
ensureDirectory() {
|
|
4635
4466
|
if (!this.initialized) {
|
|
4636
|
-
const dir =
|
|
4467
|
+
const dir = dirname7(this.filePath);
|
|
4637
4468
|
if (!existsSync2(dir)) {
|
|
4638
4469
|
mkdirSync2(dir, { recursive: true });
|
|
4639
4470
|
}
|
|
@@ -4696,77 +4527,182 @@ var NoOpSink = class {
|
|
|
4696
4527
|
}
|
|
4697
4528
|
};
|
|
4698
4529
|
|
|
4530
|
+
// src/architecture/sync-constraints.ts
|
|
4531
|
+
function syncConstraintNodes(store, rules, violations) {
|
|
4532
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4533
|
+
const ruleIds = new Set(rules.map((r) => r.id));
|
|
4534
|
+
const violationsByCategory = /* @__PURE__ */ new Map();
|
|
4535
|
+
for (const result of violations) {
|
|
4536
|
+
const files = result.violations.map((v) => v.file);
|
|
4537
|
+
const existing = violationsByCategory.get(result.category) ?? [];
|
|
4538
|
+
violationsByCategory.set(result.category, [...existing, ...files]);
|
|
4539
|
+
}
|
|
4540
|
+
const existingNodesById = /* @__PURE__ */ new Map();
|
|
4541
|
+
for (const node of store.findNodes({ type: "constraint" })) {
|
|
4542
|
+
existingNodesById.set(node.id, node);
|
|
4543
|
+
}
|
|
4544
|
+
for (const rule of rules) {
|
|
4545
|
+
const existing = existingNodesById.get(rule.id);
|
|
4546
|
+
const createdAt = existing?.createdAt ?? now;
|
|
4547
|
+
const previousLastViolatedAt = existing?.lastViolatedAt ?? null;
|
|
4548
|
+
const hasViolation = hasMatchingViolation(rule, violationsByCategory);
|
|
4549
|
+
const lastViolatedAt = hasViolation ? now : previousLastViolatedAt;
|
|
4550
|
+
store.upsertNode({
|
|
4551
|
+
id: rule.id,
|
|
4552
|
+
type: "constraint",
|
|
4553
|
+
name: rule.description,
|
|
4554
|
+
category: rule.category,
|
|
4555
|
+
scope: rule.scope,
|
|
4556
|
+
createdAt,
|
|
4557
|
+
lastViolatedAt
|
|
4558
|
+
});
|
|
4559
|
+
}
|
|
4560
|
+
const existingConstraints = store.findNodes({ type: "constraint" });
|
|
4561
|
+
for (const node of existingConstraints) {
|
|
4562
|
+
if (!ruleIds.has(node.id)) {
|
|
4563
|
+
store.removeNode(node.id);
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
function hasMatchingViolation(rule, violationsByCategory) {
|
|
4568
|
+
const files = violationsByCategory.get(rule.category);
|
|
4569
|
+
if (!files || files.length === 0) return false;
|
|
4570
|
+
if (rule.scope === "project") return true;
|
|
4571
|
+
return files.some((file) => file.startsWith(rule.scope));
|
|
4572
|
+
}
|
|
4573
|
+
|
|
4574
|
+
// src/architecture/detect-stale.ts
|
|
4575
|
+
function detectStaleConstraints(store, windowDays = 30, category) {
|
|
4576
|
+
const now = Date.now();
|
|
4577
|
+
const windowMs = windowDays * 24 * 60 * 60 * 1e3;
|
|
4578
|
+
const cutoff = now - windowMs;
|
|
4579
|
+
let constraints = store.findNodes({ type: "constraint" });
|
|
4580
|
+
if (category) {
|
|
4581
|
+
constraints = constraints.filter((n) => n.category === category);
|
|
4582
|
+
}
|
|
4583
|
+
const totalConstraints = constraints.length;
|
|
4584
|
+
const staleConstraints = [];
|
|
4585
|
+
for (const node of constraints) {
|
|
4586
|
+
const lastViolatedAt = node.lastViolatedAt ?? null;
|
|
4587
|
+
const createdAt = node.createdAt;
|
|
4588
|
+
const comparisonTimestamp = lastViolatedAt ?? createdAt;
|
|
4589
|
+
if (!comparisonTimestamp) continue;
|
|
4590
|
+
const timestampMs = new Date(comparisonTimestamp).getTime();
|
|
4591
|
+
if (timestampMs < cutoff) {
|
|
4592
|
+
const daysSince = Math.floor((now - timestampMs) / (24 * 60 * 60 * 1e3));
|
|
4593
|
+
staleConstraints.push({
|
|
4594
|
+
id: node.id,
|
|
4595
|
+
category: node.category,
|
|
4596
|
+
description: node.name ?? "",
|
|
4597
|
+
scope: node.scope ?? "project",
|
|
4598
|
+
lastViolatedAt,
|
|
4599
|
+
daysSinceLastViolation: daysSince
|
|
4600
|
+
});
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
staleConstraints.sort((a, b) => b.daysSinceLastViolation - a.daysSinceLastViolation);
|
|
4604
|
+
return { staleConstraints, totalConstraints, windowDays };
|
|
4605
|
+
}
|
|
4606
|
+
|
|
4607
|
+
// src/architecture/config.ts
|
|
4608
|
+
function resolveThresholds(scope, config) {
|
|
4609
|
+
const projectThresholds = {};
|
|
4610
|
+
for (const [key, val] of Object.entries(config.thresholds)) {
|
|
4611
|
+
projectThresholds[key] = typeof val === "object" && val !== null && !Array.isArray(val) ? { ...val } : val;
|
|
4612
|
+
}
|
|
4613
|
+
if (scope === "project") {
|
|
4614
|
+
return projectThresholds;
|
|
4615
|
+
}
|
|
4616
|
+
const moduleOverrides = config.modules[scope];
|
|
4617
|
+
if (!moduleOverrides) {
|
|
4618
|
+
return projectThresholds;
|
|
4619
|
+
}
|
|
4620
|
+
const merged = { ...projectThresholds };
|
|
4621
|
+
for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
|
|
4622
|
+
const projectValue = projectThresholds[category];
|
|
4623
|
+
if (projectValue !== void 0 && typeof projectValue === "object" && !Array.isArray(projectValue) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
|
|
4624
|
+
merged[category] = {
|
|
4625
|
+
...projectValue,
|
|
4626
|
+
...moduleValue
|
|
4627
|
+
};
|
|
4628
|
+
} else {
|
|
4629
|
+
merged[category] = moduleValue;
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
return merged;
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4699
4635
|
// src/state/types.ts
|
|
4700
|
-
import { z as
|
|
4701
|
-
var FailureEntrySchema =
|
|
4702
|
-
date:
|
|
4703
|
-
skill:
|
|
4704
|
-
type:
|
|
4705
|
-
description:
|
|
4636
|
+
import { z as z3 } from "zod";
|
|
4637
|
+
var FailureEntrySchema = z3.object({
|
|
4638
|
+
date: z3.string(),
|
|
4639
|
+
skill: z3.string(),
|
|
4640
|
+
type: z3.string(),
|
|
4641
|
+
description: z3.string()
|
|
4706
4642
|
});
|
|
4707
|
-
var HandoffSchema =
|
|
4708
|
-
timestamp:
|
|
4709
|
-
fromSkill:
|
|
4710
|
-
phase:
|
|
4711
|
-
summary:
|
|
4712
|
-
completed:
|
|
4713
|
-
pending:
|
|
4714
|
-
concerns:
|
|
4715
|
-
decisions:
|
|
4716
|
-
|
|
4717
|
-
what:
|
|
4718
|
-
why:
|
|
4643
|
+
var HandoffSchema = z3.object({
|
|
4644
|
+
timestamp: z3.string(),
|
|
4645
|
+
fromSkill: z3.string(),
|
|
4646
|
+
phase: z3.string(),
|
|
4647
|
+
summary: z3.string(),
|
|
4648
|
+
completed: z3.array(z3.string()).default([]),
|
|
4649
|
+
pending: z3.array(z3.string()).default([]),
|
|
4650
|
+
concerns: z3.array(z3.string()).default([]),
|
|
4651
|
+
decisions: z3.array(
|
|
4652
|
+
z3.object({
|
|
4653
|
+
what: z3.string(),
|
|
4654
|
+
why: z3.string()
|
|
4719
4655
|
})
|
|
4720
4656
|
).default([]),
|
|
4721
|
-
blockers:
|
|
4722
|
-
contextKeywords:
|
|
4657
|
+
blockers: z3.array(z3.string()).default([]),
|
|
4658
|
+
contextKeywords: z3.array(z3.string()).default([])
|
|
4723
4659
|
});
|
|
4724
|
-
var GateCheckSchema =
|
|
4725
|
-
name:
|
|
4726
|
-
passed:
|
|
4727
|
-
command:
|
|
4728
|
-
output:
|
|
4729
|
-
duration:
|
|
4660
|
+
var GateCheckSchema = z3.object({
|
|
4661
|
+
name: z3.string(),
|
|
4662
|
+
passed: z3.boolean(),
|
|
4663
|
+
command: z3.string(),
|
|
4664
|
+
output: z3.string().optional(),
|
|
4665
|
+
duration: z3.number().optional()
|
|
4730
4666
|
});
|
|
4731
|
-
var GateResultSchema =
|
|
4732
|
-
passed:
|
|
4733
|
-
checks:
|
|
4667
|
+
var GateResultSchema = z3.object({
|
|
4668
|
+
passed: z3.boolean(),
|
|
4669
|
+
checks: z3.array(GateCheckSchema)
|
|
4734
4670
|
});
|
|
4735
|
-
var GateConfigSchema =
|
|
4736
|
-
checks:
|
|
4737
|
-
|
|
4738
|
-
name:
|
|
4739
|
-
command:
|
|
4671
|
+
var GateConfigSchema = z3.object({
|
|
4672
|
+
checks: z3.array(
|
|
4673
|
+
z3.object({
|
|
4674
|
+
name: z3.string(),
|
|
4675
|
+
command: z3.string()
|
|
4740
4676
|
})
|
|
4741
4677
|
).optional(),
|
|
4742
|
-
trace:
|
|
4678
|
+
trace: z3.boolean().optional()
|
|
4743
4679
|
});
|
|
4744
|
-
var HarnessStateSchema =
|
|
4745
|
-
schemaVersion:
|
|
4746
|
-
position:
|
|
4747
|
-
phase:
|
|
4748
|
-
task:
|
|
4680
|
+
var HarnessStateSchema = z3.object({
|
|
4681
|
+
schemaVersion: z3.literal(1),
|
|
4682
|
+
position: z3.object({
|
|
4683
|
+
phase: z3.string().optional(),
|
|
4684
|
+
task: z3.string().optional()
|
|
4749
4685
|
}).default({}),
|
|
4750
|
-
decisions:
|
|
4751
|
-
|
|
4752
|
-
date:
|
|
4753
|
-
decision:
|
|
4754
|
-
context:
|
|
4686
|
+
decisions: z3.array(
|
|
4687
|
+
z3.object({
|
|
4688
|
+
date: z3.string(),
|
|
4689
|
+
decision: z3.string(),
|
|
4690
|
+
context: z3.string()
|
|
4755
4691
|
})
|
|
4756
4692
|
).default([]),
|
|
4757
|
-
blockers:
|
|
4758
|
-
|
|
4759
|
-
id:
|
|
4760
|
-
description:
|
|
4761
|
-
status:
|
|
4693
|
+
blockers: z3.array(
|
|
4694
|
+
z3.object({
|
|
4695
|
+
id: z3.string(),
|
|
4696
|
+
description: z3.string(),
|
|
4697
|
+
status: z3.enum(["open", "resolved"])
|
|
4762
4698
|
})
|
|
4763
4699
|
).default([]),
|
|
4764
|
-
progress:
|
|
4765
|
-
lastSession:
|
|
4766
|
-
date:
|
|
4767
|
-
summary:
|
|
4768
|
-
lastSkill:
|
|
4769
|
-
pendingTasks:
|
|
4700
|
+
progress: z3.record(z3.enum(["pending", "in_progress", "complete"])).default({}),
|
|
4701
|
+
lastSession: z3.object({
|
|
4702
|
+
date: z3.string(),
|
|
4703
|
+
summary: z3.string(),
|
|
4704
|
+
lastSkill: z3.string().optional(),
|
|
4705
|
+
pendingTasks: z3.array(z3.string()).optional()
|
|
4770
4706
|
}).optional()
|
|
4771
4707
|
});
|
|
4772
4708
|
var DEFAULT_STATE = {
|
|
@@ -4778,27 +4714,27 @@ var DEFAULT_STATE = {
|
|
|
4778
4714
|
};
|
|
4779
4715
|
|
|
4780
4716
|
// src/state/state-manager.ts
|
|
4781
|
-
import * as
|
|
4717
|
+
import * as fs6 from "fs";
|
|
4782
4718
|
import * as path3 from "path";
|
|
4783
4719
|
import { execSync as execSync2 } from "child_process";
|
|
4784
4720
|
|
|
4785
4721
|
// src/state/stream-resolver.ts
|
|
4786
|
-
import * as
|
|
4722
|
+
import * as fs5 from "fs";
|
|
4787
4723
|
import * as path2 from "path";
|
|
4788
4724
|
import { execSync } from "child_process";
|
|
4789
4725
|
|
|
4790
4726
|
// src/state/stream-types.ts
|
|
4791
|
-
import { z as
|
|
4792
|
-
var StreamInfoSchema =
|
|
4793
|
-
name:
|
|
4794
|
-
branch:
|
|
4795
|
-
createdAt:
|
|
4796
|
-
lastActiveAt:
|
|
4727
|
+
import { z as z4 } from "zod";
|
|
4728
|
+
var StreamInfoSchema = z4.object({
|
|
4729
|
+
name: z4.string(),
|
|
4730
|
+
branch: z4.string().optional(),
|
|
4731
|
+
createdAt: z4.string(),
|
|
4732
|
+
lastActiveAt: z4.string()
|
|
4797
4733
|
});
|
|
4798
|
-
var StreamIndexSchema =
|
|
4799
|
-
schemaVersion:
|
|
4800
|
-
activeStream:
|
|
4801
|
-
streams:
|
|
4734
|
+
var StreamIndexSchema = z4.object({
|
|
4735
|
+
schemaVersion: z4.literal(1),
|
|
4736
|
+
activeStream: z4.string().nullable(),
|
|
4737
|
+
streams: z4.record(StreamInfoSchema)
|
|
4802
4738
|
});
|
|
4803
4739
|
var DEFAULT_STREAM_INDEX = {
|
|
4804
4740
|
schemaVersion: 1,
|
|
@@ -4829,11 +4765,11 @@ function validateStreamName(name) {
|
|
|
4829
4765
|
}
|
|
4830
4766
|
async function loadStreamIndex(projectPath) {
|
|
4831
4767
|
const idxPath = indexPath(projectPath);
|
|
4832
|
-
if (!
|
|
4768
|
+
if (!fs5.existsSync(idxPath)) {
|
|
4833
4769
|
return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
4834
4770
|
}
|
|
4835
4771
|
try {
|
|
4836
|
-
const raw =
|
|
4772
|
+
const raw = fs5.readFileSync(idxPath, "utf-8");
|
|
4837
4773
|
const parsed = JSON.parse(raw);
|
|
4838
4774
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
4839
4775
|
if (!result.success) {
|
|
@@ -4851,8 +4787,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
4851
4787
|
async function saveStreamIndex(projectPath, index) {
|
|
4852
4788
|
const dir = streamsDir(projectPath);
|
|
4853
4789
|
try {
|
|
4854
|
-
|
|
4855
|
-
|
|
4790
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
4791
|
+
fs5.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
4856
4792
|
return Ok(void 0);
|
|
4857
4793
|
} catch (error) {
|
|
4858
4794
|
return Err(
|
|
@@ -4934,7 +4870,7 @@ async function createStream(projectPath, name, branch) {
|
|
|
4934
4870
|
}
|
|
4935
4871
|
const streamPath = path2.join(streamsDir(projectPath), name);
|
|
4936
4872
|
try {
|
|
4937
|
-
|
|
4873
|
+
fs5.mkdirSync(streamPath, { recursive: true });
|
|
4938
4874
|
} catch (error) {
|
|
4939
4875
|
return Err(
|
|
4940
4876
|
new Error(
|
|
@@ -4978,9 +4914,9 @@ async function archiveStream(projectPath, name) {
|
|
|
4978
4914
|
const streamPath = path2.join(streamsDir(projectPath), name);
|
|
4979
4915
|
const archiveDir = path2.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
4980
4916
|
try {
|
|
4981
|
-
|
|
4917
|
+
fs5.mkdirSync(archiveDir, { recursive: true });
|
|
4982
4918
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4983
|
-
|
|
4919
|
+
fs5.renameSync(streamPath, path2.join(archiveDir, `${name}-${date}`));
|
|
4984
4920
|
} catch (error) {
|
|
4985
4921
|
return Err(
|
|
4986
4922
|
new Error(
|
|
@@ -5003,18 +4939,18 @@ function getStreamForBranch(index, branch) {
|
|
|
5003
4939
|
var STATE_FILES = ["state.json", "handoff.json", "learnings.md", "failures.md"];
|
|
5004
4940
|
async function migrateToStreams(projectPath) {
|
|
5005
4941
|
const harnessDir = path2.join(projectPath, HARNESS_DIR);
|
|
5006
|
-
if (
|
|
4942
|
+
if (fs5.existsSync(indexPath(projectPath))) {
|
|
5007
4943
|
return Ok(void 0);
|
|
5008
4944
|
}
|
|
5009
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
4945
|
+
const filesToMove = STATE_FILES.filter((f) => fs5.existsSync(path2.join(harnessDir, f)));
|
|
5010
4946
|
if (filesToMove.length === 0) {
|
|
5011
4947
|
return Ok(void 0);
|
|
5012
4948
|
}
|
|
5013
4949
|
const defaultDir = path2.join(streamsDir(projectPath), "default");
|
|
5014
4950
|
try {
|
|
5015
|
-
|
|
4951
|
+
fs5.mkdirSync(defaultDir, { recursive: true });
|
|
5016
4952
|
for (const file of filesToMove) {
|
|
5017
|
-
|
|
4953
|
+
fs5.renameSync(path2.join(harnessDir, file), path2.join(defaultDir, file));
|
|
5018
4954
|
}
|
|
5019
4955
|
} catch (error) {
|
|
5020
4956
|
return Err(
|
|
@@ -5055,7 +4991,7 @@ function evictIfNeeded(map) {
|
|
|
5055
4991
|
}
|
|
5056
4992
|
async function getStateDir(projectPath, stream) {
|
|
5057
4993
|
const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
|
|
5058
|
-
const hasStreams =
|
|
4994
|
+
const hasStreams = fs6.existsSync(streamsIndexPath);
|
|
5059
4995
|
if (stream || hasStreams) {
|
|
5060
4996
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
5061
4997
|
if (result.ok) {
|
|
@@ -5073,10 +5009,10 @@ async function loadState(projectPath, stream) {
|
|
|
5073
5009
|
if (!dirResult.ok) return dirResult;
|
|
5074
5010
|
const stateDir = dirResult.value;
|
|
5075
5011
|
const statePath = path3.join(stateDir, STATE_FILE);
|
|
5076
|
-
if (!
|
|
5012
|
+
if (!fs6.existsSync(statePath)) {
|
|
5077
5013
|
return Ok({ ...DEFAULT_STATE });
|
|
5078
5014
|
}
|
|
5079
|
-
const raw =
|
|
5015
|
+
const raw = fs6.readFileSync(statePath, "utf-8");
|
|
5080
5016
|
const parsed = JSON.parse(raw);
|
|
5081
5017
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
5082
5018
|
if (!result.success) {
|
|
@@ -5095,8 +5031,8 @@ async function saveState(projectPath, state, stream) {
|
|
|
5095
5031
|
if (!dirResult.ok) return dirResult;
|
|
5096
5032
|
const stateDir = dirResult.value;
|
|
5097
5033
|
const statePath = path3.join(stateDir, STATE_FILE);
|
|
5098
|
-
|
|
5099
|
-
|
|
5034
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5035
|
+
fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
5100
5036
|
return Ok(void 0);
|
|
5101
5037
|
} catch (error) {
|
|
5102
5038
|
return Err(
|
|
@@ -5110,7 +5046,7 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5110
5046
|
if (!dirResult.ok) return dirResult;
|
|
5111
5047
|
const stateDir = dirResult.value;
|
|
5112
5048
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5113
|
-
|
|
5049
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5114
5050
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5115
5051
|
let entry;
|
|
5116
5052
|
if (skillName && outcome) {
|
|
@@ -5126,11 +5062,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5126
5062
|
- **${timestamp}:** ${learning}
|
|
5127
5063
|
`;
|
|
5128
5064
|
}
|
|
5129
|
-
if (!
|
|
5130
|
-
|
|
5065
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
5066
|
+
fs6.writeFileSync(learningsPath, `# Learnings
|
|
5131
5067
|
${entry}`);
|
|
5132
5068
|
} else {
|
|
5133
|
-
|
|
5069
|
+
fs6.appendFileSync(learningsPath, entry);
|
|
5134
5070
|
}
|
|
5135
5071
|
learningsCacheMap.delete(learningsPath);
|
|
5136
5072
|
return Ok(void 0);
|
|
@@ -5148,17 +5084,17 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
5148
5084
|
if (!dirResult.ok) return dirResult;
|
|
5149
5085
|
const stateDir = dirResult.value;
|
|
5150
5086
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5151
|
-
if (!
|
|
5087
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
5152
5088
|
return Ok([]);
|
|
5153
5089
|
}
|
|
5154
|
-
const stats =
|
|
5090
|
+
const stats = fs6.statSync(learningsPath);
|
|
5155
5091
|
const cacheKey = learningsPath;
|
|
5156
5092
|
const cached = learningsCacheMap.get(cacheKey);
|
|
5157
5093
|
let entries;
|
|
5158
5094
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5159
5095
|
entries = cached.entries;
|
|
5160
5096
|
} else {
|
|
5161
|
-
const content =
|
|
5097
|
+
const content = fs6.readFileSync(learningsPath, "utf-8");
|
|
5162
5098
|
const lines = content.split("\n");
|
|
5163
5099
|
entries = [];
|
|
5164
5100
|
let currentBlock = [];
|
|
@@ -5201,16 +5137,16 @@ async function appendFailure(projectPath, description, skillName, type, stream)
|
|
|
5201
5137
|
if (!dirResult.ok) return dirResult;
|
|
5202
5138
|
const stateDir = dirResult.value;
|
|
5203
5139
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5204
|
-
|
|
5140
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5205
5141
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5206
5142
|
const entry = `
|
|
5207
5143
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
5208
5144
|
`;
|
|
5209
|
-
if (!
|
|
5210
|
-
|
|
5145
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5146
|
+
fs6.writeFileSync(failuresPath, `# Failures
|
|
5211
5147
|
${entry}`);
|
|
5212
5148
|
} else {
|
|
5213
|
-
|
|
5149
|
+
fs6.appendFileSync(failuresPath, entry);
|
|
5214
5150
|
}
|
|
5215
5151
|
failuresCacheMap.delete(failuresPath);
|
|
5216
5152
|
return Ok(void 0);
|
|
@@ -5228,16 +5164,16 @@ async function loadFailures(projectPath, stream) {
|
|
|
5228
5164
|
if (!dirResult.ok) return dirResult;
|
|
5229
5165
|
const stateDir = dirResult.value;
|
|
5230
5166
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5231
|
-
if (!
|
|
5167
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5232
5168
|
return Ok([]);
|
|
5233
5169
|
}
|
|
5234
|
-
const stats =
|
|
5170
|
+
const stats = fs6.statSync(failuresPath);
|
|
5235
5171
|
const cacheKey = failuresPath;
|
|
5236
5172
|
const cached = failuresCacheMap.get(cacheKey);
|
|
5237
5173
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5238
5174
|
return Ok(cached.entries);
|
|
5239
5175
|
}
|
|
5240
|
-
const content =
|
|
5176
|
+
const content = fs6.readFileSync(failuresPath, "utf-8");
|
|
5241
5177
|
const entries = [];
|
|
5242
5178
|
for (const line of content.split("\n")) {
|
|
5243
5179
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -5267,19 +5203,19 @@ async function archiveFailures(projectPath, stream) {
|
|
|
5267
5203
|
if (!dirResult.ok) return dirResult;
|
|
5268
5204
|
const stateDir = dirResult.value;
|
|
5269
5205
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5270
|
-
if (!
|
|
5206
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5271
5207
|
return Ok(void 0);
|
|
5272
5208
|
}
|
|
5273
5209
|
const archiveDir = path3.join(stateDir, "archive");
|
|
5274
|
-
|
|
5210
|
+
fs6.mkdirSync(archiveDir, { recursive: true });
|
|
5275
5211
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5276
5212
|
let archiveName = `failures-${date}.md`;
|
|
5277
5213
|
let counter = 2;
|
|
5278
|
-
while (
|
|
5214
|
+
while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
|
|
5279
5215
|
archiveName = `failures-${date}-${counter}.md`;
|
|
5280
5216
|
counter++;
|
|
5281
5217
|
}
|
|
5282
|
-
|
|
5218
|
+
fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
|
|
5283
5219
|
failuresCacheMap.delete(failuresPath);
|
|
5284
5220
|
return Ok(void 0);
|
|
5285
5221
|
} catch (error) {
|
|
@@ -5296,8 +5232,8 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
5296
5232
|
if (!dirResult.ok) return dirResult;
|
|
5297
5233
|
const stateDir = dirResult.value;
|
|
5298
5234
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5299
|
-
|
|
5300
|
-
|
|
5235
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5236
|
+
fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
5301
5237
|
return Ok(void 0);
|
|
5302
5238
|
} catch (error) {
|
|
5303
5239
|
return Err(
|
|
@@ -5311,10 +5247,10 @@ async function loadHandoff(projectPath, stream) {
|
|
|
5311
5247
|
if (!dirResult.ok) return dirResult;
|
|
5312
5248
|
const stateDir = dirResult.value;
|
|
5313
5249
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5314
|
-
if (!
|
|
5250
|
+
if (!fs6.existsSync(handoffPath)) {
|
|
5315
5251
|
return Ok(null);
|
|
5316
5252
|
}
|
|
5317
|
-
const raw =
|
|
5253
|
+
const raw = fs6.readFileSync(handoffPath, "utf-8");
|
|
5318
5254
|
const parsed = JSON.parse(raw);
|
|
5319
5255
|
const result = HandoffSchema.safeParse(parsed);
|
|
5320
5256
|
if (!result.success) {
|
|
@@ -5332,8 +5268,8 @@ async function runMechanicalGate(projectPath) {
|
|
|
5332
5268
|
const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
|
|
5333
5269
|
try {
|
|
5334
5270
|
let checks = [];
|
|
5335
|
-
if (
|
|
5336
|
-
const raw = JSON.parse(
|
|
5271
|
+
if (fs6.existsSync(gateConfigPath)) {
|
|
5272
|
+
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
5337
5273
|
const config = GateConfigSchema.safeParse(raw);
|
|
5338
5274
|
if (config.success && config.data.checks) {
|
|
5339
5275
|
checks = config.data.checks;
|
|
@@ -5341,19 +5277,19 @@ async function runMechanicalGate(projectPath) {
|
|
|
5341
5277
|
}
|
|
5342
5278
|
if (checks.length === 0) {
|
|
5343
5279
|
const packageJsonPath = path3.join(projectPath, "package.json");
|
|
5344
|
-
if (
|
|
5345
|
-
const pkg = JSON.parse(
|
|
5280
|
+
if (fs6.existsSync(packageJsonPath)) {
|
|
5281
|
+
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
5346
5282
|
const scripts = pkg.scripts || {};
|
|
5347
5283
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
5348
5284
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
5349
5285
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
5350
5286
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
5351
5287
|
}
|
|
5352
|
-
if (
|
|
5288
|
+
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
5353
5289
|
checks.push({ name: "test", command: "go test ./..." });
|
|
5354
5290
|
checks.push({ name: "build", command: "go build ./..." });
|
|
5355
5291
|
}
|
|
5356
|
-
if (
|
|
5292
|
+
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
5357
5293
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
5358
5294
|
}
|
|
5359
5295
|
}
|
|
@@ -5556,7 +5492,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
5556
5492
|
}
|
|
5557
5493
|
|
|
5558
5494
|
// src/security/scanner.ts
|
|
5559
|
-
import * as
|
|
5495
|
+
import * as fs8 from "fs/promises";
|
|
5560
5496
|
|
|
5561
5497
|
// src/security/rules/registry.ts
|
|
5562
5498
|
var RuleRegistry = class {
|
|
@@ -5587,7 +5523,7 @@ var RuleRegistry = class {
|
|
|
5587
5523
|
};
|
|
5588
5524
|
|
|
5589
5525
|
// src/security/config.ts
|
|
5590
|
-
import { z as
|
|
5526
|
+
import { z as z5 } from "zod";
|
|
5591
5527
|
|
|
5592
5528
|
// src/security/types.ts
|
|
5593
5529
|
var DEFAULT_SECURITY_CONFIG = {
|
|
@@ -5598,19 +5534,19 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
5598
5534
|
};
|
|
5599
5535
|
|
|
5600
5536
|
// 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:
|
|
5537
|
+
var RuleOverrideSchema = z5.enum(["off", "error", "warning", "info"]);
|
|
5538
|
+
var SecurityConfigSchema = z5.object({
|
|
5539
|
+
enabled: z5.boolean().default(true),
|
|
5540
|
+
strict: z5.boolean().default(false),
|
|
5541
|
+
rules: z5.record(z5.string(), RuleOverrideSchema).optional().default({}),
|
|
5542
|
+
exclude: z5.array(z5.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
|
|
5543
|
+
external: z5.object({
|
|
5544
|
+
semgrep: z5.object({
|
|
5545
|
+
enabled: z5.union([z5.literal("auto"), z5.boolean()]).default("auto"),
|
|
5546
|
+
rulesets: z5.array(z5.string()).optional()
|
|
5611
5547
|
}).optional(),
|
|
5612
|
-
gitleaks:
|
|
5613
|
-
enabled:
|
|
5548
|
+
gitleaks: z5.object({
|
|
5549
|
+
enabled: z5.union([z5.literal("auto"), z5.boolean()]).default("auto")
|
|
5614
5550
|
}).optional()
|
|
5615
5551
|
}).optional()
|
|
5616
5552
|
});
|
|
@@ -5643,15 +5579,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
5643
5579
|
}
|
|
5644
5580
|
|
|
5645
5581
|
// src/security/stack-detector.ts
|
|
5646
|
-
import * as
|
|
5582
|
+
import * as fs7 from "fs";
|
|
5647
5583
|
import * as path4 from "path";
|
|
5648
5584
|
function detectStack(projectRoot) {
|
|
5649
5585
|
const stacks = [];
|
|
5650
5586
|
const pkgJsonPath = path4.join(projectRoot, "package.json");
|
|
5651
|
-
if (
|
|
5587
|
+
if (fs7.existsSync(pkgJsonPath)) {
|
|
5652
5588
|
stacks.push("node");
|
|
5653
5589
|
try {
|
|
5654
|
-
const pkgJson = JSON.parse(
|
|
5590
|
+
const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
|
|
5655
5591
|
const allDeps = {
|
|
5656
5592
|
...pkgJson.dependencies,
|
|
5657
5593
|
...pkgJson.devDependencies
|
|
@@ -5667,12 +5603,12 @@ function detectStack(projectRoot) {
|
|
|
5667
5603
|
}
|
|
5668
5604
|
}
|
|
5669
5605
|
const goModPath = path4.join(projectRoot, "go.mod");
|
|
5670
|
-
if (
|
|
5606
|
+
if (fs7.existsSync(goModPath)) {
|
|
5671
5607
|
stacks.push("go");
|
|
5672
5608
|
}
|
|
5673
5609
|
const requirementsPath = path4.join(projectRoot, "requirements.txt");
|
|
5674
5610
|
const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
|
|
5675
|
-
if (
|
|
5611
|
+
if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
|
|
5676
5612
|
stacks.push("python");
|
|
5677
5613
|
}
|
|
5678
5614
|
return stacks;
|
|
@@ -6099,7 +6035,7 @@ var SecurityScanner = class {
|
|
|
6099
6035
|
}
|
|
6100
6036
|
async scanFile(filePath) {
|
|
6101
6037
|
if (!this.config.enabled) return [];
|
|
6102
|
-
const content = await
|
|
6038
|
+
const content = await fs8.readFile(filePath, "utf-8");
|
|
6103
6039
|
return this.scanContent(content, filePath, 1);
|
|
6104
6040
|
}
|
|
6105
6041
|
async scanFiles(filePaths) {
|
|
@@ -6132,7 +6068,8 @@ var ALL_CHECKS = [
|
|
|
6132
6068
|
"entropy",
|
|
6133
6069
|
"security",
|
|
6134
6070
|
"perf",
|
|
6135
|
-
"phase-gate"
|
|
6071
|
+
"phase-gate",
|
|
6072
|
+
"arch"
|
|
6136
6073
|
];
|
|
6137
6074
|
async function runSingleCheck(name, projectRoot, config) {
|
|
6138
6075
|
const start = Date.now();
|
|
@@ -6196,7 +6133,17 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6196
6133
|
}
|
|
6197
6134
|
case "docs": {
|
|
6198
6135
|
const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
|
|
6199
|
-
const
|
|
6136
|
+
const entropyConfig = config.entropy || {};
|
|
6137
|
+
const result = await checkDocCoverage("project", {
|
|
6138
|
+
docsDir,
|
|
6139
|
+
sourceDir: projectRoot,
|
|
6140
|
+
excludePatterns: entropyConfig.excludePatterns || [
|
|
6141
|
+
"**/node_modules/**",
|
|
6142
|
+
"**/dist/**",
|
|
6143
|
+
"**/*.test.ts",
|
|
6144
|
+
"**/fixtures/**"
|
|
6145
|
+
]
|
|
6146
|
+
});
|
|
6200
6147
|
if (!result.ok) {
|
|
6201
6148
|
issues.push({ severity: "warning", message: result.error.message });
|
|
6202
6149
|
} else if (result.value.gaps.length > 0) {
|
|
@@ -6271,11 +6218,13 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6271
6218
|
break;
|
|
6272
6219
|
}
|
|
6273
6220
|
case "perf": {
|
|
6221
|
+
const perfConfig = config.performance || {};
|
|
6274
6222
|
const perfAnalyzer = new EntropyAnalyzer({
|
|
6275
6223
|
rootDir: projectRoot,
|
|
6276
6224
|
analyze: {
|
|
6277
|
-
complexity: true,
|
|
6278
|
-
coupling: true
|
|
6225
|
+
complexity: perfConfig.complexity || true,
|
|
6226
|
+
coupling: perfConfig.coupling || true,
|
|
6227
|
+
sizeBudget: perfConfig.sizeBudget || false
|
|
6279
6228
|
}
|
|
6280
6229
|
});
|
|
6281
6230
|
const perfResult = await perfAnalyzer.analyze();
|
|
@@ -6316,6 +6265,43 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6316
6265
|
});
|
|
6317
6266
|
break;
|
|
6318
6267
|
}
|
|
6268
|
+
case "arch": {
|
|
6269
|
+
const rawArchConfig = config.architecture;
|
|
6270
|
+
const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
|
|
6271
|
+
if (!archConfig.enabled) break;
|
|
6272
|
+
const results = await runAll(archConfig, projectRoot);
|
|
6273
|
+
const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
|
|
6274
|
+
const baseline = baselineManager.load();
|
|
6275
|
+
if (baseline) {
|
|
6276
|
+
const diffResult = diff(results, baseline);
|
|
6277
|
+
if (!diffResult.passed) {
|
|
6278
|
+
for (const v of diffResult.newViolations) {
|
|
6279
|
+
issues.push({
|
|
6280
|
+
severity: v.severity,
|
|
6281
|
+
message: `[${v.category || "arch"}] NEW: ${v.detail}`,
|
|
6282
|
+
file: v.file
|
|
6283
|
+
});
|
|
6284
|
+
}
|
|
6285
|
+
for (const r of diffResult.regressions) {
|
|
6286
|
+
issues.push({
|
|
6287
|
+
severity: "error",
|
|
6288
|
+
message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
|
|
6289
|
+
});
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
} else {
|
|
6293
|
+
for (const result of results) {
|
|
6294
|
+
for (const v of result.violations) {
|
|
6295
|
+
issues.push({
|
|
6296
|
+
severity: v.severity,
|
|
6297
|
+
message: `[${result.category}] ${v.detail}`,
|
|
6298
|
+
file: v.file
|
|
6299
|
+
});
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
break;
|
|
6304
|
+
}
|
|
6319
6305
|
}
|
|
6320
6306
|
} catch (error) {
|
|
6321
6307
|
issues.push({
|
|
@@ -6650,22 +6636,22 @@ var PREFIX_PATTERNS = [
|
|
|
6650
6636
|
];
|
|
6651
6637
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
6652
6638
|
var MD_FILE_PATTERN = /\.md$/;
|
|
6653
|
-
function detectChangeType(commitMessage,
|
|
6639
|
+
function detectChangeType(commitMessage, diff2) {
|
|
6654
6640
|
const trimmed = commitMessage.trim();
|
|
6655
6641
|
for (const { pattern, type } of PREFIX_PATTERNS) {
|
|
6656
6642
|
if (pattern.test(trimmed)) {
|
|
6657
6643
|
return type;
|
|
6658
6644
|
}
|
|
6659
6645
|
}
|
|
6660
|
-
if (
|
|
6646
|
+
if (diff2.changedFiles.length > 0 && diff2.changedFiles.every((f) => MD_FILE_PATTERN.test(f))) {
|
|
6661
6647
|
return "docs";
|
|
6662
6648
|
}
|
|
6663
|
-
const newNonTestFiles =
|
|
6649
|
+
const newNonTestFiles = diff2.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
|
|
6664
6650
|
if (newNonTestFiles.length > 0) {
|
|
6665
6651
|
return "feature";
|
|
6666
6652
|
}
|
|
6667
|
-
const hasNewTestFile =
|
|
6668
|
-
if (
|
|
6653
|
+
const hasNewTestFile = diff2.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
|
|
6654
|
+
if (diff2.totalDiffLines < 20 && hasNewTestFile) {
|
|
6669
6655
|
return "bugfix";
|
|
6670
6656
|
}
|
|
6671
6657
|
return "feature";
|
|
@@ -6704,7 +6690,7 @@ function extractImportSources(content) {
|
|
|
6704
6690
|
}
|
|
6705
6691
|
return sources;
|
|
6706
6692
|
}
|
|
6707
|
-
async function
|
|
6693
|
+
async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
6708
6694
|
if (!importSource.startsWith(".")) return null;
|
|
6709
6695
|
const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
|
|
6710
6696
|
const basePath = path7.resolve(fromDir, importSource);
|
|
@@ -6739,7 +6725,7 @@ async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
|
6739
6725
|
const sources = extractImportSources(cf.content);
|
|
6740
6726
|
for (const source of sources) {
|
|
6741
6727
|
if (linesGathered >= budget) break;
|
|
6742
|
-
const resolved = await
|
|
6728
|
+
const resolved = await resolveImportPath(projectRoot, cf.path, source);
|
|
6743
6729
|
if (resolved && !seen.has(resolved)) {
|
|
6744
6730
|
seen.add(resolved);
|
|
6745
6731
|
const contextFile = await readContextFile(projectRoot, resolved, "import");
|
|
@@ -6903,11 +6889,11 @@ async function scopeArchitectureContext(projectRoot, changedFiles, budget, optio
|
|
|
6903
6889
|
return contextFiles;
|
|
6904
6890
|
}
|
|
6905
6891
|
async function scopeContext(options) {
|
|
6906
|
-
const { projectRoot, diff, commitMessage } = options;
|
|
6907
|
-
const changeType = detectChangeType(commitMessage,
|
|
6908
|
-
const budget = computeContextBudget(
|
|
6892
|
+
const { projectRoot, diff: diff2, commitMessage } = options;
|
|
6893
|
+
const changeType = detectChangeType(commitMessage, diff2);
|
|
6894
|
+
const budget = computeContextBudget(diff2.totalDiffLines);
|
|
6909
6895
|
const changedFiles = [];
|
|
6910
|
-
for (const filePath of
|
|
6896
|
+
for (const filePath of diff2.changedFiles) {
|
|
6911
6897
|
const cf = await readContextFile(projectRoot, filePath, "changed");
|
|
6912
6898
|
if (cf) changedFiles.push(cf);
|
|
6913
6899
|
}
|
|
@@ -6927,7 +6913,7 @@ async function scopeContext(options) {
|
|
|
6927
6913
|
changedFiles: [...changedFiles],
|
|
6928
6914
|
contextFiles,
|
|
6929
6915
|
commitHistory: options.commitHistory ?? [],
|
|
6930
|
-
diffLines:
|
|
6916
|
+
diffLines: diff2.totalDiffLines,
|
|
6931
6917
|
contextLines
|
|
6932
6918
|
});
|
|
6933
6919
|
}
|
|
@@ -7937,7 +7923,7 @@ function formatGitHubSummary(options) {
|
|
|
7937
7923
|
async function runReviewPipeline(options) {
|
|
7938
7924
|
const {
|
|
7939
7925
|
projectRoot,
|
|
7940
|
-
diff,
|
|
7926
|
+
diff: diff2,
|
|
7941
7927
|
commitMessage,
|
|
7942
7928
|
flags,
|
|
7943
7929
|
graph,
|
|
@@ -7971,7 +7957,7 @@ async function runReviewPipeline(options) {
|
|
|
7971
7957
|
const mechResult = await runMechanicalChecks({
|
|
7972
7958
|
projectRoot,
|
|
7973
7959
|
config,
|
|
7974
|
-
changedFiles:
|
|
7960
|
+
changedFiles: diff2.changedFiles
|
|
7975
7961
|
});
|
|
7976
7962
|
if (mechResult.ok) {
|
|
7977
7963
|
mechanicalResult = mechResult.value;
|
|
@@ -8010,7 +7996,7 @@ async function runReviewPipeline(options) {
|
|
|
8010
7996
|
try {
|
|
8011
7997
|
contextBundles = await scopeContext({
|
|
8012
7998
|
projectRoot,
|
|
8013
|
-
diff,
|
|
7999
|
+
diff: diff2,
|
|
8014
8000
|
commitMessage,
|
|
8015
8001
|
...graph != null ? { graph } : {},
|
|
8016
8002
|
...conventionFiles != null ? { conventionFiles } : {},
|
|
@@ -8024,14 +8010,14 @@ async function runReviewPipeline(options) {
|
|
|
8024
8010
|
changedFiles: [],
|
|
8025
8011
|
contextFiles: [],
|
|
8026
8012
|
commitHistory: [],
|
|
8027
|
-
diffLines:
|
|
8013
|
+
diffLines: diff2.totalDiffLines,
|
|
8028
8014
|
contextLines: 0
|
|
8029
8015
|
}));
|
|
8030
8016
|
}
|
|
8031
8017
|
const agentResults = await fanOutReview({ bundles: contextBundles });
|
|
8032
8018
|
const rawFindings = agentResults.flatMap((r) => r.findings);
|
|
8033
8019
|
const fileContents = /* @__PURE__ */ new Map();
|
|
8034
|
-
for (const [file, content] of
|
|
8020
|
+
for (const [file, content] of diff2.fileDiffs) {
|
|
8035
8021
|
fileContents.set(file, content);
|
|
8036
8022
|
}
|
|
8037
8023
|
const validatedFindings = await validateFindings({
|
|
@@ -8230,7 +8216,7 @@ function serializeFeature(feature) {
|
|
|
8230
8216
|
}
|
|
8231
8217
|
|
|
8232
8218
|
// src/roadmap/sync.ts
|
|
8233
|
-
import * as
|
|
8219
|
+
import * as fs9 from "fs";
|
|
8234
8220
|
import * as path9 from "path";
|
|
8235
8221
|
import { Ok as Ok3 } from "@harness-engineering/types";
|
|
8236
8222
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
@@ -8247,9 +8233,9 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8247
8233
|
const useRootState = featuresWithPlans.length <= 1;
|
|
8248
8234
|
if (useRootState) {
|
|
8249
8235
|
const rootStatePath = path9.join(projectPath, ".harness", "state.json");
|
|
8250
|
-
if (
|
|
8236
|
+
if (fs9.existsSync(rootStatePath)) {
|
|
8251
8237
|
try {
|
|
8252
|
-
const raw =
|
|
8238
|
+
const raw = fs9.readFileSync(rootStatePath, "utf-8");
|
|
8253
8239
|
const state = JSON.parse(raw);
|
|
8254
8240
|
if (state.progress) {
|
|
8255
8241
|
for (const status of Object.values(state.progress)) {
|
|
@@ -8261,15 +8247,15 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8261
8247
|
}
|
|
8262
8248
|
}
|
|
8263
8249
|
const sessionsDir = path9.join(projectPath, ".harness", "sessions");
|
|
8264
|
-
if (
|
|
8250
|
+
if (fs9.existsSync(sessionsDir)) {
|
|
8265
8251
|
try {
|
|
8266
|
-
const sessionDirs =
|
|
8252
|
+
const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
|
|
8267
8253
|
for (const entry of sessionDirs) {
|
|
8268
8254
|
if (!entry.isDirectory()) continue;
|
|
8269
8255
|
const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
8270
|
-
if (!
|
|
8256
|
+
if (!fs9.existsSync(autopilotPath)) continue;
|
|
8271
8257
|
try {
|
|
8272
|
-
const raw =
|
|
8258
|
+
const raw = fs9.readFileSync(autopilotPath, "utf-8");
|
|
8273
8259
|
const autopilot = JSON.parse(raw);
|
|
8274
8260
|
if (!autopilot.phases) continue;
|
|
8275
8261
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -8320,42 +8306,184 @@ function syncRoadmap(options) {
|
|
|
8320
8306
|
}
|
|
8321
8307
|
|
|
8322
8308
|
// src/interaction/types.ts
|
|
8323
|
-
import { z as
|
|
8324
|
-
var InteractionTypeSchema =
|
|
8325
|
-
var QuestionSchema =
|
|
8326
|
-
text:
|
|
8327
|
-
options:
|
|
8328
|
-
default:
|
|
8309
|
+
import { z as z6 } from "zod";
|
|
8310
|
+
var InteractionTypeSchema = z6.enum(["question", "confirmation", "transition"]);
|
|
8311
|
+
var QuestionSchema = z6.object({
|
|
8312
|
+
text: z6.string(),
|
|
8313
|
+
options: z6.array(z6.string()).optional(),
|
|
8314
|
+
default: z6.string().optional()
|
|
8329
8315
|
});
|
|
8330
|
-
var ConfirmationSchema =
|
|
8331
|
-
text:
|
|
8332
|
-
context:
|
|
8316
|
+
var ConfirmationSchema = z6.object({
|
|
8317
|
+
text: z6.string(),
|
|
8318
|
+
context: z6.string()
|
|
8333
8319
|
});
|
|
8334
|
-
var TransitionSchema =
|
|
8335
|
-
completedPhase:
|
|
8336
|
-
suggestedNext:
|
|
8337
|
-
reason:
|
|
8338
|
-
artifacts:
|
|
8339
|
-
requiresConfirmation:
|
|
8340
|
-
summary:
|
|
8320
|
+
var TransitionSchema = z6.object({
|
|
8321
|
+
completedPhase: z6.string(),
|
|
8322
|
+
suggestedNext: z6.string(),
|
|
8323
|
+
reason: z6.string(),
|
|
8324
|
+
artifacts: z6.array(z6.string()),
|
|
8325
|
+
requiresConfirmation: z6.boolean(),
|
|
8326
|
+
summary: z6.string()
|
|
8341
8327
|
});
|
|
8342
|
-
var EmitInteractionInputSchema =
|
|
8343
|
-
path:
|
|
8328
|
+
var EmitInteractionInputSchema = z6.object({
|
|
8329
|
+
path: z6.string(),
|
|
8344
8330
|
type: InteractionTypeSchema,
|
|
8345
|
-
stream:
|
|
8331
|
+
stream: z6.string().optional(),
|
|
8346
8332
|
question: QuestionSchema.optional(),
|
|
8347
8333
|
confirmation: ConfirmationSchema.optional(),
|
|
8348
8334
|
transition: TransitionSchema.optional()
|
|
8349
8335
|
});
|
|
8350
8336
|
|
|
8351
|
-
// src/
|
|
8352
|
-
import * as
|
|
8337
|
+
// src/blueprint/scanner.ts
|
|
8338
|
+
import * as fs10 from "fs/promises";
|
|
8353
8339
|
import * as path10 from "path";
|
|
8340
|
+
var ProjectScanner = class {
|
|
8341
|
+
constructor(rootDir) {
|
|
8342
|
+
this.rootDir = rootDir;
|
|
8343
|
+
}
|
|
8344
|
+
async scan() {
|
|
8345
|
+
let projectName = path10.basename(this.rootDir);
|
|
8346
|
+
try {
|
|
8347
|
+
const pkgPath = path10.join(this.rootDir, "package.json");
|
|
8348
|
+
const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
|
|
8349
|
+
const pkg = JSON.parse(pkgRaw);
|
|
8350
|
+
if (pkg.name) projectName = pkg.name;
|
|
8351
|
+
} catch {
|
|
8352
|
+
}
|
|
8353
|
+
return {
|
|
8354
|
+
projectName,
|
|
8355
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8356
|
+
modules: [
|
|
8357
|
+
{
|
|
8358
|
+
id: "foundations",
|
|
8359
|
+
title: "Foundations",
|
|
8360
|
+
description: "Utility files and basic types.",
|
|
8361
|
+
files: []
|
|
8362
|
+
},
|
|
8363
|
+
{
|
|
8364
|
+
id: "core-logic",
|
|
8365
|
+
title: "Core Logic",
|
|
8366
|
+
description: "Mid-level services and domain logic.",
|
|
8367
|
+
files: []
|
|
8368
|
+
},
|
|
8369
|
+
{
|
|
8370
|
+
id: "interaction-surface",
|
|
8371
|
+
title: "Interaction Surface",
|
|
8372
|
+
description: "APIs, CLIs, and Entry Points.",
|
|
8373
|
+
files: []
|
|
8374
|
+
},
|
|
8375
|
+
{
|
|
8376
|
+
id: "cross-cutting",
|
|
8377
|
+
title: "Cross-Cutting Concerns",
|
|
8378
|
+
description: "Security, Logging, and Observability.",
|
|
8379
|
+
files: []
|
|
8380
|
+
}
|
|
8381
|
+
],
|
|
8382
|
+
hotspots: [],
|
|
8383
|
+
dependencies: []
|
|
8384
|
+
};
|
|
8385
|
+
}
|
|
8386
|
+
};
|
|
8387
|
+
|
|
8388
|
+
// src/blueprint/generator.ts
|
|
8389
|
+
import * as fs11 from "fs/promises";
|
|
8390
|
+
import * as path11 from "path";
|
|
8391
|
+
import * as ejs from "ejs";
|
|
8392
|
+
|
|
8393
|
+
// src/blueprint/templates.ts
|
|
8394
|
+
var SHELL_TEMPLATE = `
|
|
8395
|
+
<!DOCTYPE html>
|
|
8396
|
+
<html lang="en">
|
|
8397
|
+
<head>
|
|
8398
|
+
<meta charset="UTF-8">
|
|
8399
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8400
|
+
<title>Blueprint: <%= projectName %></title>
|
|
8401
|
+
<style><%- styles %></style>
|
|
8402
|
+
</head>
|
|
8403
|
+
<body>
|
|
8404
|
+
<div id="app">
|
|
8405
|
+
<header>
|
|
8406
|
+
<h1>Blueprint: <%= projectName %></h1>
|
|
8407
|
+
<p>Generated at: <%= generatedAt %></p>
|
|
8408
|
+
</header>
|
|
8409
|
+
<main>
|
|
8410
|
+
<section class="modules">
|
|
8411
|
+
<% modules.forEach(module => { %>
|
|
8412
|
+
<article class="module" id="<%= module.id %>">
|
|
8413
|
+
<h2><%= module.title %></h2>
|
|
8414
|
+
<p><%= module.description %></p>
|
|
8415
|
+
<div class="content">
|
|
8416
|
+
<h3>Code Translation</h3>
|
|
8417
|
+
<div class="translation"><%- module.content.codeTranslation %></div>
|
|
8418
|
+
</div>
|
|
8419
|
+
</article>
|
|
8420
|
+
|
|
8421
|
+
<% }) %>
|
|
8422
|
+
</section>
|
|
8423
|
+
</main>
|
|
8424
|
+
</div>
|
|
8425
|
+
<script><%- scripts %></script>
|
|
8426
|
+
</body>
|
|
8427
|
+
</html>
|
|
8428
|
+
`;
|
|
8429
|
+
var STYLES = `
|
|
8430
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; color: #333; }
|
|
8431
|
+
header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10px; }
|
|
8432
|
+
.module { background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-radius: 4px; }
|
|
8433
|
+
.module h2 { margin-top: 0; color: #0066cc; }
|
|
8434
|
+
`;
|
|
8435
|
+
var SCRIPTS = `
|
|
8436
|
+
console.log('Blueprint player initialized.');
|
|
8437
|
+
`;
|
|
8438
|
+
|
|
8439
|
+
// src/shared/llm.ts
|
|
8440
|
+
var MockLLMService = class {
|
|
8441
|
+
async generate(prompt) {
|
|
8442
|
+
return "This is a mock LLM response for: " + prompt;
|
|
8443
|
+
}
|
|
8444
|
+
};
|
|
8445
|
+
var llmService = new MockLLMService();
|
|
8446
|
+
|
|
8447
|
+
// src/blueprint/content-pipeline.ts
|
|
8448
|
+
var ContentPipeline = class {
|
|
8449
|
+
async generateModuleContent(module) {
|
|
8450
|
+
const codeContext = module.files.join("\n");
|
|
8451
|
+
const translation = await llmService.generate(
|
|
8452
|
+
`You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
|
|
8453
|
+
);
|
|
8454
|
+
return {
|
|
8455
|
+
codeTranslation: translation
|
|
8456
|
+
};
|
|
8457
|
+
}
|
|
8458
|
+
};
|
|
8459
|
+
|
|
8460
|
+
// src/blueprint/generator.ts
|
|
8461
|
+
var BlueprintGenerator = class {
|
|
8462
|
+
contentPipeline = new ContentPipeline();
|
|
8463
|
+
async generate(data, options) {
|
|
8464
|
+
await Promise.all(
|
|
8465
|
+
data.modules.map(async (module) => {
|
|
8466
|
+
module.content = await this.contentPipeline.generateModuleContent(module);
|
|
8467
|
+
})
|
|
8468
|
+
);
|
|
8469
|
+
const html = ejs.render(SHELL_TEMPLATE, {
|
|
8470
|
+
...data,
|
|
8471
|
+
styles: STYLES,
|
|
8472
|
+
scripts: SCRIPTS
|
|
8473
|
+
});
|
|
8474
|
+
await fs11.mkdir(options.outputDir, { recursive: true });
|
|
8475
|
+
await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
|
|
8476
|
+
}
|
|
8477
|
+
};
|
|
8478
|
+
|
|
8479
|
+
// src/update-checker.ts
|
|
8480
|
+
import * as fs12 from "fs";
|
|
8481
|
+
import * as path12 from "path";
|
|
8354
8482
|
import * as os from "os";
|
|
8355
8483
|
import { spawn } from "child_process";
|
|
8356
8484
|
function getStatePath() {
|
|
8357
8485
|
const home = process.env["HOME"] || os.homedir();
|
|
8358
|
-
return
|
|
8486
|
+
return path12.join(home, ".harness", "update-check.json");
|
|
8359
8487
|
}
|
|
8360
8488
|
function isUpdateCheckEnabled(configInterval) {
|
|
8361
8489
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -8368,7 +8496,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
8368
8496
|
}
|
|
8369
8497
|
function readCheckState() {
|
|
8370
8498
|
try {
|
|
8371
|
-
const raw =
|
|
8499
|
+
const raw = fs12.readFileSync(getStatePath(), "utf-8");
|
|
8372
8500
|
const parsed = JSON.parse(raw);
|
|
8373
8501
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
8374
8502
|
const state = parsed;
|
|
@@ -8385,7 +8513,7 @@ function readCheckState() {
|
|
|
8385
8513
|
}
|
|
8386
8514
|
function spawnBackgroundCheck(currentVersion) {
|
|
8387
8515
|
const statePath = getStatePath();
|
|
8388
|
-
const stateDir =
|
|
8516
|
+
const stateDir = path12.dirname(statePath);
|
|
8389
8517
|
const script = `
|
|
8390
8518
|
const { execSync } = require('child_process');
|
|
8391
8519
|
const fs = require('fs');
|
|
@@ -8439,38 +8567,63 @@ Run "harness update" to upgrade.`;
|
|
|
8439
8567
|
}
|
|
8440
8568
|
|
|
8441
8569
|
// src/index.ts
|
|
8442
|
-
var VERSION = "
|
|
8570
|
+
var VERSION = "0.11.0";
|
|
8443
8571
|
export {
|
|
8444
8572
|
AGENT_DESCRIPTORS,
|
|
8445
8573
|
ARCHITECTURE_DESCRIPTOR,
|
|
8446
8574
|
AgentActionEmitter,
|
|
8575
|
+
ArchBaselineManager,
|
|
8576
|
+
ArchBaselineSchema,
|
|
8577
|
+
ArchConfigSchema,
|
|
8578
|
+
ArchDiffResultSchema,
|
|
8579
|
+
ArchMetricCategorySchema,
|
|
8447
8580
|
BUG_DETECTION_DESCRIPTOR,
|
|
8448
8581
|
BaselineManager,
|
|
8449
8582
|
BenchmarkRunner,
|
|
8583
|
+
BlueprintGenerator,
|
|
8584
|
+
BundleConstraintsSchema,
|
|
8585
|
+
BundleSchema,
|
|
8450
8586
|
COMPLIANCE_DESCRIPTOR,
|
|
8587
|
+
CategoryBaselineSchema,
|
|
8588
|
+
CategoryRegressionSchema,
|
|
8451
8589
|
ChecklistBuilder,
|
|
8590
|
+
CircularDepsCollector,
|
|
8591
|
+
ComplexityCollector,
|
|
8452
8592
|
ConfirmationSchema,
|
|
8453
8593
|
ConsoleSink,
|
|
8594
|
+
ConstraintRuleSchema,
|
|
8595
|
+
ContentPipeline,
|
|
8596
|
+
ContributionsSchema,
|
|
8597
|
+
CouplingCollector,
|
|
8454
8598
|
CriticalPathResolver,
|
|
8455
8599
|
DEFAULT_PROVIDER_TIERS,
|
|
8456
8600
|
DEFAULT_SECURITY_CONFIG,
|
|
8457
8601
|
DEFAULT_STATE,
|
|
8458
8602
|
DEFAULT_STREAM_INDEX,
|
|
8603
|
+
DepDepthCollector,
|
|
8459
8604
|
EmitInteractionInputSchema,
|
|
8460
8605
|
EntropyAnalyzer,
|
|
8461
8606
|
EntropyConfigSchema,
|
|
8462
8607
|
ExclusionSet,
|
|
8463
8608
|
FailureEntrySchema,
|
|
8464
8609
|
FileSink,
|
|
8610
|
+
ForbiddenImportCollector,
|
|
8465
8611
|
GateConfigSchema,
|
|
8466
8612
|
GateResultSchema,
|
|
8467
8613
|
HandoffSchema,
|
|
8468
8614
|
HarnessStateSchema,
|
|
8469
8615
|
InteractionTypeSchema,
|
|
8616
|
+
LayerViolationCollector,
|
|
8617
|
+
LockfilePackageSchema,
|
|
8618
|
+
LockfileSchema,
|
|
8619
|
+
ManifestSchema,
|
|
8620
|
+
MetricResultSchema,
|
|
8621
|
+
ModuleSizeCollector,
|
|
8470
8622
|
NoOpExecutor,
|
|
8471
8623
|
NoOpSink,
|
|
8472
8624
|
NoOpTelemetryAdapter,
|
|
8473
8625
|
PatternConfigSchema,
|
|
8626
|
+
ProjectScanner,
|
|
8474
8627
|
QuestionSchema,
|
|
8475
8628
|
REQUIRED_SECTIONS,
|
|
8476
8629
|
RegressionDetector,
|
|
@@ -8478,16 +8631,26 @@ export {
|
|
|
8478
8631
|
SECURITY_DESCRIPTOR,
|
|
8479
8632
|
SecurityConfigSchema,
|
|
8480
8633
|
SecurityScanner,
|
|
8634
|
+
SharableBoundaryConfigSchema,
|
|
8635
|
+
SharableForbiddenImportSchema,
|
|
8636
|
+
SharableLayerSchema,
|
|
8637
|
+
SharableSecurityRulesSchema,
|
|
8481
8638
|
StreamIndexSchema,
|
|
8482
8639
|
StreamInfoSchema,
|
|
8640
|
+
ThresholdConfigSchema,
|
|
8483
8641
|
TransitionSchema,
|
|
8484
8642
|
TypeScriptParser,
|
|
8485
8643
|
VERSION,
|
|
8644
|
+
ViolationSchema,
|
|
8645
|
+
addProvenance,
|
|
8486
8646
|
analyzeDiff,
|
|
8487
8647
|
appendFailure,
|
|
8488
8648
|
appendLearning,
|
|
8489
8649
|
applyFixes,
|
|
8490
8650
|
applyHotspotDowngrade,
|
|
8651
|
+
archMatchers,
|
|
8652
|
+
archModule,
|
|
8653
|
+
architecture,
|
|
8491
8654
|
archiveFailures,
|
|
8492
8655
|
archiveStream,
|
|
8493
8656
|
buildDependencyGraph,
|
|
@@ -8497,6 +8660,7 @@ export {
|
|
|
8497
8660
|
checkEligibility,
|
|
8498
8661
|
classifyFinding,
|
|
8499
8662
|
configureFeedback,
|
|
8663
|
+
constraintRuleId,
|
|
8500
8664
|
contextBudget,
|
|
8501
8665
|
contextFilter,
|
|
8502
8666
|
createBoundaryValidator,
|
|
@@ -8511,6 +8675,8 @@ export {
|
|
|
8511
8675
|
cryptoRules,
|
|
8512
8676
|
deduplicateCleanupFindings,
|
|
8513
8677
|
deduplicateFindings,
|
|
8678
|
+
deepMergeConstraints,
|
|
8679
|
+
defaultCollectors,
|
|
8514
8680
|
defineLayer,
|
|
8515
8681
|
deserializationRules,
|
|
8516
8682
|
detectChangeType,
|
|
@@ -8523,9 +8689,12 @@ export {
|
|
|
8523
8689
|
detectPatternViolations,
|
|
8524
8690
|
detectSizeBudgetViolations,
|
|
8525
8691
|
detectStack,
|
|
8692
|
+
detectStaleConstraints,
|
|
8526
8693
|
determineAssessment,
|
|
8694
|
+
diff,
|
|
8527
8695
|
executeWorkflow,
|
|
8528
8696
|
expressRules,
|
|
8697
|
+
extractBundle,
|
|
8529
8698
|
extractMarkdownLinks,
|
|
8530
8699
|
extractSections,
|
|
8531
8700
|
fanOutReview,
|
|
@@ -8556,6 +8725,7 @@ export {
|
|
|
8556
8725
|
networkRules,
|
|
8557
8726
|
nodeRules,
|
|
8558
8727
|
parseDiff,
|
|
8728
|
+
parseManifest,
|
|
8559
8729
|
parseRoadmap,
|
|
8560
8730
|
parseSecurityConfig,
|
|
8561
8731
|
parseSize,
|
|
@@ -8563,6 +8733,9 @@ export {
|
|
|
8563
8733
|
previewFix,
|
|
8564
8734
|
reactRules,
|
|
8565
8735
|
readCheckState,
|
|
8736
|
+
readLockfile,
|
|
8737
|
+
removeContributions,
|
|
8738
|
+
removeProvenance,
|
|
8566
8739
|
requestMultiplePeerReviews,
|
|
8567
8740
|
requestPeerReview,
|
|
8568
8741
|
resetFeedbackConfig,
|
|
@@ -8570,6 +8743,8 @@ export {
|
|
|
8570
8743
|
resolveModelTier,
|
|
8571
8744
|
resolveRuleSeverity,
|
|
8572
8745
|
resolveStreamPath,
|
|
8746
|
+
resolveThresholds,
|
|
8747
|
+
runAll,
|
|
8573
8748
|
runArchitectureAgent,
|
|
8574
8749
|
runBugDetectionAgent,
|
|
8575
8750
|
runCIChecks,
|
|
@@ -8589,6 +8764,7 @@ export {
|
|
|
8589
8764
|
setActiveStream,
|
|
8590
8765
|
shouldRunCheck,
|
|
8591
8766
|
spawnBackgroundCheck,
|
|
8767
|
+
syncConstraintNodes,
|
|
8592
8768
|
syncRoadmap,
|
|
8593
8769
|
touchStream,
|
|
8594
8770
|
trackAction,
|
|
@@ -8601,5 +8777,8 @@ export {
|
|
|
8601
8777
|
validateFindings,
|
|
8602
8778
|
validateKnowledgeMap,
|
|
8603
8779
|
validatePatternConfig,
|
|
8780
|
+
violationId,
|
|
8781
|
+
writeConfig,
|
|
8782
|
+
writeLockfile,
|
|
8604
8783
|
xssRules
|
|
8605
8784
|
};
|