@harness-engineering/core 0.5.0 → 0.7.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,22 +1,6 @@
1
1
  // src/index.ts
2
2
  export * from "@harness-engineering/types";
3
3
 
4
- // src/shared/result.ts
5
- var Ok = (value) => ({
6
- ok: true,
7
- value
8
- });
9
- function isOk(result) {
10
- return result.ok === true;
11
- }
12
- var Err = (error) => ({
13
- ok: false,
14
- error
15
- });
16
- function isErr(result) {
17
- return result.ok === false;
18
- }
19
-
20
4
  // src/shared/errors.ts
21
5
  function createError(code, message, details = {}, suggestions = []) {
22
6
  return { code, message, details, suggestions };
@@ -25,23 +9,26 @@ function createEntropyError(code, message, details = {}, suggestions = []) {
25
9
  return { code, message, details, suggestions };
26
10
  }
27
11
 
12
+ // src/shared/result.ts
13
+ import { Ok, Err, isOk, isErr } from "@harness-engineering/types";
14
+
28
15
  // src/shared/fs-utils.ts
29
16
  import { access, constants, readFile } from "fs";
30
17
  import { promisify } from "util";
31
18
  import { glob } from "glob";
32
19
  var accessAsync = promisify(access);
33
20
  var readFileAsync = promisify(readFile);
34
- async function fileExists(path2) {
21
+ async function fileExists(path3) {
35
22
  try {
36
- await accessAsync(path2, constants.F_OK);
23
+ await accessAsync(path3, constants.F_OK);
37
24
  return true;
38
25
  } catch {
39
26
  return false;
40
27
  }
41
28
  }
42
- async function readFileContent(path2) {
29
+ async function readFileContent(path3) {
43
30
  try {
44
- const content = await readFileAsync(path2, "utf-8");
31
+ const content = await readFileAsync(path3, "utf-8");
45
32
  return Ok(content);
46
33
  } catch (error) {
47
34
  return Err(error);
@@ -89,15 +76,15 @@ function validateConfig(data, schema) {
89
76
  let message = "Configuration validation failed";
90
77
  const suggestions = [];
91
78
  if (firstError) {
92
- const path2 = firstError.path.join(".");
93
- const pathDisplay = path2 ? ` at "${path2}"` : "";
79
+ const path3 = firstError.path.join(".");
80
+ const pathDisplay = path3 ? ` at "${path3}"` : "";
94
81
  if (firstError.code === "invalid_type") {
95
82
  const received = firstError.received;
96
83
  const expected = firstError.expected;
97
84
  if (received === "undefined") {
98
85
  code = "MISSING_FIELD";
99
86
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
100
- suggestions.push(`Field "${path2}" is required and must be of type "${expected}"`);
87
+ suggestions.push(`Field "${path3}" is required and must be of type "${expected}"`);
101
88
  } else {
102
89
  code = "INVALID_TYPE";
103
90
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -310,27 +297,27 @@ function extractSections(content) {
310
297
  return result;
311
298
  });
312
299
  }
313
- function isExternalLink(path2) {
314
- return path2.startsWith("http://") || path2.startsWith("https://") || path2.startsWith("#") || path2.startsWith("mailto:");
300
+ function isExternalLink(path3) {
301
+ return path3.startsWith("http://") || path3.startsWith("https://") || path3.startsWith("#") || path3.startsWith("mailto:");
315
302
  }
316
303
  function resolveLinkPath(linkPath, baseDir) {
317
304
  return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
318
305
  }
319
- async function validateAgentsMap(path2 = "./AGENTS.md") {
320
- const contentResult = await readFileContent(path2);
306
+ async function validateAgentsMap(path3 = "./AGENTS.md") {
307
+ const contentResult = await readFileContent(path3);
321
308
  if (!contentResult.ok) {
322
309
  return Err(
323
310
  createError(
324
311
  "PARSE_ERROR",
325
312
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
326
- { path: path2 },
313
+ { path: path3 },
327
314
  ["Ensure the file exists", "Check file permissions"]
328
315
  )
329
316
  );
330
317
  }
331
318
  const content = contentResult.value;
332
319
  const sections = extractSections(content);
333
- const baseDir = dirname(path2);
320
+ const baseDir = dirname(path3);
334
321
  const sectionTitles = sections.map((s) => s.title);
335
322
  const missingSections = REQUIRED_SECTIONS.filter(
336
323
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -458,8 +445,8 @@ async function checkDocCoverage(domain, options = {}) {
458
445
 
459
446
  // src/context/knowledge-map.ts
460
447
  import { join as join2, basename as basename2, relative as relative2 } from "path";
461
- function suggestFix(path2, existingFiles) {
462
- const targetName = basename2(path2).toLowerCase();
448
+ function suggestFix(path3, existingFiles) {
449
+ const targetName = basename2(path3).toLowerCase();
463
450
  const similar = existingFiles.find((file) => {
464
451
  const fileName = basename2(file).toLowerCase();
465
452
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -467,7 +454,7 @@ function suggestFix(path2, existingFiles) {
467
454
  if (similar) {
468
455
  return `Did you mean "${similar}"?`;
469
456
  }
470
- return `Create the file "${path2}" or remove the link`;
457
+ return `Create the file "${path3}" or remove the link`;
471
458
  }
472
459
  async function validateKnowledgeMap(rootDir = process.cwd()) {
473
460
  const agentsPath = join2(rootDir, "AGENTS.md");
@@ -962,8 +949,8 @@ function createBoundaryValidator(schema, name) {
962
949
  return Ok(result.data);
963
950
  }
964
951
  const suggestions = result.error.issues.map((issue) => {
965
- const path2 = issue.path.join(".");
966
- return path2 ? `${path2}: ${issue.message}` : issue.message;
952
+ const path3 = issue.path.join(".");
953
+ return path3 ? `${path3}: ${issue.message}` : issue.message;
967
954
  });
968
955
  return Err(
969
956
  createError(
@@ -1032,11 +1019,11 @@ function walk(node, visitor) {
1032
1019
  var TypeScriptParser = class {
1033
1020
  name = "typescript";
1034
1021
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1035
- async parseFile(path2) {
1036
- const contentResult = await readFileContent(path2);
1022
+ async parseFile(path3) {
1023
+ const contentResult = await readFileContent(path3);
1037
1024
  if (!contentResult.ok) {
1038
1025
  return Err(
1039
- createParseError("NOT_FOUND", `File not found: ${path2}`, { path: path2 }, [
1026
+ createParseError("NOT_FOUND", `File not found: ${path3}`, { path: path3 }, [
1040
1027
  "Check that the file exists",
1041
1028
  "Verify the path is correct"
1042
1029
  ])
@@ -1046,7 +1033,7 @@ var TypeScriptParser = class {
1046
1033
  const ast = parse(contentResult.value, {
1047
1034
  loc: true,
1048
1035
  range: true,
1049
- jsx: path2.endsWith(".tsx"),
1036
+ jsx: path3.endsWith(".tsx"),
1050
1037
  errorOnUnknownASTType: false
1051
1038
  });
1052
1039
  return Ok({
@@ -1057,7 +1044,7 @@ var TypeScriptParser = class {
1057
1044
  } catch (e) {
1058
1045
  const error = e;
1059
1046
  return Err(
1060
- createParseError("SYNTAX_ERROR", `Failed to parse ${path2}: ${error.message}`, { path: path2 }, [
1047
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path3}: ${error.message}`, { path: path3 }, [
1061
1048
  "Check for syntax errors in the file",
1062
1049
  "Ensure valid TypeScript syntax"
1063
1050
  ])
@@ -1341,22 +1328,22 @@ function extractInlineRefs(content) {
1341
1328
  }
1342
1329
  return refs;
1343
1330
  }
1344
- async function parseDocumentationFile(path2) {
1345
- const contentResult = await readFileContent(path2);
1331
+ async function parseDocumentationFile(path3) {
1332
+ const contentResult = await readFileContent(path3);
1346
1333
  if (!contentResult.ok) {
1347
1334
  return Err(
1348
1335
  createEntropyError(
1349
1336
  "PARSE_ERROR",
1350
- `Failed to read documentation file: ${path2}`,
1351
- { file: path2 },
1337
+ `Failed to read documentation file: ${path3}`,
1338
+ { file: path3 },
1352
1339
  ["Check that the file exists"]
1353
1340
  )
1354
1341
  );
1355
1342
  }
1356
1343
  const content = contentResult.value;
1357
- const type = path2.endsWith(".md") ? "markdown" : "text";
1344
+ const type = path3.endsWith(".md") ? "markdown" : "text";
1358
1345
  return Ok({
1359
- path: path2,
1346
+ path: path3,
1360
1347
  type,
1361
1348
  content,
1362
1349
  codeBlocks: extractCodeBlocks(content),
@@ -3867,8 +3854,183 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
3867
3854
  };
3868
3855
  }
3869
3856
 
3857
+ // src/ci/check-orchestrator.ts
3858
+ import * as path2 from "path";
3859
+ var ALL_CHECKS = ["validate", "deps", "docs", "entropy", "phase-gate"];
3860
+ async function runSingleCheck(name, projectRoot, config) {
3861
+ const start = Date.now();
3862
+ const issues = [];
3863
+ try {
3864
+ switch (name) {
3865
+ case "validate": {
3866
+ const agentsPath = path2.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
3867
+ const result = await validateAgentsMap(agentsPath);
3868
+ if (!result.ok) {
3869
+ issues.push({ severity: "error", message: result.error.message });
3870
+ } else if (!result.value.valid) {
3871
+ if (result.value.errors) {
3872
+ for (const err of result.value.errors) {
3873
+ issues.push({ severity: "error", message: err.message });
3874
+ }
3875
+ }
3876
+ for (const section of result.value.missingSections) {
3877
+ issues.push({ severity: "warning", message: `Missing section: ${section}` });
3878
+ }
3879
+ for (const link of result.value.brokenLinks) {
3880
+ issues.push({
3881
+ severity: "warning",
3882
+ message: `Broken link: ${link.text} \u2192 ${link.path}`,
3883
+ file: link.path
3884
+ });
3885
+ }
3886
+ }
3887
+ break;
3888
+ }
3889
+ case "deps": {
3890
+ const layers = config.layers;
3891
+ if (layers && layers.length > 0) {
3892
+ const parser = new TypeScriptParser();
3893
+ const result = await validateDependencies({
3894
+ layers,
3895
+ rootDir: projectRoot,
3896
+ parser
3897
+ });
3898
+ if (!result.ok) {
3899
+ issues.push({ severity: "error", message: result.error.message });
3900
+ } else if (result.value.violations.length > 0) {
3901
+ for (const v of result.value.violations) {
3902
+ issues.push({
3903
+ severity: "error",
3904
+ message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
3905
+ file: v.file,
3906
+ line: v.line
3907
+ });
3908
+ }
3909
+ }
3910
+ }
3911
+ break;
3912
+ }
3913
+ case "docs": {
3914
+ const docsDir = path2.join(projectRoot, config.docsDir ?? "docs");
3915
+ const result = await checkDocCoverage("project", { docsDir });
3916
+ if (!result.ok) {
3917
+ issues.push({ severity: "warning", message: result.error.message });
3918
+ } else if (result.value.gaps.length > 0) {
3919
+ for (const gap of result.value.gaps) {
3920
+ issues.push({
3921
+ severity: "warning",
3922
+ message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
3923
+ file: gap.file
3924
+ });
3925
+ }
3926
+ }
3927
+ break;
3928
+ }
3929
+ case "entropy": {
3930
+ const analyzer = new EntropyAnalyzer({
3931
+ rootDir: projectRoot,
3932
+ analyze: { drift: true, deadCode: true, patterns: false }
3933
+ });
3934
+ const result = await analyzer.analyze();
3935
+ if (!result.ok) {
3936
+ issues.push({ severity: "warning", message: result.error.message });
3937
+ } else {
3938
+ const report = result.value;
3939
+ if (report.drift) {
3940
+ for (const drift of report.drift.drifts) {
3941
+ issues.push({
3942
+ severity: "warning",
3943
+ message: `Doc drift (${drift.type}): ${drift.details}`,
3944
+ file: drift.docFile,
3945
+ line: drift.line
3946
+ });
3947
+ }
3948
+ }
3949
+ if (report.deadCode) {
3950
+ for (const dead of report.deadCode.deadExports) {
3951
+ issues.push({
3952
+ severity: "warning",
3953
+ message: `Dead export: ${dead.name}`,
3954
+ file: dead.file,
3955
+ line: dead.line
3956
+ });
3957
+ }
3958
+ }
3959
+ }
3960
+ break;
3961
+ }
3962
+ case "phase-gate": {
3963
+ const phaseGates = config.phaseGates;
3964
+ if (!phaseGates?.enabled) {
3965
+ break;
3966
+ }
3967
+ issues.push({
3968
+ severity: "warning",
3969
+ message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
3970
+ });
3971
+ break;
3972
+ }
3973
+ }
3974
+ } catch (error) {
3975
+ issues.push({
3976
+ severity: "error",
3977
+ message: `Check '${name}' threw: ${error instanceof Error ? error.message : String(error)}`
3978
+ });
3979
+ }
3980
+ const hasErrors = issues.some((i) => i.severity === "error");
3981
+ const hasWarnings = issues.some((i) => i.severity === "warning");
3982
+ const status = hasErrors ? "fail" : hasWarnings ? "warn" : "pass";
3983
+ return {
3984
+ name,
3985
+ status,
3986
+ issues,
3987
+ durationMs: Date.now() - start
3988
+ };
3989
+ }
3990
+ function buildSummary(checks) {
3991
+ return {
3992
+ total: checks.length,
3993
+ passed: checks.filter((c) => c.status === "pass").length,
3994
+ failed: checks.filter((c) => c.status === "fail").length,
3995
+ warnings: checks.filter((c) => c.status === "warn").length,
3996
+ skipped: checks.filter((c) => c.status === "skip").length
3997
+ };
3998
+ }
3999
+ function determineExitCode(summary, failOn = "error") {
4000
+ if (summary.failed > 0) return 1;
4001
+ if (failOn === "warning" && summary.warnings > 0) return 1;
4002
+ return 0;
4003
+ }
4004
+ async function runCIChecks(input) {
4005
+ const { projectRoot, config, skip = [], failOn = "error" } = input;
4006
+ try {
4007
+ const checks = [];
4008
+ for (const name of ALL_CHECKS) {
4009
+ if (skip.includes(name)) {
4010
+ checks.push({ name, status: "skip", issues: [], durationMs: 0 });
4011
+ } else {
4012
+ const result = await runSingleCheck(name, projectRoot, config);
4013
+ checks.push(result);
4014
+ }
4015
+ }
4016
+ const summary = buildSummary(checks);
4017
+ const exitCode = determineExitCode(summary, failOn);
4018
+ const report = {
4019
+ version: 1,
4020
+ project: config.name ?? "unknown",
4021
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4022
+ checks,
4023
+ summary,
4024
+ exitCode
4025
+ };
4026
+ return Ok(report);
4027
+ } catch (error) {
4028
+ return Err(error instanceof Error ? error : new Error(String(error)));
4029
+ }
4030
+ }
4031
+
3870
4032
  // src/index.ts
3871
- var VERSION = "0.5.0";
4033
+ var VERSION = "0.6.0";
3872
4034
  export {
3873
4035
  AgentActionEmitter,
3874
4036
  ChecklistBuilder,
@@ -3876,7 +4038,6 @@ export {
3876
4038
  DEFAULT_STATE,
3877
4039
  EntropyAnalyzer,
3878
4040
  EntropyConfigSchema,
3879
- Err,
3880
4041
  FailureEntrySchema,
3881
4042
  FileSink,
3882
4043
  GateConfigSchema,
@@ -3886,7 +4047,6 @@ export {
3886
4047
  NoOpExecutor,
3887
4048
  NoOpSink,
3888
4049
  NoOpTelemetryAdapter,
3889
- Ok,
3890
4050
  PatternConfigSchema,
3891
4051
  REQUIRED_SECTIONS,
3892
4052
  TypeScriptParser,
@@ -3897,9 +4057,7 @@ export {
3897
4057
  applyFixes,
3898
4058
  archiveFailures,
3899
4059
  buildDependencyGraph,
3900
- buildReachabilityMap,
3901
4060
  buildSnapshot,
3902
- checkConfigPattern,
3903
4061
  checkDocCoverage,
3904
4062
  configureFeedback,
3905
4063
  contextBudget,
@@ -3918,28 +4076,23 @@ export {
3918
4076
  executeWorkflow,
3919
4077
  extractMarkdownLinks,
3920
4078
  extractSections,
3921
- findPossibleMatches,
3922
4079
  generateAgentsMap,
3923
4080
  generateSuggestions,
3924
4081
  getActionEmitter,
3925
4082
  getFeedbackConfig,
3926
4083
  getPhaseCategories,
3927
- isErr,
3928
- isOk,
3929
- levenshteinDistance,
3930
4084
  loadFailures,
3931
4085
  loadHandoff,
3932
4086
  loadRelevantLearnings,
3933
4087
  loadState,
3934
4088
  logAgentAction,
3935
4089
  parseDiff,
3936
- parseDocumentationFile,
3937
4090
  previewFix,
3938
4091
  requestMultiplePeerReviews,
3939
4092
  requestPeerReview,
3940
4093
  resetFeedbackConfig,
3941
- resolveEntryPoints,
3942
4094
  resolveFileToLayer,
4095
+ runCIChecks,
3943
4096
  runMechanicalGate,
3944
4097
  runMultiTurnPipeline,
3945
4098
  runPipeline,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/core",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Core library for Harness Engineering toolkit",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -41,7 +41,7 @@
41
41
  "glob": "^10.3.0",
42
42
  "minimatch": "^10.2.4",
43
43
  "zod": "^3.22.0",
44
- "@harness-engineering/types": "0.0.0"
44
+ "@harness-engineering/types": "0.1.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@vitest/coverage-v8": "^4.0.18",
@@ -56,7 +56,8 @@
56
56
  "lint": "eslint src",
57
57
  "typecheck": "tsc --noEmit",
58
58
  "clean": "rm -rf dist",
59
- "test": "vitest",
59
+ "test": "vitest run",
60
+ "test:watch": "vitest",
60
61
  "test:coverage": "vitest run --coverage",
61
62
  "test:ui": "vitest --ui"
62
63
  }