@harness-engineering/core 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,491 @@ function validateBoundaries(boundaries, data) {
1119
877
  });
1120
878
  }
1121
879
 
880
+ // src/constraints/sharing/types.ts
881
+ import { z } from "zod";
882
+ var ManifestSchema = z.object({
883
+ name: z.string(),
884
+ version: z.string(),
885
+ description: z.string().optional(),
886
+ include: z.array(z.string()).min(1),
887
+ minHarnessVersion: z.string().optional(),
888
+ keywords: z.array(z.string()).optional().default([]),
889
+ layers: z.record(z.array(z.string())).optional(),
890
+ boundaries: z.array(
891
+ z.object({
892
+ name: z.string(),
893
+ layer: z.string(),
894
+ direction: z.enum(["input", "output"]),
895
+ schema: z.string()
896
+ })
897
+ ).optional()
898
+ });
899
+ var BundleConstraintsSchema = z.object({
900
+ layers: z.array(
901
+ z.object({
902
+ name: z.string(),
903
+ pattern: z.string(),
904
+ allowedDependencies: z.array(z.string())
905
+ })
906
+ ).optional(),
907
+ forbiddenImports: z.array(
908
+ z.object({
909
+ from: z.string(),
910
+ disallow: z.array(z.string()),
911
+ message: z.string().optional()
912
+ })
913
+ ).optional(),
914
+ boundaries: z.object({
915
+ requireSchema: z.array(z.string()).optional()
916
+ }).optional(),
917
+ architecture: z.object({
918
+ thresholds: z.record(z.unknown()).optional(),
919
+ modules: z.record(z.record(z.unknown())).optional()
920
+ }).optional(),
921
+ security: z.object({
922
+ rules: z.record(z.string()).optional()
923
+ }).optional()
924
+ });
925
+ var BundleSchema = z.object({
926
+ name: z.string(),
927
+ version: z.string(),
928
+ description: z.string().optional(),
929
+ minHarnessVersion: z.string().optional(),
930
+ manifest: ManifestSchema,
931
+ constraints: BundleConstraintsSchema,
932
+ contributions: z.record(z.unknown()).optional()
933
+ });
934
+ var ContributionsSchema = z.record(z.unknown());
935
+ var LockfilePackageSchema = z.object({
936
+ version: z.string(),
937
+ source: z.string(),
938
+ installedAt: z.string(),
939
+ contributions: z.record(z.unknown()).optional().nullable(),
940
+ resolved: z.string().optional(),
941
+ integrity: z.string().optional(),
942
+ provenance: z.array(z.string()).optional()
943
+ });
944
+ var LockfileSchema = z.object({
945
+ version: z.literal(1).optional(),
946
+ // Used by some tests
947
+ lockfileVersion: z.literal(1).optional(),
948
+ // Standard field
949
+ packages: z.record(LockfilePackageSchema)
950
+ }).refine((data) => data.version !== void 0 || data.lockfileVersion !== void 0, {
951
+ message: "Either 'version' or 'lockfileVersion' must be present and equal to 1"
952
+ });
953
+ var SharableLayerSchema = z.unknown();
954
+ var SharableForbiddenImportSchema = z.unknown();
955
+ var SharableBoundaryConfigSchema = z.unknown();
956
+ var SharableSecurityRulesSchema = z.unknown();
957
+
958
+ // src/constraints/sharing/write-config.ts
959
+ import * as fs from "fs/promises";
960
+ async function writeConfig(filePath, content) {
961
+ try {
962
+ const json = JSON.stringify(content, null, 2) + "\n";
963
+ await fs.writeFile(filePath, json, "utf-8");
964
+ return Ok(void 0);
965
+ } catch (error) {
966
+ return Err(error instanceof Error ? error : new Error(String(error)));
967
+ }
968
+ }
969
+
970
+ // src/constraints/sharing/manifest.ts
971
+ function parseManifest(parsed) {
972
+ const result = ManifestSchema.safeParse(parsed);
973
+ if (!result.success) {
974
+ const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
975
+ return { ok: false, error: `Invalid manifest: ${issues}` };
976
+ }
977
+ return { ok: true, value: result.data };
978
+ }
979
+
980
+ // src/constraints/sharing/bundle.ts
981
+ function resolveDotPath(obj, dotPath) {
982
+ const segments = dotPath.split(".");
983
+ let current = obj;
984
+ for (const segment of segments) {
985
+ if (current === null || typeof current !== "object") {
986
+ return void 0;
987
+ }
988
+ current = current[segment];
989
+ }
990
+ return current;
991
+ }
992
+ function setDotPath(obj, dotPath, value) {
993
+ const segments = dotPath.split(".");
994
+ const lastSegment = segments[segments.length - 1];
995
+ const parentSegments = segments.slice(0, -1);
996
+ let current = obj;
997
+ for (const segment of parentSegments) {
998
+ if (current[segment] === void 0 || current[segment] === null || typeof current[segment] !== "object") {
999
+ current[segment] = {};
1000
+ }
1001
+ current = current[segment];
1002
+ }
1003
+ if (lastSegment !== void 0) {
1004
+ current[lastSegment] = value;
1005
+ }
1006
+ }
1007
+ function extractBundle(manifest, config) {
1008
+ const constraints = {};
1009
+ for (const includePath of manifest.include) {
1010
+ const value = resolveDotPath(config, includePath);
1011
+ if (value !== void 0) {
1012
+ setDotPath(constraints, includePath, value);
1013
+ }
1014
+ }
1015
+ const bundle = {
1016
+ name: manifest.name,
1017
+ version: manifest.version,
1018
+ ...manifest.minHarnessVersion !== void 0 && {
1019
+ minHarnessVersion: manifest.minHarnessVersion
1020
+ },
1021
+ ...manifest.description !== void 0 && {
1022
+ description: manifest.description
1023
+ },
1024
+ manifest,
1025
+ constraints
1026
+ };
1027
+ const parsed = BundleSchema.safeParse(bundle);
1028
+ if (!parsed.success) {
1029
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
1030
+ return { ok: false, error: `Invalid bundle: ${issues}` };
1031
+ }
1032
+ return { ok: true, value: parsed.data };
1033
+ }
1034
+
1035
+ // src/constraints/sharing/merge.ts
1036
+ function deepEqual(a, b) {
1037
+ if (a === b) return true;
1038
+ if (typeof a !== typeof b) return false;
1039
+ if (typeof a !== "object" || a === null || b === null) return false;
1040
+ if (Array.isArray(a) && Array.isArray(b)) {
1041
+ if (a.length !== b.length) return false;
1042
+ return a.every((val, i) => deepEqual(val, b[i]));
1043
+ }
1044
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
1045
+ const keysA = Object.keys(a);
1046
+ const keysB = Object.keys(b);
1047
+ if (keysA.length !== keysB.length) return false;
1048
+ return keysA.every(
1049
+ (key) => deepEqual(a[key], b[key])
1050
+ );
1051
+ }
1052
+ function stringArraysEqual(a, b) {
1053
+ if (a.length !== b.length) return false;
1054
+ const sortedA = [...a].sort();
1055
+ const sortedB = [...b].sort();
1056
+ return sortedA.every((val, i) => val === sortedB[i]);
1057
+ }
1058
+ function deepMergeConstraints(localConfig, bundleConstraints, _existingContributions) {
1059
+ const config = { ...localConfig };
1060
+ const contributions = {};
1061
+ const conflicts = [];
1062
+ if (bundleConstraints.layers && bundleConstraints.layers.length > 0) {
1063
+ const localLayers = Array.isArray(localConfig.layers) ? localConfig.layers : [];
1064
+ const mergedLayers = [...localLayers];
1065
+ const contributedLayerNames = [];
1066
+ for (const bundleLayer of bundleConstraints.layers) {
1067
+ const existing = localLayers.find((l) => l.name === bundleLayer.name);
1068
+ if (!existing) {
1069
+ mergedLayers.push(bundleLayer);
1070
+ contributedLayerNames.push(bundleLayer.name);
1071
+ } else {
1072
+ const same = existing.pattern === bundleLayer.pattern && stringArraysEqual(existing.allowedDependencies, bundleLayer.allowedDependencies);
1073
+ if (!same) {
1074
+ conflicts.push({
1075
+ section: "layers",
1076
+ key: bundleLayer.name,
1077
+ localValue: existing,
1078
+ packageValue: bundleLayer,
1079
+ description: `Layer '${bundleLayer.name}' already exists locally with different configuration`
1080
+ });
1081
+ }
1082
+ }
1083
+ }
1084
+ config.layers = mergedLayers;
1085
+ if (contributedLayerNames.length > 0) {
1086
+ contributions.layers = contributedLayerNames;
1087
+ }
1088
+ }
1089
+ if (bundleConstraints.forbiddenImports && bundleConstraints.forbiddenImports.length > 0) {
1090
+ const localFI = Array.isArray(localConfig.forbiddenImports) ? localConfig.forbiddenImports : [];
1091
+ const mergedFI = [...localFI];
1092
+ const contributedFromKeys = [];
1093
+ for (const bundleRule of bundleConstraints.forbiddenImports) {
1094
+ const existing = localFI.find((r) => r.from === bundleRule.from);
1095
+ if (!existing) {
1096
+ const entry = {
1097
+ from: bundleRule.from,
1098
+ disallow: bundleRule.disallow
1099
+ };
1100
+ if (bundleRule.message !== void 0) {
1101
+ entry.message = bundleRule.message;
1102
+ }
1103
+ mergedFI.push(entry);
1104
+ contributedFromKeys.push(bundleRule.from);
1105
+ } else {
1106
+ const same = stringArraysEqual(existing.disallow, bundleRule.disallow);
1107
+ if (!same) {
1108
+ conflicts.push({
1109
+ section: "forbiddenImports",
1110
+ key: bundleRule.from,
1111
+ localValue: existing,
1112
+ packageValue: bundleRule,
1113
+ description: `Forbidden import rule for '${bundleRule.from}' already exists locally with different disallow list`
1114
+ });
1115
+ }
1116
+ }
1117
+ }
1118
+ config.forbiddenImports = mergedFI;
1119
+ if (contributedFromKeys.length > 0) {
1120
+ contributions.forbiddenImports = contributedFromKeys;
1121
+ }
1122
+ }
1123
+ if (bundleConstraints.boundaries) {
1124
+ const localBoundaries = localConfig.boundaries ?? { requireSchema: [] };
1125
+ const localSchemas = new Set(localBoundaries.requireSchema ?? []);
1126
+ const bundleSchemas = bundleConstraints.boundaries.requireSchema ?? [];
1127
+ const newSchemas = [];
1128
+ for (const schema of bundleSchemas) {
1129
+ if (!localSchemas.has(schema)) {
1130
+ newSchemas.push(schema);
1131
+ localSchemas.add(schema);
1132
+ }
1133
+ }
1134
+ config.boundaries = {
1135
+ requireSchema: [...localBoundaries.requireSchema ?? [], ...newSchemas]
1136
+ };
1137
+ if (newSchemas.length > 0) {
1138
+ contributions.boundaries = newSchemas;
1139
+ }
1140
+ }
1141
+ if (bundleConstraints.architecture) {
1142
+ const localArch = localConfig.architecture ?? {
1143
+ thresholds: {},
1144
+ modules: {}
1145
+ };
1146
+ const mergedThresholds = { ...localArch.thresholds };
1147
+ const contributedThresholdKeys = [];
1148
+ const bundleThresholds = bundleConstraints.architecture.thresholds ?? {};
1149
+ for (const [category, value] of Object.entries(bundleThresholds)) {
1150
+ if (!(category in mergedThresholds)) {
1151
+ mergedThresholds[category] = value;
1152
+ contributedThresholdKeys.push(category);
1153
+ } else if (!deepEqual(mergedThresholds[category], value)) {
1154
+ conflicts.push({
1155
+ section: "architecture.thresholds",
1156
+ key: category,
1157
+ localValue: mergedThresholds[category],
1158
+ packageValue: value,
1159
+ description: `Architecture threshold '${category}' already exists locally with a different value`
1160
+ });
1161
+ }
1162
+ }
1163
+ const mergedModules = { ...localArch.modules };
1164
+ const contributedModuleKeys = [];
1165
+ const bundleModules = bundleConstraints.architecture.modules ?? {};
1166
+ for (const [modulePath, bundleCategoryMap] of Object.entries(bundleModules)) {
1167
+ if (!(modulePath in mergedModules)) {
1168
+ mergedModules[modulePath] = bundleCategoryMap;
1169
+ for (const cat of Object.keys(bundleCategoryMap)) {
1170
+ contributedModuleKeys.push(`${modulePath}:${cat}`);
1171
+ }
1172
+ } else {
1173
+ const localCategoryMap = mergedModules[modulePath];
1174
+ const mergedCategoryMap = { ...localCategoryMap };
1175
+ for (const [category, value] of Object.entries(bundleCategoryMap)) {
1176
+ if (!(category in mergedCategoryMap)) {
1177
+ mergedCategoryMap[category] = value;
1178
+ contributedModuleKeys.push(`${modulePath}:${category}`);
1179
+ } else if (!deepEqual(mergedCategoryMap[category], value)) {
1180
+ conflicts.push({
1181
+ section: "architecture.modules",
1182
+ key: `${modulePath}:${category}`,
1183
+ localValue: mergedCategoryMap[category],
1184
+ packageValue: value,
1185
+ description: `Architecture module override '${modulePath}' category '${category}' already exists locally with a different value`
1186
+ });
1187
+ }
1188
+ }
1189
+ mergedModules[modulePath] = mergedCategoryMap;
1190
+ }
1191
+ }
1192
+ config.architecture = {
1193
+ ...localArch,
1194
+ thresholds: mergedThresholds,
1195
+ modules: mergedModules
1196
+ };
1197
+ if (contributedThresholdKeys.length > 0) {
1198
+ contributions["architecture.thresholds"] = contributedThresholdKeys;
1199
+ }
1200
+ if (contributedModuleKeys.length > 0) {
1201
+ contributions["architecture.modules"] = contributedModuleKeys;
1202
+ }
1203
+ }
1204
+ if (bundleConstraints.security?.rules) {
1205
+ const localSecurity = localConfig.security ?? { rules: {} };
1206
+ const localRules = localSecurity.rules ?? {};
1207
+ const mergedRules = { ...localRules };
1208
+ const contributedRuleIds = [];
1209
+ for (const [ruleId, severity] of Object.entries(bundleConstraints.security.rules)) {
1210
+ if (!(ruleId in mergedRules)) {
1211
+ mergedRules[ruleId] = severity;
1212
+ contributedRuleIds.push(ruleId);
1213
+ } else if (mergedRules[ruleId] !== severity) {
1214
+ conflicts.push({
1215
+ section: "security.rules",
1216
+ key: ruleId,
1217
+ localValue: mergedRules[ruleId],
1218
+ packageValue: severity,
1219
+ description: `Security rule '${ruleId}' already exists locally with severity '${mergedRules[ruleId]}', bundle has '${severity}'`
1220
+ });
1221
+ }
1222
+ }
1223
+ config.security = { ...localSecurity, rules: mergedRules };
1224
+ if (contributedRuleIds.length > 0) {
1225
+ contributions["security.rules"] = contributedRuleIds;
1226
+ }
1227
+ }
1228
+ return { config, contributions, conflicts };
1229
+ }
1230
+
1231
+ // src/constraints/sharing/lockfile.ts
1232
+ import * as fs2 from "fs/promises";
1233
+ async function readLockfile(lockfilePath) {
1234
+ let raw;
1235
+ try {
1236
+ raw = await fs2.readFile(lockfilePath, "utf-8");
1237
+ } catch (err) {
1238
+ if (isNodeError(err) && err.code === "ENOENT") {
1239
+ return { ok: true, value: null };
1240
+ }
1241
+ return {
1242
+ ok: false,
1243
+ error: `Failed to read lockfile: ${err instanceof Error ? err.message : String(err)}`
1244
+ };
1245
+ }
1246
+ let parsed;
1247
+ try {
1248
+ parsed = JSON.parse(raw);
1249
+ } catch {
1250
+ return {
1251
+ ok: false,
1252
+ error: `Failed to parse lockfile as JSON: file contains invalid JSON`
1253
+ };
1254
+ }
1255
+ const result = LockfileSchema.safeParse(parsed);
1256
+ if (!result.success) {
1257
+ return {
1258
+ ok: false,
1259
+ error: `Lockfile schema validation failed: ${result.error.issues.map((i) => i.message).join(", ")}`
1260
+ };
1261
+ }
1262
+ return { ok: true, value: result.data };
1263
+ }
1264
+ async function writeLockfile(lockfilePath, lockfile) {
1265
+ return writeConfig(lockfilePath, lockfile);
1266
+ }
1267
+ function addProvenance(lockfile, packageName, entry) {
1268
+ return {
1269
+ ...lockfile,
1270
+ packages: {
1271
+ ...lockfile.packages,
1272
+ [packageName]: entry
1273
+ }
1274
+ };
1275
+ }
1276
+ function removeProvenance(lockfile, packageName) {
1277
+ const existing = lockfile.packages[packageName];
1278
+ if (!existing) {
1279
+ return { lockfile, contributions: null };
1280
+ }
1281
+ const { [packageName]: _removed, ...remaining } = lockfile.packages;
1282
+ return {
1283
+ lockfile: {
1284
+ ...lockfile,
1285
+ packages: remaining
1286
+ },
1287
+ contributions: existing.contributions ?? null
1288
+ };
1289
+ }
1290
+ function isNodeError(err) {
1291
+ return err instanceof Error && "code" in err;
1292
+ }
1293
+
1294
+ // src/constraints/sharing/remove.ts
1295
+ function removeContributions(config, contributions) {
1296
+ const result = { ...config };
1297
+ const layerNames = contributions.layers;
1298
+ if (layerNames && layerNames.length > 0 && Array.isArray(result.layers)) {
1299
+ const nameSet = new Set(layerNames);
1300
+ result.layers = result.layers.filter((l) => !nameSet.has(l.name));
1301
+ }
1302
+ const fromKeys = contributions.forbiddenImports;
1303
+ if (fromKeys && fromKeys.length > 0 && Array.isArray(result.forbiddenImports)) {
1304
+ const fromSet = new Set(fromKeys);
1305
+ result.forbiddenImports = result.forbiddenImports.filter(
1306
+ (r) => !fromSet.has(r.from)
1307
+ );
1308
+ }
1309
+ const boundarySchemas = contributions.boundaries;
1310
+ if (boundarySchemas && boundarySchemas.length > 0 && result.boundaries) {
1311
+ const boundaries = result.boundaries;
1312
+ if (boundaries.requireSchema) {
1313
+ const schemaSet = new Set(boundarySchemas);
1314
+ result.boundaries = {
1315
+ ...boundaries,
1316
+ requireSchema: boundaries.requireSchema.filter((s) => !schemaSet.has(s))
1317
+ };
1318
+ }
1319
+ }
1320
+ const thresholdKeys = contributions["architecture.thresholds"];
1321
+ if (thresholdKeys && thresholdKeys.length > 0 && result.architecture) {
1322
+ const arch = { ...result.architecture };
1323
+ const thresholds = { ...arch.thresholds };
1324
+ for (const key of thresholdKeys) {
1325
+ delete thresholds[key];
1326
+ }
1327
+ arch.thresholds = thresholds;
1328
+ result.architecture = arch;
1329
+ }
1330
+ const moduleKeys = contributions["architecture.modules"];
1331
+ if (moduleKeys && moduleKeys.length > 0 && result.architecture) {
1332
+ const arch = { ...result.architecture };
1333
+ const modules = { ...arch.modules };
1334
+ for (const key of moduleKeys) {
1335
+ const colonIdx = key.indexOf(":");
1336
+ if (colonIdx === -1) continue;
1337
+ const modulePath = key.substring(0, colonIdx);
1338
+ const category = key.substring(colonIdx + 1);
1339
+ if (modules[modulePath]) {
1340
+ const moduleCategories = { ...modules[modulePath] };
1341
+ delete moduleCategories[category];
1342
+ if (Object.keys(moduleCategories).length === 0) {
1343
+ delete modules[modulePath];
1344
+ } else {
1345
+ modules[modulePath] = moduleCategories;
1346
+ }
1347
+ }
1348
+ }
1349
+ arch.modules = modules;
1350
+ result.architecture = arch;
1351
+ }
1352
+ const ruleIds = contributions["security.rules"];
1353
+ if (ruleIds && ruleIds.length > 0 && result.security) {
1354
+ const security = { ...result.security };
1355
+ const rules = { ...security.rules };
1356
+ for (const id of ruleIds) {
1357
+ delete rules[id];
1358
+ }
1359
+ security.rules = rules;
1360
+ result.security = security;
1361
+ }
1362
+ return result;
1363
+ }
1364
+
1122
1365
  // src/shared/parsers/typescript.ts
1123
1366
  import { parse } from "@typescript-eslint/typescript-estree";
1124
1367
 
@@ -1144,11 +1387,11 @@ function walk(node, visitor) {
1144
1387
  var TypeScriptParser = class {
1145
1388
  name = "typescript";
1146
1389
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1147
- async parseFile(path11) {
1148
- const contentResult = await readFileContent(path11);
1390
+ async parseFile(path13) {
1391
+ const contentResult = await readFileContent(path13);
1149
1392
  if (!contentResult.ok) {
1150
1393
  return Err(
1151
- createParseError("NOT_FOUND", `File not found: ${path11}`, { path: path11 }, [
1394
+ createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
1152
1395
  "Check that the file exists",
1153
1396
  "Verify the path is correct"
1154
1397
  ])
@@ -1158,7 +1401,7 @@ var TypeScriptParser = class {
1158
1401
  const ast = parse(contentResult.value, {
1159
1402
  loc: true,
1160
1403
  range: true,
1161
- jsx: path11.endsWith(".tsx"),
1404
+ jsx: path13.endsWith(".tsx"),
1162
1405
  errorOnUnknownASTType: false
1163
1406
  });
1164
1407
  return Ok({
@@ -1169,7 +1412,7 @@ var TypeScriptParser = class {
1169
1412
  } catch (e) {
1170
1413
  const error = e;
1171
1414
  return Err(
1172
- createParseError("SYNTAX_ERROR", `Failed to parse ${path11}: ${error.message}`, { path: path11 }, [
1415
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
1173
1416
  "Check for syntax errors in the file",
1174
1417
  "Ensure valid TypeScript syntax"
1175
1418
  ])
@@ -1335,11 +1578,11 @@ var TypeScriptParser = class {
1335
1578
  };
1336
1579
 
1337
1580
  // src/entropy/snapshot.ts
1338
- import { join as join3, resolve as resolve2, relative as relative5 } from "path";
1581
+ import { join as join3, resolve, relative as relative4 } from "path";
1339
1582
  import { minimatch as minimatch2 } from "minimatch";
1340
1583
  async function resolveEntryPoints(rootDir, explicitEntries) {
1341
1584
  if (explicitEntries && explicitEntries.length > 0) {
1342
- const resolved = explicitEntries.map((e) => resolve2(rootDir, e));
1585
+ const resolved = explicitEntries.map((e) => resolve(rootDir, e));
1343
1586
  return Ok(resolved);
1344
1587
  }
1345
1588
  const pkgPath = join3(rootDir, "package.json");
@@ -1352,27 +1595,27 @@ async function resolveEntryPoints(rootDir, explicitEntries) {
1352
1595
  if (pkg["exports"]) {
1353
1596
  const exports = pkg["exports"];
1354
1597
  if (typeof exports === "string") {
1355
- entries.push(resolve2(rootDir, exports));
1598
+ entries.push(resolve(rootDir, exports));
1356
1599
  } else if (typeof exports === "object" && exports !== null) {
1357
1600
  for (const value of Object.values(exports)) {
1358
1601
  if (typeof value === "string") {
1359
- entries.push(resolve2(rootDir, value));
1602
+ entries.push(resolve(rootDir, value));
1360
1603
  }
1361
1604
  }
1362
1605
  }
1363
1606
  }
1364
1607
  const main = pkg["main"];
1365
1608
  if (typeof main === "string" && entries.length === 0) {
1366
- entries.push(resolve2(rootDir, main));
1609
+ entries.push(resolve(rootDir, main));
1367
1610
  }
1368
1611
  const bin = pkg["bin"];
1369
1612
  if (bin) {
1370
1613
  if (typeof bin === "string") {
1371
- entries.push(resolve2(rootDir, bin));
1614
+ entries.push(resolve(rootDir, bin));
1372
1615
  } else if (typeof bin === "object") {
1373
1616
  for (const value of Object.values(bin)) {
1374
1617
  if (typeof value === "string") {
1375
- entries.push(resolve2(rootDir, value));
1618
+ entries.push(resolve(rootDir, value));
1376
1619
  }
1377
1620
  }
1378
1621
  }
@@ -1453,22 +1696,22 @@ function extractInlineRefs(content) {
1453
1696
  }
1454
1697
  return refs;
1455
1698
  }
1456
- async function parseDocumentationFile(path11) {
1457
- const contentResult = await readFileContent(path11);
1699
+ async function parseDocumentationFile(path13) {
1700
+ const contentResult = await readFileContent(path13);
1458
1701
  if (!contentResult.ok) {
1459
1702
  return Err(
1460
1703
  createEntropyError(
1461
1704
  "PARSE_ERROR",
1462
- `Failed to read documentation file: ${path11}`,
1463
- { file: path11 },
1705
+ `Failed to read documentation file: ${path13}`,
1706
+ { file: path13 },
1464
1707
  ["Check that the file exists"]
1465
1708
  )
1466
1709
  );
1467
1710
  }
1468
1711
  const content = contentResult.value;
1469
- const type = path11.endsWith(".md") ? "markdown" : "text";
1712
+ const type = path13.endsWith(".md") ? "markdown" : "text";
1470
1713
  return Ok({
1471
- path: path11,
1714
+ path: path13,
1472
1715
  type,
1473
1716
  content,
1474
1717
  codeBlocks: extractCodeBlocks(content),
@@ -1581,7 +1824,7 @@ function extractAllCodeReferences(docs) {
1581
1824
  async function buildSnapshot(config) {
1582
1825
  const startTime = Date.now();
1583
1826
  const parser = config.parser || new TypeScriptParser();
1584
- const rootDir = resolve2(config.rootDir);
1827
+ const rootDir = resolve(config.rootDir);
1585
1828
  const entryPointsResult = await resolveEntryPoints(rootDir, config.entryPoints);
1586
1829
  if (!entryPointsResult.ok) {
1587
1830
  return Err(entryPointsResult.error);
@@ -1599,7 +1842,7 @@ async function buildSnapshot(config) {
1599
1842
  sourceFilePaths.push(...files2);
1600
1843
  }
1601
1844
  sourceFilePaths = sourceFilePaths.filter((f) => {
1602
- const rel = relative5(rootDir, f);
1845
+ const rel = relative4(rootDir, f);
1603
1846
  return !excludePatterns.some((p) => minimatch2(rel, p));
1604
1847
  });
1605
1848
  const files = [];
@@ -1652,7 +1895,7 @@ async function buildSnapshot(config) {
1652
1895
  }
1653
1896
 
1654
1897
  // src/entropy/detectors/drift.ts
1655
- import { dirname as dirname4, resolve as resolve3 } from "path";
1898
+ import { dirname as dirname3, resolve as resolve2 } from "path";
1656
1899
  function levenshteinDistance(a, b) {
1657
1900
  const matrix = [];
1658
1901
  for (let i = 0; i <= b.length; i++) {
@@ -1757,7 +2000,7 @@ async function checkStructureDrift(snapshot, _config) {
1757
2000
  for (const doc of snapshot.docs) {
1758
2001
  const fileLinks = extractFileLinks(doc.content);
1759
2002
  for (const { link, line } of fileLinks) {
1760
- const resolvedPath = resolve3(dirname4(doc.path), link);
2003
+ const resolvedPath = resolve2(dirname3(doc.path), link);
1761
2004
  const exists = await fileExists(resolvedPath);
1762
2005
  if (!exists) {
1763
2006
  drifts.push({
@@ -1840,19 +2083,19 @@ async function detectDocDrift(snapshot, config, graphDriftData) {
1840
2083
  }
1841
2084
 
1842
2085
  // src/entropy/detectors/dead-code.ts
1843
- import { dirname as dirname5, resolve as resolve4 } from "path";
2086
+ import { dirname as dirname4, resolve as resolve3 } from "path";
1844
2087
  function resolveImportToFile(importSource, fromFile, snapshot) {
1845
2088
  if (!importSource.startsWith(".")) {
1846
2089
  return null;
1847
2090
  }
1848
- const fromDir = dirname5(fromFile);
1849
- let resolved = resolve4(fromDir, importSource);
2091
+ const fromDir = dirname4(fromFile);
2092
+ let resolved = resolve3(fromDir, importSource);
1850
2093
  if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
1851
2094
  const withTs = resolved + ".ts";
1852
2095
  if (snapshot.files.some((f) => f.path === withTs)) {
1853
2096
  return withTs;
1854
2097
  }
1855
- const withIndex = resolve4(resolved, "index.ts");
2098
+ const withIndex = resolve3(resolved, "index.ts");
1856
2099
  if (snapshot.files.some((f) => f.path === withIndex)) {
1857
2100
  return withIndex;
1858
2101
  }
@@ -2131,14 +2374,14 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
2131
2374
 
2132
2375
  // src/entropy/detectors/patterns.ts
2133
2376
  import { minimatch as minimatch3 } from "minimatch";
2134
- import { relative as relative6 } from "path";
2377
+ import { relative as relative5 } from "path";
2135
2378
  function fileMatchesPattern(filePath, pattern, rootDir) {
2136
- const relativePath = relative6(rootDir, filePath);
2379
+ const relativePath = relative5(rootDir, filePath);
2137
2380
  return minimatch3(relativePath, pattern);
2138
2381
  }
2139
2382
  function checkConfigPattern(pattern, file, rootDir) {
2140
2383
  const matches = [];
2141
- const fileMatches = pattern.files.some((glob2) => fileMatchesPattern(file.path, glob2, rootDir));
2384
+ const fileMatches = pattern.files.some((glob) => fileMatchesPattern(file.path, glob, rootDir));
2142
2385
  if (!fileMatches) {
2143
2386
  return matches;
2144
2387
  }
@@ -2296,418 +2539,6 @@ async function detectPatternViolations(snapshot, config) {
2296
2539
  });
2297
2540
  }
2298
2541
 
2299
- // src/entropy/detectors/complexity.ts
2300
- import { readFile as readFile2 } from "fs/promises";
2301
- var DEFAULT_THRESHOLDS = {
2302
- cyclomaticComplexity: { error: 15, warn: 10 },
2303
- nestingDepth: { warn: 4 },
2304
- functionLength: { warn: 50 },
2305
- parameterCount: { warn: 5 },
2306
- fileLength: { info: 300 },
2307
- hotspotPercentile: { error: 95 }
2308
- };
2309
- function extractFunctions(content) {
2310
- const functions = [];
2311
- const lines = content.split("\n");
2312
- const patterns = [
2313
- // function declarations: function name(params) {
2314
- /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/,
2315
- // method declarations: name(params) {
2316
- /^\s*(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*[^{]+)?\s*\{/,
2317
- // arrow functions assigned to const/let/var: const name = (params) =>
2318
- /^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*[^=]+)?\s*=>/,
2319
- // arrow functions assigned to const/let/var with single param: const name = param =>
2320
- /^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(\w+)\s*=>/
2321
- ];
2322
- for (let i = 0; i < lines.length; i++) {
2323
- const line = lines[i];
2324
- for (const pattern of patterns) {
2325
- const match = line.match(pattern);
2326
- if (match) {
2327
- const name = match[1] ?? "anonymous";
2328
- const paramsStr = match[2] || "";
2329
- const params = paramsStr.trim() === "" ? 0 : paramsStr.split(",").length;
2330
- const endLine = findFunctionEnd(lines, i);
2331
- const body = lines.slice(i, endLine + 1).join("\n");
2332
- functions.push({
2333
- name,
2334
- line: i + 1,
2335
- params,
2336
- startLine: i + 1,
2337
- endLine: endLine + 1,
2338
- body
2339
- });
2340
- break;
2341
- }
2342
- }
2343
- }
2344
- return functions;
2345
- }
2346
- function findFunctionEnd(lines, startIdx) {
2347
- let depth = 0;
2348
- let foundOpen = false;
2349
- for (let i = startIdx; i < lines.length; i++) {
2350
- const line = lines[i];
2351
- for (const ch of line) {
2352
- if (ch === "{") {
2353
- depth++;
2354
- foundOpen = true;
2355
- } else if (ch === "}") {
2356
- depth--;
2357
- if (foundOpen && depth === 0) {
2358
- return i;
2359
- }
2360
- }
2361
- }
2362
- }
2363
- return lines.length - 1;
2364
- }
2365
- function computeCyclomaticComplexity(body) {
2366
- let complexity = 1;
2367
- const decisionPatterns = [
2368
- /\bif\s*\(/g,
2369
- /\belse\s+if\s*\(/g,
2370
- /\bwhile\s*\(/g,
2371
- /\bfor\s*\(/g,
2372
- /\bcase\s+/g,
2373
- /&&/g,
2374
- /\|\|/g,
2375
- /\?(?!=)/g,
2376
- // Ternary ? but not ?. or ??
2377
- /\bcatch\s*\(/g
2378
- ];
2379
- for (const pattern of decisionPatterns) {
2380
- const matches = body.match(pattern);
2381
- if (matches) {
2382
- complexity += matches.length;
2383
- }
2384
- }
2385
- const elseIfMatches = body.match(/\belse\s+if\s*\(/g);
2386
- if (elseIfMatches) {
2387
- complexity -= elseIfMatches.length;
2388
- }
2389
- return complexity;
2390
- }
2391
- function computeNestingDepth(body) {
2392
- let maxDepth = 0;
2393
- let currentDepth = 0;
2394
- let functionBodyStarted = false;
2395
- for (const ch of body) {
2396
- if (ch === "{") {
2397
- if (!functionBodyStarted) {
2398
- functionBodyStarted = true;
2399
- continue;
2400
- }
2401
- currentDepth++;
2402
- if (currentDepth > maxDepth) {
2403
- maxDepth = currentDepth;
2404
- }
2405
- } else if (ch === "}") {
2406
- if (currentDepth > 0) {
2407
- currentDepth--;
2408
- }
2409
- }
2410
- }
2411
- return maxDepth;
2412
- }
2413
- async function detectComplexityViolations(snapshot, config, graphData) {
2414
- const violations = [];
2415
- const thresholds = {
2416
- cyclomaticComplexity: {
2417
- error: config?.thresholds?.cyclomaticComplexity?.error ?? DEFAULT_THRESHOLDS.cyclomaticComplexity.error,
2418
- warn: config?.thresholds?.cyclomaticComplexity?.warn ?? DEFAULT_THRESHOLDS.cyclomaticComplexity.warn
2419
- },
2420
- nestingDepth: {
2421
- warn: config?.thresholds?.nestingDepth?.warn ?? DEFAULT_THRESHOLDS.nestingDepth.warn
2422
- },
2423
- functionLength: {
2424
- warn: config?.thresholds?.functionLength?.warn ?? DEFAULT_THRESHOLDS.functionLength.warn
2425
- },
2426
- parameterCount: {
2427
- warn: config?.thresholds?.parameterCount?.warn ?? DEFAULT_THRESHOLDS.parameterCount.warn
2428
- },
2429
- fileLength: {
2430
- info: config?.thresholds?.fileLength?.info ?? DEFAULT_THRESHOLDS.fileLength.info
2431
- }
2432
- };
2433
- let totalFunctions = 0;
2434
- for (const file of snapshot.files) {
2435
- let content;
2436
- try {
2437
- content = await readFile2(file.path, "utf-8");
2438
- } catch {
2439
- continue;
2440
- }
2441
- const lines = content.split("\n");
2442
- if (lines.length > thresholds.fileLength.info) {
2443
- violations.push({
2444
- file: file.path,
2445
- function: "<file>",
2446
- line: 1,
2447
- metric: "fileLength",
2448
- value: lines.length,
2449
- threshold: thresholds.fileLength.info,
2450
- tier: 3,
2451
- severity: "info",
2452
- message: `File has ${lines.length} lines (threshold: ${thresholds.fileLength.info})`
2453
- });
2454
- }
2455
- const functions = extractFunctions(content);
2456
- totalFunctions += functions.length;
2457
- for (const fn of functions) {
2458
- const complexity = computeCyclomaticComplexity(fn.body);
2459
- if (complexity > thresholds.cyclomaticComplexity.error) {
2460
- violations.push({
2461
- file: file.path,
2462
- function: fn.name,
2463
- line: fn.line,
2464
- metric: "cyclomaticComplexity",
2465
- value: complexity,
2466
- threshold: thresholds.cyclomaticComplexity.error,
2467
- tier: 1,
2468
- severity: "error",
2469
- message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (error threshold: ${thresholds.cyclomaticComplexity.error})`
2470
- });
2471
- } else if (complexity > thresholds.cyclomaticComplexity.warn) {
2472
- violations.push({
2473
- file: file.path,
2474
- function: fn.name,
2475
- line: fn.line,
2476
- metric: "cyclomaticComplexity",
2477
- value: complexity,
2478
- threshold: thresholds.cyclomaticComplexity.warn,
2479
- tier: 2,
2480
- severity: "warning",
2481
- message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (warning threshold: ${thresholds.cyclomaticComplexity.warn})`
2482
- });
2483
- }
2484
- const nestingDepth = computeNestingDepth(fn.body);
2485
- if (nestingDepth > thresholds.nestingDepth.warn) {
2486
- violations.push({
2487
- file: file.path,
2488
- function: fn.name,
2489
- line: fn.line,
2490
- metric: "nestingDepth",
2491
- value: nestingDepth,
2492
- threshold: thresholds.nestingDepth.warn,
2493
- tier: 2,
2494
- severity: "warning",
2495
- message: `Function "${fn.name}" has nesting depth of ${nestingDepth} (threshold: ${thresholds.nestingDepth.warn})`
2496
- });
2497
- }
2498
- const fnLength = fn.endLine - fn.startLine + 1;
2499
- if (fnLength > thresholds.functionLength.warn) {
2500
- violations.push({
2501
- file: file.path,
2502
- function: fn.name,
2503
- line: fn.line,
2504
- metric: "functionLength",
2505
- value: fnLength,
2506
- threshold: thresholds.functionLength.warn,
2507
- tier: 2,
2508
- severity: "warning",
2509
- message: `Function "${fn.name}" is ${fnLength} lines long (threshold: ${thresholds.functionLength.warn})`
2510
- });
2511
- }
2512
- if (fn.params > thresholds.parameterCount.warn) {
2513
- violations.push({
2514
- file: file.path,
2515
- function: fn.name,
2516
- line: fn.line,
2517
- metric: "parameterCount",
2518
- value: fn.params,
2519
- threshold: thresholds.parameterCount.warn,
2520
- tier: 2,
2521
- severity: "warning",
2522
- message: `Function "${fn.name}" has ${fn.params} parameters (threshold: ${thresholds.parameterCount.warn})`
2523
- });
2524
- }
2525
- if (graphData) {
2526
- const hotspot = graphData.hotspots.find(
2527
- (h) => h.file === file.path && h.function === fn.name
2528
- );
2529
- if (hotspot && hotspot.hotspotScore > graphData.percentile95Score) {
2530
- violations.push({
2531
- file: file.path,
2532
- function: fn.name,
2533
- line: fn.line,
2534
- metric: "hotspotScore",
2535
- value: hotspot.hotspotScore,
2536
- threshold: graphData.percentile95Score,
2537
- tier: 1,
2538
- severity: "error",
2539
- message: `Function "${fn.name}" is a complexity hotspot (score: ${hotspot.hotspotScore}, p95: ${graphData.percentile95Score})`
2540
- });
2541
- }
2542
- }
2543
- }
2544
- }
2545
- const errorCount = violations.filter((v) => v.severity === "error").length;
2546
- const warningCount = violations.filter((v) => v.severity === "warning").length;
2547
- const infoCount = violations.filter((v) => v.severity === "info").length;
2548
- return Ok({
2549
- violations,
2550
- stats: {
2551
- filesAnalyzed: snapshot.files.length,
2552
- functionsAnalyzed: totalFunctions,
2553
- violationCount: violations.length,
2554
- errorCount,
2555
- warningCount,
2556
- infoCount
2557
- }
2558
- });
2559
- }
2560
-
2561
- // src/entropy/detectors/coupling.ts
2562
- var DEFAULT_THRESHOLDS2 = {
2563
- fanOut: { warn: 15 },
2564
- fanIn: { info: 20 },
2565
- couplingRatio: { warn: 0.7 },
2566
- transitiveDependencyDepth: { info: 30 }
2567
- };
2568
- function computeMetricsFromSnapshot(snapshot) {
2569
- const fanInMap = /* @__PURE__ */ new Map();
2570
- for (const file of snapshot.files) {
2571
- for (const imp of file.imports) {
2572
- const resolved = resolveImportSource(imp.source, file.path, snapshot);
2573
- if (resolved) {
2574
- fanInMap.set(resolved, (fanInMap.get(resolved) || 0) + 1);
2575
- }
2576
- }
2577
- }
2578
- return snapshot.files.map((file) => {
2579
- const fanOut = file.imports.length;
2580
- const fanIn = fanInMap.get(file.path) || 0;
2581
- const total = fanIn + fanOut;
2582
- const couplingRatio = total > 0 ? fanOut / total : 0;
2583
- return {
2584
- file: file.path,
2585
- fanIn,
2586
- fanOut,
2587
- couplingRatio,
2588
- transitiveDepth: 0
2589
- };
2590
- });
2591
- }
2592
- function resolveRelativePath(from, source) {
2593
- const dir = from.includes("/") ? from.substring(0, from.lastIndexOf("/")) : ".";
2594
- const parts = dir.split("/");
2595
- for (const segment of source.split("/")) {
2596
- if (segment === ".") continue;
2597
- if (segment === "..") {
2598
- parts.pop();
2599
- } else {
2600
- parts.push(segment);
2601
- }
2602
- }
2603
- return parts.join("/");
2604
- }
2605
- function resolveImportSource(source, fromFile, snapshot) {
2606
- if (!source.startsWith(".") && !source.startsWith("/")) {
2607
- return void 0;
2608
- }
2609
- const resolved = resolveRelativePath(fromFile, source);
2610
- const filePaths = snapshot.files.map((f) => f.path);
2611
- const candidates = [
2612
- resolved,
2613
- `${resolved}.ts`,
2614
- `${resolved}.tsx`,
2615
- `${resolved}/index.ts`,
2616
- `${resolved}/index.tsx`
2617
- ];
2618
- for (const candidate of candidates) {
2619
- const match = filePaths.find((fp) => fp === candidate);
2620
- if (match) return match;
2621
- }
2622
- return void 0;
2623
- }
2624
- function checkViolations(metrics, config) {
2625
- const thresholds = {
2626
- fanOut: { ...DEFAULT_THRESHOLDS2.fanOut, ...config?.thresholds?.fanOut },
2627
- fanIn: { ...DEFAULT_THRESHOLDS2.fanIn, ...config?.thresholds?.fanIn },
2628
- couplingRatio: { ...DEFAULT_THRESHOLDS2.couplingRatio, ...config?.thresholds?.couplingRatio },
2629
- transitiveDependencyDepth: {
2630
- ...DEFAULT_THRESHOLDS2.transitiveDependencyDepth,
2631
- ...config?.thresholds?.transitiveDependencyDepth
2632
- }
2633
- };
2634
- const violations = [];
2635
- for (const m of metrics) {
2636
- if (thresholds.fanOut.warn !== void 0 && m.fanOut > thresholds.fanOut.warn) {
2637
- violations.push({
2638
- file: m.file,
2639
- metric: "fanOut",
2640
- value: m.fanOut,
2641
- threshold: thresholds.fanOut.warn,
2642
- tier: 2,
2643
- severity: "warning",
2644
- message: `File has ${m.fanOut} imports (threshold: ${thresholds.fanOut.warn})`
2645
- });
2646
- }
2647
- if (thresholds.fanIn.info !== void 0 && m.fanIn > thresholds.fanIn.info) {
2648
- violations.push({
2649
- file: m.file,
2650
- metric: "fanIn",
2651
- value: m.fanIn,
2652
- threshold: thresholds.fanIn.info,
2653
- tier: 3,
2654
- severity: "info",
2655
- message: `File is imported by ${m.fanIn} files (threshold: ${thresholds.fanIn.info})`
2656
- });
2657
- }
2658
- const totalConnections = m.fanIn + m.fanOut;
2659
- if (totalConnections > 5 && thresholds.couplingRatio.warn !== void 0 && m.couplingRatio > thresholds.couplingRatio.warn) {
2660
- violations.push({
2661
- file: m.file,
2662
- metric: "couplingRatio",
2663
- value: m.couplingRatio,
2664
- threshold: thresholds.couplingRatio.warn,
2665
- tier: 2,
2666
- severity: "warning",
2667
- message: `Coupling ratio is ${m.couplingRatio.toFixed(2)} (threshold: ${thresholds.couplingRatio.warn})`
2668
- });
2669
- }
2670
- if (thresholds.transitiveDependencyDepth.info !== void 0 && m.transitiveDepth > thresholds.transitiveDependencyDepth.info) {
2671
- violations.push({
2672
- file: m.file,
2673
- metric: "transitiveDependencyDepth",
2674
- value: m.transitiveDepth,
2675
- threshold: thresholds.transitiveDependencyDepth.info,
2676
- tier: 3,
2677
- severity: "info",
2678
- message: `Transitive dependency depth is ${m.transitiveDepth} (threshold: ${thresholds.transitiveDependencyDepth.info})`
2679
- });
2680
- }
2681
- }
2682
- return violations;
2683
- }
2684
- async function detectCouplingViolations(snapshot, config, graphData) {
2685
- let metrics;
2686
- if (graphData) {
2687
- metrics = graphData.files.map((f) => ({
2688
- file: f.file,
2689
- fanIn: f.fanIn,
2690
- fanOut: f.fanOut,
2691
- couplingRatio: f.couplingRatio,
2692
- transitiveDepth: f.transitiveDepth
2693
- }));
2694
- } else {
2695
- metrics = computeMetricsFromSnapshot(snapshot);
2696
- }
2697
- const violations = checkViolations(metrics, config);
2698
- const warningCount = violations.filter((v) => v.severity === "warning").length;
2699
- const infoCount = violations.filter((v) => v.severity === "info").length;
2700
- return Ok({
2701
- violations,
2702
- stats: {
2703
- filesAnalyzed: metrics.length,
2704
- violationCount: violations.length,
2705
- warningCount,
2706
- infoCount
2707
- }
2708
- });
2709
- }
2710
-
2711
2542
  // src/entropy/detectors/size-budget.ts
2712
2543
  import { readdirSync, statSync } from "fs";
2713
2544
  import { join as join4 } from "path";
@@ -3136,14 +2967,14 @@ var EntropyAnalyzer = class {
3136
2967
  };
3137
2968
 
3138
2969
  // src/entropy/fixers/safe-fixes.ts
3139
- import * as 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);
2970
+ import * as fs3 from "fs";
2971
+ import { promisify } from "util";
2972
+ import { dirname as dirname5, basename as basename4, join as join5 } from "path";
2973
+ var readFile3 = promisify(fs3.readFile);
2974
+ var writeFile3 = promisify(fs3.writeFile);
2975
+ var unlink2 = promisify(fs3.unlink);
2976
+ var mkdir2 = promisify(fs3.mkdir);
2977
+ var copyFile2 = promisify(fs3.copyFile);
3147
2978
  var DEFAULT_FIX_CONFIG = {
3148
2979
  dryRun: false,
3149
2980
  fixTypes: ["unused-imports", "dead-files"],
@@ -3239,7 +3070,7 @@ function previewFix(fix) {
3239
3070
  async function createBackup(filePath, backupDir) {
3240
3071
  const backupPath = join5(backupDir, `${Date.now()}-${basename4(filePath)}`);
3241
3072
  try {
3242
- await mkdir2(dirname6(backupPath), { recursive: true });
3073
+ await mkdir2(dirname5(backupPath), { recursive: true });
3243
3074
  await copyFile2(filePath, backupPath);
3244
3075
  return Ok(backupPath);
3245
3076
  } catch (e) {
@@ -3270,25 +3101,25 @@ async function applySingleFix(fix, config) {
3270
3101
  break;
3271
3102
  case "delete-lines":
3272
3103
  if (fix.line !== void 0) {
3273
- const content = await readFile4(fix.file, "utf-8");
3104
+ const content = await readFile3(fix.file, "utf-8");
3274
3105
  const lines = content.split("\n");
3275
3106
  lines.splice(fix.line - 1, 1);
3276
- await writeFile2(fix.file, lines.join("\n"));
3107
+ await writeFile3(fix.file, lines.join("\n"));
3277
3108
  }
3278
3109
  break;
3279
3110
  case "replace":
3280
3111
  if (fix.oldContent && fix.newContent !== void 0) {
3281
- const content = await readFile4(fix.file, "utf-8");
3112
+ const content = await readFile3(fix.file, "utf-8");
3282
3113
  const newContent = content.replace(fix.oldContent, fix.newContent);
3283
- await writeFile2(fix.file, newContent);
3114
+ await writeFile3(fix.file, newContent);
3284
3115
  }
3285
3116
  break;
3286
3117
  case "insert":
3287
3118
  if (fix.line !== void 0 && fix.newContent) {
3288
- const content = await readFile4(fix.file, "utf-8");
3119
+ const content = await readFile3(fix.file, "utf-8");
3289
3120
  const lines = content.split("\n");
3290
3121
  lines.splice(fix.line - 1, 0, fix.newContent);
3291
- await writeFile2(fix.file, lines.join("\n"));
3122
+ await writeFile3(fix.file, lines.join("\n"));
3292
3123
  }
3293
3124
  break;
3294
3125
  }
@@ -3465,46 +3296,46 @@ function deduplicateCleanupFindings(findings) {
3465
3296
  }
3466
3297
 
3467
3298
  // src/entropy/config/schema.ts
3468
- import { z } from "zod";
3469
- var MustExportRuleSchema = z.object({
3470
- type: z.literal("must-export"),
3471
- names: z.array(z.string())
3299
+ import { z as z2 } from "zod";
3300
+ var MustExportRuleSchema = z2.object({
3301
+ type: z2.literal("must-export"),
3302
+ names: z2.array(z2.string())
3472
3303
  });
3473
- var MustExportDefaultRuleSchema = z.object({
3474
- type: z.literal("must-export-default"),
3475
- kind: z.enum(["class", "function", "object"]).optional()
3304
+ var MustExportDefaultRuleSchema = z2.object({
3305
+ type: z2.literal("must-export-default"),
3306
+ kind: z2.enum(["class", "function", "object"]).optional()
3476
3307
  });
3477
- var NoExportRuleSchema = z.object({
3478
- type: z.literal("no-export"),
3479
- names: z.array(z.string())
3308
+ var NoExportRuleSchema = z2.object({
3309
+ type: z2.literal("no-export"),
3310
+ names: z2.array(z2.string())
3480
3311
  });
3481
- var MustImportRuleSchema = z.object({
3482
- type: z.literal("must-import"),
3483
- from: z.string(),
3484
- names: z.array(z.string()).optional()
3312
+ var MustImportRuleSchema = z2.object({
3313
+ type: z2.literal("must-import"),
3314
+ from: z2.string(),
3315
+ names: z2.array(z2.string()).optional()
3485
3316
  });
3486
- var NoImportRuleSchema = z.object({
3487
- type: z.literal("no-import"),
3488
- from: z.string()
3317
+ var NoImportRuleSchema = z2.object({
3318
+ type: z2.literal("no-import"),
3319
+ from: z2.string()
3489
3320
  });
3490
- var NamingRuleSchema = z.object({
3491
- type: z.literal("naming"),
3492
- match: z.string(),
3493
- convention: z.enum(["camelCase", "PascalCase", "UPPER_SNAKE", "kebab-case"])
3321
+ var NamingRuleSchema = z2.object({
3322
+ type: z2.literal("naming"),
3323
+ match: z2.string(),
3324
+ convention: z2.enum(["camelCase", "PascalCase", "UPPER_SNAKE", "kebab-case"])
3494
3325
  });
3495
- var MaxExportsRuleSchema = z.object({
3496
- type: z.literal("max-exports"),
3497
- count: z.number().positive()
3326
+ var MaxExportsRuleSchema = z2.object({
3327
+ type: z2.literal("max-exports"),
3328
+ count: z2.number().positive()
3498
3329
  });
3499
- var MaxLinesRuleSchema = z.object({
3500
- type: z.literal("max-lines"),
3501
- count: z.number().positive()
3330
+ var MaxLinesRuleSchema = z2.object({
3331
+ type: z2.literal("max-lines"),
3332
+ count: z2.number().positive()
3502
3333
  });
3503
- var RequireJSDocRuleSchema = z.object({
3504
- type: z.literal("require-jsdoc"),
3505
- for: z.array(z.enum(["function", "class", "export"]))
3334
+ var RequireJSDocRuleSchema = z2.object({
3335
+ type: z2.literal("require-jsdoc"),
3336
+ for: z2.array(z2.enum(["function", "class", "export"]))
3506
3337
  });
3507
- var RuleSchema = z.discriminatedUnion("type", [
3338
+ var RuleSchema = z2.discriminatedUnion("type", [
3508
3339
  MustExportRuleSchema,
3509
3340
  MustExportDefaultRuleSchema,
3510
3341
  NoExportRuleSchema,
@@ -3515,47 +3346,47 @@ var RuleSchema = z.discriminatedUnion("type", [
3515
3346
  MaxLinesRuleSchema,
3516
3347
  RequireJSDocRuleSchema
3517
3348
  ]);
3518
- var ConfigPatternSchema = z.object({
3519
- name: z.string().min(1),
3520
- description: z.string(),
3521
- severity: z.enum(["error", "warning"]),
3522
- files: z.array(z.string()),
3349
+ var ConfigPatternSchema = z2.object({
3350
+ name: z2.string().min(1),
3351
+ description: z2.string(),
3352
+ severity: z2.enum(["error", "warning"]),
3353
+ files: z2.array(z2.string()),
3523
3354
  rule: RuleSchema,
3524
- message: z.string().optional()
3355
+ message: z2.string().optional()
3525
3356
  });
3526
- var PatternConfigSchema = z.object({
3527
- patterns: z.array(ConfigPatternSchema),
3528
- customPatterns: z.array(z.any()).optional(),
3357
+ var PatternConfigSchema = z2.object({
3358
+ patterns: z2.array(ConfigPatternSchema),
3359
+ customPatterns: z2.array(z2.any()).optional(),
3529
3360
  // Code patterns are functions, can't validate
3530
- ignoreFiles: z.array(z.string()).optional()
3361
+ ignoreFiles: z2.array(z2.string()).optional()
3531
3362
  });
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()
3363
+ var DriftConfigSchema = z2.object({
3364
+ docPaths: z2.array(z2.string()).optional(),
3365
+ checkApiSignatures: z2.boolean().optional(),
3366
+ checkExamples: z2.boolean().optional(),
3367
+ checkStructure: z2.boolean().optional(),
3368
+ ignorePatterns: z2.array(z2.string()).optional()
3538
3369
  });
3539
- var DeadCodeConfigSchema = 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()
3370
+ var DeadCodeConfigSchema = z2.object({
3371
+ entryPoints: z2.array(z2.string()).optional(),
3372
+ includeTypes: z2.boolean().optional(),
3373
+ includeInternals: z2.boolean().optional(),
3374
+ ignorePatterns: z2.array(z2.string()).optional(),
3375
+ treatDynamicImportsAs: z2.enum(["used", "unknown"]).optional()
3545
3376
  });
3546
- var EntropyConfigSchema = z.object({
3547
- rootDir: z.string(),
3548
- parser: z.any().optional(),
3377
+ var EntropyConfigSchema = z2.object({
3378
+ rootDir: z2.string(),
3379
+ parser: z2.any().optional(),
3549
3380
  // 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()
3381
+ entryPoints: z2.array(z2.string()).optional(),
3382
+ analyze: z2.object({
3383
+ drift: z2.union([z2.boolean(), DriftConfigSchema]).optional(),
3384
+ deadCode: z2.union([z2.boolean(), DeadCodeConfigSchema]).optional(),
3385
+ patterns: z2.union([z2.boolean(), PatternConfigSchema]).optional()
3555
3386
  }),
3556
- include: z.array(z.string()).optional(),
3557
- exclude: z.array(z.string()).optional(),
3558
- docPaths: z.array(z.string()).optional()
3387
+ include: z2.array(z2.string()).optional(),
3388
+ exclude: z2.array(z2.string()).optional(),
3389
+ docPaths: z2.array(z2.string()).optional()
3559
3390
  });
3560
3391
  function validatePatternConfig(config) {
3561
3392
  const result = PatternConfigSchema.safeParse(config);
@@ -3575,7 +3406,7 @@ function validatePatternConfig(config) {
3575
3406
 
3576
3407
  // src/performance/baseline-manager.ts
3577
3408
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
3578
- import { join as join6, dirname as dirname7 } from "path";
3409
+ import { join as join6, dirname as dirname6 } from "path";
3579
3410
  var BaselineManager = class {
3580
3411
  baselinesPath;
3581
3412
  constructor(projectRoot) {
@@ -3619,7 +3450,7 @@ var BaselineManager = class {
3619
3450
  updatedFrom: commitHash,
3620
3451
  benchmarks
3621
3452
  };
3622
- const dir = dirname7(this.baselinesPath);
3453
+ const dir = dirname6(this.baselinesPath);
3623
3454
  if (!existsSync(dir)) {
3624
3455
  mkdirSync(dir, { recursive: true });
3625
3456
  }
@@ -3653,7 +3484,7 @@ var BenchmarkRunner = class {
3653
3484
  /**
3654
3485
  * Discover .bench.ts files matching the glob pattern.
3655
3486
  */
3656
- discover(cwd, glob2) {
3487
+ discover(cwd, glob) {
3657
3488
  try {
3658
3489
  const result = execFileSync(
3659
3490
  "find",
@@ -3672,8 +3503,8 @@ var BenchmarkRunner = class {
3672
3503
  ).trim();
3673
3504
  if (!result) return [];
3674
3505
  const files = result.split("\n").filter(Boolean);
3675
- if (glob2 && glob2 !== "**/*.bench.ts") {
3676
- return files.filter((f) => f.includes(glob2.replace(/\*/g, "")));
3506
+ if (glob && glob !== "**/*.bench.ts") {
3507
+ return files.filter((f) => f.includes(glob.replace(/\*/g, "")));
3677
3508
  }
3678
3509
  return files;
3679
3510
  } catch {
@@ -3687,10 +3518,10 @@ var BenchmarkRunner = class {
3687
3518
  async run(options = {}) {
3688
3519
  const cwd = options.cwd ?? process.cwd();
3689
3520
  const timeout = options.timeout ?? 12e4;
3690
- const glob2 = options.glob;
3521
+ const glob = options.glob;
3691
3522
  const args = ["vitest", "bench", "--run"];
3692
- if (glob2) {
3693
- args.push(glob2);
3523
+ if (glob) {
3524
+ args.push(glob);
3694
3525
  }
3695
3526
  args.push("--reporter=json");
3696
3527
  try {
@@ -3812,7 +3643,7 @@ var RegressionDetector = class {
3812
3643
  };
3813
3644
 
3814
3645
  // src/performance/critical-path.ts
3815
- import * as fs2 from "fs";
3646
+ import * as fs4 from "fs";
3816
3647
  import * as path from "path";
3817
3648
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
3818
3649
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
@@ -3864,7 +3695,7 @@ var CriticalPathResolver = class {
3864
3695
  walkDir(dir, entries) {
3865
3696
  let items;
3866
3697
  try {
3867
- items = fs2.readdirSync(dir, { withFileTypes: true });
3698
+ items = fs4.readdirSync(dir, { withFileTypes: true });
3868
3699
  } catch {
3869
3700
  return;
3870
3701
  }
@@ -3880,7 +3711,7 @@ var CriticalPathResolver = class {
3880
3711
  scanFile(filePath, entries) {
3881
3712
  let content;
3882
3713
  try {
3883
- content = fs2.readFileSync(filePath, "utf-8");
3714
+ content = fs4.readFileSync(filePath, "utf-8");
3884
3715
  } catch {
3885
3716
  return;
3886
3717
  }
@@ -4059,17 +3890,17 @@ function resetFeedbackConfig() {
4059
3890
  }
4060
3891
 
4061
3892
  // src/feedback/review/diff-analyzer.ts
4062
- function parseDiff(diff) {
3893
+ function parseDiff(diff2) {
4063
3894
  try {
4064
- if (!diff.trim()) {
4065
- return Ok({ diff, files: [] });
3895
+ if (!diff2.trim()) {
3896
+ return Ok({ diff: diff2, files: [] });
4066
3897
  }
4067
3898
  const files = [];
4068
3899
  const newFileRegex = /new file mode/;
4069
3900
  const deletedFileRegex = /deleted file mode/;
4070
3901
  const additionRegex = /^\+(?!\+\+)/gm;
4071
3902
  const deletionRegex = /^-(?!--)/gm;
4072
- const diffParts = diff.split(/(?=diff --git)/);
3903
+ const diffParts = diff2.split(/(?=diff --git)/);
4073
3904
  for (const part of diffParts) {
4074
3905
  if (!part.trim()) continue;
4075
3906
  const headerMatch = /diff --git a\/(.+?) b\/(.+?)(?:\n|$)/.exec(part);
@@ -4092,7 +3923,7 @@ function parseDiff(diff) {
4092
3923
  deletions
4093
3924
  });
4094
3925
  }
4095
- return Ok({ diff, files });
3926
+ return Ok({ diff: diff2, files });
4096
3927
  } catch (error) {
4097
3928
  return Err({
4098
3929
  code: "DIFF_PARSE_ERROR",
@@ -4610,7 +4441,7 @@ async function requestMultiplePeerReviews(requests) {
4610
4441
 
4611
4442
  // src/feedback/logging/file-sink.ts
4612
4443
  import { appendFileSync, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
4613
- import { dirname as dirname8 } from "path";
4444
+ import { dirname as dirname7 } from "path";
4614
4445
  var FileSink = class {
4615
4446
  name = "file";
4616
4447
  filePath;
@@ -4633,7 +4464,7 @@ var FileSink = class {
4633
4464
  }
4634
4465
  ensureDirectory() {
4635
4466
  if (!this.initialized) {
4636
- const dir = dirname8(this.filePath);
4467
+ const dir = dirname7(this.filePath);
4637
4468
  if (!existsSync2(dir)) {
4638
4469
  mkdirSync2(dir, { recursive: true });
4639
4470
  }
@@ -4696,77 +4527,182 @@ var NoOpSink = class {
4696
4527
  }
4697
4528
  };
4698
4529
 
4530
+ // src/architecture/sync-constraints.ts
4531
+ function syncConstraintNodes(store, rules, violations) {
4532
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4533
+ const ruleIds = new Set(rules.map((r) => r.id));
4534
+ const violationsByCategory = /* @__PURE__ */ new Map();
4535
+ for (const result of violations) {
4536
+ const files = result.violations.map((v) => v.file);
4537
+ const existing = violationsByCategory.get(result.category) ?? [];
4538
+ violationsByCategory.set(result.category, [...existing, ...files]);
4539
+ }
4540
+ const existingNodesById = /* @__PURE__ */ new Map();
4541
+ for (const node of store.findNodes({ type: "constraint" })) {
4542
+ existingNodesById.set(node.id, node);
4543
+ }
4544
+ for (const rule of rules) {
4545
+ const existing = existingNodesById.get(rule.id);
4546
+ const createdAt = existing?.createdAt ?? now;
4547
+ const previousLastViolatedAt = existing?.lastViolatedAt ?? null;
4548
+ const hasViolation = hasMatchingViolation(rule, violationsByCategory);
4549
+ const lastViolatedAt = hasViolation ? now : previousLastViolatedAt;
4550
+ store.upsertNode({
4551
+ id: rule.id,
4552
+ type: "constraint",
4553
+ name: rule.description,
4554
+ category: rule.category,
4555
+ scope: rule.scope,
4556
+ createdAt,
4557
+ lastViolatedAt
4558
+ });
4559
+ }
4560
+ const existingConstraints = store.findNodes({ type: "constraint" });
4561
+ for (const node of existingConstraints) {
4562
+ if (!ruleIds.has(node.id)) {
4563
+ store.removeNode(node.id);
4564
+ }
4565
+ }
4566
+ }
4567
+ function hasMatchingViolation(rule, violationsByCategory) {
4568
+ const files = violationsByCategory.get(rule.category);
4569
+ if (!files || files.length === 0) return false;
4570
+ if (rule.scope === "project") return true;
4571
+ return files.some((file) => file.startsWith(rule.scope));
4572
+ }
4573
+
4574
+ // src/architecture/detect-stale.ts
4575
+ function detectStaleConstraints(store, windowDays = 30, category) {
4576
+ const now = Date.now();
4577
+ const windowMs = windowDays * 24 * 60 * 60 * 1e3;
4578
+ const cutoff = now - windowMs;
4579
+ let constraints = store.findNodes({ type: "constraint" });
4580
+ if (category) {
4581
+ constraints = constraints.filter((n) => n.category === category);
4582
+ }
4583
+ const totalConstraints = constraints.length;
4584
+ const staleConstraints = [];
4585
+ for (const node of constraints) {
4586
+ const lastViolatedAt = node.lastViolatedAt ?? null;
4587
+ const createdAt = node.createdAt;
4588
+ const comparisonTimestamp = lastViolatedAt ?? createdAt;
4589
+ if (!comparisonTimestamp) continue;
4590
+ const timestampMs = new Date(comparisonTimestamp).getTime();
4591
+ if (timestampMs < cutoff) {
4592
+ const daysSince = Math.floor((now - timestampMs) / (24 * 60 * 60 * 1e3));
4593
+ staleConstraints.push({
4594
+ id: node.id,
4595
+ category: node.category,
4596
+ description: node.name ?? "",
4597
+ scope: node.scope ?? "project",
4598
+ lastViolatedAt,
4599
+ daysSinceLastViolation: daysSince
4600
+ });
4601
+ }
4602
+ }
4603
+ staleConstraints.sort((a, b) => b.daysSinceLastViolation - a.daysSinceLastViolation);
4604
+ return { staleConstraints, totalConstraints, windowDays };
4605
+ }
4606
+
4607
+ // src/architecture/config.ts
4608
+ function resolveThresholds(scope, config) {
4609
+ const projectThresholds = {};
4610
+ for (const [key, val] of Object.entries(config.thresholds)) {
4611
+ projectThresholds[key] = typeof val === "object" && val !== null && !Array.isArray(val) ? { ...val } : val;
4612
+ }
4613
+ if (scope === "project") {
4614
+ return projectThresholds;
4615
+ }
4616
+ const moduleOverrides = config.modules[scope];
4617
+ if (!moduleOverrides) {
4618
+ return projectThresholds;
4619
+ }
4620
+ const merged = { ...projectThresholds };
4621
+ for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
4622
+ const projectValue = projectThresholds[category];
4623
+ if (projectValue !== void 0 && typeof projectValue === "object" && !Array.isArray(projectValue) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
4624
+ merged[category] = {
4625
+ ...projectValue,
4626
+ ...moduleValue
4627
+ };
4628
+ } else {
4629
+ merged[category] = moduleValue;
4630
+ }
4631
+ }
4632
+ return merged;
4633
+ }
4634
+
4699
4635
  // src/state/types.ts
4700
- import { z as z2 } from "zod";
4701
- var FailureEntrySchema = z2.object({
4702
- date: z2.string(),
4703
- skill: z2.string(),
4704
- type: z2.string(),
4705
- description: z2.string()
4636
+ import { z as z3 } from "zod";
4637
+ var FailureEntrySchema = z3.object({
4638
+ date: z3.string(),
4639
+ skill: z3.string(),
4640
+ type: z3.string(),
4641
+ description: z3.string()
4706
4642
  });
4707
- var HandoffSchema = 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()
4643
+ var HandoffSchema = z3.object({
4644
+ timestamp: z3.string(),
4645
+ fromSkill: z3.string(),
4646
+ phase: z3.string(),
4647
+ summary: z3.string(),
4648
+ completed: z3.array(z3.string()).default([]),
4649
+ pending: z3.array(z3.string()).default([]),
4650
+ concerns: z3.array(z3.string()).default([]),
4651
+ decisions: z3.array(
4652
+ z3.object({
4653
+ what: z3.string(),
4654
+ why: z3.string()
4719
4655
  })
4720
4656
  ).default([]),
4721
- blockers: z2.array(z2.string()).default([]),
4722
- contextKeywords: z2.array(z2.string()).default([])
4657
+ blockers: z3.array(z3.string()).default([]),
4658
+ contextKeywords: z3.array(z3.string()).default([])
4723
4659
  });
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()
4660
+ var GateCheckSchema = z3.object({
4661
+ name: z3.string(),
4662
+ passed: z3.boolean(),
4663
+ command: z3.string(),
4664
+ output: z3.string().optional(),
4665
+ duration: z3.number().optional()
4730
4666
  });
4731
- var GateResultSchema = z2.object({
4732
- passed: z2.boolean(),
4733
- checks: z2.array(GateCheckSchema)
4667
+ var GateResultSchema = z3.object({
4668
+ passed: z3.boolean(),
4669
+ checks: z3.array(GateCheckSchema)
4734
4670
  });
4735
- var GateConfigSchema = z2.object({
4736
- checks: z2.array(
4737
- z2.object({
4738
- name: z2.string(),
4739
- command: z2.string()
4671
+ var GateConfigSchema = z3.object({
4672
+ checks: z3.array(
4673
+ z3.object({
4674
+ name: z3.string(),
4675
+ command: z3.string()
4740
4676
  })
4741
4677
  ).optional(),
4742
- trace: z2.boolean().optional()
4678
+ trace: z3.boolean().optional()
4743
4679
  });
4744
- var HarnessStateSchema = z2.object({
4745
- schemaVersion: z2.literal(1),
4746
- position: z2.object({
4747
- phase: z2.string().optional(),
4748
- task: z2.string().optional()
4680
+ var HarnessStateSchema = z3.object({
4681
+ schemaVersion: z3.literal(1),
4682
+ position: z3.object({
4683
+ phase: z3.string().optional(),
4684
+ task: z3.string().optional()
4749
4685
  }).default({}),
4750
- decisions: z2.array(
4751
- z2.object({
4752
- date: z2.string(),
4753
- decision: z2.string(),
4754
- context: z2.string()
4686
+ decisions: z3.array(
4687
+ z3.object({
4688
+ date: z3.string(),
4689
+ decision: z3.string(),
4690
+ context: z3.string()
4755
4691
  })
4756
4692
  ).default([]),
4757
- blockers: z2.array(
4758
- z2.object({
4759
- id: z2.string(),
4760
- description: z2.string(),
4761
- status: z2.enum(["open", "resolved"])
4693
+ blockers: z3.array(
4694
+ z3.object({
4695
+ id: z3.string(),
4696
+ description: z3.string(),
4697
+ status: z3.enum(["open", "resolved"])
4762
4698
  })
4763
4699
  ).default([]),
4764
- progress: 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()
4700
+ progress: z3.record(z3.enum(["pending", "in_progress", "complete"])).default({}),
4701
+ lastSession: z3.object({
4702
+ date: z3.string(),
4703
+ summary: z3.string(),
4704
+ lastSkill: z3.string().optional(),
4705
+ pendingTasks: z3.array(z3.string()).optional()
4770
4706
  }).optional()
4771
4707
  });
4772
4708
  var DEFAULT_STATE = {
@@ -4778,27 +4714,27 @@ var DEFAULT_STATE = {
4778
4714
  };
4779
4715
 
4780
4716
  // src/state/state-manager.ts
4781
- import * as fs4 from "fs";
4717
+ import * as fs6 from "fs";
4782
4718
  import * as path3 from "path";
4783
4719
  import { execSync as execSync2 } from "child_process";
4784
4720
 
4785
4721
  // src/state/stream-resolver.ts
4786
- import * as fs3 from "fs";
4722
+ import * as fs5 from "fs";
4787
4723
  import * as path2 from "path";
4788
4724
  import { execSync } from "child_process";
4789
4725
 
4790
4726
  // src/state/stream-types.ts
4791
- import { z as 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()
4727
+ import { z as z4 } from "zod";
4728
+ var StreamInfoSchema = z4.object({
4729
+ name: z4.string(),
4730
+ branch: z4.string().optional(),
4731
+ createdAt: z4.string(),
4732
+ lastActiveAt: z4.string()
4797
4733
  });
4798
- var StreamIndexSchema = z3.object({
4799
- schemaVersion: z3.literal(1),
4800
- activeStream: z3.string().nullable(),
4801
- streams: z3.record(StreamInfoSchema)
4734
+ var StreamIndexSchema = z4.object({
4735
+ schemaVersion: z4.literal(1),
4736
+ activeStream: z4.string().nullable(),
4737
+ streams: z4.record(StreamInfoSchema)
4802
4738
  });
4803
4739
  var DEFAULT_STREAM_INDEX = {
4804
4740
  schemaVersion: 1,
@@ -4829,11 +4765,11 @@ function validateStreamName(name) {
4829
4765
  }
4830
4766
  async function loadStreamIndex(projectPath) {
4831
4767
  const idxPath = indexPath(projectPath);
4832
- if (!fs3.existsSync(idxPath)) {
4768
+ if (!fs5.existsSync(idxPath)) {
4833
4769
  return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
4834
4770
  }
4835
4771
  try {
4836
- const raw = fs3.readFileSync(idxPath, "utf-8");
4772
+ const raw = fs5.readFileSync(idxPath, "utf-8");
4837
4773
  const parsed = JSON.parse(raw);
4838
4774
  const result = StreamIndexSchema.safeParse(parsed);
4839
4775
  if (!result.success) {
@@ -4851,8 +4787,8 @@ async function loadStreamIndex(projectPath) {
4851
4787
  async function saveStreamIndex(projectPath, index) {
4852
4788
  const dir = streamsDir(projectPath);
4853
4789
  try {
4854
- fs3.mkdirSync(dir, { recursive: true });
4855
- fs3.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
4790
+ fs5.mkdirSync(dir, { recursive: true });
4791
+ fs5.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
4856
4792
  return Ok(void 0);
4857
4793
  } catch (error) {
4858
4794
  return Err(
@@ -4934,7 +4870,7 @@ async function createStream(projectPath, name, branch) {
4934
4870
  }
4935
4871
  const streamPath = path2.join(streamsDir(projectPath), name);
4936
4872
  try {
4937
- fs3.mkdirSync(streamPath, { recursive: true });
4873
+ fs5.mkdirSync(streamPath, { recursive: true });
4938
4874
  } catch (error) {
4939
4875
  return Err(
4940
4876
  new Error(
@@ -4978,9 +4914,9 @@ async function archiveStream(projectPath, name) {
4978
4914
  const streamPath = path2.join(streamsDir(projectPath), name);
4979
4915
  const archiveDir = path2.join(projectPath, HARNESS_DIR, "archive", "streams");
4980
4916
  try {
4981
- fs3.mkdirSync(archiveDir, { recursive: true });
4917
+ fs5.mkdirSync(archiveDir, { recursive: true });
4982
4918
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4983
- fs3.renameSync(streamPath, path2.join(archiveDir, `${name}-${date}`));
4919
+ fs5.renameSync(streamPath, path2.join(archiveDir, `${name}-${date}`));
4984
4920
  } catch (error) {
4985
4921
  return Err(
4986
4922
  new Error(
@@ -5003,18 +4939,18 @@ function getStreamForBranch(index, branch) {
5003
4939
  var STATE_FILES = ["state.json", "handoff.json", "learnings.md", "failures.md"];
5004
4940
  async function migrateToStreams(projectPath) {
5005
4941
  const harnessDir = path2.join(projectPath, HARNESS_DIR);
5006
- if (fs3.existsSync(indexPath(projectPath))) {
4942
+ if (fs5.existsSync(indexPath(projectPath))) {
5007
4943
  return Ok(void 0);
5008
4944
  }
5009
- const filesToMove = STATE_FILES.filter((f) => fs3.existsSync(path2.join(harnessDir, f)));
4945
+ const filesToMove = STATE_FILES.filter((f) => fs5.existsSync(path2.join(harnessDir, f)));
5010
4946
  if (filesToMove.length === 0) {
5011
4947
  return Ok(void 0);
5012
4948
  }
5013
4949
  const defaultDir = path2.join(streamsDir(projectPath), "default");
5014
4950
  try {
5015
- fs3.mkdirSync(defaultDir, { recursive: true });
4951
+ fs5.mkdirSync(defaultDir, { recursive: true });
5016
4952
  for (const file of filesToMove) {
5017
- fs3.renameSync(path2.join(harnessDir, file), path2.join(defaultDir, file));
4953
+ fs5.renameSync(path2.join(harnessDir, file), path2.join(defaultDir, file));
5018
4954
  }
5019
4955
  } catch (error) {
5020
4956
  return Err(
@@ -5055,7 +4991,7 @@ function evictIfNeeded(map) {
5055
4991
  }
5056
4992
  async function getStateDir(projectPath, stream) {
5057
4993
  const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
5058
- const hasStreams = fs4.existsSync(streamsIndexPath);
4994
+ const hasStreams = fs6.existsSync(streamsIndexPath);
5059
4995
  if (stream || hasStreams) {
5060
4996
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
5061
4997
  if (result.ok) {
@@ -5073,10 +5009,10 @@ async function loadState(projectPath, stream) {
5073
5009
  if (!dirResult.ok) return dirResult;
5074
5010
  const stateDir = dirResult.value;
5075
5011
  const statePath = path3.join(stateDir, STATE_FILE);
5076
- if (!fs4.existsSync(statePath)) {
5012
+ if (!fs6.existsSync(statePath)) {
5077
5013
  return Ok({ ...DEFAULT_STATE });
5078
5014
  }
5079
- const raw = fs4.readFileSync(statePath, "utf-8");
5015
+ const raw = fs6.readFileSync(statePath, "utf-8");
5080
5016
  const parsed = JSON.parse(raw);
5081
5017
  const result = HarnessStateSchema.safeParse(parsed);
5082
5018
  if (!result.success) {
@@ -5095,8 +5031,8 @@ async function saveState(projectPath, state, stream) {
5095
5031
  if (!dirResult.ok) return dirResult;
5096
5032
  const stateDir = dirResult.value;
5097
5033
  const statePath = path3.join(stateDir, STATE_FILE);
5098
- fs4.mkdirSync(stateDir, { recursive: true });
5099
- fs4.writeFileSync(statePath, JSON.stringify(state, null, 2));
5034
+ fs6.mkdirSync(stateDir, { recursive: true });
5035
+ fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
5100
5036
  return Ok(void 0);
5101
5037
  } catch (error) {
5102
5038
  return Err(
@@ -5110,7 +5046,7 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
5110
5046
  if (!dirResult.ok) return dirResult;
5111
5047
  const stateDir = dirResult.value;
5112
5048
  const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
5113
- fs4.mkdirSync(stateDir, { recursive: true });
5049
+ fs6.mkdirSync(stateDir, { recursive: true });
5114
5050
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5115
5051
  let entry;
5116
5052
  if (skillName && outcome) {
@@ -5126,11 +5062,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
5126
5062
  - **${timestamp}:** ${learning}
5127
5063
  `;
5128
5064
  }
5129
- if (!fs4.existsSync(learningsPath)) {
5130
- fs4.writeFileSync(learningsPath, `# Learnings
5065
+ if (!fs6.existsSync(learningsPath)) {
5066
+ fs6.writeFileSync(learningsPath, `# Learnings
5131
5067
  ${entry}`);
5132
5068
  } else {
5133
- fs4.appendFileSync(learningsPath, entry);
5069
+ fs6.appendFileSync(learningsPath, entry);
5134
5070
  }
5135
5071
  learningsCacheMap.delete(learningsPath);
5136
5072
  return Ok(void 0);
@@ -5148,17 +5084,17 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
5148
5084
  if (!dirResult.ok) return dirResult;
5149
5085
  const stateDir = dirResult.value;
5150
5086
  const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
5151
- if (!fs4.existsSync(learningsPath)) {
5087
+ if (!fs6.existsSync(learningsPath)) {
5152
5088
  return Ok([]);
5153
5089
  }
5154
- const stats = fs4.statSync(learningsPath);
5090
+ const stats = fs6.statSync(learningsPath);
5155
5091
  const cacheKey = learningsPath;
5156
5092
  const cached = learningsCacheMap.get(cacheKey);
5157
5093
  let entries;
5158
5094
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5159
5095
  entries = cached.entries;
5160
5096
  } else {
5161
- const content = fs4.readFileSync(learningsPath, "utf-8");
5097
+ const content = fs6.readFileSync(learningsPath, "utf-8");
5162
5098
  const lines = content.split("\n");
5163
5099
  entries = [];
5164
5100
  let currentBlock = [];
@@ -5201,16 +5137,16 @@ async function appendFailure(projectPath, description, skillName, type, stream)
5201
5137
  if (!dirResult.ok) return dirResult;
5202
5138
  const stateDir = dirResult.value;
5203
5139
  const failuresPath = path3.join(stateDir, FAILURES_FILE);
5204
- fs4.mkdirSync(stateDir, { recursive: true });
5140
+ fs6.mkdirSync(stateDir, { recursive: true });
5205
5141
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5206
5142
  const entry = `
5207
5143
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
5208
5144
  `;
5209
- if (!fs4.existsSync(failuresPath)) {
5210
- fs4.writeFileSync(failuresPath, `# Failures
5145
+ if (!fs6.existsSync(failuresPath)) {
5146
+ fs6.writeFileSync(failuresPath, `# Failures
5211
5147
  ${entry}`);
5212
5148
  } else {
5213
- fs4.appendFileSync(failuresPath, entry);
5149
+ fs6.appendFileSync(failuresPath, entry);
5214
5150
  }
5215
5151
  failuresCacheMap.delete(failuresPath);
5216
5152
  return Ok(void 0);
@@ -5228,16 +5164,16 @@ async function loadFailures(projectPath, stream) {
5228
5164
  if (!dirResult.ok) return dirResult;
5229
5165
  const stateDir = dirResult.value;
5230
5166
  const failuresPath = path3.join(stateDir, FAILURES_FILE);
5231
- if (!fs4.existsSync(failuresPath)) {
5167
+ if (!fs6.existsSync(failuresPath)) {
5232
5168
  return Ok([]);
5233
5169
  }
5234
- const stats = fs4.statSync(failuresPath);
5170
+ const stats = fs6.statSync(failuresPath);
5235
5171
  const cacheKey = failuresPath;
5236
5172
  const cached = failuresCacheMap.get(cacheKey);
5237
5173
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5238
5174
  return Ok(cached.entries);
5239
5175
  }
5240
- const content = fs4.readFileSync(failuresPath, "utf-8");
5176
+ const content = fs6.readFileSync(failuresPath, "utf-8");
5241
5177
  const entries = [];
5242
5178
  for (const line of content.split("\n")) {
5243
5179
  const match = line.match(FAILURE_LINE_REGEX);
@@ -5267,19 +5203,19 @@ async function archiveFailures(projectPath, stream) {
5267
5203
  if (!dirResult.ok) return dirResult;
5268
5204
  const stateDir = dirResult.value;
5269
5205
  const failuresPath = path3.join(stateDir, FAILURES_FILE);
5270
- if (!fs4.existsSync(failuresPath)) {
5206
+ if (!fs6.existsSync(failuresPath)) {
5271
5207
  return Ok(void 0);
5272
5208
  }
5273
5209
  const archiveDir = path3.join(stateDir, "archive");
5274
- fs4.mkdirSync(archiveDir, { recursive: true });
5210
+ fs6.mkdirSync(archiveDir, { recursive: true });
5275
5211
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5276
5212
  let archiveName = `failures-${date}.md`;
5277
5213
  let counter = 2;
5278
- while (fs4.existsSync(path3.join(archiveDir, archiveName))) {
5214
+ while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
5279
5215
  archiveName = `failures-${date}-${counter}.md`;
5280
5216
  counter++;
5281
5217
  }
5282
- fs4.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5218
+ fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5283
5219
  failuresCacheMap.delete(failuresPath);
5284
5220
  return Ok(void 0);
5285
5221
  } catch (error) {
@@ -5296,8 +5232,8 @@ async function saveHandoff(projectPath, handoff, stream) {
5296
5232
  if (!dirResult.ok) return dirResult;
5297
5233
  const stateDir = dirResult.value;
5298
5234
  const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5299
- fs4.mkdirSync(stateDir, { recursive: true });
5300
- fs4.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
5235
+ fs6.mkdirSync(stateDir, { recursive: true });
5236
+ fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
5301
5237
  return Ok(void 0);
5302
5238
  } catch (error) {
5303
5239
  return Err(
@@ -5311,10 +5247,10 @@ async function loadHandoff(projectPath, stream) {
5311
5247
  if (!dirResult.ok) return dirResult;
5312
5248
  const stateDir = dirResult.value;
5313
5249
  const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5314
- if (!fs4.existsSync(handoffPath)) {
5250
+ if (!fs6.existsSync(handoffPath)) {
5315
5251
  return Ok(null);
5316
5252
  }
5317
- const raw = fs4.readFileSync(handoffPath, "utf-8");
5253
+ const raw = fs6.readFileSync(handoffPath, "utf-8");
5318
5254
  const parsed = JSON.parse(raw);
5319
5255
  const result = HandoffSchema.safeParse(parsed);
5320
5256
  if (!result.success) {
@@ -5332,8 +5268,8 @@ async function runMechanicalGate(projectPath) {
5332
5268
  const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
5333
5269
  try {
5334
5270
  let checks = [];
5335
- if (fs4.existsSync(gateConfigPath)) {
5336
- const raw = JSON.parse(fs4.readFileSync(gateConfigPath, "utf-8"));
5271
+ if (fs6.existsSync(gateConfigPath)) {
5272
+ const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
5337
5273
  const config = GateConfigSchema.safeParse(raw);
5338
5274
  if (config.success && config.data.checks) {
5339
5275
  checks = config.data.checks;
@@ -5341,19 +5277,19 @@ async function runMechanicalGate(projectPath) {
5341
5277
  }
5342
5278
  if (checks.length === 0) {
5343
5279
  const packageJsonPath = path3.join(projectPath, "package.json");
5344
- if (fs4.existsSync(packageJsonPath)) {
5345
- const pkg = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
5280
+ if (fs6.existsSync(packageJsonPath)) {
5281
+ const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
5346
5282
  const scripts = pkg.scripts || {};
5347
5283
  if (scripts.test) checks.push({ name: "test", command: "npm test" });
5348
5284
  if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
5349
5285
  if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
5350
5286
  if (scripts.build) checks.push({ name: "build", command: "npm run build" });
5351
5287
  }
5352
- if (fs4.existsSync(path3.join(projectPath, "go.mod"))) {
5288
+ if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
5353
5289
  checks.push({ name: "test", command: "go test ./..." });
5354
5290
  checks.push({ name: "build", command: "go build ./..." });
5355
5291
  }
5356
- if (fs4.existsSync(path3.join(projectPath, "pyproject.toml")) || fs4.existsSync(path3.join(projectPath, "setup.py"))) {
5292
+ if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
5357
5293
  checks.push({ name: "test", command: "python -m pytest" });
5358
5294
  }
5359
5295
  }
@@ -5556,7 +5492,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
5556
5492
  }
5557
5493
 
5558
5494
  // src/security/scanner.ts
5559
- import * as fs6 from "fs/promises";
5495
+ import * as fs8 from "fs/promises";
5560
5496
 
5561
5497
  // src/security/rules/registry.ts
5562
5498
  var RuleRegistry = class {
@@ -5587,7 +5523,7 @@ var RuleRegistry = class {
5587
5523
  };
5588
5524
 
5589
5525
  // src/security/config.ts
5590
- import { z as z4 } from "zod";
5526
+ import { z as z5 } from "zod";
5591
5527
 
5592
5528
  // src/security/types.ts
5593
5529
  var DEFAULT_SECURITY_CONFIG = {
@@ -5598,19 +5534,19 @@ var DEFAULT_SECURITY_CONFIG = {
5598
5534
  };
5599
5535
 
5600
5536
  // src/security/config.ts
5601
- var RuleOverrideSchema = 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()
5537
+ var RuleOverrideSchema = z5.enum(["off", "error", "warning", "info"]);
5538
+ var SecurityConfigSchema = z5.object({
5539
+ enabled: z5.boolean().default(true),
5540
+ strict: z5.boolean().default(false),
5541
+ rules: z5.record(z5.string(), RuleOverrideSchema).optional().default({}),
5542
+ exclude: z5.array(z5.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
5543
+ external: z5.object({
5544
+ semgrep: z5.object({
5545
+ enabled: z5.union([z5.literal("auto"), z5.boolean()]).default("auto"),
5546
+ rulesets: z5.array(z5.string()).optional()
5611
5547
  }).optional(),
5612
- gitleaks: z4.object({
5613
- enabled: z4.union([z4.literal("auto"), z4.boolean()]).default("auto")
5548
+ gitleaks: z5.object({
5549
+ enabled: z5.union([z5.literal("auto"), z5.boolean()]).default("auto")
5614
5550
  }).optional()
5615
5551
  }).optional()
5616
5552
  });
@@ -5643,15 +5579,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
5643
5579
  }
5644
5580
 
5645
5581
  // src/security/stack-detector.ts
5646
- import * as fs5 from "fs";
5582
+ import * as fs7 from "fs";
5647
5583
  import * as path4 from "path";
5648
5584
  function detectStack(projectRoot) {
5649
5585
  const stacks = [];
5650
5586
  const pkgJsonPath = path4.join(projectRoot, "package.json");
5651
- if (fs5.existsSync(pkgJsonPath)) {
5587
+ if (fs7.existsSync(pkgJsonPath)) {
5652
5588
  stacks.push("node");
5653
5589
  try {
5654
- const pkgJson = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
5590
+ const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5655
5591
  const allDeps = {
5656
5592
  ...pkgJson.dependencies,
5657
5593
  ...pkgJson.devDependencies
@@ -5667,12 +5603,12 @@ function detectStack(projectRoot) {
5667
5603
  }
5668
5604
  }
5669
5605
  const goModPath = path4.join(projectRoot, "go.mod");
5670
- if (fs5.existsSync(goModPath)) {
5606
+ if (fs7.existsSync(goModPath)) {
5671
5607
  stacks.push("go");
5672
5608
  }
5673
5609
  const requirementsPath = path4.join(projectRoot, "requirements.txt");
5674
5610
  const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
5675
- if (fs5.existsSync(requirementsPath) || fs5.existsSync(pyprojectPath)) {
5611
+ if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
5676
5612
  stacks.push("python");
5677
5613
  }
5678
5614
  return stacks;
@@ -6099,7 +6035,7 @@ var SecurityScanner = class {
6099
6035
  }
6100
6036
  async scanFile(filePath) {
6101
6037
  if (!this.config.enabled) return [];
6102
- const content = await fs6.readFile(filePath, "utf-8");
6038
+ const content = await fs8.readFile(filePath, "utf-8");
6103
6039
  return this.scanContent(content, filePath, 1);
6104
6040
  }
6105
6041
  async scanFiles(filePaths) {
@@ -6132,7 +6068,8 @@ var ALL_CHECKS = [
6132
6068
  "entropy",
6133
6069
  "security",
6134
6070
  "perf",
6135
- "phase-gate"
6071
+ "phase-gate",
6072
+ "arch"
6136
6073
  ];
6137
6074
  async function runSingleCheck(name, projectRoot, config) {
6138
6075
  const start = Date.now();
@@ -6196,7 +6133,17 @@ async function runSingleCheck(name, projectRoot, config) {
6196
6133
  }
6197
6134
  case "docs": {
6198
6135
  const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
6199
- const result = await checkDocCoverage("project", { docsDir });
6136
+ const entropyConfig = config.entropy || {};
6137
+ const result = await checkDocCoverage("project", {
6138
+ docsDir,
6139
+ sourceDir: projectRoot,
6140
+ excludePatterns: entropyConfig.excludePatterns || [
6141
+ "**/node_modules/**",
6142
+ "**/dist/**",
6143
+ "**/*.test.ts",
6144
+ "**/fixtures/**"
6145
+ ]
6146
+ });
6200
6147
  if (!result.ok) {
6201
6148
  issues.push({ severity: "warning", message: result.error.message });
6202
6149
  } else if (result.value.gaps.length > 0) {
@@ -6271,11 +6218,13 @@ async function runSingleCheck(name, projectRoot, config) {
6271
6218
  break;
6272
6219
  }
6273
6220
  case "perf": {
6221
+ const perfConfig = config.performance || {};
6274
6222
  const perfAnalyzer = new EntropyAnalyzer({
6275
6223
  rootDir: projectRoot,
6276
6224
  analyze: {
6277
- complexity: true,
6278
- coupling: true
6225
+ complexity: perfConfig.complexity || true,
6226
+ coupling: perfConfig.coupling || true,
6227
+ sizeBudget: perfConfig.sizeBudget || false
6279
6228
  }
6280
6229
  });
6281
6230
  const perfResult = await perfAnalyzer.analyze();
@@ -6316,6 +6265,43 @@ async function runSingleCheck(name, projectRoot, config) {
6316
6265
  });
6317
6266
  break;
6318
6267
  }
6268
+ case "arch": {
6269
+ const rawArchConfig = config.architecture;
6270
+ const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
6271
+ if (!archConfig.enabled) break;
6272
+ const results = await runAll(archConfig, projectRoot);
6273
+ const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
6274
+ const baseline = baselineManager.load();
6275
+ if (baseline) {
6276
+ const diffResult = diff(results, baseline);
6277
+ if (!diffResult.passed) {
6278
+ for (const v of diffResult.newViolations) {
6279
+ issues.push({
6280
+ severity: v.severity,
6281
+ message: `[${v.category || "arch"}] NEW: ${v.detail}`,
6282
+ file: v.file
6283
+ });
6284
+ }
6285
+ for (const r of diffResult.regressions) {
6286
+ issues.push({
6287
+ severity: "error",
6288
+ message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
6289
+ });
6290
+ }
6291
+ }
6292
+ } else {
6293
+ for (const result of results) {
6294
+ for (const v of result.violations) {
6295
+ issues.push({
6296
+ severity: v.severity,
6297
+ message: `[${result.category}] ${v.detail}`,
6298
+ file: v.file
6299
+ });
6300
+ }
6301
+ }
6302
+ }
6303
+ break;
6304
+ }
6319
6305
  }
6320
6306
  } catch (error) {
6321
6307
  issues.push({
@@ -6650,22 +6636,22 @@ var PREFIX_PATTERNS = [
6650
6636
  ];
6651
6637
  var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mts|cts)$/;
6652
6638
  var MD_FILE_PATTERN = /\.md$/;
6653
- function detectChangeType(commitMessage, diff) {
6639
+ function detectChangeType(commitMessage, diff2) {
6654
6640
  const trimmed = commitMessage.trim();
6655
6641
  for (const { pattern, type } of PREFIX_PATTERNS) {
6656
6642
  if (pattern.test(trimmed)) {
6657
6643
  return type;
6658
6644
  }
6659
6645
  }
6660
- if (diff.changedFiles.length > 0 && diff.changedFiles.every((f) => MD_FILE_PATTERN.test(f))) {
6646
+ if (diff2.changedFiles.length > 0 && diff2.changedFiles.every((f) => MD_FILE_PATTERN.test(f))) {
6661
6647
  return "docs";
6662
6648
  }
6663
- const newNonTestFiles = diff.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
6649
+ const newNonTestFiles = diff2.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
6664
6650
  if (newNonTestFiles.length > 0) {
6665
6651
  return "feature";
6666
6652
  }
6667
- const hasNewTestFile = diff.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
6668
- if (diff.totalDiffLines < 20 && hasNewTestFile) {
6653
+ const hasNewTestFile = diff2.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
6654
+ if (diff2.totalDiffLines < 20 && hasNewTestFile) {
6669
6655
  return "bugfix";
6670
6656
  }
6671
6657
  return "feature";
@@ -6704,7 +6690,7 @@ function extractImportSources(content) {
6704
6690
  }
6705
6691
  return sources;
6706
6692
  }
6707
- async function resolveImportPath2(projectRoot, fromFile, importSource) {
6693
+ async function resolveImportPath(projectRoot, fromFile, importSource) {
6708
6694
  if (!importSource.startsWith(".")) return null;
6709
6695
  const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
6710
6696
  const basePath = path7.resolve(fromDir, importSource);
@@ -6739,7 +6725,7 @@ async function gatherImportContext(projectRoot, changedFiles, budget) {
6739
6725
  const sources = extractImportSources(cf.content);
6740
6726
  for (const source of sources) {
6741
6727
  if (linesGathered >= budget) break;
6742
- const resolved = await resolveImportPath2(projectRoot, cf.path, source);
6728
+ const resolved = await resolveImportPath(projectRoot, cf.path, source);
6743
6729
  if (resolved && !seen.has(resolved)) {
6744
6730
  seen.add(resolved);
6745
6731
  const contextFile = await readContextFile(projectRoot, resolved, "import");
@@ -6903,11 +6889,11 @@ async function scopeArchitectureContext(projectRoot, changedFiles, budget, optio
6903
6889
  return contextFiles;
6904
6890
  }
6905
6891
  async function scopeContext(options) {
6906
- const { projectRoot, diff, commitMessage } = options;
6907
- const changeType = detectChangeType(commitMessage, diff);
6908
- const budget = computeContextBudget(diff.totalDiffLines);
6892
+ const { projectRoot, diff: diff2, commitMessage } = options;
6893
+ const changeType = detectChangeType(commitMessage, diff2);
6894
+ const budget = computeContextBudget(diff2.totalDiffLines);
6909
6895
  const changedFiles = [];
6910
- for (const filePath of diff.changedFiles) {
6896
+ for (const filePath of diff2.changedFiles) {
6911
6897
  const cf = await readContextFile(projectRoot, filePath, "changed");
6912
6898
  if (cf) changedFiles.push(cf);
6913
6899
  }
@@ -6927,7 +6913,7 @@ async function scopeContext(options) {
6927
6913
  changedFiles: [...changedFiles],
6928
6914
  contextFiles,
6929
6915
  commitHistory: options.commitHistory ?? [],
6930
- diffLines: diff.totalDiffLines,
6916
+ diffLines: diff2.totalDiffLines,
6931
6917
  contextLines
6932
6918
  });
6933
6919
  }
@@ -7937,7 +7923,7 @@ function formatGitHubSummary(options) {
7937
7923
  async function runReviewPipeline(options) {
7938
7924
  const {
7939
7925
  projectRoot,
7940
- diff,
7926
+ diff: diff2,
7941
7927
  commitMessage,
7942
7928
  flags,
7943
7929
  graph,
@@ -7971,7 +7957,7 @@ async function runReviewPipeline(options) {
7971
7957
  const mechResult = await runMechanicalChecks({
7972
7958
  projectRoot,
7973
7959
  config,
7974
- changedFiles: diff.changedFiles
7960
+ changedFiles: diff2.changedFiles
7975
7961
  });
7976
7962
  if (mechResult.ok) {
7977
7963
  mechanicalResult = mechResult.value;
@@ -8010,7 +7996,7 @@ async function runReviewPipeline(options) {
8010
7996
  try {
8011
7997
  contextBundles = await scopeContext({
8012
7998
  projectRoot,
8013
- diff,
7999
+ diff: diff2,
8014
8000
  commitMessage,
8015
8001
  ...graph != null ? { graph } : {},
8016
8002
  ...conventionFiles != null ? { conventionFiles } : {},
@@ -8024,14 +8010,14 @@ async function runReviewPipeline(options) {
8024
8010
  changedFiles: [],
8025
8011
  contextFiles: [],
8026
8012
  commitHistory: [],
8027
- diffLines: diff.totalDiffLines,
8013
+ diffLines: diff2.totalDiffLines,
8028
8014
  contextLines: 0
8029
8015
  }));
8030
8016
  }
8031
8017
  const agentResults = await fanOutReview({ bundles: contextBundles });
8032
8018
  const rawFindings = agentResults.flatMap((r) => r.findings);
8033
8019
  const fileContents = /* @__PURE__ */ new Map();
8034
- for (const [file, content] of diff.fileDiffs) {
8020
+ for (const [file, content] of diff2.fileDiffs) {
8035
8021
  fileContents.set(file, content);
8036
8022
  }
8037
8023
  const validatedFindings = await validateFindings({
@@ -8230,7 +8216,7 @@ function serializeFeature(feature) {
8230
8216
  }
8231
8217
 
8232
8218
  // src/roadmap/sync.ts
8233
- import * as fs7 from "fs";
8219
+ import * as fs9 from "fs";
8234
8220
  import * as path9 from "path";
8235
8221
  import { Ok as Ok3 } from "@harness-engineering/types";
8236
8222
  function inferStatus(feature, projectPath, allFeatures) {
@@ -8247,9 +8233,9 @@ function inferStatus(feature, projectPath, allFeatures) {
8247
8233
  const useRootState = featuresWithPlans.length <= 1;
8248
8234
  if (useRootState) {
8249
8235
  const rootStatePath = path9.join(projectPath, ".harness", "state.json");
8250
- if (fs7.existsSync(rootStatePath)) {
8236
+ if (fs9.existsSync(rootStatePath)) {
8251
8237
  try {
8252
- const raw = fs7.readFileSync(rootStatePath, "utf-8");
8238
+ const raw = fs9.readFileSync(rootStatePath, "utf-8");
8253
8239
  const state = JSON.parse(raw);
8254
8240
  if (state.progress) {
8255
8241
  for (const status of Object.values(state.progress)) {
@@ -8261,15 +8247,15 @@ function inferStatus(feature, projectPath, allFeatures) {
8261
8247
  }
8262
8248
  }
8263
8249
  const sessionsDir = path9.join(projectPath, ".harness", "sessions");
8264
- if (fs7.existsSync(sessionsDir)) {
8250
+ if (fs9.existsSync(sessionsDir)) {
8265
8251
  try {
8266
- const sessionDirs = fs7.readdirSync(sessionsDir, { withFileTypes: true });
8252
+ const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
8267
8253
  for (const entry of sessionDirs) {
8268
8254
  if (!entry.isDirectory()) continue;
8269
8255
  const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
8270
- if (!fs7.existsSync(autopilotPath)) continue;
8256
+ if (!fs9.existsSync(autopilotPath)) continue;
8271
8257
  try {
8272
- const raw = fs7.readFileSync(autopilotPath, "utf-8");
8258
+ const raw = fs9.readFileSync(autopilotPath, "utf-8");
8273
8259
  const autopilot = JSON.parse(raw);
8274
8260
  if (!autopilot.phases) continue;
8275
8261
  const linkedPhases = autopilot.phases.filter(
@@ -8320,42 +8306,184 @@ function syncRoadmap(options) {
8320
8306
  }
8321
8307
 
8322
8308
  // src/interaction/types.ts
8323
- import { z as 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()
8309
+ import { z as z6 } from "zod";
8310
+ var InteractionTypeSchema = z6.enum(["question", "confirmation", "transition"]);
8311
+ var QuestionSchema = z6.object({
8312
+ text: z6.string(),
8313
+ options: z6.array(z6.string()).optional(),
8314
+ default: z6.string().optional()
8329
8315
  });
8330
- var ConfirmationSchema = z5.object({
8331
- text: z5.string(),
8332
- context: z5.string()
8316
+ var ConfirmationSchema = z6.object({
8317
+ text: z6.string(),
8318
+ context: z6.string()
8333
8319
  });
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()
8320
+ var TransitionSchema = z6.object({
8321
+ completedPhase: z6.string(),
8322
+ suggestedNext: z6.string(),
8323
+ reason: z6.string(),
8324
+ artifacts: z6.array(z6.string()),
8325
+ requiresConfirmation: z6.boolean(),
8326
+ summary: z6.string()
8341
8327
  });
8342
- var EmitInteractionInputSchema = z5.object({
8343
- path: z5.string(),
8328
+ var EmitInteractionInputSchema = z6.object({
8329
+ path: z6.string(),
8344
8330
  type: InteractionTypeSchema,
8345
- stream: z5.string().optional(),
8331
+ stream: z6.string().optional(),
8346
8332
  question: QuestionSchema.optional(),
8347
8333
  confirmation: ConfirmationSchema.optional(),
8348
8334
  transition: TransitionSchema.optional()
8349
8335
  });
8350
8336
 
8351
- // src/update-checker.ts
8352
- import * as fs8 from "fs";
8337
+ // src/blueprint/scanner.ts
8338
+ import * as fs10 from "fs/promises";
8353
8339
  import * as path10 from "path";
8340
+ var ProjectScanner = class {
8341
+ constructor(rootDir) {
8342
+ this.rootDir = rootDir;
8343
+ }
8344
+ async scan() {
8345
+ let projectName = path10.basename(this.rootDir);
8346
+ try {
8347
+ const pkgPath = path10.join(this.rootDir, "package.json");
8348
+ const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
8349
+ const pkg = JSON.parse(pkgRaw);
8350
+ if (pkg.name) projectName = pkg.name;
8351
+ } catch {
8352
+ }
8353
+ return {
8354
+ projectName,
8355
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8356
+ modules: [
8357
+ {
8358
+ id: "foundations",
8359
+ title: "Foundations",
8360
+ description: "Utility files and basic types.",
8361
+ files: []
8362
+ },
8363
+ {
8364
+ id: "core-logic",
8365
+ title: "Core Logic",
8366
+ description: "Mid-level services and domain logic.",
8367
+ files: []
8368
+ },
8369
+ {
8370
+ id: "interaction-surface",
8371
+ title: "Interaction Surface",
8372
+ description: "APIs, CLIs, and Entry Points.",
8373
+ files: []
8374
+ },
8375
+ {
8376
+ id: "cross-cutting",
8377
+ title: "Cross-Cutting Concerns",
8378
+ description: "Security, Logging, and Observability.",
8379
+ files: []
8380
+ }
8381
+ ],
8382
+ hotspots: [],
8383
+ dependencies: []
8384
+ };
8385
+ }
8386
+ };
8387
+
8388
+ // src/blueprint/generator.ts
8389
+ import * as fs11 from "fs/promises";
8390
+ import * as path11 from "path";
8391
+ import * as ejs from "ejs";
8392
+
8393
+ // src/blueprint/templates.ts
8394
+ var SHELL_TEMPLATE = `
8395
+ <!DOCTYPE html>
8396
+ <html lang="en">
8397
+ <head>
8398
+ <meta charset="UTF-8">
8399
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8400
+ <title>Blueprint: <%= projectName %></title>
8401
+ <style><%- styles %></style>
8402
+ </head>
8403
+ <body>
8404
+ <div id="app">
8405
+ <header>
8406
+ <h1>Blueprint: <%= projectName %></h1>
8407
+ <p>Generated at: <%= generatedAt %></p>
8408
+ </header>
8409
+ <main>
8410
+ <section class="modules">
8411
+ <% modules.forEach(module => { %>
8412
+ <article class="module" id="<%= module.id %>">
8413
+ <h2><%= module.title %></h2>
8414
+ <p><%= module.description %></p>
8415
+ <div class="content">
8416
+ <h3>Code Translation</h3>
8417
+ <div class="translation"><%- module.content.codeTranslation %></div>
8418
+ </div>
8419
+ </article>
8420
+
8421
+ <% }) %>
8422
+ </section>
8423
+ </main>
8424
+ </div>
8425
+ <script><%- scripts %></script>
8426
+ </body>
8427
+ </html>
8428
+ `;
8429
+ var STYLES = `
8430
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; color: #333; }
8431
+ header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10px; }
8432
+ .module { background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-radius: 4px; }
8433
+ .module h2 { margin-top: 0; color: #0066cc; }
8434
+ `;
8435
+ var SCRIPTS = `
8436
+ console.log('Blueprint player initialized.');
8437
+ `;
8438
+
8439
+ // src/shared/llm.ts
8440
+ var MockLLMService = class {
8441
+ async generate(prompt) {
8442
+ return "This is a mock LLM response for: " + prompt;
8443
+ }
8444
+ };
8445
+ var llmService = new MockLLMService();
8446
+
8447
+ // src/blueprint/content-pipeline.ts
8448
+ var ContentPipeline = class {
8449
+ async generateModuleContent(module) {
8450
+ const codeContext = module.files.join("\n");
8451
+ const translation = await llmService.generate(
8452
+ `You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
8453
+ );
8454
+ return {
8455
+ codeTranslation: translation
8456
+ };
8457
+ }
8458
+ };
8459
+
8460
+ // src/blueprint/generator.ts
8461
+ var BlueprintGenerator = class {
8462
+ contentPipeline = new ContentPipeline();
8463
+ async generate(data, options) {
8464
+ await Promise.all(
8465
+ data.modules.map(async (module) => {
8466
+ module.content = await this.contentPipeline.generateModuleContent(module);
8467
+ })
8468
+ );
8469
+ const html = ejs.render(SHELL_TEMPLATE, {
8470
+ ...data,
8471
+ styles: STYLES,
8472
+ scripts: SCRIPTS
8473
+ });
8474
+ await fs11.mkdir(options.outputDir, { recursive: true });
8475
+ await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
8476
+ }
8477
+ };
8478
+
8479
+ // src/update-checker.ts
8480
+ import * as fs12 from "fs";
8481
+ import * as path12 from "path";
8354
8482
  import * as os from "os";
8355
8483
  import { spawn } from "child_process";
8356
8484
  function getStatePath() {
8357
8485
  const home = process.env["HOME"] || os.homedir();
8358
- return path10.join(home, ".harness", "update-check.json");
8486
+ return path12.join(home, ".harness", "update-check.json");
8359
8487
  }
8360
8488
  function isUpdateCheckEnabled(configInterval) {
8361
8489
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -8368,7 +8496,7 @@ function shouldRunCheck(state, intervalMs) {
8368
8496
  }
8369
8497
  function readCheckState() {
8370
8498
  try {
8371
- const raw = fs8.readFileSync(getStatePath(), "utf-8");
8499
+ const raw = fs12.readFileSync(getStatePath(), "utf-8");
8372
8500
  const parsed = JSON.parse(raw);
8373
8501
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
8374
8502
  const state = parsed;
@@ -8385,7 +8513,7 @@ function readCheckState() {
8385
8513
  }
8386
8514
  function spawnBackgroundCheck(currentVersion) {
8387
8515
  const statePath = getStatePath();
8388
- const stateDir = path10.dirname(statePath);
8516
+ const stateDir = path12.dirname(statePath);
8389
8517
  const script = `
8390
8518
  const { execSync } = require('child_process');
8391
8519
  const fs = require('fs');
@@ -8439,38 +8567,63 @@ Run "harness update" to upgrade.`;
8439
8567
  }
8440
8568
 
8441
8569
  // src/index.ts
8442
- var VERSION = "1.8.2";
8570
+ var VERSION = "0.11.0";
8443
8571
  export {
8444
8572
  AGENT_DESCRIPTORS,
8445
8573
  ARCHITECTURE_DESCRIPTOR,
8446
8574
  AgentActionEmitter,
8575
+ ArchBaselineManager,
8576
+ ArchBaselineSchema,
8577
+ ArchConfigSchema,
8578
+ ArchDiffResultSchema,
8579
+ ArchMetricCategorySchema,
8447
8580
  BUG_DETECTION_DESCRIPTOR,
8448
8581
  BaselineManager,
8449
8582
  BenchmarkRunner,
8583
+ BlueprintGenerator,
8584
+ BundleConstraintsSchema,
8585
+ BundleSchema,
8450
8586
  COMPLIANCE_DESCRIPTOR,
8587
+ CategoryBaselineSchema,
8588
+ CategoryRegressionSchema,
8451
8589
  ChecklistBuilder,
8590
+ CircularDepsCollector,
8591
+ ComplexityCollector,
8452
8592
  ConfirmationSchema,
8453
8593
  ConsoleSink,
8594
+ ConstraintRuleSchema,
8595
+ ContentPipeline,
8596
+ ContributionsSchema,
8597
+ CouplingCollector,
8454
8598
  CriticalPathResolver,
8455
8599
  DEFAULT_PROVIDER_TIERS,
8456
8600
  DEFAULT_SECURITY_CONFIG,
8457
8601
  DEFAULT_STATE,
8458
8602
  DEFAULT_STREAM_INDEX,
8603
+ DepDepthCollector,
8459
8604
  EmitInteractionInputSchema,
8460
8605
  EntropyAnalyzer,
8461
8606
  EntropyConfigSchema,
8462
8607
  ExclusionSet,
8463
8608
  FailureEntrySchema,
8464
8609
  FileSink,
8610
+ ForbiddenImportCollector,
8465
8611
  GateConfigSchema,
8466
8612
  GateResultSchema,
8467
8613
  HandoffSchema,
8468
8614
  HarnessStateSchema,
8469
8615
  InteractionTypeSchema,
8616
+ LayerViolationCollector,
8617
+ LockfilePackageSchema,
8618
+ LockfileSchema,
8619
+ ManifestSchema,
8620
+ MetricResultSchema,
8621
+ ModuleSizeCollector,
8470
8622
  NoOpExecutor,
8471
8623
  NoOpSink,
8472
8624
  NoOpTelemetryAdapter,
8473
8625
  PatternConfigSchema,
8626
+ ProjectScanner,
8474
8627
  QuestionSchema,
8475
8628
  REQUIRED_SECTIONS,
8476
8629
  RegressionDetector,
@@ -8478,16 +8631,26 @@ export {
8478
8631
  SECURITY_DESCRIPTOR,
8479
8632
  SecurityConfigSchema,
8480
8633
  SecurityScanner,
8634
+ SharableBoundaryConfigSchema,
8635
+ SharableForbiddenImportSchema,
8636
+ SharableLayerSchema,
8637
+ SharableSecurityRulesSchema,
8481
8638
  StreamIndexSchema,
8482
8639
  StreamInfoSchema,
8640
+ ThresholdConfigSchema,
8483
8641
  TransitionSchema,
8484
8642
  TypeScriptParser,
8485
8643
  VERSION,
8644
+ ViolationSchema,
8645
+ addProvenance,
8486
8646
  analyzeDiff,
8487
8647
  appendFailure,
8488
8648
  appendLearning,
8489
8649
  applyFixes,
8490
8650
  applyHotspotDowngrade,
8651
+ archMatchers,
8652
+ archModule,
8653
+ architecture,
8491
8654
  archiveFailures,
8492
8655
  archiveStream,
8493
8656
  buildDependencyGraph,
@@ -8497,6 +8660,7 @@ export {
8497
8660
  checkEligibility,
8498
8661
  classifyFinding,
8499
8662
  configureFeedback,
8663
+ constraintRuleId,
8500
8664
  contextBudget,
8501
8665
  contextFilter,
8502
8666
  createBoundaryValidator,
@@ -8511,6 +8675,8 @@ export {
8511
8675
  cryptoRules,
8512
8676
  deduplicateCleanupFindings,
8513
8677
  deduplicateFindings,
8678
+ deepMergeConstraints,
8679
+ defaultCollectors,
8514
8680
  defineLayer,
8515
8681
  deserializationRules,
8516
8682
  detectChangeType,
@@ -8523,9 +8689,12 @@ export {
8523
8689
  detectPatternViolations,
8524
8690
  detectSizeBudgetViolations,
8525
8691
  detectStack,
8692
+ detectStaleConstraints,
8526
8693
  determineAssessment,
8694
+ diff,
8527
8695
  executeWorkflow,
8528
8696
  expressRules,
8697
+ extractBundle,
8529
8698
  extractMarkdownLinks,
8530
8699
  extractSections,
8531
8700
  fanOutReview,
@@ -8556,6 +8725,7 @@ export {
8556
8725
  networkRules,
8557
8726
  nodeRules,
8558
8727
  parseDiff,
8728
+ parseManifest,
8559
8729
  parseRoadmap,
8560
8730
  parseSecurityConfig,
8561
8731
  parseSize,
@@ -8563,6 +8733,9 @@ export {
8563
8733
  previewFix,
8564
8734
  reactRules,
8565
8735
  readCheckState,
8736
+ readLockfile,
8737
+ removeContributions,
8738
+ removeProvenance,
8566
8739
  requestMultiplePeerReviews,
8567
8740
  requestPeerReview,
8568
8741
  resetFeedbackConfig,
@@ -8570,6 +8743,8 @@ export {
8570
8743
  resolveModelTier,
8571
8744
  resolveRuleSeverity,
8572
8745
  resolveStreamPath,
8746
+ resolveThresholds,
8747
+ runAll,
8573
8748
  runArchitectureAgent,
8574
8749
  runBugDetectionAgent,
8575
8750
  runCIChecks,
@@ -8589,6 +8764,7 @@ export {
8589
8764
  setActiveStream,
8590
8765
  shouldRunCheck,
8591
8766
  spawnBackgroundCheck,
8767
+ syncConstraintNodes,
8592
8768
  syncRoadmap,
8593
8769
  touchStream,
8594
8770
  trackAction,
@@ -8601,5 +8777,8 @@ export {
8601
8777
  validateFindings,
8602
8778
  validateKnowledgeMap,
8603
8779
  validatePatternConfig,
8780
+ violationId,
8781
+ writeConfig,
8782
+ writeLockfile,
8604
8783
  xssRules
8605
8784
  };