@harness-engineering/core 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,11 +4993,11 @@ 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
  }
5135
5002
  learningsCacheMap.delete(learningsPath);
5136
5003
  return Ok(void 0);
@@ -5148,17 +5015,17 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
5148
5015
  if (!dirResult.ok) return dirResult;
5149
5016
  const stateDir = dirResult.value;
5150
5017
  const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
5151
- if (!fs4.existsSync(learningsPath)) {
5018
+ if (!fs6.existsSync(learningsPath)) {
5152
5019
  return Ok([]);
5153
5020
  }
5154
- const stats = fs4.statSync(learningsPath);
5021
+ const stats = fs6.statSync(learningsPath);
5155
5022
  const cacheKey = learningsPath;
5156
5023
  const cached = learningsCacheMap.get(cacheKey);
5157
5024
  let entries;
5158
5025
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5159
5026
  entries = cached.entries;
5160
5027
  } else {
5161
- const content = fs4.readFileSync(learningsPath, "utf-8");
5028
+ const content = fs6.readFileSync(learningsPath, "utf-8");
5162
5029
  const lines = content.split("\n");
5163
5030
  entries = [];
5164
5031
  let currentBlock = [];
@@ -5201,16 +5068,16 @@ async function appendFailure(projectPath, description, skillName, type, stream)
5201
5068
  if (!dirResult.ok) return dirResult;
5202
5069
  const stateDir = dirResult.value;
5203
5070
  const failuresPath = path3.join(stateDir, FAILURES_FILE);
5204
- fs4.mkdirSync(stateDir, { recursive: true });
5071
+ fs6.mkdirSync(stateDir, { recursive: true });
5205
5072
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5206
5073
  const entry = `
5207
5074
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
5208
5075
  `;
5209
- if (!fs4.existsSync(failuresPath)) {
5210
- fs4.writeFileSync(failuresPath, `# Failures
5076
+ if (!fs6.existsSync(failuresPath)) {
5077
+ fs6.writeFileSync(failuresPath, `# Failures
5211
5078
  ${entry}`);
5212
5079
  } else {
5213
- fs4.appendFileSync(failuresPath, entry);
5080
+ fs6.appendFileSync(failuresPath, entry);
5214
5081
  }
5215
5082
  failuresCacheMap.delete(failuresPath);
5216
5083
  return Ok(void 0);
@@ -5228,16 +5095,16 @@ async function loadFailures(projectPath, stream) {
5228
5095
  if (!dirResult.ok) return dirResult;
5229
5096
  const stateDir = dirResult.value;
5230
5097
  const failuresPath = path3.join(stateDir, FAILURES_FILE);
5231
- if (!fs4.existsSync(failuresPath)) {
5098
+ if (!fs6.existsSync(failuresPath)) {
5232
5099
  return Ok([]);
5233
5100
  }
5234
- const stats = fs4.statSync(failuresPath);
5101
+ const stats = fs6.statSync(failuresPath);
5235
5102
  const cacheKey = failuresPath;
5236
5103
  const cached = failuresCacheMap.get(cacheKey);
5237
5104
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5238
5105
  return Ok(cached.entries);
5239
5106
  }
5240
- const content = fs4.readFileSync(failuresPath, "utf-8");
5107
+ const content = fs6.readFileSync(failuresPath, "utf-8");
5241
5108
  const entries = [];
5242
5109
  for (const line of content.split("\n")) {
5243
5110
  const match = line.match(FAILURE_LINE_REGEX);
@@ -5267,19 +5134,19 @@ async function archiveFailures(projectPath, stream) {
5267
5134
  if (!dirResult.ok) return dirResult;
5268
5135
  const stateDir = dirResult.value;
5269
5136
  const failuresPath = path3.join(stateDir, FAILURES_FILE);
5270
- if (!fs4.existsSync(failuresPath)) {
5137
+ if (!fs6.existsSync(failuresPath)) {
5271
5138
  return Ok(void 0);
5272
5139
  }
5273
5140
  const archiveDir = path3.join(stateDir, "archive");
5274
- fs4.mkdirSync(archiveDir, { recursive: true });
5141
+ fs6.mkdirSync(archiveDir, { recursive: true });
5275
5142
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5276
5143
  let archiveName = `failures-${date}.md`;
5277
5144
  let counter = 2;
5278
- while (fs4.existsSync(path3.join(archiveDir, archiveName))) {
5145
+ while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
5279
5146
  archiveName = `failures-${date}-${counter}.md`;
5280
5147
  counter++;
5281
5148
  }
5282
- fs4.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5149
+ fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5283
5150
  failuresCacheMap.delete(failuresPath);
5284
5151
  return Ok(void 0);
5285
5152
  } catch (error) {
@@ -5296,8 +5163,8 @@ async function saveHandoff(projectPath, handoff, stream) {
5296
5163
  if (!dirResult.ok) return dirResult;
5297
5164
  const stateDir = dirResult.value;
5298
5165
  const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5299
- fs4.mkdirSync(stateDir, { recursive: true });
5300
- fs4.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
5166
+ fs6.mkdirSync(stateDir, { recursive: true });
5167
+ fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
5301
5168
  return Ok(void 0);
5302
5169
  } catch (error) {
5303
5170
  return Err(
@@ -5311,10 +5178,10 @@ async function loadHandoff(projectPath, stream) {
5311
5178
  if (!dirResult.ok) return dirResult;
5312
5179
  const stateDir = dirResult.value;
5313
5180
  const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5314
- if (!fs4.existsSync(handoffPath)) {
5181
+ if (!fs6.existsSync(handoffPath)) {
5315
5182
  return Ok(null);
5316
5183
  }
5317
- const raw = fs4.readFileSync(handoffPath, "utf-8");
5184
+ const raw = fs6.readFileSync(handoffPath, "utf-8");
5318
5185
  const parsed = JSON.parse(raw);
5319
5186
  const result = HandoffSchema.safeParse(parsed);
5320
5187
  if (!result.success) {
@@ -5332,8 +5199,8 @@ async function runMechanicalGate(projectPath) {
5332
5199
  const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
5333
5200
  try {
5334
5201
  let checks = [];
5335
- if (fs4.existsSync(gateConfigPath)) {
5336
- const raw = JSON.parse(fs4.readFileSync(gateConfigPath, "utf-8"));
5202
+ if (fs6.existsSync(gateConfigPath)) {
5203
+ const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
5337
5204
  const config = GateConfigSchema.safeParse(raw);
5338
5205
  if (config.success && config.data.checks) {
5339
5206
  checks = config.data.checks;
@@ -5341,19 +5208,19 @@ async function runMechanicalGate(projectPath) {
5341
5208
  }
5342
5209
  if (checks.length === 0) {
5343
5210
  const packageJsonPath = path3.join(projectPath, "package.json");
5344
- if (fs4.existsSync(packageJsonPath)) {
5345
- const pkg = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
5211
+ if (fs6.existsSync(packageJsonPath)) {
5212
+ const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
5346
5213
  const scripts = pkg.scripts || {};
5347
5214
  if (scripts.test) checks.push({ name: "test", command: "npm test" });
5348
5215
  if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
5349
5216
  if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
5350
5217
  if (scripts.build) checks.push({ name: "build", command: "npm run build" });
5351
5218
  }
5352
- if (fs4.existsSync(path3.join(projectPath, "go.mod"))) {
5219
+ if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
5353
5220
  checks.push({ name: "test", command: "go test ./..." });
5354
5221
  checks.push({ name: "build", command: "go build ./..." });
5355
5222
  }
5356
- if (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"))) {
5357
5224
  checks.push({ name: "test", command: "python -m pytest" });
5358
5225
  }
5359
5226
  }
@@ -5556,7 +5423,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
5556
5423
  }
5557
5424
 
5558
5425
  // src/security/scanner.ts
5559
- import * as fs6 from "fs/promises";
5426
+ import * as fs8 from "fs/promises";
5560
5427
 
5561
5428
  // src/security/rules/registry.ts
5562
5429
  var RuleRegistry = class {
@@ -5587,7 +5454,7 @@ var RuleRegistry = class {
5587
5454
  };
5588
5455
 
5589
5456
  // src/security/config.ts
5590
- import { z as z4 } from "zod";
5457
+ import { z as z5 } from "zod";
5591
5458
 
5592
5459
  // src/security/types.ts
5593
5460
  var DEFAULT_SECURITY_CONFIG = {
@@ -5598,19 +5465,19 @@ var DEFAULT_SECURITY_CONFIG = {
5598
5465
  };
5599
5466
 
5600
5467
  // src/security/config.ts
5601
- var RuleOverrideSchema = z4.enum(["off", "error", "warning", "info"]);
5602
- var SecurityConfigSchema = z4.object({
5603
- enabled: z4.boolean().default(true),
5604
- strict: z4.boolean().default(false),
5605
- rules: z4.record(z4.string(), RuleOverrideSchema).optional().default({}),
5606
- exclude: z4.array(z4.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
5607
- external: z4.object({
5608
- semgrep: z4.object({
5609
- enabled: z4.union([z4.literal("auto"), z4.boolean()]).default("auto"),
5610
- 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()
5611
5478
  }).optional(),
5612
- gitleaks: z4.object({
5613
- 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")
5614
5481
  }).optional()
5615
5482
  }).optional()
5616
5483
  });
@@ -5643,15 +5510,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
5643
5510
  }
5644
5511
 
5645
5512
  // src/security/stack-detector.ts
5646
- import * as fs5 from "fs";
5513
+ import * as fs7 from "fs";
5647
5514
  import * as path4 from "path";
5648
5515
  function detectStack(projectRoot) {
5649
5516
  const stacks = [];
5650
5517
  const pkgJsonPath = path4.join(projectRoot, "package.json");
5651
- if (fs5.existsSync(pkgJsonPath)) {
5518
+ if (fs7.existsSync(pkgJsonPath)) {
5652
5519
  stacks.push("node");
5653
5520
  try {
5654
- const pkgJson = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
5521
+ const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5655
5522
  const allDeps = {
5656
5523
  ...pkgJson.dependencies,
5657
5524
  ...pkgJson.devDependencies
@@ -5667,12 +5534,12 @@ function detectStack(projectRoot) {
5667
5534
  }
5668
5535
  }
5669
5536
  const goModPath = path4.join(projectRoot, "go.mod");
5670
- if (fs5.existsSync(goModPath)) {
5537
+ if (fs7.existsSync(goModPath)) {
5671
5538
  stacks.push("go");
5672
5539
  }
5673
5540
  const requirementsPath = path4.join(projectRoot, "requirements.txt");
5674
5541
  const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
5675
- if (fs5.existsSync(requirementsPath) || fs5.existsSync(pyprojectPath)) {
5542
+ if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
5676
5543
  stacks.push("python");
5677
5544
  }
5678
5545
  return stacks;
@@ -6099,7 +5966,7 @@ var SecurityScanner = class {
6099
5966
  }
6100
5967
  async scanFile(filePath) {
6101
5968
  if (!this.config.enabled) return [];
6102
- const content = await fs6.readFile(filePath, "utf-8");
5969
+ const content = await fs8.readFile(filePath, "utf-8");
6103
5970
  return this.scanContent(content, filePath, 1);
6104
5971
  }
6105
5972
  async scanFiles(filePaths) {
@@ -6132,7 +5999,8 @@ var ALL_CHECKS = [
6132
5999
  "entropy",
6133
6000
  "security",
6134
6001
  "perf",
6135
- "phase-gate"
6002
+ "phase-gate",
6003
+ "arch"
6136
6004
  ];
6137
6005
  async function runSingleCheck(name, projectRoot, config) {
6138
6006
  const start = Date.now();
@@ -6196,7 +6064,17 @@ async function runSingleCheck(name, projectRoot, config) {
6196
6064
  }
6197
6065
  case "docs": {
6198
6066
  const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
6199
- const 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
+ });
6200
6078
  if (!result.ok) {
6201
6079
  issues.push({ severity: "warning", message: result.error.message });
6202
6080
  } else if (result.value.gaps.length > 0) {
@@ -6271,11 +6149,13 @@ async function runSingleCheck(name, projectRoot, config) {
6271
6149
  break;
6272
6150
  }
6273
6151
  case "perf": {
6152
+ const perfConfig = config.performance || {};
6274
6153
  const perfAnalyzer = new EntropyAnalyzer({
6275
6154
  rootDir: projectRoot,
6276
6155
  analyze: {
6277
- complexity: true,
6278
- coupling: true
6156
+ complexity: perfConfig.complexity || true,
6157
+ coupling: perfConfig.coupling || true,
6158
+ sizeBudget: perfConfig.sizeBudget || false
6279
6159
  }
6280
6160
  });
6281
6161
  const perfResult = await perfAnalyzer.analyze();
@@ -6316,6 +6196,43 @@ async function runSingleCheck(name, projectRoot, config) {
6316
6196
  });
6317
6197
  break;
6318
6198
  }
6199
+ case "arch": {
6200
+ const rawArchConfig = config.architecture;
6201
+ const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
6202
+ if (!archConfig.enabled) break;
6203
+ const results = await runAll(archConfig, projectRoot);
6204
+ const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
6205
+ const baseline = baselineManager.load();
6206
+ if (baseline) {
6207
+ const diffResult = diff(results, baseline);
6208
+ if (!diffResult.passed) {
6209
+ for (const v of diffResult.newViolations) {
6210
+ issues.push({
6211
+ severity: v.severity,
6212
+ message: `[${v.category || "arch"}] NEW: ${v.detail}`,
6213
+ file: v.file
6214
+ });
6215
+ }
6216
+ for (const r of diffResult.regressions) {
6217
+ issues.push({
6218
+ severity: "error",
6219
+ message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
6220
+ });
6221
+ }
6222
+ }
6223
+ } else {
6224
+ for (const result of results) {
6225
+ for (const v of result.violations) {
6226
+ issues.push({
6227
+ severity: v.severity,
6228
+ message: `[${result.category}] ${v.detail}`,
6229
+ file: v.file
6230
+ });
6231
+ }
6232
+ }
6233
+ }
6234
+ break;
6235
+ }
6319
6236
  }
6320
6237
  } catch (error) {
6321
6238
  issues.push({
@@ -6650,22 +6567,22 @@ var PREFIX_PATTERNS = [
6650
6567
  ];
6651
6568
  var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mts|cts)$/;
6652
6569
  var MD_FILE_PATTERN = /\.md$/;
6653
- function detectChangeType(commitMessage, diff) {
6570
+ function detectChangeType(commitMessage, diff2) {
6654
6571
  const trimmed = commitMessage.trim();
6655
6572
  for (const { pattern, type } of PREFIX_PATTERNS) {
6656
6573
  if (pattern.test(trimmed)) {
6657
6574
  return type;
6658
6575
  }
6659
6576
  }
6660
- if (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))) {
6661
6578
  return "docs";
6662
6579
  }
6663
- const newNonTestFiles = diff.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
6580
+ const newNonTestFiles = diff2.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
6664
6581
  if (newNonTestFiles.length > 0) {
6665
6582
  return "feature";
6666
6583
  }
6667
- const hasNewTestFile = diff.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
6668
- if (diff.totalDiffLines < 20 && hasNewTestFile) {
6584
+ const hasNewTestFile = diff2.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
6585
+ if (diff2.totalDiffLines < 20 && hasNewTestFile) {
6669
6586
  return "bugfix";
6670
6587
  }
6671
6588
  return "feature";
@@ -6704,7 +6621,7 @@ function extractImportSources(content) {
6704
6621
  }
6705
6622
  return sources;
6706
6623
  }
6707
- async function resolveImportPath2(projectRoot, fromFile, importSource) {
6624
+ async function resolveImportPath(projectRoot, fromFile, importSource) {
6708
6625
  if (!importSource.startsWith(".")) return null;
6709
6626
  const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
6710
6627
  const basePath = path7.resolve(fromDir, importSource);
@@ -6739,7 +6656,7 @@ async function gatherImportContext(projectRoot, changedFiles, budget) {
6739
6656
  const sources = extractImportSources(cf.content);
6740
6657
  for (const source of sources) {
6741
6658
  if (linesGathered >= budget) break;
6742
- const resolved = await resolveImportPath2(projectRoot, cf.path, source);
6659
+ const resolved = await resolveImportPath(projectRoot, cf.path, source);
6743
6660
  if (resolved && !seen.has(resolved)) {
6744
6661
  seen.add(resolved);
6745
6662
  const contextFile = await readContextFile(projectRoot, resolved, "import");
@@ -6903,11 +6820,11 @@ async function scopeArchitectureContext(projectRoot, changedFiles, budget, optio
6903
6820
  return contextFiles;
6904
6821
  }
6905
6822
  async function scopeContext(options) {
6906
- const { projectRoot, diff, commitMessage } = options;
6907
- const changeType = detectChangeType(commitMessage, diff);
6908
- const budget = computeContextBudget(diff.totalDiffLines);
6823
+ const { projectRoot, diff: diff2, commitMessage } = options;
6824
+ const changeType = detectChangeType(commitMessage, diff2);
6825
+ const budget = computeContextBudget(diff2.totalDiffLines);
6909
6826
  const changedFiles = [];
6910
- for (const filePath of diff.changedFiles) {
6827
+ for (const filePath of diff2.changedFiles) {
6911
6828
  const cf = await readContextFile(projectRoot, filePath, "changed");
6912
6829
  if (cf) changedFiles.push(cf);
6913
6830
  }
@@ -6927,7 +6844,7 @@ async function scopeContext(options) {
6927
6844
  changedFiles: [...changedFiles],
6928
6845
  contextFiles,
6929
6846
  commitHistory: options.commitHistory ?? [],
6930
- diffLines: diff.totalDiffLines,
6847
+ diffLines: diff2.totalDiffLines,
6931
6848
  contextLines
6932
6849
  });
6933
6850
  }
@@ -7937,7 +7854,7 @@ function formatGitHubSummary(options) {
7937
7854
  async function runReviewPipeline(options) {
7938
7855
  const {
7939
7856
  projectRoot,
7940
- diff,
7857
+ diff: diff2,
7941
7858
  commitMessage,
7942
7859
  flags,
7943
7860
  graph,
@@ -7971,7 +7888,7 @@ async function runReviewPipeline(options) {
7971
7888
  const mechResult = await runMechanicalChecks({
7972
7889
  projectRoot,
7973
7890
  config,
7974
- changedFiles: diff.changedFiles
7891
+ changedFiles: diff2.changedFiles
7975
7892
  });
7976
7893
  if (mechResult.ok) {
7977
7894
  mechanicalResult = mechResult.value;
@@ -8010,7 +7927,7 @@ async function runReviewPipeline(options) {
8010
7927
  try {
8011
7928
  contextBundles = await scopeContext({
8012
7929
  projectRoot,
8013
- diff,
7930
+ diff: diff2,
8014
7931
  commitMessage,
8015
7932
  ...graph != null ? { graph } : {},
8016
7933
  ...conventionFiles != null ? { conventionFiles } : {},
@@ -8024,14 +7941,14 @@ async function runReviewPipeline(options) {
8024
7941
  changedFiles: [],
8025
7942
  contextFiles: [],
8026
7943
  commitHistory: [],
8027
- diffLines: diff.totalDiffLines,
7944
+ diffLines: diff2.totalDiffLines,
8028
7945
  contextLines: 0
8029
7946
  }));
8030
7947
  }
8031
7948
  const agentResults = await fanOutReview({ bundles: contextBundles });
8032
7949
  const rawFindings = agentResults.flatMap((r) => r.findings);
8033
7950
  const fileContents = /* @__PURE__ */ new Map();
8034
- for (const [file, content] of diff.fileDiffs) {
7951
+ for (const [file, content] of diff2.fileDiffs) {
8035
7952
  fileContents.set(file, content);
8036
7953
  }
8037
7954
  const validatedFindings = await validateFindings({
@@ -8230,7 +8147,7 @@ function serializeFeature(feature) {
8230
8147
  }
8231
8148
 
8232
8149
  // src/roadmap/sync.ts
8233
- import * as fs7 from "fs";
8150
+ import * as fs9 from "fs";
8234
8151
  import * as path9 from "path";
8235
8152
  import { Ok as Ok3 } from "@harness-engineering/types";
8236
8153
  function inferStatus(feature, projectPath, allFeatures) {
@@ -8247,9 +8164,9 @@ function inferStatus(feature, projectPath, allFeatures) {
8247
8164
  const useRootState = featuresWithPlans.length <= 1;
8248
8165
  if (useRootState) {
8249
8166
  const rootStatePath = path9.join(projectPath, ".harness", "state.json");
8250
- if (fs7.existsSync(rootStatePath)) {
8167
+ if (fs9.existsSync(rootStatePath)) {
8251
8168
  try {
8252
- const raw = fs7.readFileSync(rootStatePath, "utf-8");
8169
+ const raw = fs9.readFileSync(rootStatePath, "utf-8");
8253
8170
  const state = JSON.parse(raw);
8254
8171
  if (state.progress) {
8255
8172
  for (const status of Object.values(state.progress)) {
@@ -8261,15 +8178,15 @@ function inferStatus(feature, projectPath, allFeatures) {
8261
8178
  }
8262
8179
  }
8263
8180
  const sessionsDir = path9.join(projectPath, ".harness", "sessions");
8264
- if (fs7.existsSync(sessionsDir)) {
8181
+ if (fs9.existsSync(sessionsDir)) {
8265
8182
  try {
8266
- const sessionDirs = fs7.readdirSync(sessionsDir, { withFileTypes: true });
8183
+ const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
8267
8184
  for (const entry of sessionDirs) {
8268
8185
  if (!entry.isDirectory()) continue;
8269
8186
  const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
8270
- if (!fs7.existsSync(autopilotPath)) continue;
8187
+ if (!fs9.existsSync(autopilotPath)) continue;
8271
8188
  try {
8272
- const raw = fs7.readFileSync(autopilotPath, "utf-8");
8189
+ const raw = fs9.readFileSync(autopilotPath, "utf-8");
8273
8190
  const autopilot = JSON.parse(raw);
8274
8191
  if (!autopilot.phases) continue;
8275
8192
  const linkedPhases = autopilot.phases.filter(
@@ -8320,42 +8237,210 @@ function syncRoadmap(options) {
8320
8237
  }
8321
8238
 
8322
8239
  // src/interaction/types.ts
8323
- import { z as z5 } from "zod";
8324
- var InteractionTypeSchema = z5.enum(["question", "confirmation", "transition"]);
8325
- var QuestionSchema = z5.object({
8326
- text: z5.string(),
8327
- options: z5.array(z5.string()).optional(),
8328
- 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()
8329
8246
  });
8330
- var ConfirmationSchema = z5.object({
8331
- text: z5.string(),
8332
- context: z5.string()
8247
+ var ConfirmationSchema = z6.object({
8248
+ text: z6.string(),
8249
+ context: z6.string()
8333
8250
  });
8334
- var TransitionSchema = z5.object({
8335
- completedPhase: z5.string(),
8336
- suggestedNext: z5.string(),
8337
- reason: z5.string(),
8338
- artifacts: z5.array(z5.string()),
8339
- requiresConfirmation: z5.boolean(),
8340
- 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()
8341
8258
  });
8342
- var EmitInteractionInputSchema = z5.object({
8343
- path: z5.string(),
8259
+ var EmitInteractionInputSchema = z6.object({
8260
+ path: z6.string(),
8344
8261
  type: InteractionTypeSchema,
8345
- stream: z5.string().optional(),
8262
+ stream: z6.string().optional(),
8346
8263
  question: QuestionSchema.optional(),
8347
8264
  confirmation: ConfirmationSchema.optional(),
8348
8265
  transition: TransitionSchema.optional()
8349
8266
  });
8350
8267
 
8351
- // src/update-checker.ts
8352
- import * as fs8 from "fs";
8268
+ // src/blueprint/scanner.ts
8269
+ import * as fs10 from "fs/promises";
8353
8270
  import * as path10 from "path";
8271
+ var ProjectScanner = class {
8272
+ constructor(rootDir) {
8273
+ this.rootDir = rootDir;
8274
+ }
8275
+ async scan() {
8276
+ let projectName = path10.basename(this.rootDir);
8277
+ try {
8278
+ const pkgPath = path10.join(this.rootDir, "package.json");
8279
+ const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
8280
+ const pkg = JSON.parse(pkgRaw);
8281
+ if (pkg.name) projectName = pkg.name;
8282
+ } catch {
8283
+ }
8284
+ return {
8285
+ projectName,
8286
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8287
+ modules: [
8288
+ {
8289
+ id: "foundations",
8290
+ title: "Foundations",
8291
+ description: "Utility files and basic types.",
8292
+ files: []
8293
+ },
8294
+ {
8295
+ id: "core-logic",
8296
+ title: "Core Logic",
8297
+ description: "Mid-level services and domain logic.",
8298
+ files: []
8299
+ },
8300
+ {
8301
+ id: "interaction-surface",
8302
+ title: "Interaction Surface",
8303
+ description: "APIs, CLIs, and Entry Points.",
8304
+ files: []
8305
+ },
8306
+ {
8307
+ id: "cross-cutting",
8308
+ title: "Cross-Cutting Concerns",
8309
+ description: "Security, Logging, and Observability.",
8310
+ files: []
8311
+ }
8312
+ ],
8313
+ hotspots: [],
8314
+ dependencies: []
8315
+ };
8316
+ }
8317
+ };
8318
+
8319
+ // src/blueprint/generator.ts
8320
+ import * as fs11 from "fs/promises";
8321
+ import * as path11 from "path";
8322
+ import * as ejs from "ejs";
8323
+
8324
+ // src/blueprint/templates.ts
8325
+ var SHELL_TEMPLATE = `
8326
+ <!DOCTYPE html>
8327
+ <html lang="en">
8328
+ <head>
8329
+ <meta charset="UTF-8">
8330
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8331
+ <title>Blueprint: <%= projectName %></title>
8332
+ <style><%- styles %></style>
8333
+ </head>
8334
+ <body>
8335
+ <div id="app">
8336
+ <header>
8337
+ <h1>Blueprint: <%= projectName %></h1>
8338
+ <p>Generated at: <%= generatedAt %></p>
8339
+ </header>
8340
+ <main>
8341
+ <section class="modules">
8342
+ <% modules.forEach(module => { %>
8343
+ <article class="module" id="<%= module.id %>">
8344
+ <h2><%= module.title %></h2>
8345
+ <p><%= module.description %></p>
8346
+ <div class="content">
8347
+ <h3>Code Translation</h3>
8348
+ <div class="translation"><%- module.content.codeTranslation %></div>
8349
+ <h3>Knowledge Check</h3>
8350
+ <div class="quiz">
8351
+ <% module.content.quiz.questions.forEach((q, i) => { %>
8352
+ <div class="question">
8353
+ <p><%= q.question %></p>
8354
+ <button onclick="reveal(this)">Reveal Answer</button>
8355
+ <p class="answer" style="display:none;"><%= q.answer %></p>
8356
+ </div>
8357
+ <% }) %>
8358
+ </div>
8359
+ </div>
8360
+ </article>
8361
+
8362
+ <% }) %>
8363
+ </section>
8364
+ </main>
8365
+ </div>
8366
+ <script><%- scripts %></script>
8367
+ </body>
8368
+ </html>
8369
+ `;
8370
+ var STYLES = `
8371
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; color: #333; }
8372
+ header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10px; }
8373
+ .module { background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-radius: 4px; }
8374
+ .module h2 { margin-top: 0; color: #0066cc; }
8375
+ `;
8376
+ var SCRIPTS = `
8377
+ function reveal(btn) {
8378
+ btn.nextElementSibling.style.display = 'block';
8379
+ btn.style.display = 'none';
8380
+ }
8381
+ console.log('Blueprint player initialized.');
8382
+ `;
8383
+
8384
+ // src/shared/llm.ts
8385
+ var MockLLMService = class {
8386
+ async generate(prompt) {
8387
+ return "This is a mock LLM response for: " + prompt;
8388
+ }
8389
+ };
8390
+ var llmService = new MockLLMService();
8391
+
8392
+ // src/blueprint/content-pipeline.ts
8393
+ var ContentPipeline = class {
8394
+ async generateModuleContent(module) {
8395
+ const codeContext = module.files.join("\n");
8396
+ const translation = await llmService.generate(
8397
+ `You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
8398
+ );
8399
+ const quizJson = await llmService.generate(
8400
+ `Create 3 technical quiz questions for this code. Return ONLY valid JSON in this format: { "questions": [{ "question": "...", "answer": "..." }] }. Code: ${codeContext}`
8401
+ );
8402
+ let quiz;
8403
+ try {
8404
+ const cleanJson = quizJson.replace(/```json/g, "").replace(/```/g, "").trim();
8405
+ quiz = JSON.parse(cleanJson);
8406
+ } catch (e) {
8407
+ console.error("Failed to parse quiz JSON", e);
8408
+ quiz = { questions: [{ question: "Failed to generate quiz", answer: "N/A" }] };
8409
+ }
8410
+ return {
8411
+ codeTranslation: translation,
8412
+ quiz
8413
+ };
8414
+ }
8415
+ };
8416
+
8417
+ // src/blueprint/generator.ts
8418
+ var BlueprintGenerator = class {
8419
+ contentPipeline = new ContentPipeline();
8420
+ async generate(data, options) {
8421
+ await Promise.all(
8422
+ data.modules.map(async (module) => {
8423
+ module.content = await this.contentPipeline.generateModuleContent(module);
8424
+ })
8425
+ );
8426
+ const html = ejs.render(SHELL_TEMPLATE, {
8427
+ ...data,
8428
+ styles: STYLES,
8429
+ scripts: SCRIPTS
8430
+ });
8431
+ await fs11.mkdir(options.outputDir, { recursive: true });
8432
+ await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
8433
+ }
8434
+ };
8435
+
8436
+ // src/update-checker.ts
8437
+ import * as fs12 from "fs";
8438
+ import * as path12 from "path";
8354
8439
  import * as os from "os";
8355
8440
  import { spawn } from "child_process";
8356
8441
  function getStatePath() {
8357
8442
  const home = process.env["HOME"] || os.homedir();
8358
- return path10.join(home, ".harness", "update-check.json");
8443
+ return path12.join(home, ".harness", "update-check.json");
8359
8444
  }
8360
8445
  function isUpdateCheckEnabled(configInterval) {
8361
8446
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -8368,7 +8453,7 @@ function shouldRunCheck(state, intervalMs) {
8368
8453
  }
8369
8454
  function readCheckState() {
8370
8455
  try {
8371
- const raw = fs8.readFileSync(getStatePath(), "utf-8");
8456
+ const raw = fs12.readFileSync(getStatePath(), "utf-8");
8372
8457
  const parsed = JSON.parse(raw);
8373
8458
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
8374
8459
  const state = parsed;
@@ -8385,7 +8470,7 @@ function readCheckState() {
8385
8470
  }
8386
8471
  function spawnBackgroundCheck(currentVersion) {
8387
8472
  const statePath = getStatePath();
8388
- const stateDir = path10.dirname(statePath);
8473
+ const stateDir = path12.dirname(statePath);
8389
8474
  const script = `
8390
8475
  const { execSync } = require('child_process');
8391
8476
  const fs = require('fs');
@@ -8439,38 +8524,63 @@ Run "harness update" to upgrade.`;
8439
8524
  }
8440
8525
 
8441
8526
  // src/index.ts
8442
- var VERSION = "1.8.2";
8527
+ var VERSION = "0.11.0";
8443
8528
  export {
8444
8529
  AGENT_DESCRIPTORS,
8445
8530
  ARCHITECTURE_DESCRIPTOR,
8446
8531
  AgentActionEmitter,
8532
+ ArchBaselineManager,
8533
+ ArchBaselineSchema,
8534
+ ArchConfigSchema,
8535
+ ArchDiffResultSchema,
8536
+ ArchMetricCategorySchema,
8447
8537
  BUG_DETECTION_DESCRIPTOR,
8448
8538
  BaselineManager,
8449
8539
  BenchmarkRunner,
8540
+ BlueprintGenerator,
8541
+ BundleConstraintsSchema,
8542
+ BundleSchema,
8450
8543
  COMPLIANCE_DESCRIPTOR,
8544
+ CategoryBaselineSchema,
8545
+ CategoryRegressionSchema,
8451
8546
  ChecklistBuilder,
8547
+ CircularDepsCollector,
8548
+ ComplexityCollector,
8452
8549
  ConfirmationSchema,
8453
8550
  ConsoleSink,
8551
+ ConstraintRuleSchema,
8552
+ ContentPipeline,
8553
+ ContributionsSchema,
8554
+ CouplingCollector,
8454
8555
  CriticalPathResolver,
8455
8556
  DEFAULT_PROVIDER_TIERS,
8456
8557
  DEFAULT_SECURITY_CONFIG,
8457
8558
  DEFAULT_STATE,
8458
8559
  DEFAULT_STREAM_INDEX,
8560
+ DepDepthCollector,
8459
8561
  EmitInteractionInputSchema,
8460
8562
  EntropyAnalyzer,
8461
8563
  EntropyConfigSchema,
8462
8564
  ExclusionSet,
8463
8565
  FailureEntrySchema,
8464
8566
  FileSink,
8567
+ ForbiddenImportCollector,
8465
8568
  GateConfigSchema,
8466
8569
  GateResultSchema,
8467
8570
  HandoffSchema,
8468
8571
  HarnessStateSchema,
8469
8572
  InteractionTypeSchema,
8573
+ LayerViolationCollector,
8574
+ LockfilePackageSchema,
8575
+ LockfileSchema,
8576
+ ManifestSchema,
8577
+ MetricResultSchema,
8578
+ ModuleSizeCollector,
8470
8579
  NoOpExecutor,
8471
8580
  NoOpSink,
8472
8581
  NoOpTelemetryAdapter,
8473
8582
  PatternConfigSchema,
8583
+ ProjectScanner,
8474
8584
  QuestionSchema,
8475
8585
  REQUIRED_SECTIONS,
8476
8586
  RegressionDetector,
@@ -8478,16 +8588,26 @@ export {
8478
8588
  SECURITY_DESCRIPTOR,
8479
8589
  SecurityConfigSchema,
8480
8590
  SecurityScanner,
8591
+ SharableBoundaryConfigSchema,
8592
+ SharableForbiddenImportSchema,
8593
+ SharableLayerSchema,
8594
+ SharableSecurityRulesSchema,
8481
8595
  StreamIndexSchema,
8482
8596
  StreamInfoSchema,
8597
+ ThresholdConfigSchema,
8483
8598
  TransitionSchema,
8484
8599
  TypeScriptParser,
8485
8600
  VERSION,
8601
+ ViolationSchema,
8602
+ addProvenance,
8486
8603
  analyzeDiff,
8487
8604
  appendFailure,
8488
8605
  appendLearning,
8489
8606
  applyFixes,
8490
8607
  applyHotspotDowngrade,
8608
+ archMatchers,
8609
+ archModule,
8610
+ architecture,
8491
8611
  archiveFailures,
8492
8612
  archiveStream,
8493
8613
  buildDependencyGraph,
@@ -8497,6 +8617,7 @@ export {
8497
8617
  checkEligibility,
8498
8618
  classifyFinding,
8499
8619
  configureFeedback,
8620
+ constraintRuleId,
8500
8621
  contextBudget,
8501
8622
  contextFilter,
8502
8623
  createBoundaryValidator,
@@ -8511,6 +8632,8 @@ export {
8511
8632
  cryptoRules,
8512
8633
  deduplicateCleanupFindings,
8513
8634
  deduplicateFindings,
8635
+ deepMergeConstraints,
8636
+ defaultCollectors,
8514
8637
  defineLayer,
8515
8638
  deserializationRules,
8516
8639
  detectChangeType,
@@ -8523,9 +8646,12 @@ export {
8523
8646
  detectPatternViolations,
8524
8647
  detectSizeBudgetViolations,
8525
8648
  detectStack,
8649
+ detectStaleConstraints,
8526
8650
  determineAssessment,
8651
+ diff,
8527
8652
  executeWorkflow,
8528
8653
  expressRules,
8654
+ extractBundle,
8529
8655
  extractMarkdownLinks,
8530
8656
  extractSections,
8531
8657
  fanOutReview,
@@ -8556,6 +8682,7 @@ export {
8556
8682
  networkRules,
8557
8683
  nodeRules,
8558
8684
  parseDiff,
8685
+ parseManifest,
8559
8686
  parseRoadmap,
8560
8687
  parseSecurityConfig,
8561
8688
  parseSize,
@@ -8563,6 +8690,8 @@ export {
8563
8690
  previewFix,
8564
8691
  reactRules,
8565
8692
  readCheckState,
8693
+ readLockfile,
8694
+ removeProvenance,
8566
8695
  requestMultiplePeerReviews,
8567
8696
  requestPeerReview,
8568
8697
  resetFeedbackConfig,
@@ -8570,6 +8699,8 @@ export {
8570
8699
  resolveModelTier,
8571
8700
  resolveRuleSeverity,
8572
8701
  resolveStreamPath,
8702
+ resolveThresholds,
8703
+ runAll,
8573
8704
  runArchitectureAgent,
8574
8705
  runBugDetectionAgent,
8575
8706
  runCIChecks,
@@ -8589,6 +8720,7 @@ export {
8589
8720
  setActiveStream,
8590
8721
  shouldRunCheck,
8591
8722
  spawnBackgroundCheck,
8723
+ syncConstraintNodes,
8592
8724
  syncRoadmap,
8593
8725
  touchStream,
8594
8726
  trackAction,
@@ -8601,5 +8733,8 @@ export {
8601
8733
  validateFindings,
8602
8734
  validateKnowledgeMap,
8603
8735
  validatePatternConfig,
8736
+ violationId,
8737
+ writeConfig,
8738
+ writeLockfile,
8604
8739
  xssRules
8605
8740
  };