@harness-engineering/core 0.10.0 → 0.11.0

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