@harness-engineering/core 0.13.0 → 0.13.1

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.js CHANGED
@@ -273,6 +273,7 @@ var import_types = require("@harness-engineering/types");
273
273
  // src/shared/fs-utils.ts
274
274
  var import_fs = require("fs");
275
275
  var import_util = require("util");
276
+ var import_node_path = require("path");
276
277
  var import_glob = require("glob");
277
278
  var accessAsync = (0, import_util.promisify)(import_fs.access);
278
279
  var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
@@ -295,6 +296,9 @@ async function readFileContent(path20) {
295
296
  async function findFiles(pattern, cwd = process.cwd()) {
296
297
  return (0, import_glob.glob)(pattern, { cwd, absolute: true });
297
298
  }
299
+ function relativePosix(from, to) {
300
+ return (0, import_node_path.relative)(from, to).replaceAll("\\", "/");
301
+ }
298
302
 
299
303
  // src/validation/file-structure.ts
300
304
  async function validateFileStructure(projectPath, conventions) {
@@ -656,7 +660,7 @@ async function checkDocCoverage(domain, options = {}) {
656
660
  try {
657
661
  const sourceFiles = await findFiles("**/*.{ts,js,tsx,jsx}", sourceDir);
658
662
  const filteredSourceFiles = sourceFiles.filter((file) => {
659
- const relativePath = (0, import_path2.relative)(sourceDir, file);
663
+ const relativePath = relativePosix(sourceDir, file);
660
664
  return !excludePatterns.some((pattern) => {
661
665
  return (0, import_minimatch.minimatch)(relativePath, pattern, { dot: true }) || (0, import_minimatch.minimatch)(file, pattern, { dot: true });
662
666
  });
@@ -679,7 +683,7 @@ async function checkDocCoverage(domain, options = {}) {
679
683
  const undocumented = [];
680
684
  const gaps = [];
681
685
  for (const sourceFile of filteredSourceFiles) {
682
- const relativePath = (0, import_path2.relative)(sourceDir, sourceFile);
686
+ const relativePath = relativePosix(sourceDir, sourceFile);
683
687
  const fileName = (0, import_path2.basename)(sourceFile);
684
688
  const isDocumented = documentedPaths.has(relativePath) || documentedPaths.has(fileName) || documentedPaths.has(`src/${relativePath}`);
685
689
  if (isDocumented) {
@@ -739,7 +743,7 @@ async function validateKnowledgeMap(rootDir = process.cwd()) {
739
743
  totalLinks: agentsTotalLinks
740
744
  } = agentsResult.value;
741
745
  const existingFiles = await findFiles("**/*", rootDir);
742
- const relativeExistingFiles = existingFiles.map((f) => (0, import_path3.relative)(rootDir, f));
746
+ const relativeExistingFiles = existingFiles.map((f) => relativePosix(rootDir, f));
743
747
  const brokenLinks = agentsBrokenLinks.map((link) => {
744
748
  const section = sections.find(
745
749
  (s) => s.links.some((l) => l.path === link.path && l.line === link.line)
@@ -780,7 +784,7 @@ var DEFAULT_SECTIONS = [
780
784
  function groupByDirectory(files, rootDir) {
781
785
  const groups = /* @__PURE__ */ new Map();
782
786
  for (const file of files) {
783
- const relativePath = (0, import_path4.relative)(rootDir, file);
787
+ const relativePath = relativePosix(rootDir, file);
784
788
  const dir = (0, import_path4.dirname)(relativePath);
785
789
  if (!groups.has(dir)) {
786
790
  groups.set(dir, []);
@@ -836,7 +840,7 @@ async function generateAgentsMap(config, graphSections) {
836
840
  allFiles.push(...files);
837
841
  }
838
842
  const filteredFiles = allFiles.filter((file) => {
839
- const relativePath = (0, import_path4.relative)(rootDir, file);
843
+ const relativePath = relativePosix(rootDir, file);
840
844
  return !matchesExcludePattern(relativePath, excludePaths);
841
845
  });
842
846
  lines.push("## Repository Structure");
@@ -864,11 +868,11 @@ async function generateAgentsMap(config, graphSections) {
864
868
  }
865
869
  const sectionFiles = await findFiles(section.pattern, rootDir);
866
870
  const filteredSectionFiles = sectionFiles.filter((file) => {
867
- const relativePath = (0, import_path4.relative)(rootDir, file);
871
+ const relativePath = relativePosix(rootDir, file);
868
872
  return !matchesExcludePattern(relativePath, excludePaths);
869
873
  });
870
874
  for (const file of filteredSectionFiles.slice(0, 20)) {
871
- lines.push(formatFileLink((0, import_path4.relative)(rootDir, file)));
875
+ lines.push(formatFileLink(relativePosix(rootDir, file)));
872
876
  }
873
877
  if (filteredSectionFiles.length > 20) {
874
878
  lines.push(`- _... and ${filteredSectionFiles.length - 20} more files_`);
@@ -1139,8 +1143,8 @@ async function buildDependencyGraph(files, parser, graphDependencyData) {
1139
1143
  function checkLayerViolations(graph, layers, rootDir) {
1140
1144
  const violations = [];
1141
1145
  for (const edge of graph.edges) {
1142
- const fromRelative = (0, import_path5.relative)(rootDir, edge.from);
1143
- const toRelative = (0, import_path5.relative)(rootDir, edge.to);
1146
+ const fromRelative = relativePosix(rootDir, edge.from);
1147
+ const toRelative = relativePosix(rootDir, edge.to);
1144
1148
  const fromLayer = resolveFileToLayer(fromRelative, layers);
1145
1149
  const toLayer = resolveFileToLayer(toRelative, layers);
1146
1150
  if (!fromLayer || !toLayer) continue;
@@ -2335,7 +2339,7 @@ async function buildSnapshot(config) {
2335
2339
  sourceFilePaths.push(...files2);
2336
2340
  }
2337
2341
  sourceFilePaths = sourceFilePaths.filter((f) => {
2338
- const rel = (0, import_path6.relative)(rootDir, f);
2342
+ const rel = relativePosix(rootDir, f);
2339
2343
  return !excludePatterns.some((p) => (0, import_minimatch3.minimatch)(rel, p));
2340
2344
  });
2341
2345
  const files = [];
@@ -2867,9 +2871,8 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
2867
2871
 
2868
2872
  // src/entropy/detectors/patterns.ts
2869
2873
  var import_minimatch4 = require("minimatch");
2870
- var import_path9 = require("path");
2871
2874
  function fileMatchesPattern(filePath, pattern, rootDir) {
2872
- const relativePath = (0, import_path9.relative)(rootDir, filePath);
2875
+ const relativePath = relativePosix(rootDir, filePath);
2873
2876
  return (0, import_minimatch4.minimatch)(relativePath, pattern);
2874
2877
  }
2875
2878
  function checkConfigPattern(pattern, file, rootDir) {
@@ -3465,7 +3468,7 @@ async function detectCouplingViolations(snapshot, config, graphData) {
3465
3468
 
3466
3469
  // src/entropy/detectors/size-budget.ts
3467
3470
  var import_node_fs = require("fs");
3468
- var import_node_path = require("path");
3471
+ var import_node_path2 = require("path");
3469
3472
  function parseSize(size) {
3470
3473
  const match = size.trim().match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB|B)?$/i);
3471
3474
  if (!match) return 0;
@@ -3492,7 +3495,7 @@ function dirSize(dirPath) {
3492
3495
  }
3493
3496
  for (const entry of entries) {
3494
3497
  if (entry === "node_modules" || entry === ".git") continue;
3495
- const fullPath = (0, import_node_path.join)(dirPath, entry);
3498
+ const fullPath = (0, import_node_path2.join)(dirPath, entry);
3496
3499
  try {
3497
3500
  const stat = (0, import_node_fs.statSync)(fullPath);
3498
3501
  if (stat.isDirectory()) {
@@ -3512,7 +3515,7 @@ async function detectSizeBudgetViolations(rootDir, config) {
3512
3515
  let packagesChecked = 0;
3513
3516
  for (const [pkgPath, budget] of Object.entries(budgets)) {
3514
3517
  packagesChecked++;
3515
- const distPath = (0, import_node_path.join)(rootDir, pkgPath, "dist");
3518
+ const distPath = (0, import_node_path2.join)(rootDir, pkgPath, "dist");
3516
3519
  const currentSize = dirSize(distPath);
3517
3520
  if (budget.warn) {
3518
3521
  const budgetBytes = parseSize(budget.warn);
@@ -3893,7 +3896,7 @@ var EntropyAnalyzer = class {
3893
3896
  // src/entropy/fixers/safe-fixes.ts
3894
3897
  var fs3 = __toESM(require("fs"));
3895
3898
  var import_util2 = require("util");
3896
- var import_path10 = require("path");
3899
+ var import_path9 = require("path");
3897
3900
  var readFile5 = (0, import_util2.promisify)(fs3.readFile);
3898
3901
  var writeFile3 = (0, import_util2.promisify)(fs3.writeFile);
3899
3902
  var unlink2 = (0, import_util2.promisify)(fs3.unlink);
@@ -3909,7 +3912,7 @@ function createDeadFileFixes(deadCodeReport) {
3909
3912
  return deadCodeReport.deadFiles.map((file) => ({
3910
3913
  type: "dead-files",
3911
3914
  file: file.path,
3912
- description: `Delete dead file (${file.reason}): ${(0, import_path10.basename)(file.path)}`,
3915
+ description: `Delete dead file (${file.reason}): ${(0, import_path9.basename)(file.path)}`,
3913
3916
  action: "delete-file",
3914
3917
  safe: true,
3915
3918
  reversible: true
@@ -3992,9 +3995,9 @@ function previewFix(fix) {
3992
3995
  }
3993
3996
  }
3994
3997
  async function createBackup(filePath, backupDir) {
3995
- const backupPath = (0, import_path10.join)(backupDir, `${Date.now()}-${(0, import_path10.basename)(filePath)}`);
3998
+ const backupPath = (0, import_path9.join)(backupDir, `${Date.now()}-${(0, import_path9.basename)(filePath)}`);
3996
3999
  try {
3997
- await mkdir2((0, import_path10.dirname)(backupPath), { recursive: true });
4000
+ await mkdir2((0, import_path9.dirname)(backupPath), { recursive: true });
3998
4001
  await copyFile2(filePath, backupPath);
3999
4002
  return (0, import_types.Ok)(backupPath);
4000
4003
  } catch (e) {
@@ -4330,11 +4333,11 @@ function validatePatternConfig(config) {
4330
4333
 
4331
4334
  // src/performance/baseline-manager.ts
4332
4335
  var import_node_fs2 = require("fs");
4333
- var import_node_path2 = require("path");
4336
+ var import_node_path3 = require("path");
4334
4337
  var BaselineManager = class {
4335
4338
  baselinesPath;
4336
4339
  constructor(projectRoot) {
4337
- this.baselinesPath = (0, import_node_path2.join)(projectRoot, ".harness", "perf", "baselines.json");
4340
+ this.baselinesPath = (0, import_node_path3.join)(projectRoot, ".harness", "perf", "baselines.json");
4338
4341
  }
4339
4342
  /**
4340
4343
  * Load the baselines file from disk.
@@ -4374,7 +4377,7 @@ var BaselineManager = class {
4374
4377
  updatedFrom: commitHash,
4375
4378
  benchmarks
4376
4379
  };
4377
- const dir = (0, import_node_path2.dirname)(this.baselinesPath);
4380
+ const dir = (0, import_node_path3.dirname)(this.baselinesPath);
4378
4381
  if (!(0, import_node_fs2.existsSync)(dir)) {
4379
4382
  (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
4380
4383
  }
@@ -5365,7 +5368,7 @@ async function requestMultiplePeerReviews(requests) {
5365
5368
 
5366
5369
  // src/feedback/logging/file-sink.ts
5367
5370
  var import_fs2 = require("fs");
5368
- var import_path11 = require("path");
5371
+ var import_path10 = require("path");
5369
5372
  var FileSink = class {
5370
5373
  name = "file";
5371
5374
  filePath;
@@ -5388,7 +5391,7 @@ var FileSink = class {
5388
5391
  }
5389
5392
  ensureDirectory() {
5390
5393
  if (!this.initialized) {
5391
- const dir = (0, import_path11.dirname)(this.filePath);
5394
+ const dir = (0, import_path10.dirname)(this.filePath);
5392
5395
  if (!(0, import_fs2.existsSync)(dir)) {
5393
5396
  (0, import_fs2.mkdirSync)(dir, { recursive: true });
5394
5397
  }
@@ -5535,14 +5538,10 @@ var ConstraintRuleSchema = import_zod3.z.object({
5535
5538
  // forward-compat for governs edges
5536
5539
  });
5537
5540
 
5538
- // src/architecture/collectors/circular-deps.ts
5539
- var import_node_path3 = require("path");
5540
-
5541
5541
  // src/architecture/collectors/hash.ts
5542
5542
  var import_node_crypto = require("crypto");
5543
5543
  function violationId(relativePath, category, normalizedDetail) {
5544
- const path20 = relativePath.replace(/\\/g, "/");
5545
- const input = `${path20}:${category}:${normalizedDetail}`;
5544
+ const input = `${relativePath}:${category}:${normalizedDetail}`;
5546
5545
  return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
5547
5546
  }
5548
5547
  function constraintRuleId(category, scope, description) {
@@ -5606,8 +5605,8 @@ var CircularDepsCollector = class {
5606
5605
  }
5607
5606
  const { cycles, largestCycle } = result.value;
5608
5607
  const violations = cycles.map((cycle) => {
5609
- const cyclePath = cycle.cycle.map((f) => (0, import_node_path3.relative)(rootDir, f)).join(" -> ");
5610
- const firstFile = (0, import_node_path3.relative)(rootDir, cycle.cycle[0]);
5608
+ const cyclePath = cycle.cycle.map((f) => relativePosix(rootDir, f)).join(" -> ");
5609
+ const firstFile = relativePosix(rootDir, cycle.cycle[0]);
5611
5610
  return {
5612
5611
  id: violationId(firstFile, this.category, cyclePath),
5613
5612
  file: firstFile,
@@ -5628,7 +5627,6 @@ var CircularDepsCollector = class {
5628
5627
  };
5629
5628
 
5630
5629
  // src/architecture/collectors/layer-violations.ts
5631
- var import_node_path4 = require("path");
5632
5630
  var LayerViolationCollector = class {
5633
5631
  category = "layer-violations";
5634
5632
  getRules(_config, _rootDir) {
@@ -5672,8 +5670,8 @@ var LayerViolationCollector = class {
5672
5670
  (v) => v.reason === "WRONG_LAYER"
5673
5671
  );
5674
5672
  const violations = layerViolations.map((v) => {
5675
- const relFile = (0, import_node_path4.relative)(rootDir, v.file);
5676
- const relImport = (0, import_node_path4.relative)(rootDir, v.imports);
5673
+ const relFile = relativePosix(rootDir, v.file);
5674
+ const relImport = relativePosix(rootDir, v.imports);
5677
5675
  const detail = `${v.fromLayer} -> ${v.toLayer}: ${relFile} imports ${relImport}`;
5678
5676
  return {
5679
5677
  id: violationId(relFile, this.category, detail),
@@ -5695,7 +5693,6 @@ var LayerViolationCollector = class {
5695
5693
  };
5696
5694
 
5697
5695
  // src/architecture/collectors/complexity.ts
5698
- var import_node_path5 = require("path");
5699
5696
  var ComplexityCollector = class {
5700
5697
  category = "complexity";
5701
5698
  getRules(_config, _rootDir) {
@@ -5756,7 +5753,7 @@ var ComplexityCollector = class {
5756
5753
  (v) => v.severity === "error" || v.severity === "warning"
5757
5754
  );
5758
5755
  const violations = filtered.map((v) => {
5759
- const relFile = (0, import_node_path5.relative)(rootDir, v.file);
5756
+ const relFile = relativePosix(rootDir, v.file);
5760
5757
  const idDetail = `${v.metric}:${v.function}`;
5761
5758
  return {
5762
5759
  id: violationId(relFile, this.category, idDetail),
@@ -5782,7 +5779,6 @@ var ComplexityCollector = class {
5782
5779
  };
5783
5780
 
5784
5781
  // src/architecture/collectors/coupling.ts
5785
- var import_node_path6 = require("path");
5786
5782
  var CouplingCollector = class {
5787
5783
  category = "coupling";
5788
5784
  getRules(_config, _rootDir) {
@@ -5833,7 +5829,7 @@ var CouplingCollector = class {
5833
5829
  (v) => v.severity === "error" || v.severity === "warning"
5834
5830
  );
5835
5831
  const violations = filtered.map((v) => {
5836
- const relFile = (0, import_node_path6.relative)(rootDir, v.file);
5832
+ const relFile = relativePosix(rootDir, v.file);
5837
5833
  const idDetail = `${v.metric}`;
5838
5834
  return {
5839
5835
  id: violationId(relFile, this.category, idDetail),
@@ -5856,7 +5852,6 @@ var CouplingCollector = class {
5856
5852
  };
5857
5853
 
5858
5854
  // src/architecture/collectors/forbidden-imports.ts
5859
- var import_node_path7 = require("path");
5860
5855
  var ForbiddenImportCollector = class {
5861
5856
  category = "forbidden-imports";
5862
5857
  getRules(_config, _rootDir) {
@@ -5900,8 +5895,8 @@ var ForbiddenImportCollector = class {
5900
5895
  (v) => v.reason === "FORBIDDEN_IMPORT"
5901
5896
  );
5902
5897
  const violations = forbidden.map((v) => {
5903
- const relFile = (0, import_node_path7.relative)(rootDir, v.file);
5904
- const relImport = (0, import_node_path7.relative)(rootDir, v.imports);
5898
+ const relFile = relativePosix(rootDir, v.file);
5899
+ const relImport = relativePosix(rootDir, v.imports);
5905
5900
  const detail = `forbidden import: ${relFile} -> ${relImport}`;
5906
5901
  return {
5907
5902
  id: violationId(relFile, this.category, detail),
@@ -5924,7 +5919,7 @@ var ForbiddenImportCollector = class {
5924
5919
 
5925
5920
  // src/architecture/collectors/module-size.ts
5926
5921
  var import_promises2 = require("fs/promises");
5927
- var import_node_path8 = require("path");
5922
+ var import_node_path4 = require("path");
5928
5923
  async function discoverModules(rootDir) {
5929
5924
  const modules = [];
5930
5925
  async function scanDir(dir) {
@@ -5940,7 +5935,7 @@ async function discoverModules(rootDir) {
5940
5935
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
5941
5936
  continue;
5942
5937
  }
5943
- const fullPath = (0, import_node_path8.join)(dir, entry.name);
5938
+ const fullPath = (0, import_node_path4.join)(dir, entry.name);
5944
5939
  if (entry.isDirectory()) {
5945
5940
  subdirs.push(fullPath);
5946
5941
  } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".test.tsx") && !entry.name.endsWith(".spec.ts")) {
@@ -5957,10 +5952,10 @@ async function discoverModules(rootDir) {
5957
5952
  }
5958
5953
  }
5959
5954
  modules.push({
5960
- modulePath: (0, import_node_path8.relative)(rootDir, dir),
5955
+ modulePath: relativePosix(rootDir, dir),
5961
5956
  fileCount: tsFiles.length,
5962
5957
  totalLoc,
5963
- files: tsFiles.map((f) => (0, import_node_path8.relative)(rootDir, f))
5958
+ files: tsFiles.map((f) => relativePosix(rootDir, f))
5964
5959
  });
5965
5960
  }
5966
5961
  for (const sub of subdirs) {
@@ -6052,16 +6047,16 @@ var ModuleSizeCollector = class {
6052
6047
 
6053
6048
  // src/architecture/collectors/dep-depth.ts
6054
6049
  var import_promises3 = require("fs/promises");
6055
- var import_node_path9 = require("path");
6050
+ var import_node_path5 = require("path");
6056
6051
  function extractImportSources(content, filePath) {
6057
6052
  const importRegex = /(?:import|export)\s+.*?from\s+['"](\.[^'"]+)['"]/g;
6058
6053
  const dynamicRegex = /import\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
6059
6054
  const sources = [];
6060
- const dir = (0, import_node_path9.dirname)(filePath);
6055
+ const dir = (0, import_node_path5.dirname)(filePath);
6061
6056
  for (const regex of [importRegex, dynamicRegex]) {
6062
6057
  let match;
6063
6058
  while ((match = regex.exec(content)) !== null) {
6064
- let resolved = (0, import_node_path9.resolve)(dir, match[1]);
6059
+ let resolved = (0, import_node_path5.resolve)(dir, match[1]);
6065
6060
  if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
6066
6061
  resolved += ".ts";
6067
6062
  }
@@ -6082,7 +6077,7 @@ async function collectTsFiles(dir) {
6082
6077
  for (const entry of entries) {
6083
6078
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
6084
6079
  continue;
6085
- const fullPath = (0, import_node_path9.join)(d, entry.name);
6080
+ const fullPath = (0, import_node_path5.join)(d, entry.name);
6086
6081
  if (entry.isDirectory()) {
6087
6082
  await scan(fullPath);
6088
6083
  } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".test.tsx") && !entry.name.endsWith(".spec.ts")) {
@@ -6136,7 +6131,7 @@ var DepDepthCollector = class {
6136
6131
  }
6137
6132
  const moduleMap = /* @__PURE__ */ new Map();
6138
6133
  for (const file of allFiles) {
6139
- const relDir = (0, import_node_path9.relative)(rootDir, (0, import_node_path9.dirname)(file));
6134
+ const relDir = relativePosix(rootDir, (0, import_node_path5.dirname)(file));
6140
6135
  if (!moduleMap.has(relDir)) moduleMap.set(relDir, []);
6141
6136
  moduleMap.get(relDir).push(file);
6142
6137
  }
@@ -6280,11 +6275,11 @@ function detectStaleConstraints(store, windowDays = 30, category) {
6280
6275
  // src/architecture/baseline-manager.ts
6281
6276
  var import_node_fs3 = require("fs");
6282
6277
  var import_node_crypto2 = require("crypto");
6283
- var import_node_path10 = require("path");
6278
+ var import_node_path6 = require("path");
6284
6279
  var ArchBaselineManager = class {
6285
6280
  baselinesPath;
6286
6281
  constructor(projectRoot, baselinePath) {
6287
- this.baselinesPath = baselinePath ? (0, import_node_path10.join)(projectRoot, baselinePath) : (0, import_node_path10.join)(projectRoot, ".harness", "arch", "baselines.json");
6282
+ this.baselinesPath = baselinePath ? (0, import_node_path6.join)(projectRoot, baselinePath) : (0, import_node_path6.join)(projectRoot, ".harness", "arch", "baselines.json");
6288
6283
  }
6289
6284
  /**
6290
6285
  * Snapshot the current metric results into an ArchBaseline.
@@ -6345,7 +6340,7 @@ var ArchBaselineManager = class {
6345
6340
  * Uses atomic write (write to temp file, then rename) to prevent corruption.
6346
6341
  */
6347
6342
  save(baseline) {
6348
- const dir = (0, import_node_path10.dirname)(this.baselinesPath);
6343
+ const dir = (0, import_node_path6.dirname)(this.baselinesPath);
6349
6344
  if (!(0, import_node_fs3.existsSync)(dir)) {
6350
6345
  (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
6351
6346
  }
@@ -8399,238 +8394,270 @@ var ALL_CHECKS = [
8399
8394
  "phase-gate",
8400
8395
  "arch"
8401
8396
  ];
8402
- async function runSingleCheck(name, projectRoot, config) {
8403
- const start = Date.now();
8397
+ async function runValidateCheck(projectRoot, config) {
8404
8398
  const issues = [];
8405
- try {
8406
- switch (name) {
8407
- case "validate": {
8408
- const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8409
- const result = await validateAgentsMap(agentsPath);
8410
- if (!result.ok) {
8411
- issues.push({ severity: "error", message: result.error.message });
8412
- } else if (!result.value.valid) {
8413
- if (result.value.errors) {
8414
- for (const err of result.value.errors) {
8415
- issues.push({ severity: "error", message: err.message });
8416
- }
8417
- }
8418
- for (const section of result.value.missingSections) {
8419
- issues.push({ severity: "warning", message: `Missing section: ${section}` });
8420
- }
8421
- for (const link of result.value.brokenLinks) {
8422
- issues.push({
8423
- severity: "warning",
8424
- message: `Broken link: ${link.text} \u2192 ${link.path}`,
8425
- file: link.path
8426
- });
8427
- }
8428
- }
8429
- break;
8399
+ const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8400
+ const result = await validateAgentsMap(agentsPath);
8401
+ if (!result.ok) {
8402
+ issues.push({ severity: "error", message: result.error.message });
8403
+ } else if (!result.value.valid) {
8404
+ if (result.value.errors) {
8405
+ for (const err of result.value.errors) {
8406
+ issues.push({ severity: "error", message: err.message });
8430
8407
  }
8431
- case "deps": {
8432
- const rawLayers = config.layers;
8433
- if (rawLayers && rawLayers.length > 0) {
8434
- const parser = new TypeScriptParser();
8435
- const layers = rawLayers.map(
8436
- (l) => defineLayer(
8437
- l.name,
8438
- Array.isArray(l.patterns) ? l.patterns : [l.pattern],
8439
- l.allowedDependencies
8440
- )
8441
- );
8442
- const result = await validateDependencies({
8443
- layers,
8444
- rootDir: projectRoot,
8445
- parser
8446
- });
8447
- if (!result.ok) {
8448
- issues.push({ severity: "error", message: result.error.message });
8449
- } else if (result.value.violations.length > 0) {
8450
- for (const v of result.value.violations) {
8451
- issues.push({
8452
- severity: "error",
8453
- message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
8454
- file: v.file,
8455
- line: v.line
8456
- });
8457
- }
8458
- }
8459
- }
8460
- break;
8408
+ }
8409
+ for (const section of result.value.missingSections) {
8410
+ issues.push({ severity: "warning", message: `Missing section: ${section}` });
8411
+ }
8412
+ for (const link of result.value.brokenLinks) {
8413
+ issues.push({
8414
+ severity: "warning",
8415
+ message: `Broken link: ${link.text} \u2192 ${link.path}`,
8416
+ file: link.path
8417
+ });
8418
+ }
8419
+ }
8420
+ return issues;
8421
+ }
8422
+ async function runDepsCheck(projectRoot, config) {
8423
+ const issues = [];
8424
+ const rawLayers = config.layers;
8425
+ if (rawLayers && rawLayers.length > 0) {
8426
+ const parser = new TypeScriptParser();
8427
+ const layers = rawLayers.map(
8428
+ (l) => defineLayer(
8429
+ l.name,
8430
+ Array.isArray(l.patterns) ? l.patterns : [l.pattern],
8431
+ l.allowedDependencies
8432
+ )
8433
+ );
8434
+ const result = await validateDependencies({
8435
+ layers,
8436
+ rootDir: projectRoot,
8437
+ parser
8438
+ });
8439
+ if (!result.ok) {
8440
+ issues.push({ severity: "error", message: result.error.message });
8441
+ } else if (result.value.violations.length > 0) {
8442
+ for (const v of result.value.violations) {
8443
+ issues.push({
8444
+ severity: "error",
8445
+ message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
8446
+ file: v.file,
8447
+ line: v.line
8448
+ });
8461
8449
  }
8462
- case "docs": {
8463
- const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
8464
- const entropyConfig = config.entropy || {};
8465
- const result = await checkDocCoverage("project", {
8466
- docsDir,
8467
- sourceDir: projectRoot,
8468
- excludePatterns: entropyConfig.excludePatterns || [
8469
- "**/node_modules/**",
8470
- "**/dist/**",
8471
- "**/*.test.ts",
8472
- "**/fixtures/**"
8473
- ]
8450
+ }
8451
+ }
8452
+ return issues;
8453
+ }
8454
+ async function runDocsCheck(projectRoot, config) {
8455
+ const issues = [];
8456
+ const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
8457
+ const entropyConfig = config.entropy || {};
8458
+ const result = await checkDocCoverage("project", {
8459
+ docsDir,
8460
+ sourceDir: projectRoot,
8461
+ excludePatterns: entropyConfig.excludePatterns || [
8462
+ "**/node_modules/**",
8463
+ "**/dist/**",
8464
+ "**/*.test.ts",
8465
+ "**/fixtures/**"
8466
+ ]
8467
+ });
8468
+ if (!result.ok) {
8469
+ issues.push({ severity: "warning", message: result.error.message });
8470
+ } else if (result.value.gaps.length > 0) {
8471
+ for (const gap of result.value.gaps) {
8472
+ issues.push({
8473
+ severity: "warning",
8474
+ message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
8475
+ file: gap.file
8476
+ });
8477
+ }
8478
+ }
8479
+ return issues;
8480
+ }
8481
+ async function runEntropyCheck(projectRoot, _config) {
8482
+ const issues = [];
8483
+ const analyzer = new EntropyAnalyzer({
8484
+ rootDir: projectRoot,
8485
+ analyze: { drift: true, deadCode: true, patterns: false }
8486
+ });
8487
+ const result = await analyzer.analyze();
8488
+ if (!result.ok) {
8489
+ issues.push({ severity: "warning", message: result.error.message });
8490
+ } else {
8491
+ const report = result.value;
8492
+ if (report.drift) {
8493
+ for (const drift of report.drift.drifts) {
8494
+ issues.push({
8495
+ severity: "warning",
8496
+ message: `Doc drift (${drift.type}): ${drift.details}`,
8497
+ file: drift.docFile,
8498
+ line: drift.line
8474
8499
  });
8475
- if (!result.ok) {
8476
- issues.push({ severity: "warning", message: result.error.message });
8477
- } else if (result.value.gaps.length > 0) {
8478
- for (const gap of result.value.gaps) {
8479
- issues.push({
8480
- severity: "warning",
8481
- message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
8482
- file: gap.file
8483
- });
8484
- }
8485
- }
8486
- break;
8487
8500
  }
8488
- case "entropy": {
8489
- const analyzer = new EntropyAnalyzer({
8490
- rootDir: projectRoot,
8491
- analyze: { drift: true, deadCode: true, patterns: false }
8501
+ }
8502
+ if (report.deadCode) {
8503
+ for (const dead of report.deadCode.deadExports) {
8504
+ issues.push({
8505
+ severity: "warning",
8506
+ message: `Dead export: ${dead.name}`,
8507
+ file: dead.file,
8508
+ line: dead.line
8492
8509
  });
8493
- const result = await analyzer.analyze();
8494
- if (!result.ok) {
8495
- issues.push({ severity: "warning", message: result.error.message });
8496
- } else {
8497
- const report = result.value;
8498
- if (report.drift) {
8499
- for (const drift of report.drift.drifts) {
8500
- issues.push({
8501
- severity: "warning",
8502
- message: `Doc drift (${drift.type}): ${drift.details}`,
8503
- file: drift.docFile,
8504
- line: drift.line
8505
- });
8506
- }
8507
- }
8508
- if (report.deadCode) {
8509
- for (const dead of report.deadCode.deadExports) {
8510
- issues.push({
8511
- severity: "warning",
8512
- message: `Dead export: ${dead.name}`,
8513
- file: dead.file,
8514
- line: dead.line
8515
- });
8516
- }
8517
- }
8518
- }
8519
- break;
8520
8510
  }
8521
- case "security": {
8522
- const securityConfig = parseSecurityConfig(config.security);
8523
- if (!securityConfig.enabled) break;
8524
- const scanner = new SecurityScanner(securityConfig);
8525
- scanner.configureForProject(projectRoot);
8526
- const { glob: globFn } = await import("glob");
8527
- const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
8528
- cwd: projectRoot,
8529
- ignore: securityConfig.exclude ?? [
8530
- "**/node_modules/**",
8531
- "**/dist/**",
8532
- "**/*.test.ts",
8533
- "**/fixtures/**"
8534
- ],
8535
- absolute: true
8511
+ }
8512
+ }
8513
+ return issues;
8514
+ }
8515
+ async function runSecurityCheck(projectRoot, config) {
8516
+ const issues = [];
8517
+ const securityConfig = parseSecurityConfig(config.security);
8518
+ if (!securityConfig.enabled) return issues;
8519
+ const scanner = new SecurityScanner(securityConfig);
8520
+ scanner.configureForProject(projectRoot);
8521
+ const { glob: globFn } = await import("glob");
8522
+ const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
8523
+ cwd: projectRoot,
8524
+ ignore: securityConfig.exclude ?? [
8525
+ "**/node_modules/**",
8526
+ "**/dist/**",
8527
+ "**/*.test.ts",
8528
+ "**/fixtures/**"
8529
+ ],
8530
+ absolute: true
8531
+ });
8532
+ const scanResult = await scanner.scanFiles(sourceFiles);
8533
+ for (const finding of scanResult.findings) {
8534
+ issues.push({
8535
+ severity: finding.severity === "info" ? "warning" : finding.severity,
8536
+ message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
8537
+ file: finding.file,
8538
+ line: finding.line
8539
+ });
8540
+ }
8541
+ return issues;
8542
+ }
8543
+ async function runPerfCheck(projectRoot, config) {
8544
+ const issues = [];
8545
+ const perfConfig = config.performance || {};
8546
+ const perfAnalyzer = new EntropyAnalyzer({
8547
+ rootDir: projectRoot,
8548
+ analyze: {
8549
+ complexity: perfConfig.complexity || true,
8550
+ coupling: perfConfig.coupling || true,
8551
+ sizeBudget: perfConfig.sizeBudget || false
8552
+ }
8553
+ });
8554
+ const perfResult = await perfAnalyzer.analyze();
8555
+ if (!perfResult.ok) {
8556
+ issues.push({ severity: "warning", message: perfResult.error.message });
8557
+ } else {
8558
+ const perfReport = perfResult.value;
8559
+ if (perfReport.complexity) {
8560
+ for (const v of perfReport.complexity.violations) {
8561
+ issues.push({
8562
+ severity: v.severity === "info" ? "warning" : v.severity,
8563
+ message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
8564
+ file: v.file,
8565
+ line: v.line
8536
8566
  });
8537
- const scanResult = await scanner.scanFiles(sourceFiles);
8538
- for (const finding of scanResult.findings) {
8539
- issues.push({
8540
- severity: finding.severity === "info" ? "warning" : finding.severity,
8541
- message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
8542
- file: finding.file,
8543
- line: finding.line
8544
- });
8545
- }
8546
- break;
8547
8567
  }
8548
- case "perf": {
8549
- const perfConfig = config.performance || {};
8550
- const perfAnalyzer = new EntropyAnalyzer({
8551
- rootDir: projectRoot,
8552
- analyze: {
8553
- complexity: perfConfig.complexity || true,
8554
- coupling: perfConfig.coupling || true,
8555
- sizeBudget: perfConfig.sizeBudget || false
8556
- }
8568
+ }
8569
+ if (perfReport.coupling) {
8570
+ for (const v of perfReport.coupling.violations) {
8571
+ issues.push({
8572
+ severity: v.severity === "info" ? "warning" : v.severity,
8573
+ message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
8574
+ file: v.file
8557
8575
  });
8558
- const perfResult = await perfAnalyzer.analyze();
8559
- if (!perfResult.ok) {
8560
- issues.push({ severity: "warning", message: perfResult.error.message });
8561
- } else {
8562
- const perfReport = perfResult.value;
8563
- if (perfReport.complexity) {
8564
- for (const v of perfReport.complexity.violations) {
8565
- issues.push({
8566
- severity: v.severity === "info" ? "warning" : v.severity,
8567
- message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
8568
- file: v.file,
8569
- line: v.line
8570
- });
8571
- }
8572
- }
8573
- if (perfReport.coupling) {
8574
- for (const v of perfReport.coupling.violations) {
8575
- issues.push({
8576
- severity: v.severity === "info" ? "warning" : v.severity,
8577
- message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
8578
- file: v.file
8579
- });
8580
- }
8581
- }
8582
- }
8583
- break;
8584
8576
  }
8585
- case "phase-gate": {
8586
- const phaseGates = config.phaseGates;
8587
- if (!phaseGates?.enabled) {
8588
- break;
8589
- }
8577
+ }
8578
+ }
8579
+ return issues;
8580
+ }
8581
+ async function runPhaseGateCheck(_projectRoot, config) {
8582
+ const issues = [];
8583
+ const phaseGates = config.phaseGates;
8584
+ if (!phaseGates?.enabled) {
8585
+ return issues;
8586
+ }
8587
+ issues.push({
8588
+ severity: "warning",
8589
+ message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
8590
+ });
8591
+ return issues;
8592
+ }
8593
+ async function runArchCheck(projectRoot, config) {
8594
+ const issues = [];
8595
+ const rawArchConfig = config.architecture;
8596
+ const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
8597
+ if (!archConfig.enabled) return issues;
8598
+ const results = await runAll(archConfig, projectRoot);
8599
+ const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
8600
+ const baseline = baselineManager.load();
8601
+ if (baseline) {
8602
+ const diffResult = diff(results, baseline);
8603
+ if (!diffResult.passed) {
8604
+ for (const v of diffResult.newViolations) {
8590
8605
  issues.push({
8591
- severity: "warning",
8592
- message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
8606
+ severity: v.severity,
8607
+ message: `[${v.category || "arch"}] NEW: ${v.detail}`,
8608
+ file: v.file
8593
8609
  });
8594
- break;
8595
8610
  }
8596
- case "arch": {
8597
- const rawArchConfig = config.architecture;
8598
- const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
8599
- if (!archConfig.enabled) break;
8600
- const results = await runAll(archConfig, projectRoot);
8601
- const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
8602
- const baseline = baselineManager.load();
8603
- if (baseline) {
8604
- const diffResult = diff(results, baseline);
8605
- if (!diffResult.passed) {
8606
- for (const v of diffResult.newViolations) {
8607
- issues.push({
8608
- severity: v.severity,
8609
- message: `[${v.category || "arch"}] NEW: ${v.detail}`,
8610
- file: v.file
8611
- });
8612
- }
8613
- for (const r of diffResult.regressions) {
8614
- issues.push({
8615
- severity: "error",
8616
- message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
8617
- });
8618
- }
8619
- }
8620
- } else {
8621
- for (const result of results) {
8622
- for (const v of result.violations) {
8623
- issues.push({
8624
- severity: v.severity,
8625
- message: `[${result.category}] ${v.detail}`,
8626
- file: v.file
8627
- });
8628
- }
8629
- }
8630
- }
8631
- break;
8611
+ for (const r of diffResult.regressions) {
8612
+ issues.push({
8613
+ severity: "error",
8614
+ message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
8615
+ });
8632
8616
  }
8633
8617
  }
8618
+ } else {
8619
+ for (const result of results) {
8620
+ for (const v of result.violations) {
8621
+ issues.push({
8622
+ severity: v.severity,
8623
+ message: `[${result.category}] ${v.detail}`,
8624
+ file: v.file
8625
+ });
8626
+ }
8627
+ }
8628
+ }
8629
+ return issues;
8630
+ }
8631
+ async function runSingleCheck(name, projectRoot, config) {
8632
+ const start = Date.now();
8633
+ const issues = [];
8634
+ try {
8635
+ switch (name) {
8636
+ case "validate":
8637
+ issues.push(...await runValidateCheck(projectRoot, config));
8638
+ break;
8639
+ case "deps":
8640
+ issues.push(...await runDepsCheck(projectRoot, config));
8641
+ break;
8642
+ case "docs":
8643
+ issues.push(...await runDocsCheck(projectRoot, config));
8644
+ break;
8645
+ case "entropy":
8646
+ issues.push(...await runEntropyCheck(projectRoot, config));
8647
+ break;
8648
+ case "security":
8649
+ issues.push(...await runSecurityCheck(projectRoot, config));
8650
+ break;
8651
+ case "perf":
8652
+ issues.push(...await runPerfCheck(projectRoot, config));
8653
+ break;
8654
+ case "phase-gate":
8655
+ issues.push(...await runPhaseGateCheck(projectRoot, config));
8656
+ break;
8657
+ case "arch":
8658
+ issues.push(...await runArchCheck(projectRoot, config));
8659
+ break;
8660
+ }
8634
8661
  } catch (error) {
8635
8662
  issues.push({
8636
8663
  severity: "error",
@@ -9005,7 +9032,7 @@ async function readContextFile(projectRoot, filePath, reason) {
9005
9032
  if (!result.ok) return null;
9006
9033
  const content = result.value;
9007
9034
  const lines = content.split("\n").length;
9008
- const relPath = path14.isAbsolute(filePath) ? path14.relative(projectRoot, filePath) : filePath;
9035
+ const relPath = path14.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
9009
9036
  return { path: relPath, content, reason, lines };
9010
9037
  }
9011
9038
  function extractImportSources2(content) {
@@ -9023,7 +9050,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
9023
9050
  const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
9024
9051
  const basePath = path14.resolve(fromDir, importSource);
9025
9052
  if (!isWithinProject(basePath, projectRoot)) return null;
9026
- const relBase = path14.relative(projectRoot, basePath);
9053
+ const relBase = relativePosix(projectRoot, basePath);
9027
9054
  const candidates = [
9028
9055
  relBase + ".ts",
9029
9056
  relBase + ".tsx",
@@ -9042,7 +9069,7 @@ async function findTestFiles(projectRoot, sourceFile) {
9042
9069
  const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
9043
9070
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
9044
9071
  const results = await findFiles(pattern, projectRoot);
9045
- return results.map((f) => path14.relative(projectRoot, f));
9072
+ return results.map((f) => relativePosix(projectRoot, f));
9046
9073
  }
9047
9074
  async function gatherImportContext(projectRoot, changedFiles, budget) {
9048
9075
  const contextFiles = [];
@@ -10904,7 +10931,7 @@ Run "harness update" to upgrade.`;
10904
10931
  }
10905
10932
 
10906
10933
  // src/index.ts
10907
- var VERSION = "0.11.0";
10934
+ var VERSION = "0.13.0";
10908
10935
  // Annotate the CommonJS export names for ESM import in node:
10909
10936
  0 && (module.exports = {
10910
10937
  AGENT_DESCRIPTORS,