@harness-engineering/core 0.27.0 → 0.28.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
@@ -221,15 +221,15 @@ function validateConfig(data, schema) {
221
221
  let message = "Configuration validation failed";
222
222
  const suggestions = [];
223
223
  if (firstError) {
224
- const path48 = firstError.path.join(".");
225
- const pathDisplay = path48 ? ` at "${path48}"` : "";
224
+ const path50 = firstError.path.join(".");
225
+ const pathDisplay = path50 ? ` at "${path50}"` : "";
226
226
  if (firstError.code === "invalid_type") {
227
227
  const received = firstError.received;
228
228
  const expected = firstError.expected;
229
229
  if (received === "undefined") {
230
230
  code = "MISSING_FIELD";
231
231
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
232
- suggestions.push(`Field "${path48}" is required and must be of type "${expected}"`);
232
+ suggestions.push(`Field "${path50}" is required and must be of type "${expected}"`);
233
233
  } else {
234
234
  code = "INVALID_TYPE";
235
235
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -683,16 +683,16 @@ function makeFinding(input) {
683
683
  if (input.suggestion) finding.suggestion = input.suggestion;
684
684
  return finding;
685
685
  }
686
- function readTextSafe(path48) {
686
+ function readTextSafe(path50) {
687
687
  try {
688
- return readFileSync(path48, "utf-8");
688
+ return readFileSync(path50, "utf-8");
689
689
  } catch {
690
690
  return null;
691
691
  }
692
692
  }
693
- function safeFileSize(path48) {
693
+ function safeFileSize(path50) {
694
694
  try {
695
- return statSync(path48).size;
695
+ return statSync(path50).size;
696
696
  } catch {
697
697
  return null;
698
698
  }
@@ -1286,27 +1286,27 @@ function extractSections(content) {
1286
1286
  }
1287
1287
  return sections.map((section) => buildAgentMapSection(section, lines));
1288
1288
  }
1289
- function isExternalLink(path48) {
1290
- return path48.startsWith("http://") || path48.startsWith("https://") || path48.startsWith("#") || path48.startsWith("mailto:");
1289
+ function isExternalLink(path50) {
1290
+ return path50.startsWith("http://") || path50.startsWith("https://") || path50.startsWith("#") || path50.startsWith("mailto:");
1291
1291
  }
1292
1292
  function resolveLinkPath(linkPath, baseDir) {
1293
1293
  return linkPath.startsWith(".") ? join8(baseDir, linkPath) : linkPath;
1294
1294
  }
1295
- async function validateAgentsMap(path48 = "./AGENTS.md") {
1296
- const contentResult = await readFileContent(path48);
1295
+ async function validateAgentsMap(path50 = "./AGENTS.md") {
1296
+ const contentResult = await readFileContent(path50);
1297
1297
  if (!contentResult.ok) {
1298
1298
  return Err(
1299
1299
  createError(
1300
1300
  "PARSE_ERROR",
1301
1301
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
1302
- { path: path48 },
1302
+ { path: path50 },
1303
1303
  ["Ensure the file exists", "Check file permissions"]
1304
1304
  )
1305
1305
  );
1306
1306
  }
1307
1307
  const content = contentResult.value;
1308
1308
  const sections = extractSections(content);
1309
- const baseDir = dirname2(path48);
1309
+ const baseDir = dirname2(path50);
1310
1310
  const sectionTitles = sections.map((s) => s.title);
1311
1311
  const missingSections = REQUIRED_SECTIONS.filter(
1312
1312
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -1913,8 +1913,8 @@ async function checkDocCoverage(domain, options = {}) {
1913
1913
 
1914
1914
  // src/context/knowledge-map.ts
1915
1915
  import { join as join14, basename as basename3 } from "path";
1916
- function suggestFix(path48, existingFiles) {
1917
- const targetName = basename3(path48).toLowerCase();
1916
+ function suggestFix(path50, existingFiles) {
1917
+ const targetName = basename3(path50).toLowerCase();
1918
1918
  const similar = existingFiles.find((file) => {
1919
1919
  const fileName = basename3(file).toLowerCase();
1920
1920
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -1922,7 +1922,7 @@ function suggestFix(path48, existingFiles) {
1922
1922
  if (similar) {
1923
1923
  return `Did you mean "${similar}"?`;
1924
1924
  }
1925
- return `Create the file "${path48}" or remove the link`;
1925
+ return `Create the file "${path50}" or remove the link`;
1926
1926
  }
1927
1927
  async function validateKnowledgeMap(rootDir = process.cwd()) {
1928
1928
  const agentsPath = join14(rootDir, "AGENTS.md");
@@ -2399,8 +2399,8 @@ function createBoundaryValidator(schema, name) {
2399
2399
  return Ok(result.data);
2400
2400
  }
2401
2401
  const suggestions = result.error.issues.map((issue) => {
2402
- const path48 = issue.path.join(".");
2403
- return path48 ? `${path48}: ${issue.message}` : issue.message;
2402
+ const path50 = issue.path.join(".");
2403
+ return path50 ? `${path50}: ${issue.message}` : issue.message;
2404
2404
  });
2405
2405
  return Err(
2406
2406
  createError(
@@ -4279,12 +4279,12 @@ function parseDiffHeader(part) {
4279
4279
  return headerMatch[2];
4280
4280
  }
4281
4281
  function parseDiffPart(part) {
4282
- const path48 = parseDiffHeader(part);
4283
- if (!path48) return null;
4282
+ const path50 = parseDiffHeader(part);
4283
+ if (!path50) return null;
4284
4284
  const additionRegex = /^\+(?!\+\+)/gm;
4285
4285
  const deletionRegex = /^-(?!--)/gm;
4286
4286
  return {
4287
- path: path48,
4287
+ path: path50,
4288
4288
  status: detectFileStatus(part),
4289
4289
  additions: (part.match(additionRegex) || []).length,
4290
4290
  deletions: (part.match(deletionRegex) || []).length
@@ -9144,6 +9144,98 @@ var SecurityScanner = class {
9144
9144
  }
9145
9145
  };
9146
9146
 
9147
+ // src/security/osv-client.ts
9148
+ import * as fs30 from "fs";
9149
+ import * as path26 from "path";
9150
+ var OSV_ENDPOINT = "https://api.osv.dev/v1/query";
9151
+ var DEFAULT_CACHE_DIR = path26.join(".harness", "cache", "osv");
9152
+ var DEFAULT_TTL_HOURS = 24;
9153
+ function createOsvClient(options = {}) {
9154
+ const cacheDir = options.cacheDir ?? DEFAULT_CACHE_DIR;
9155
+ const ttlMs = (options.cacheTtlHours ?? DEFAULT_TTL_HOURS) * 60 * 60 * 1e3;
9156
+ const fetchFn = options.fetchFn ?? globalThis.fetch;
9157
+ const logger = options.logger ?? { warn: (m, ctx) => console.warn(m, ctx) };
9158
+ const strict = options.strict ?? false;
9159
+ return {
9160
+ async check(pkg) {
9161
+ const cacheKey = `${pkg.ecosystem}-${sanitizePkgName(pkg.name)}@${pkg.version ?? "latest"}.json`;
9162
+ const cachePath = path26.join(cacheDir, cacheKey);
9163
+ const cached = await readCache(cachePath, ttlMs);
9164
+ if (cached) {
9165
+ return { ...cached, source: "cache" };
9166
+ }
9167
+ try {
9168
+ const advisories = await queryOsv(fetchFn, pkg);
9169
+ const result = classify(advisories);
9170
+ await writeCache(cachePath, result);
9171
+ return { ...result, source: "network" };
9172
+ } catch (err) {
9173
+ const message = `OSV query failed for ${pkg.name}@${pkg.version ?? "latest"}: ${String(err)}`;
9174
+ if (strict) {
9175
+ throw new Error(message, { cause: err });
9176
+ }
9177
+ logger.warn(message, { pkg });
9178
+ return { malicious: [], other: [], source: "fail-open" };
9179
+ }
9180
+ },
9181
+ async clearCache() {
9182
+ try {
9183
+ await fs30.promises.rm(cacheDir, { recursive: true, force: true });
9184
+ } catch {
9185
+ }
9186
+ }
9187
+ };
9188
+ }
9189
+ async function queryOsv(fetchFn, pkg) {
9190
+ const body = {
9191
+ package: { ecosystem: pkg.ecosystem, name: pkg.name }
9192
+ };
9193
+ if (pkg.version) body.version = pkg.version;
9194
+ const response = await fetchFn(OSV_ENDPOINT, {
9195
+ method: "POST",
9196
+ headers: { "content-type": "application/json" },
9197
+ body: JSON.stringify(body)
9198
+ });
9199
+ if (!response.ok) {
9200
+ throw new Error(`OSV returned HTTP ${response.status}`);
9201
+ }
9202
+ const json = await response.json();
9203
+ return json.vulns ?? [];
9204
+ }
9205
+ function classify(advisories) {
9206
+ const malicious = [];
9207
+ const other = [];
9208
+ for (const a of advisories) {
9209
+ if (typeof a?.id === "string" && a.id.startsWith("MAL-")) {
9210
+ malicious.push(a);
9211
+ } else {
9212
+ other.push(a);
9213
+ }
9214
+ }
9215
+ return { malicious, other };
9216
+ }
9217
+ async function readCache(cachePath, ttlMs) {
9218
+ try {
9219
+ const stat2 = await fs30.promises.stat(cachePath);
9220
+ if (Date.now() - stat2.mtimeMs > ttlMs) return null;
9221
+ const buf = await fs30.promises.readFile(cachePath, "utf-8");
9222
+ const parsed = JSON.parse(buf);
9223
+ return { malicious: parsed.malicious ?? [], other: parsed.other ?? [] };
9224
+ } catch {
9225
+ return null;
9226
+ }
9227
+ }
9228
+ async function writeCache(cachePath, payload) {
9229
+ try {
9230
+ await fs30.promises.mkdir(path26.dirname(cachePath), { recursive: true });
9231
+ await fs30.promises.writeFile(cachePath, JSON.stringify(payload, null, 2), "utf-8");
9232
+ } catch {
9233
+ }
9234
+ }
9235
+ function sanitizePkgName(name) {
9236
+ return name.replace(/[/\\]/g, "__");
9237
+ }
9238
+
9147
9239
  // src/security/injection-patterns.ts
9148
9240
  var hiddenUnicodePatterns = [
9149
9241
  {
@@ -9382,12 +9474,12 @@ var DESTRUCTIVE_BASH = [
9382
9474
 
9383
9475
  // src/security/taint.ts
9384
9476
  import { readFileSync as readFileSync22, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2, mkdirSync as mkdirSync13, readdirSync as readdirSync3 } from "fs";
9385
- import { join as join37, dirname as dirname9 } from "path";
9477
+ import { join as join38, dirname as dirname10 } from "path";
9386
9478
  var TAINT_DURATION_MS = 30 * 60 * 1e3;
9387
9479
  var DEFAULT_SESSION_ID = "default";
9388
9480
  function getTaintFilePath(projectRoot, sessionId) {
9389
9481
  const id = sessionId || DEFAULT_SESSION_ID;
9390
- return join37(projectRoot, ".harness", `session-taint-${id}.json`);
9482
+ return join38(projectRoot, ".harness", `session-taint-${id}.json`);
9391
9483
  }
9392
9484
  function readTaint(projectRoot, sessionId) {
9393
9485
  const filePath = getTaintFilePath(projectRoot, sessionId);
@@ -9437,7 +9529,7 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
9437
9529
  const id = sessionId || DEFAULT_SESSION_ID;
9438
9530
  const filePath = getTaintFilePath(projectRoot, id);
9439
9531
  const now = (/* @__PURE__ */ new Date()).toISOString();
9440
- const dir = dirname9(filePath);
9532
+ const dir = dirname10(filePath);
9441
9533
  mkdirSync13(dir, { recursive: true });
9442
9534
  const existing = readTaint(projectRoot, id);
9443
9535
  const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
@@ -9469,14 +9561,14 @@ function clearTaint(projectRoot, sessionId) {
9469
9561
  return 0;
9470
9562
  }
9471
9563
  }
9472
- const harnessDir = join37(projectRoot, ".harness");
9564
+ const harnessDir = join38(projectRoot, ".harness");
9473
9565
  let count = 0;
9474
9566
  try {
9475
9567
  const files = readdirSync3(harnessDir);
9476
9568
  for (const file of files) {
9477
9569
  if (file.startsWith("session-taint-") && file.endsWith(".json")) {
9478
9570
  try {
9479
- unlinkSync2(join37(harnessDir, file));
9571
+ unlinkSync2(join38(harnessDir, file));
9480
9572
  count++;
9481
9573
  } catch {
9482
9574
  }
@@ -9487,7 +9579,7 @@ function clearTaint(projectRoot, sessionId) {
9487
9579
  return count;
9488
9580
  }
9489
9581
  function listTaintedSessions(projectRoot) {
9490
- const harnessDir = join37(projectRoot, ".harness");
9582
+ const harnessDir = join38(projectRoot, ".harness");
9491
9583
  const sessions = [];
9492
9584
  try {
9493
9585
  const files = readdirSync3(harnessDir);
@@ -9659,13 +9751,13 @@ var EMPTY_SUPPLY_CHAIN = {
9659
9751
  import { readFileSync as readFileSync23, writeFileSync as writeFileSync15, renameSync as renameSync7, mkdirSync as mkdirSync14, existsSync as existsSync34 } from "fs";
9660
9752
  import { execSync as execSync3 } from "child_process";
9661
9753
  import { randomBytes } from "crypto";
9662
- import { isAbsolute as isAbsolute4, join as join38, relative as relative3, dirname as dirname10 } from "path";
9754
+ import { isAbsolute as isAbsolute4, join as join39, relative as relative3, dirname as dirname11 } from "path";
9663
9755
  var SecurityTimelineManager = class _SecurityTimelineManager {
9664
9756
  rootDir;
9665
9757
  timelinePath;
9666
9758
  constructor(rootDir) {
9667
9759
  this.rootDir = rootDir;
9668
- this.timelinePath = join38(rootDir, ".harness", "security", "timeline.json");
9760
+ this.timelinePath = join39(rootDir, ".harness", "security", "timeline.json");
9669
9761
  }
9670
9762
  /**
9671
9763
  * Load timeline from disk.
@@ -9709,7 +9801,7 @@ var SecurityTimelineManager = class _SecurityTimelineManager {
9709
9801
  * Save timeline to disk using atomic write (temp file + rename).
9710
9802
  */
9711
9803
  save(timeline) {
9712
- const dir = dirname10(this.timelinePath);
9804
+ const dir = dirname11(this.timelinePath);
9713
9805
  if (!existsSync34(dir)) {
9714
9806
  mkdirSync14(dir, { recursive: true });
9715
9807
  }
@@ -10059,7 +10151,7 @@ var SecurityTimelineManager = class _SecurityTimelineManager {
10059
10151
  };
10060
10152
 
10061
10153
  // src/ci/check-orchestrator.ts
10062
- import * as path26 from "path";
10154
+ import * as path27 from "path";
10063
10155
  import { skipDirGlobs as skipDirGlobs3 } from "@harness-engineering/graph";
10064
10156
  import { GraphStore, queryTraceability } from "@harness-engineering/graph";
10065
10157
  var ALL_CHECKS = [
@@ -10075,7 +10167,7 @@ var ALL_CHECKS = [
10075
10167
  ];
10076
10168
  async function runValidateCheck(projectRoot, config) {
10077
10169
  const issues = [];
10078
- const agentsPath = path26.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10170
+ const agentsPath = path27.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10079
10171
  const result = await validateAgentsMap(agentsPath);
10080
10172
  if (!result.ok) {
10081
10173
  issues.push({ severity: "error", message: result.error.message });
@@ -10132,7 +10224,7 @@ async function runDepsCheck(projectRoot, config) {
10132
10224
  }
10133
10225
  async function runDocsCheck(projectRoot, config) {
10134
10226
  const issues = [];
10135
- const docsDir = path26.join(projectRoot, config.docsDir ?? "docs");
10227
+ const docsDir = path27.join(projectRoot, config.docsDir ?? "docs");
10136
10228
  const entropyConfig = config.entropy || {};
10137
10229
  const result = await checkDocCoverage("project", {
10138
10230
  docsDir,
@@ -10311,7 +10403,7 @@ async function runTraceabilityCheck(projectRoot, config) {
10311
10403
  const issues = [];
10312
10404
  const traceConfig = config.traceability || {};
10313
10405
  if (traceConfig.enabled === false) return issues;
10314
- const graphDir = path26.join(projectRoot, ".harness", "graph");
10406
+ const graphDir = path27.join(projectRoot, ".harness", "graph");
10315
10407
  const store = new GraphStore();
10316
10408
  const loaded = await store.load(graphDir);
10317
10409
  if (!loaded) {
@@ -10534,7 +10626,7 @@ var CINotifier = class {
10534
10626
  };
10535
10627
 
10536
10628
  // src/review/mechanical-checks.ts
10537
- import * as path27 from "path";
10629
+ import * as path28 from "path";
10538
10630
  async function runMechanicalChecks(options) {
10539
10631
  const { projectRoot, config, skip = [], changedFiles } = options;
10540
10632
  const findings = [];
@@ -10546,7 +10638,7 @@ async function runMechanicalChecks(options) {
10546
10638
  };
10547
10639
  if (!skip.includes("validate")) {
10548
10640
  try {
10549
- const agentsPath = path27.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10641
+ const agentsPath = path28.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10550
10642
  const result = await validateAgentsMap(agentsPath);
10551
10643
  if (!result.ok) {
10552
10644
  statuses.validate = "fail";
@@ -10583,7 +10675,7 @@ async function runMechanicalChecks(options) {
10583
10675
  statuses.validate = "fail";
10584
10676
  findings.push({
10585
10677
  tool: "validate",
10586
- file: path27.join(projectRoot, "AGENTS.md"),
10678
+ file: path28.join(projectRoot, "AGENTS.md"),
10587
10679
  message: err instanceof Error ? err.message : String(err),
10588
10680
  severity: "error"
10589
10681
  });
@@ -10647,7 +10739,7 @@ async function runMechanicalChecks(options) {
10647
10739
  (async () => {
10648
10740
  const localFindings = [];
10649
10741
  try {
10650
- const docsDir = path27.join(projectRoot, config.docsDir ?? "docs");
10742
+ const docsDir = path28.join(projectRoot, config.docsDir ?? "docs");
10651
10743
  const result = await checkDocCoverage("project", { docsDir });
10652
10744
  if (!result.ok) {
10653
10745
  statuses["check-docs"] = "warn";
@@ -10674,7 +10766,7 @@ async function runMechanicalChecks(options) {
10674
10766
  statuses["check-docs"] = "warn";
10675
10767
  localFindings.push({
10676
10768
  tool: "check-docs",
10677
- file: path27.join(projectRoot, "docs"),
10769
+ file: path28.join(projectRoot, "docs"),
10678
10770
  message: err instanceof Error ? err.message : String(err),
10679
10771
  severity: "warning"
10680
10772
  });
@@ -10822,7 +10914,7 @@ function detectChangeType(commitMessage, diff2) {
10822
10914
  }
10823
10915
 
10824
10916
  // src/review/context-scoper.ts
10825
- import * as path28 from "path";
10917
+ import * as path29 from "path";
10826
10918
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture", "learnings"];
10827
10919
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
10828
10920
  function computeContextBudget(diffLines) {
@@ -10830,18 +10922,18 @@ function computeContextBudget(diffLines) {
10830
10922
  return diffLines;
10831
10923
  }
10832
10924
  function isWithinProject(absPath, projectRoot) {
10833
- const resolvedRoot = path28.resolve(projectRoot) + path28.sep;
10834
- const resolvedPath = path28.resolve(absPath);
10835
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path28.resolve(projectRoot);
10925
+ const resolvedRoot = path29.resolve(projectRoot) + path29.sep;
10926
+ const resolvedPath = path29.resolve(absPath);
10927
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path29.resolve(projectRoot);
10836
10928
  }
10837
10929
  async function readContextFile(projectRoot, filePath, reason) {
10838
- const absPath = path28.isAbsolute(filePath) ? filePath : path28.join(projectRoot, filePath);
10930
+ const absPath = path29.isAbsolute(filePath) ? filePath : path29.join(projectRoot, filePath);
10839
10931
  if (!isWithinProject(absPath, projectRoot)) return null;
10840
10932
  const result = await readFileContent(absPath);
10841
10933
  if (!result.ok) return null;
10842
10934
  const content = result.value;
10843
10935
  const lines = content.split("\n").length;
10844
- const relPath2 = path28.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
10936
+ const relPath2 = path29.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
10845
10937
  return { path: relPath2, content, reason, lines };
10846
10938
  }
10847
10939
  function extractImportSources(content) {
@@ -10862,24 +10954,24 @@ var JS_EXT_FALLBACKS = {
10862
10954
  };
10863
10955
  async function resolveImportPath(projectRoot, fromFile, importSource) {
10864
10956
  if (!importSource.startsWith(".")) return null;
10865
- const fromDir = path28.dirname(path28.join(projectRoot, fromFile));
10866
- const basePath = path28.resolve(fromDir, importSource);
10957
+ const fromDir = path29.dirname(path29.join(projectRoot, fromFile));
10958
+ const basePath = path29.resolve(fromDir, importSource);
10867
10959
  if (!isWithinProject(basePath, projectRoot)) return null;
10868
10960
  const relBase = relativePosix(projectRoot, basePath);
10869
- const sourceExt = path28.extname(relBase);
10961
+ const sourceExt = path29.extname(relBase);
10870
10962
  const candidates = [];
10871
10963
  const fallbacks = JS_EXT_FALLBACKS[sourceExt];
10872
10964
  if (fallbacks) {
10873
10965
  const stripped = relBase.slice(0, -sourceExt.length);
10874
10966
  for (const ext of fallbacks) candidates.push(stripped + ext);
10875
- candidates.push(path28.join(stripped, "index.ts"));
10876
- candidates.push(path28.join(stripped, "index.tsx"));
10877
- candidates.push(path28.join(stripped, "index.jsx"));
10967
+ candidates.push(path29.join(stripped, "index.ts"));
10968
+ candidates.push(path29.join(stripped, "index.tsx"));
10969
+ candidates.push(path29.join(stripped, "index.jsx"));
10878
10970
  }
10879
10971
  candidates.push(relBase + ".ts", relBase + ".tsx", relBase + ".mts");
10880
- candidates.push(path28.join(relBase, "index.ts"));
10972
+ candidates.push(path29.join(relBase, "index.ts"));
10881
10973
  for (const candidate of candidates) {
10882
- const absCandidate = path28.join(projectRoot, candidate);
10974
+ const absCandidate = path29.join(projectRoot, candidate);
10883
10975
  if (await fileExists(absCandidate)) {
10884
10976
  return candidate;
10885
10977
  }
@@ -10887,7 +10979,7 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
10887
10979
  return null;
10888
10980
  }
10889
10981
  async function findTestFiles(projectRoot, sourceFile) {
10890
- const baseName = path28.basename(sourceFile, path28.extname(sourceFile));
10982
+ const baseName = path29.basename(sourceFile, path29.extname(sourceFile));
10891
10983
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
10892
10984
  const results = await findFiles(pattern, projectRoot);
10893
10985
  return results.map((f) => relativePosix(projectRoot, f));
@@ -11783,7 +11875,7 @@ async function fanOutReview(options) {
11783
11875
  }
11784
11876
 
11785
11877
  // src/review/validate-findings.ts
11786
- import * as path29 from "path";
11878
+ import * as path30 from "path";
11787
11879
  var DOWNGRADE_MAP = {
11788
11880
  critical: "important",
11789
11881
  important: "suggestion",
@@ -11804,7 +11896,7 @@ function normalizePath(filePath, projectRoot) {
11804
11896
  let normalized = filePath;
11805
11897
  normalized = normalized.replace(/\\/g, "/");
11806
11898
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
11807
- if (path29.isAbsolute(normalized)) {
11899
+ if (path30.isAbsolute(normalized)) {
11808
11900
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
11809
11901
  if (normalized.startsWith(root)) {
11810
11902
  normalized = normalized.slice(root.length);
@@ -11816,12 +11908,12 @@ function normalizePath(filePath, projectRoot) {
11816
11908
  return normalized;
11817
11909
  }
11818
11910
  function resolveImportPath2(currentFile, importPath) {
11819
- const dir = path29.dirname(currentFile);
11820
- let resolved = path29.join(dir, importPath).replace(/\\/g, "/");
11911
+ const dir = path30.dirname(currentFile);
11912
+ let resolved = path30.join(dir, importPath).replace(/\\/g, "/");
11821
11913
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
11822
11914
  resolved += ".ts";
11823
11915
  }
11824
- return path29.normalize(resolved).replace(/\\/g, "/");
11916
+ return path30.normalize(resolved).replace(/\\/g, "/");
11825
11917
  }
11826
11918
  function enqueueImports(content, current, visited, queue, maxDepth) {
11827
11919
  const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
@@ -11853,7 +11945,7 @@ function isMechanicallyExcluded(finding, exclusionSet, projectRoot) {
11853
11945
  const normalizedFile = normalizePath(finding.file, projectRoot);
11854
11946
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange)) return true;
11855
11947
  if (exclusionSet.isExcluded(finding.file, finding.lineRange)) return true;
11856
- const absoluteFile = path29.isAbsolute(finding.file) ? finding.file : path29.join(projectRoot, finding.file).replace(/\\/g, "/");
11948
+ const absoluteFile = path30.isAbsolute(finding.file) ? finding.file : path30.join(projectRoot, finding.file).replace(/\\/g, "/");
11857
11949
  return exclusionSet.isExcluded(absoluteFile, finding.lineRange);
11858
11950
  }
11859
11951
  async function validateWithGraph(crossFileRefs, graph) {
@@ -12914,8 +13006,8 @@ function serializeAssignmentHistory(records) {
12914
13006
  }
12915
13007
 
12916
13008
  // src/roadmap/sync.ts
12917
- import * as fs30 from "fs";
12918
- import * as path30 from "path";
13009
+ import * as fs31 from "fs";
13010
+ import * as path31 from "path";
12919
13011
  import { Ok as Ok3 } from "@harness-engineering/types";
12920
13012
 
12921
13013
  // src/roadmap/status-rank.ts
@@ -12936,7 +13028,7 @@ function isRegression(from, to) {
12936
13028
  // src/roadmap/sync.ts
12937
13029
  function collectAutopilotStatuses(autopilotPath, featurePlans, allTaskStatuses) {
12938
13030
  try {
12939
- const raw = fs30.readFileSync(autopilotPath, "utf-8");
13031
+ const raw = fs31.readFileSync(autopilotPath, "utf-8");
12940
13032
  const autopilot = JSON.parse(raw);
12941
13033
  if (!autopilot.phases) return;
12942
13034
  const linkedPhases = autopilot.phases.filter(
@@ -12968,10 +13060,10 @@ function inferStatus(feature, projectPath, allFeatures) {
12968
13060
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
12969
13061
  const useRootState = featuresWithPlans.length <= 1;
12970
13062
  if (useRootState) {
12971
- const rootStatePath = path30.join(projectPath, ".harness", "state.json");
12972
- if (fs30.existsSync(rootStatePath)) {
13063
+ const rootStatePath = path31.join(projectPath, ".harness", "state.json");
13064
+ if (fs31.existsSync(rootStatePath)) {
12973
13065
  try {
12974
- const raw = fs30.readFileSync(rootStatePath, "utf-8");
13066
+ const raw = fs31.readFileSync(rootStatePath, "utf-8");
12975
13067
  const state = JSON.parse(raw);
12976
13068
  if (state.progress) {
12977
13069
  for (const status of Object.values(state.progress)) {
@@ -12982,14 +13074,14 @@ function inferStatus(feature, projectPath, allFeatures) {
12982
13074
  }
12983
13075
  }
12984
13076
  }
12985
- const sessionsDir = path30.join(projectPath, ".harness", "sessions");
12986
- if (fs30.existsSync(sessionsDir)) {
13077
+ const sessionsDir = path31.join(projectPath, ".harness", "sessions");
13078
+ if (fs31.existsSync(sessionsDir)) {
12987
13079
  try {
12988
- const sessionDirs = fs30.readdirSync(sessionsDir, { withFileTypes: true });
13080
+ const sessionDirs = fs31.readdirSync(sessionsDir, { withFileTypes: true });
12989
13081
  for (const entry of sessionDirs) {
12990
13082
  if (!entry.isDirectory()) continue;
12991
- const autopilotPath = path30.join(sessionsDir, entry.name, "autopilot-state.json");
12992
- if (!fs30.existsSync(autopilotPath)) continue;
13083
+ const autopilotPath = path31.join(sessionsDir, entry.name, "autopilot-state.json");
13084
+ if (!fs31.existsSync(autopilotPath)) continue;
12993
13085
  collectAutopilotStatuses(autopilotPath, feature.plans, allTaskStatuses);
12994
13086
  }
12995
13087
  } catch {
@@ -13476,8 +13568,8 @@ var GitHubIssuesSyncAdapter = class {
13476
13568
  };
13477
13569
 
13478
13570
  // src/roadmap/tracker-config.ts
13479
- import * as fs31 from "fs";
13480
- import * as path31 from "path";
13571
+ import * as fs32 from "fs";
13572
+ import * as path32 from "path";
13481
13573
  function isValidTrackerShape(tracker) {
13482
13574
  if (!tracker || typeof tracker !== "object") return false;
13483
13575
  const t = tracker;
@@ -13490,9 +13582,9 @@ function isValidTrackerShape(tracker) {
13490
13582
  }
13491
13583
  function loadTrackerSyncConfig(projectRoot) {
13492
13584
  try {
13493
- const configPath = path31.join(projectRoot, "harness.config.json");
13494
- if (!fs31.existsSync(configPath)) return null;
13495
- const raw = fs31.readFileSync(configPath, "utf-8");
13585
+ const configPath = path32.join(projectRoot, "harness.config.json");
13586
+ if (!fs32.existsSync(configPath)) return null;
13587
+ const raw = fs32.readFileSync(configPath, "utf-8");
13496
13588
  const config = JSON.parse(raw);
13497
13589
  const tracker = config.roadmap?.tracker;
13498
13590
  if (!isValidTrackerShape(tracker)) return null;
@@ -13503,7 +13595,7 @@ function loadTrackerSyncConfig(projectRoot) {
13503
13595
  }
13504
13596
 
13505
13597
  // src/roadmap/sync-engine.ts
13506
- import * as fs32 from "fs";
13598
+ import * as fs33 from "fs";
13507
13599
  function emptySyncResult() {
13508
13600
  return { created: [], updated: [], assignmentChanges: [], errors: [] };
13509
13601
  }
@@ -13616,7 +13708,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
13616
13708
  });
13617
13709
  await previousSync;
13618
13710
  try {
13619
- const raw = fs32.readFileSync(roadmapPath, "utf-8");
13711
+ const raw = fs33.readFileSync(roadmapPath, "utf-8");
13620
13712
  const parseResult = parseRoadmap(raw);
13621
13713
  if (!parseResult.ok) {
13622
13714
  return {
@@ -13629,7 +13721,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
13629
13721
  const tickets = fetchResult.ok ? fetchResult.value : void 0;
13630
13722
  const pushResult = await syncToExternal(roadmap, adapter2, config, tickets);
13631
13723
  const pullResult = await syncFromExternal(roadmap, adapter2, config, options);
13632
- fs32.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13724
+ fs33.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13633
13725
  return {
13634
13726
  created: pushResult.created,
13635
13727
  updated: pushResult.updated,
@@ -14589,13 +14681,13 @@ function makeTrackerConflictBody(err, opts = {}) {
14589
14681
  }
14590
14682
 
14591
14683
  // src/roadmap/load-mode.ts
14592
- import * as fs33 from "fs";
14593
- import * as path32 from "path";
14684
+ import * as fs34 from "fs";
14685
+ import * as path33 from "path";
14594
14686
  function loadProjectRoadmapMode(projectRoot) {
14595
14687
  try {
14596
- const configPath = path32.join(projectRoot, "harness.config.json");
14597
- if (!fs33.existsSync(configPath)) return getRoadmapMode(null);
14598
- const raw = fs33.readFileSync(configPath, "utf-8");
14688
+ const configPath = path33.join(projectRoot, "harness.config.json");
14689
+ if (!fs34.existsSync(configPath)) return getRoadmapMode(null);
14690
+ const raw = fs34.readFileSync(configPath, "utf-8");
14599
14691
  const parsed = JSON.parse(raw);
14600
14692
  return getRoadmapMode(parsed);
14601
14693
  } catch {
@@ -14604,16 +14696,16 @@ function loadProjectRoadmapMode(projectRoot) {
14604
14696
  }
14605
14697
 
14606
14698
  // src/roadmap/load-tracker-client-config.ts
14607
- import * as fs34 from "fs";
14608
- import * as path33 from "path";
14699
+ import * as fs35 from "fs";
14700
+ import * as path34 from "path";
14609
14701
  import { Ok as Ok7, Err as Err6 } from "@harness-engineering/types";
14610
14702
  function loadTrackerClientConfigFromProject(projectRoot) {
14611
14703
  try {
14612
- const configPath = path33.join(projectRoot, "harness.config.json");
14613
- if (!fs34.existsSync(configPath)) {
14704
+ const configPath = path34.join(projectRoot, "harness.config.json");
14705
+ if (!fs35.existsSync(configPath)) {
14614
14706
  return Err6(new Error("harness.config.json not found"));
14615
14707
  }
14616
- const cfg = JSON.parse(fs34.readFileSync(configPath, "utf-8"));
14708
+ const cfg = JSON.parse(fs35.readFileSync(configPath, "utf-8"));
14617
14709
  const tracker = cfg.roadmap?.tracker;
14618
14710
  if (!tracker) {
14619
14711
  return Err6(
@@ -14839,13 +14931,13 @@ function mapAction(action) {
14839
14931
 
14840
14932
  // src/roadmap/migrate/run.ts
14841
14933
  import { Ok as Ok8 } from "@harness-engineering/types";
14842
- import * as path34 from "path";
14934
+ import * as path35 from "path";
14843
14935
  async function runMigrationPlan(plan, deps, opts) {
14844
14936
  const projectRoot = opts.projectRoot;
14845
- const roadmapPath = path34.join(projectRoot, "docs", "roadmap.md");
14846
- const archivedPath = path34.join(projectRoot, "docs", "roadmap.md.archived");
14847
- const configPath = path34.join(projectRoot, "harness.config.json");
14848
- const configBackupPath = path34.join(projectRoot, "harness.config.json.pre-migration");
14937
+ const roadmapPath = path35.join(projectRoot, "docs", "roadmap.md");
14938
+ const archivedPath = path35.join(projectRoot, "docs", "roadmap.md.archived");
14939
+ const configPath = path35.join(projectRoot, "harness.config.json");
14940
+ const configBackupPath = path35.join(projectRoot, "harness.config.json.pre-migration");
14849
14941
  const createdSoFar = [];
14850
14942
  const report = {
14851
14943
  created: 0,
@@ -14986,18 +15078,18 @@ var EmitInteractionInputSchema = z11.object({
14986
15078
  });
14987
15079
 
14988
15080
  // src/blueprint/scanner.ts
14989
- import * as fs35 from "fs/promises";
14990
- import * as path35 from "path";
15081
+ import * as fs36 from "fs/promises";
15082
+ import * as path36 from "path";
14991
15083
  var ProjectScanner = class {
14992
15084
  constructor(rootDir) {
14993
15085
  this.rootDir = rootDir;
14994
15086
  }
14995
15087
  rootDir;
14996
15088
  async scan() {
14997
- let projectName = path35.basename(this.rootDir);
15089
+ let projectName = path36.basename(this.rootDir);
14998
15090
  try {
14999
- const pkgPath = path35.join(this.rootDir, "package.json");
15000
- const pkgRaw = await fs35.readFile(pkgPath, "utf-8");
15091
+ const pkgPath = path36.join(this.rootDir, "package.json");
15092
+ const pkgRaw = await fs36.readFile(pkgPath, "utf-8");
15001
15093
  const pkg = JSON.parse(pkgRaw);
15002
15094
  if (pkg.name) projectName = pkg.name;
15003
15095
  } catch {
@@ -15038,8 +15130,8 @@ var ProjectScanner = class {
15038
15130
  };
15039
15131
 
15040
15132
  // src/blueprint/generator.ts
15041
- import * as fs36 from "fs/promises";
15042
- import * as path36 from "path";
15133
+ import * as fs37 from "fs/promises";
15134
+ import * as path37 from "path";
15043
15135
  import * as ejs from "ejs";
15044
15136
 
15045
15137
  // src/blueprint/templates.ts
@@ -15123,19 +15215,19 @@ var BlueprintGenerator = class {
15123
15215
  styles: STYLES,
15124
15216
  scripts: SCRIPTS
15125
15217
  });
15126
- await fs36.mkdir(options.outputDir, { recursive: true });
15127
- await fs36.writeFile(path36.join(options.outputDir, "index.html"), html);
15218
+ await fs37.mkdir(options.outputDir, { recursive: true });
15219
+ await fs37.writeFile(path37.join(options.outputDir, "index.html"), html);
15128
15220
  }
15129
15221
  };
15130
15222
 
15131
15223
  // src/update-checker.ts
15132
- import * as fs37 from "fs";
15133
- import * as path37 from "path";
15224
+ import * as fs38 from "fs";
15225
+ import * as path38 from "path";
15134
15226
  import * as os from "os";
15135
15227
  import { spawn as spawn2 } from "child_process";
15136
15228
  function getStatePath() {
15137
15229
  const home = process.env["HOME"] || os.homedir();
15138
- return path37.join(home, ".harness", "update-check.json");
15230
+ return path38.join(home, ".harness", "update-check.json");
15139
15231
  }
15140
15232
  function isUpdateCheckEnabled(configInterval) {
15141
15233
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -15148,13 +15240,13 @@ function shouldRunCheck(state, intervalMs) {
15148
15240
  }
15149
15241
  function invalidateCheckState() {
15150
15242
  try {
15151
- fs37.unlinkSync(getStatePath());
15243
+ fs38.unlinkSync(getStatePath());
15152
15244
  } catch {
15153
15245
  }
15154
15246
  }
15155
15247
  function readCheckState() {
15156
15248
  try {
15157
- const raw = fs37.readFileSync(getStatePath(), "utf-8");
15249
+ const raw = fs38.readFileSync(getStatePath(), "utf-8");
15158
15250
  const parsed = JSON.parse(raw);
15159
15251
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
15160
15252
  const state = parsed;
@@ -15171,7 +15263,7 @@ function readCheckState() {
15171
15263
  }
15172
15264
  function spawnBackgroundCheck(currentVersion) {
15173
15265
  const statePath = getStatePath();
15174
- const stateDir = path37.dirname(statePath);
15266
+ const stateDir = path38.dirname(statePath);
15175
15267
  const script = `
15176
15268
  const { execSync } = require('child_process');
15177
15269
  const fs = require('fs');
@@ -15422,8 +15514,8 @@ function getModelPrice(model, dataset) {
15422
15514
  }
15423
15515
 
15424
15516
  // src/pricing/cache.ts
15425
- import * as fs38 from "fs/promises";
15426
- import * as path38 from "path";
15517
+ import * as fs39 from "fs/promises";
15518
+ import * as path39 from "path";
15427
15519
 
15428
15520
  // src/pricing/fallback.json
15429
15521
  var fallback_default = {
@@ -15476,14 +15568,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
15476
15568
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
15477
15569
  var STALENESS_WARNING_DAYS = 7;
15478
15570
  function getCachePath(projectRoot) {
15479
- return path38.join(projectRoot, ".harness", "cache", "pricing.json");
15571
+ return path39.join(projectRoot, ".harness", "cache", "pricing.json");
15480
15572
  }
15481
15573
  function getStalenessMarkerPath(projectRoot) {
15482
- return path38.join(projectRoot, ".harness", "cache", "staleness-marker.json");
15574
+ return path39.join(projectRoot, ".harness", "cache", "staleness-marker.json");
15483
15575
  }
15484
15576
  async function readDiskCache(projectRoot) {
15485
15577
  try {
15486
- const raw = await fs38.readFile(getCachePath(projectRoot), "utf-8");
15578
+ const raw = await fs39.readFile(getCachePath(projectRoot), "utf-8");
15487
15579
  return JSON.parse(raw);
15488
15580
  } catch {
15489
15581
  return null;
@@ -15491,8 +15583,8 @@ async function readDiskCache(projectRoot) {
15491
15583
  }
15492
15584
  async function writeDiskCache(projectRoot, data) {
15493
15585
  const cachePath = getCachePath(projectRoot);
15494
- await fs38.mkdir(path38.dirname(cachePath), { recursive: true });
15495
- await fs38.writeFile(cachePath, JSON.stringify(data, null, 2));
15586
+ await fs39.mkdir(path39.dirname(cachePath), { recursive: true });
15587
+ await fs39.writeFile(cachePath, JSON.stringify(data, null, 2));
15496
15588
  }
15497
15589
  async function fetchFromNetwork() {
15498
15590
  try {
@@ -15519,7 +15611,7 @@ function loadFallbackDataset() {
15519
15611
  async function checkAndWarnStaleness(projectRoot) {
15520
15612
  const markerPath = getStalenessMarkerPath(projectRoot);
15521
15613
  try {
15522
- const raw = await fs38.readFile(markerPath, "utf-8");
15614
+ const raw = await fs39.readFile(markerPath, "utf-8");
15523
15615
  const marker = JSON.parse(raw);
15524
15616
  const firstUse = new Date(marker.firstFallbackUse).getTime();
15525
15617
  const now = Date.now();
@@ -15531,8 +15623,8 @@ async function checkAndWarnStaleness(projectRoot) {
15531
15623
  }
15532
15624
  } catch {
15533
15625
  try {
15534
- await fs38.mkdir(path38.dirname(markerPath), { recursive: true });
15535
- await fs38.writeFile(
15626
+ await fs39.mkdir(path39.dirname(markerPath), { recursive: true });
15627
+ await fs39.writeFile(
15536
15628
  markerPath,
15537
15629
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
15538
15630
  );
@@ -15542,7 +15634,7 @@ async function checkAndWarnStaleness(projectRoot) {
15542
15634
  }
15543
15635
  async function clearStalenessMarker(projectRoot) {
15544
15636
  try {
15545
- await fs38.unlink(getStalenessMarkerPath(projectRoot));
15637
+ await fs39.unlink(getStalenessMarkerPath(projectRoot));
15546
15638
  } catch {
15547
15639
  }
15548
15640
  }
@@ -15741,8 +15833,8 @@ function aggregateByDay(records) {
15741
15833
  }
15742
15834
 
15743
15835
  // src/usage/jsonl-reader.ts
15744
- import * as fs39 from "fs";
15745
- import * as path39 from "path";
15836
+ import * as fs40 from "fs";
15837
+ import * as path40 from "path";
15746
15838
  function extractTokenUsage(entry, lineNumber) {
15747
15839
  const tokenUsage = entry.token_usage;
15748
15840
  if (!tokenUsage || typeof tokenUsage !== "object") {
@@ -15789,10 +15881,10 @@ function parseLine(line, lineNumber) {
15789
15881
  return record;
15790
15882
  }
15791
15883
  function readCostRecords(projectRoot) {
15792
- const costsFile = path39.join(projectRoot, ".harness", "metrics", "costs.jsonl");
15884
+ const costsFile = path40.join(projectRoot, ".harness", "metrics", "costs.jsonl");
15793
15885
  let raw;
15794
15886
  try {
15795
- raw = fs39.readFileSync(costsFile, "utf-8");
15887
+ raw = fs40.readFileSync(costsFile, "utf-8");
15796
15888
  } catch {
15797
15889
  return [];
15798
15890
  }
@@ -15810,8 +15902,8 @@ function readCostRecords(projectRoot) {
15810
15902
  }
15811
15903
 
15812
15904
  // src/usage/cc-parser.ts
15813
- import * as fs40 from "fs";
15814
- import * as path40 from "path";
15905
+ import * as fs41 from "fs";
15906
+ import * as path41 from "path";
15815
15907
  import * as os2 from "os";
15816
15908
  function extractUsage(entry) {
15817
15909
  if (entry.type !== "assistant") return null;
@@ -15847,7 +15939,7 @@ function parseCCLine(line, filePath, lineNumber) {
15847
15939
  entry = JSON.parse(line);
15848
15940
  } catch {
15849
15941
  console.warn(
15850
- `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path40.basename(filePath)}`
15942
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path41.basename(filePath)}`
15851
15943
  );
15852
15944
  return null;
15853
15945
  }
@@ -15861,7 +15953,7 @@ function parseCCLine(line, filePath, lineNumber) {
15861
15953
  function readCCFile(filePath) {
15862
15954
  let raw;
15863
15955
  try {
15864
- raw = fs40.readFileSync(filePath, "utf-8");
15956
+ raw = fs41.readFileSync(filePath, "utf-8");
15865
15957
  } catch {
15866
15958
  return [];
15867
15959
  }
@@ -15883,10 +15975,10 @@ function readCCFile(filePath) {
15883
15975
  }
15884
15976
  function parseCCRecords() {
15885
15977
  const homeDir = process.env.HOME ?? os2.homedir();
15886
- const projectsDir = path40.join(homeDir, ".claude", "projects");
15978
+ const projectsDir = path41.join(homeDir, ".claude", "projects");
15887
15979
  let projectDirs;
15888
15980
  try {
15889
- projectDirs = fs40.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path40.join(projectsDir, d.name));
15981
+ projectDirs = fs41.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path41.join(projectsDir, d.name));
15890
15982
  } catch {
15891
15983
  return [];
15892
15984
  }
@@ -15894,7 +15986,7 @@ function parseCCRecords() {
15894
15986
  for (const dir of projectDirs) {
15895
15987
  let files;
15896
15988
  try {
15897
- files = fs40.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path40.join(dir, f));
15989
+ files = fs41.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path41.join(dir, f));
15898
15990
  } catch {
15899
15991
  continue;
15900
15992
  }
@@ -15906,8 +15998,8 @@ function parseCCRecords() {
15906
15998
  }
15907
15999
 
15908
16000
  // src/adoption/reader.ts
15909
- import * as fs41 from "fs";
15910
- import * as path41 from "path";
16001
+ import * as fs42 from "fs";
16002
+ import * as path42 from "path";
15911
16003
  function parseLine2(line, lineNumber) {
15912
16004
  try {
15913
16005
  const parsed = JSON.parse(line);
@@ -15926,10 +16018,10 @@ function parseLine2(line, lineNumber) {
15926
16018
  }
15927
16019
  }
15928
16020
  function readAdoptionRecords(projectRoot) {
15929
- const adoptionFile = path41.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
16021
+ const adoptionFile = path42.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
15930
16022
  let raw;
15931
16023
  try {
15932
- raw = fs41.readFileSync(adoptionFile, "utf-8");
16024
+ raw = fs42.readFileSync(adoptionFile, "utf-8");
15933
16025
  } catch {
15934
16026
  return [];
15935
16027
  }
@@ -16005,17 +16097,17 @@ function topSkills(records, n) {
16005
16097
  }
16006
16098
 
16007
16099
  // src/notifications/config-loader.ts
16008
- import * as fs42 from "fs";
16009
- import * as path42 from "path";
16100
+ import * as fs43 from "fs";
16101
+ import * as path43 from "path";
16010
16102
  import { NotificationsConfigSchema } from "@harness-engineering/types";
16011
16103
  function loadNotificationsConfig(projectRoot) {
16012
- const configPath = path42.join(projectRoot, "harness.config.json");
16013
- if (!fs42.existsSync(configPath)) {
16104
+ const configPath = path43.join(projectRoot, "harness.config.json");
16105
+ if (!fs43.existsSync(configPath)) {
16014
16106
  return Ok({ sinks: [] });
16015
16107
  }
16016
16108
  let raw;
16017
16109
  try {
16018
- raw = fs42.readFileSync(configPath, "utf-8");
16110
+ raw = fs43.readFileSync(configPath, "utf-8");
16019
16111
  } catch (err) {
16020
16112
  return Err(err instanceof Error ? err : new Error(String(err)));
16021
16113
  }
@@ -16300,28 +16392,28 @@ var GeminiCacheAdapter = class {
16300
16392
  };
16301
16393
 
16302
16394
  // src/telemetry/consent.ts
16303
- import * as fs44 from "fs";
16304
- import * as path44 from "path";
16395
+ import * as fs45 from "fs";
16396
+ import * as path45 from "path";
16305
16397
  import { execFileSync as execFileSync2 } from "child_process";
16306
16398
 
16307
16399
  // src/telemetry/install-id.ts
16308
- import * as fs43 from "fs";
16309
- import * as path43 from "path";
16400
+ import * as fs44 from "fs";
16401
+ import * as path44 from "path";
16310
16402
  import * as crypto4 from "crypto";
16311
16403
  function getOrCreateInstallId(projectRoot) {
16312
- const harnessDir = path43.join(projectRoot, ".harness");
16313
- const installIdFile = path43.join(harnessDir, ".install-id");
16404
+ const harnessDir = path44.join(projectRoot, ".harness");
16405
+ const installIdFile = path44.join(harnessDir, ".install-id");
16314
16406
  const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
16315
16407
  try {
16316
- const existing = fs43.readFileSync(installIdFile, "utf-8").trim();
16408
+ const existing = fs44.readFileSync(installIdFile, "utf-8").trim();
16317
16409
  if (UUID_V4_RE.test(existing)) {
16318
16410
  return existing;
16319
16411
  }
16320
16412
  } catch {
16321
16413
  }
16322
16414
  const id = crypto4.randomUUID();
16323
- fs43.mkdirSync(harnessDir, { recursive: true });
16324
- fs43.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
16415
+ fs44.mkdirSync(harnessDir, { recursive: true });
16416
+ fs44.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
16325
16417
  return id;
16326
16418
  }
16327
16419
 
@@ -16329,7 +16421,7 @@ function getOrCreateInstallId(projectRoot) {
16329
16421
  function parseIdentityFromTelemetryFile(filePath) {
16330
16422
  const identity = {};
16331
16423
  try {
16332
- const raw = fs44.readFileSync(filePath, "utf-8");
16424
+ const raw = fs45.readFileSync(filePath, "utf-8");
16333
16425
  const parsed = JSON.parse(raw);
16334
16426
  const src = parsed?.identity;
16335
16427
  if (!src || typeof src !== "object") return identity;
@@ -16342,7 +16434,7 @@ function parseIdentityFromTelemetryFile(filePath) {
16342
16434
  }
16343
16435
  function readProjectNameFallback(configPath) {
16344
16436
  try {
16345
- const raw = fs44.readFileSync(configPath, "utf-8");
16437
+ const raw = fs45.readFileSync(configPath, "utf-8");
16346
16438
  const config = JSON.parse(raw);
16347
16439
  return typeof config?.name === "string" ? config.name : void 0;
16348
16440
  } catch {
@@ -16363,10 +16455,10 @@ function readGitAliasFallback(cwd) {
16363
16455
  }
16364
16456
  }
16365
16457
  function readIdentity(projectRoot) {
16366
- const filePath = path44.join(projectRoot, ".harness", "telemetry.json");
16458
+ const filePath = path45.join(projectRoot, ".harness", "telemetry.json");
16367
16459
  const identity = parseIdentityFromTelemetryFile(filePath);
16368
16460
  if (!identity.project) {
16369
- const fallbackProject = readProjectNameFallback(path44.join(projectRoot, "harness.config.json"));
16461
+ const fallbackProject = readProjectNameFallback(path45.join(projectRoot, "harness.config.json"));
16370
16462
  if (fallbackProject) identity.project = fallbackProject;
16371
16463
  }
16372
16464
  if (!identity.alias) {
@@ -16663,8 +16755,8 @@ var SpanKind = /* @__PURE__ */ ((SpanKind2) => {
16663
16755
  })(SpanKind || {});
16664
16756
 
16665
16757
  // src/locks/compound-lock.ts
16666
- import * as fs45 from "fs";
16667
- import * as path45 from "path";
16758
+ import * as fs46 from "fs";
16759
+ import * as path46 from "path";
16668
16760
  var CompoundLockHeldError = class extends Error {
16669
16761
  constructor(category, holderPid, lockPath) {
16670
16762
  super(
@@ -16687,12 +16779,12 @@ function acquireCompoundLock(category, opts = {}) {
16687
16779
  );
16688
16780
  }
16689
16781
  const cwd = opts.cwd ?? process.cwd();
16690
- const lockDir = path45.join(cwd, ".harness", "locks");
16691
- fs45.mkdirSync(lockDir, { recursive: true });
16692
- const lockPath = path45.join(lockDir, `compound-${category}.lock`);
16782
+ const lockDir = path46.join(cwd, ".harness", "locks");
16783
+ fs46.mkdirSync(lockDir, { recursive: true });
16784
+ const lockPath = path46.join(lockDir, `compound-${category}.lock`);
16693
16785
  let fd;
16694
16786
  try {
16695
- fd = fs45.openSync(lockPath, "wx");
16787
+ fd = fs46.openSync(lockPath, "wx");
16696
16788
  } catch (e) {
16697
16789
  const err = e;
16698
16790
  if (err.code === "EEXIST") {
@@ -16701,14 +16793,14 @@ function acquireCompoundLock(category, opts = {}) {
16701
16793
  }
16702
16794
  throw err;
16703
16795
  }
16704
- fs45.writeSync(fd, String(process.pid));
16705
- fs45.closeSync(fd);
16796
+ fs46.writeSync(fd, String(process.pid));
16797
+ fs46.closeSync(fd);
16706
16798
  let released = false;
16707
16799
  const release = () => {
16708
16800
  if (released) return;
16709
16801
  released = true;
16710
16802
  try {
16711
- fs45.unlinkSync(lockPath);
16803
+ fs46.unlinkSync(lockPath);
16712
16804
  } catch {
16713
16805
  }
16714
16806
  process.removeListener("exit", onExit);
@@ -16734,7 +16826,7 @@ function acquireCompoundLock(category, opts = {}) {
16734
16826
  }
16735
16827
  function readHolderPid(lockPath) {
16736
16828
  try {
16737
- const raw = fs45.readFileSync(lockPath, "utf-8").trim();
16829
+ const raw = fs46.readFileSync(lockPath, "utf-8").trim();
16738
16830
  const n = Number.parseInt(raw, 10);
16739
16831
  return Number.isFinite(n) ? n : -1;
16740
16832
  } catch {
@@ -16784,13 +16876,13 @@ function assertSanitized(value) {
16784
16876
  }
16785
16877
 
16786
16878
  // src/pulse/config-writer.ts
16787
- import * as fs46 from "fs";
16879
+ import * as fs47 from "fs";
16788
16880
  function writePulseConfig(config, opts) {
16789
16881
  PulseConfigSchema.parse(config);
16790
- if (!fs46.existsSync(opts.configPath)) {
16882
+ if (!fs47.existsSync(opts.configPath)) {
16791
16883
  throw new Error(`harness.config.json not found at ${opts.configPath}`);
16792
16884
  }
16793
- const raw = fs46.readFileSync(opts.configPath, "utf-8");
16885
+ const raw = fs47.readFileSync(opts.configPath, "utf-8");
16794
16886
  let parsed;
16795
16887
  try {
16796
16888
  parsed = JSON.parse(raw);
@@ -16798,18 +16890,18 @@ function writePulseConfig(config, opts) {
16798
16890
  throw new Error(`Invalid JSON in ${opts.configPath}: ${e.message}`, { cause: e });
16799
16891
  }
16800
16892
  const bakPath = `${opts.configPath}.bak`;
16801
- if (!opts.skipBackup && !fs46.existsSync(bakPath)) {
16802
- fs46.writeFileSync(bakPath, raw, "utf-8");
16893
+ if (!opts.skipBackup && !fs47.existsSync(bakPath)) {
16894
+ fs47.writeFileSync(bakPath, raw, "utf-8");
16803
16895
  }
16804
16896
  parsed.pulse = config;
16805
16897
  const serialized = JSON.stringify(parsed, null, 2) + "\n";
16806
16898
  const tmpPath = `${opts.configPath}.tmp-${process.pid}`;
16807
- fs46.writeFileSync(tmpPath, serialized, "utf-8");
16899
+ fs47.writeFileSync(tmpPath, serialized, "utf-8");
16808
16900
  try {
16809
- fs46.renameSync(tmpPath, opts.configPath);
16901
+ fs47.renameSync(tmpPath, opts.configPath);
16810
16902
  } catch (e) {
16811
16903
  try {
16812
- fs46.unlinkSync(tmpPath);
16904
+ fs47.unlinkSync(tmpPath);
16813
16905
  } catch {
16814
16906
  }
16815
16907
  throw e;
@@ -16817,16 +16909,16 @@ function writePulseConfig(config, opts) {
16817
16909
  }
16818
16910
 
16819
16911
  // src/pulse/strategy-seeder.ts
16820
- import * as fs47 from "fs";
16821
- import * as path46 from "path";
16912
+ import * as fs48 from "fs";
16913
+ import * as path47 from "path";
16822
16914
  function seedFromStrategy(opts = {}) {
16823
16915
  const cwd = opts.cwd ?? process.cwd();
16824
- const strategyPath = path46.join(cwd, "STRATEGY.md");
16916
+ const strategyPath = path47.join(cwd, "STRATEGY.md");
16825
16917
  const warnings = [];
16826
- if (!fs47.existsSync(strategyPath)) {
16918
+ if (!fs48.existsSync(strategyPath)) {
16827
16919
  return { name: null, keyMetrics: [], warnings: ["STRATEGY.md not found"] };
16828
16920
  }
16829
- const raw = fs47.readFileSync(strategyPath, "utf-8");
16921
+ const raw = fs48.readFileSync(strategyPath, "utf-8");
16830
16922
  let name = null;
16831
16923
  const fmMatch = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
16832
16924
  const fm = fmMatch?.[1];
@@ -17021,7 +17113,7 @@ async function runPulse(config, window) {
17021
17113
  // src/pulse/run/report.ts
17022
17114
  import { readFileSync as readFileSync39 } from "fs";
17023
17115
  import { fileURLToPath } from "url";
17024
- import { dirname as dirname15, join as join60 } from "path";
17116
+ import { dirname as dirname16, join as join61 } from "path";
17025
17117
  var MAX_LINES = 40;
17026
17118
  var INLINE_TEMPLATE = `# {{productName}} Pulse \u2014 {{windowLabel}}
17027
17119
 
@@ -17045,8 +17137,8 @@ function loadTemplate() {
17045
17137
  try {
17046
17138
  const url = import.meta?.url;
17047
17139
  if (!url) return INLINE_TEMPLATE;
17048
- const here = dirname15(fileURLToPath(url));
17049
- return readFileSync39(join60(here, "template.md"), "utf-8");
17140
+ const here = dirname16(fileURLToPath(url));
17141
+ return readFileSync39(join61(here, "template.md"), "utf-8");
17050
17142
  } catch {
17051
17143
  return INLINE_TEMPLATE;
17052
17144
  }
@@ -17250,16 +17342,16 @@ async function computeHotspots(opts) {
17250
17342
  }
17251
17343
  const counts = /* @__PURE__ */ new Map();
17252
17344
  for (const line of stdout.split("\n")) {
17253
- const path48 = line.trim();
17254
- if (path48.length === 0) continue;
17255
- counts.set(path48, (counts.get(path48) ?? 0) + 1);
17345
+ const path50 = line.trim();
17346
+ if (path50.length === 0) continue;
17347
+ counts.set(path50, (counts.get(path50) ?? 0) + 1);
17256
17348
  }
17257
- return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([path48, churn]) => ({ path: path48, churn })).sort((a, b) => b.churn - a.churn);
17349
+ return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([path50, churn]) => ({ path: path50, churn })).sort((a, b) => b.churn - a.churn);
17258
17350
  }
17259
17351
 
17260
17352
  // src/solutions/scan-candidates/cross-reference.ts
17261
- import * as fs48 from "fs/promises";
17262
- import * as path47 from "path";
17353
+ import * as fs49 from "fs/promises";
17354
+ import * as path48 from "path";
17263
17355
  var TRACK_CATEGORIES2 = [
17264
17356
  ["bug-track", BUG_TRACK_CATEGORIES],
17265
17357
  ["knowledge-track", KNOWLEDGE_TRACK_CATEGORIES]
@@ -17295,12 +17387,12 @@ function tokenize(text) {
17295
17387
  async function* walk2(dir) {
17296
17388
  let entries;
17297
17389
  try {
17298
- entries = await fs48.readdir(dir, { withFileTypes: true });
17390
+ entries = await fs49.readdir(dir, { withFileTypes: true });
17299
17391
  } catch {
17300
17392
  return;
17301
17393
  }
17302
17394
  for (const e of entries) {
17303
- const p = path47.join(dir, e.name);
17395
+ const p = path48.join(dir, e.name);
17304
17396
  if (e.isDirectory()) yield* walk2(p);
17305
17397
  else if (e.isFile() && e.name.endsWith(".md")) yield p;
17306
17398
  }
@@ -17309,11 +17401,11 @@ async function readDocumentedTokens(solutionsDir) {
17309
17401
  const docs = [];
17310
17402
  for (const [track, categories] of TRACK_CATEGORIES2) {
17311
17403
  for (const category of categories) {
17312
- const dir = path47.join(solutionsDir, track, category);
17404
+ const dir = path48.join(solutionsDir, track, category);
17313
17405
  for await (const file of walk2(dir)) {
17314
- const raw = await fs48.readFile(file, "utf-8");
17406
+ const raw = await fs49.readFile(file, "utf-8");
17315
17407
  const m = /^#\s+(.+)$/m.exec(raw);
17316
- const title = m?.[1] ?? path47.basename(file, ".md");
17408
+ const title = m?.[1] ?? path48.basename(file, ".md");
17317
17409
  docs.push(tokenize(title));
17318
17410
  }
17319
17411
  }
@@ -17394,6 +17486,135 @@ function assembleCandidateReport(input) {
17394
17486
  }
17395
17487
  return lines.join("\n");
17396
17488
  }
17489
+
17490
+ // src/proposals/store.ts
17491
+ import * as fs50 from "fs";
17492
+ import * as path49 from "path";
17493
+ import { randomUUID as randomUUID2 } from "crypto";
17494
+ import {
17495
+ SkillProposalSchema,
17496
+ EmitSkillProposalInputSchema
17497
+ } from "@harness-engineering/types";
17498
+ function proposalsDir(projectPath) {
17499
+ return path49.join(projectPath, ".harness", "proposals");
17500
+ }
17501
+ function proposalPath(projectPath, id) {
17502
+ return path49.join(proposalsDir(projectPath), `${id}.json`);
17503
+ }
17504
+ function ensureDir(dir) {
17505
+ fs50.mkdirSync(dir, { recursive: true });
17506
+ }
17507
+ function writeAtomic(filePath, content) {
17508
+ const tmpPath = `${filePath}.tmp`;
17509
+ fs50.writeFileSync(tmpPath, content);
17510
+ fs50.renameSync(tmpPath, filePath);
17511
+ }
17512
+ var ProposalNotFoundError = class extends Error {
17513
+ constructor(id) {
17514
+ super(`proposal not found: ${id}`);
17515
+ this.name = "ProposalNotFoundError";
17516
+ }
17517
+ };
17518
+ var ProposalConflictError = class extends Error {
17519
+ constructor(message) {
17520
+ super(message);
17521
+ this.name = "ProposalConflictError";
17522
+ }
17523
+ };
17524
+ async function createProposal(projectPath, input) {
17525
+ const validated = EmitSkillProposalInputSchema.parse(input);
17526
+ const id = `proposal_${randomUUID2().replace(/-/g, "")}`;
17527
+ const proposal = SkillProposalSchema.parse({
17528
+ id,
17529
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17530
+ kind: validated.kind,
17531
+ targetSkill: validated.targetSkill,
17532
+ proposedBy: validated.proposedBy,
17533
+ source: {
17534
+ sessionId: validated.sessionId,
17535
+ taskId: validated.taskId,
17536
+ justification: validated.justification
17537
+ },
17538
+ content: validated.content,
17539
+ status: "open"
17540
+ });
17541
+ if (proposal.kind === "refinement" && proposal.targetSkill) {
17542
+ const existing = await listProposals(projectPath, { status: "open" });
17543
+ const clash = existing.find(
17544
+ (p) => p.kind === "refinement" && p.targetSkill === proposal.targetSkill
17545
+ );
17546
+ if (clash) {
17547
+ throw new ProposalConflictError(
17548
+ `An open refinement proposal already exists for skill "${proposal.targetSkill}" (id: ${clash.id})`
17549
+ );
17550
+ }
17551
+ }
17552
+ const dir = proposalsDir(projectPath);
17553
+ ensureDir(dir);
17554
+ writeAtomic(proposalPath(projectPath, id), JSON.stringify(proposal, null, 2));
17555
+ return proposal;
17556
+ }
17557
+ async function getProposal(projectPath, id) {
17558
+ const file = proposalPath(projectPath, id);
17559
+ try {
17560
+ const raw = fs50.readFileSync(file, "utf-8");
17561
+ const parsed = SkillProposalSchema.safeParse(JSON.parse(raw));
17562
+ return parsed.success ? parsed.data : null;
17563
+ } catch {
17564
+ return null;
17565
+ }
17566
+ }
17567
+ async function listProposals(projectPath, opts = {}) {
17568
+ const dir = proposalsDir(projectPath);
17569
+ let files;
17570
+ try {
17571
+ files = fs50.readdirSync(dir);
17572
+ } catch {
17573
+ return [];
17574
+ }
17575
+ const out = [];
17576
+ for (const file of files) {
17577
+ if (!file.endsWith(".json")) continue;
17578
+ const id = file.slice(0, -".json".length);
17579
+ const proposal = await getProposal(projectPath, id);
17580
+ if (!proposal) continue;
17581
+ if (opts.status && opts.status !== "all" && proposal.status !== opts.status) continue;
17582
+ out.push(proposal);
17583
+ }
17584
+ out.sort((a, b) => a.createdAt > b.createdAt ? -1 : a.createdAt < b.createdAt ? 1 : 0);
17585
+ return out;
17586
+ }
17587
+ async function updateProposal(projectPath, id, patch) {
17588
+ const current = await getProposal(projectPath, id);
17589
+ if (!current) throw new ProposalNotFoundError(id);
17590
+ const next = SkillProposalSchema.parse({
17591
+ ...current,
17592
+ ...patch,
17593
+ id: current.id,
17594
+ createdAt: current.createdAt,
17595
+ kind: current.kind
17596
+ });
17597
+ writeAtomic(proposalPath(projectPath, id), JSON.stringify(next, null, 2));
17598
+ return next;
17599
+ }
17600
+
17601
+ // src/proposals/usage.ts
17602
+ function deriveSkillUsage(projectRoot, skillName, windowDays = 30) {
17603
+ const records = readAdoptionRecords(projectRoot);
17604
+ const cutoffMs = Date.now() - windowDays * 24 * 60 * 60 * 1e3;
17605
+ let count = 0;
17606
+ let lastUsed;
17607
+ for (const rec of records) {
17608
+ if (rec.skill !== skillName) continue;
17609
+ const startedMs = Date.parse(rec.startedAt);
17610
+ if (Number.isFinite(startedMs) && startedMs < cutoffMs) continue;
17611
+ count += 1;
17612
+ if (!lastUsed || rec.startedAt > lastUsed) lastUsed = rec.startedAt;
17613
+ }
17614
+ const stats = { count, windowDays };
17615
+ if (lastUsed) stats.lastUsed = lastUsed;
17616
+ return stats;
17617
+ }
17397
17618
  export {
17398
17619
  AGENT_DESCRIPTORS,
17399
17620
  AGREEMENT_LINE_GAP,
@@ -17496,6 +17717,8 @@ export {
17496
17717
  PredictionResultSchema,
17497
17718
  PredictionWarningSchema,
17498
17719
  ProjectScanner,
17720
+ ProposalConflictError,
17721
+ ProposalNotFoundError,
17499
17722
  PulseAdapterAlreadyRegisteredError,
17500
17723
  PulseConfigSchema,
17501
17724
  PulseDbSourceSchema,
@@ -17619,7 +17842,9 @@ export {
17619
17842
  createFixes,
17620
17843
  createForbiddenImportFixes,
17621
17844
  createOrphanedDepFixes,
17845
+ createOsvClient,
17622
17846
  createParseError,
17847
+ createProposal,
17623
17848
  createRegionMap,
17624
17849
  createSelfReview,
17625
17850
  createStream,
@@ -17631,6 +17856,7 @@ export {
17631
17856
  deepMergeConstraints,
17632
17857
  defaultCollectors,
17633
17858
  defineLayer,
17859
+ deriveSkillUsage,
17634
17860
  deserializationRules,
17635
17861
  detectChangeType,
17636
17862
  detectCircularDeps,
@@ -17683,6 +17909,7 @@ export {
17683
17909
  getOutline,
17684
17910
  getParser,
17685
17911
  getPhaseCategories,
17912
+ getProposal,
17686
17913
  getPulseAdapter,
17687
17914
  getRoadmapMode,
17688
17915
  getStreamForBranch,
@@ -17702,6 +17929,7 @@ export {
17702
17929
  isUpdateCheckEnabled,
17703
17930
  isoWeek,
17704
17931
  listActiveSessions,
17932
+ listProposals,
17705
17933
  listPulseAdapters,
17706
17934
  listStreams,
17707
17935
  listTaintedSessions,
@@ -17752,6 +17980,7 @@ export {
17752
17980
  previewFix,
17753
17981
  projectValue,
17754
17982
  promoteSessionLearnings,
17983
+ proposalsDir,
17755
17984
  pruneLearnings,
17756
17985
  reactRules,
17757
17986
  readAdoptionRecords,
@@ -17824,6 +18053,7 @@ export {
17824
18053
  trackAction,
17825
18054
  unfoldRange,
17826
18055
  unfoldSymbol,
18056
+ updateProposal,
17827
18057
  updateSessionEntryStatus,
17828
18058
  updateSessionIndex,
17829
18059
  validateAgentConfigs,