@harness-engineering/core 0.20.0 → 0.21.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
@@ -168,6 +168,7 @@ __export(index_exports, {
168
168
  clearFailuresCache: () => clearFailuresCache,
169
169
  clearLearningsCache: () => clearLearningsCache,
170
170
  clearTaint: () => clearTaint,
171
+ computeContentHash: () => computeContentHash,
171
172
  computeOverallSeverity: () => computeOverallSeverity,
172
173
  computeScanExitCode: () => computeScanExitCode,
173
174
  configureFeedback: () => configureFeedback,
@@ -261,6 +262,7 @@ __export(index_exports, {
261
262
  migrateToStreams: () => migrateToStreams,
262
263
  networkRules: () => networkRules,
263
264
  nodeRules: () => nodeRules,
265
+ normalizeLearningContent: () => normalizeLearningContent,
264
266
  parseCCRecords: () => parseCCRecords,
265
267
  parseDateFromEntry: () => parseDateFromEntry,
266
268
  parseDiff: () => parseDiff,
@@ -371,17 +373,17 @@ var import_node_path = require("path");
371
373
  var import_glob = require("glob");
372
374
  var accessAsync = (0, import_util.promisify)(import_fs.access);
373
375
  var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
374
- async function fileExists(path28) {
376
+ async function fileExists(path31) {
375
377
  try {
376
- await accessAsync(path28, import_fs.constants.F_OK);
378
+ await accessAsync(path31, import_fs.constants.F_OK);
377
379
  return true;
378
380
  } catch {
379
381
  return false;
380
382
  }
381
383
  }
382
- async function readFileContent(path28) {
384
+ async function readFileContent(path31) {
383
385
  try {
384
- const content = await readFileAsync(path28, "utf-8");
386
+ const content = await readFileAsync(path31, "utf-8");
385
387
  return (0, import_types.Ok)(content);
386
388
  } catch (error) {
387
389
  return (0, import_types.Err)(error);
@@ -432,15 +434,15 @@ function validateConfig(data, schema) {
432
434
  let message = "Configuration validation failed";
433
435
  const suggestions = [];
434
436
  if (firstError) {
435
- const path28 = firstError.path.join(".");
436
- const pathDisplay = path28 ? ` at "${path28}"` : "";
437
+ const path31 = firstError.path.join(".");
438
+ const pathDisplay = path31 ? ` at "${path31}"` : "";
437
439
  if (firstError.code === "invalid_type") {
438
440
  const received = firstError.received;
439
441
  const expected = firstError.expected;
440
442
  if (received === "undefined") {
441
443
  code = "MISSING_FIELD";
442
444
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
443
- suggestions.push(`Field "${path28}" is required and must be of type "${expected}"`);
445
+ suggestions.push(`Field "${path31}" is required and must be of type "${expected}"`);
444
446
  } else {
445
447
  code = "INVALID_TYPE";
446
448
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -656,27 +658,27 @@ function extractSections(content) {
656
658
  }
657
659
  return sections.map((section) => buildAgentMapSection(section, lines));
658
660
  }
659
- function isExternalLink(path28) {
660
- return path28.startsWith("http://") || path28.startsWith("https://") || path28.startsWith("#") || path28.startsWith("mailto:");
661
+ function isExternalLink(path31) {
662
+ return path31.startsWith("http://") || path31.startsWith("https://") || path31.startsWith("#") || path31.startsWith("mailto:");
661
663
  }
662
664
  function resolveLinkPath(linkPath, baseDir) {
663
665
  return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
664
666
  }
665
- async function validateAgentsMap(path28 = "./AGENTS.md") {
666
- const contentResult = await readFileContent(path28);
667
+ async function validateAgentsMap(path31 = "./AGENTS.md") {
668
+ const contentResult = await readFileContent(path31);
667
669
  if (!contentResult.ok) {
668
670
  return (0, import_types.Err)(
669
671
  createError(
670
672
  "PARSE_ERROR",
671
673
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
672
- { path: path28 },
674
+ { path: path31 },
673
675
  ["Ensure the file exists", "Check file permissions"]
674
676
  )
675
677
  );
676
678
  }
677
679
  const content = contentResult.value;
678
680
  const sections = extractSections(content);
679
- const baseDir = (0, import_path.dirname)(path28);
681
+ const baseDir = (0, import_path.dirname)(path31);
680
682
  const sectionTitles = sections.map((s) => s.title);
681
683
  const missingSections = REQUIRED_SECTIONS.filter(
682
684
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -817,8 +819,8 @@ async function checkDocCoverage(domain, options = {}) {
817
819
 
818
820
  // src/context/knowledge-map.ts
819
821
  var import_path3 = require("path");
820
- function suggestFix(path28, existingFiles) {
821
- const targetName = (0, import_path3.basename)(path28).toLowerCase();
822
+ function suggestFix(path31, existingFiles) {
823
+ const targetName = (0, import_path3.basename)(path31).toLowerCase();
822
824
  const similar = existingFiles.find((file) => {
823
825
  const fileName = (0, import_path3.basename)(file).toLowerCase();
824
826
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -826,7 +828,7 @@ function suggestFix(path28, existingFiles) {
826
828
  if (similar) {
827
829
  return `Did you mean "${similar}"?`;
828
830
  }
829
- return `Create the file "${path28}" or remove the link`;
831
+ return `Create the file "${path31}" or remove the link`;
830
832
  }
831
833
  async function validateKnowledgeMap(rootDir = process.cwd()) {
832
834
  const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
@@ -1432,8 +1434,8 @@ function createBoundaryValidator(schema, name) {
1432
1434
  return (0, import_types.Ok)(result.data);
1433
1435
  }
1434
1436
  const suggestions = result.error.issues.map((issue) => {
1435
- const path28 = issue.path.join(".");
1436
- return path28 ? `${path28}: ${issue.message}` : issue.message;
1437
+ const path31 = issue.path.join(".");
1438
+ return path31 ? `${path31}: ${issue.message}` : issue.message;
1437
1439
  });
1438
1440
  return (0, import_types.Err)(
1439
1441
  createError(
@@ -2065,11 +2067,11 @@ function processExportListSpecifiers(exportDecl, exports2) {
2065
2067
  var TypeScriptParser = class {
2066
2068
  name = "typescript";
2067
2069
  extensions = [".ts", ".tsx", ".mts", ".cts"];
2068
- async parseFile(path28) {
2069
- const contentResult = await readFileContent(path28);
2070
+ async parseFile(path31) {
2071
+ const contentResult = await readFileContent(path31);
2070
2072
  if (!contentResult.ok) {
2071
2073
  return (0, import_types.Err)(
2072
- createParseError("NOT_FOUND", `File not found: ${path28}`, { path: path28 }, [
2074
+ createParseError("NOT_FOUND", `File not found: ${path31}`, { path: path31 }, [
2073
2075
  "Check that the file exists",
2074
2076
  "Verify the path is correct"
2075
2077
  ])
@@ -2079,7 +2081,7 @@ var TypeScriptParser = class {
2079
2081
  const ast = (0, import_typescript_estree.parse)(contentResult.value, {
2080
2082
  loc: true,
2081
2083
  range: true,
2082
- jsx: path28.endsWith(".tsx"),
2084
+ jsx: path31.endsWith(".tsx"),
2083
2085
  errorOnUnknownASTType: false
2084
2086
  });
2085
2087
  return (0, import_types.Ok)({
@@ -2090,7 +2092,7 @@ var TypeScriptParser = class {
2090
2092
  } catch (e) {
2091
2093
  const error = e;
2092
2094
  return (0, import_types.Err)(
2093
- createParseError("SYNTAX_ERROR", `Failed to parse ${path28}: ${error.message}`, { path: path28 }, [
2095
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path31}: ${error.message}`, { path: path31 }, [
2094
2096
  "Check for syntax errors in the file",
2095
2097
  "Ensure valid TypeScript syntax"
2096
2098
  ])
@@ -2275,22 +2277,22 @@ function extractInlineRefs(content) {
2275
2277
  }
2276
2278
  return refs;
2277
2279
  }
2278
- async function parseDocumentationFile(path28) {
2279
- const contentResult = await readFileContent(path28);
2280
+ async function parseDocumentationFile(path31) {
2281
+ const contentResult = await readFileContent(path31);
2280
2282
  if (!contentResult.ok) {
2281
2283
  return (0, import_types.Err)(
2282
2284
  createEntropyError(
2283
2285
  "PARSE_ERROR",
2284
- `Failed to read documentation file: ${path28}`,
2285
- { file: path28 },
2286
+ `Failed to read documentation file: ${path31}`,
2287
+ { file: path31 },
2286
2288
  ["Check that the file exists"]
2287
2289
  )
2288
2290
  );
2289
2291
  }
2290
2292
  const content = contentResult.value;
2291
- const type = path28.endsWith(".md") ? "markdown" : "text";
2293
+ const type = path31.endsWith(".md") ? "markdown" : "text";
2292
2294
  return (0, import_types.Ok)({
2293
- path: path28,
2295
+ path: path31,
2294
2296
  type,
2295
2297
  content,
2296
2298
  codeBlocks: extractCodeBlocks(content),
@@ -7353,8 +7355,7 @@ function parseListField(fieldMap, ...keys) {
7353
7355
  if (raw === EM_DASH || raw === "none") return [];
7354
7356
  return raw.split(",").map((s) => s.trim());
7355
7357
  }
7356
- function parseFeatureFields(name, body) {
7357
- const fieldMap = extractFieldMap(body);
7358
+ function validateStatus(name, fieldMap) {
7358
7359
  const statusRaw = fieldMap.get("Status");
7359
7360
  if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
7360
7361
  return (0, import_types13.Err)(
@@ -7363,12 +7364,10 @@ function parseFeatureFields(name, body) {
7363
7364
  )
7364
7365
  );
7365
7366
  }
7366
- const specRaw = fieldMap.get("Spec") ?? EM_DASH;
7367
- const plans = parseListField(fieldMap, "Plans", "Plan");
7368
- const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
7369
- const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
7367
+ return (0, import_types13.Ok)(statusRaw);
7368
+ }
7369
+ function validatePriority(name, fieldMap) {
7370
7370
  const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
7371
- const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
7372
7371
  if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
7373
7372
  return (0, import_types13.Err)(
7374
7373
  new Error(
@@ -7376,16 +7375,28 @@ function parseFeatureFields(name, body) {
7376
7375
  )
7377
7376
  );
7378
7377
  }
7378
+ return (0, import_types13.Ok)(priorityRaw === EM_DASH ? null : priorityRaw);
7379
+ }
7380
+ function optionalField(fieldMap, key) {
7381
+ const raw = fieldMap.get(key) ?? EM_DASH;
7382
+ return raw === EM_DASH ? null : raw;
7383
+ }
7384
+ function parseFeatureFields(name, body) {
7385
+ const fieldMap = extractFieldMap(body);
7386
+ const statusResult = validateStatus(name, fieldMap);
7387
+ if (!statusResult.ok) return statusResult;
7388
+ const priorityResult = validatePriority(name, fieldMap);
7389
+ if (!priorityResult.ok) return priorityResult;
7379
7390
  return (0, import_types13.Ok)({
7380
7391
  name,
7381
- status: statusRaw,
7382
- spec: specRaw === EM_DASH ? null : specRaw,
7383
- plans,
7384
- blockedBy,
7392
+ status: statusResult.value,
7393
+ spec: optionalField(fieldMap, "Spec"),
7394
+ plans: parseListField(fieldMap, "Plans", "Plan"),
7395
+ blockedBy: parseListField(fieldMap, "Blocked by", "Blockers"),
7385
7396
  summary: fieldMap.get("Summary") ?? "",
7386
- assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
7387
- priority: priorityRaw === EM_DASH ? null : priorityRaw,
7388
- externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
7397
+ assignee: optionalField(fieldMap, "Assignee"),
7398
+ priority: priorityResult.value,
7399
+ externalId: optionalField(fieldMap, "External-ID")
7389
7400
  });
7390
7401
  }
7391
7402
  function parseAssignmentHistory(body) {
@@ -7451,82 +7462,16 @@ var PredictionEngine = class {
7451
7462
  const firstDate = new Date(snapshots[0].capturedAt).getTime();
7452
7463
  const lastSnapshot = snapshots[snapshots.length - 1];
7453
7464
  const currentT = (new Date(lastSnapshot.capturedAt).getTime() - firstDate) / (7 * 24 * 60 * 60 * 1e3);
7454
- const baselines = {};
7455
- for (const category of ALL_CATEGORIES2) {
7456
- const threshold = thresholds[category];
7457
- const shouldProcess = categoriesToProcess.includes(category);
7458
- if (!shouldProcess) {
7459
- baselines[category] = this.zeroForecast(category, threshold);
7460
- continue;
7461
- }
7462
- const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
7463
- baselines[category] = this.forecastCategory(
7464
- category,
7465
- timeSeries,
7466
- currentT,
7467
- threshold,
7468
- opts.horizon
7469
- );
7470
- }
7465
+ const baselines = this.computeBaselines(
7466
+ categoriesToProcess,
7467
+ thresholds,
7468
+ snapshots,
7469
+ firstDate,
7470
+ currentT,
7471
+ opts.horizon
7472
+ );
7471
7473
  const specImpacts = this.computeSpecImpacts(opts);
7472
- const categories = {};
7473
- for (const category of ALL_CATEGORIES2) {
7474
- const baseline = baselines[category];
7475
- const threshold = thresholds[category];
7476
- if (!specImpacts || specImpacts.length === 0) {
7477
- categories[category] = {
7478
- baseline,
7479
- adjusted: baseline,
7480
- contributingFeatures: []
7481
- };
7482
- continue;
7483
- }
7484
- let totalDelta = 0;
7485
- const contributing = [];
7486
- for (const impact of specImpacts) {
7487
- const delta = impact.deltas?.[category] ?? 0;
7488
- if (delta !== 0) {
7489
- totalDelta += delta;
7490
- contributing.push({
7491
- name: impact.featureName,
7492
- specPath: impact.specPath,
7493
- delta
7494
- });
7495
- }
7496
- }
7497
- if (totalDelta === 0) {
7498
- categories[category] = {
7499
- baseline,
7500
- adjusted: baseline,
7501
- contributingFeatures: []
7502
- };
7503
- continue;
7504
- }
7505
- const adjusted = {
7506
- ...baseline,
7507
- projectedValue4w: baseline.projectedValue4w + totalDelta,
7508
- projectedValue8w: baseline.projectedValue8w + totalDelta,
7509
- projectedValue12w: baseline.projectedValue12w + totalDelta
7510
- };
7511
- const adjustedFit = {
7512
- slope: baseline.regression.slope,
7513
- intercept: baseline.regression.intercept + totalDelta,
7514
- rSquared: baseline.regression.rSquared,
7515
- dataPoints: baseline.regression.dataPoints
7516
- };
7517
- adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
7518
- adjusted.regression = {
7519
- slope: adjustedFit.slope,
7520
- intercept: adjustedFit.intercept,
7521
- rSquared: adjustedFit.rSquared,
7522
- dataPoints: adjustedFit.dataPoints
7523
- };
7524
- categories[category] = {
7525
- baseline,
7526
- adjusted,
7527
- contributingFeatures: contributing
7528
- };
7529
- }
7474
+ const categories = this.computeAdjustedForecasts(baselines, thresholds, specImpacts, currentT);
7530
7475
  const warnings = this.generateWarnings(
7531
7476
  categories,
7532
7477
  opts.horizon
@@ -7568,6 +7513,76 @@ var PredictionEngine = class {
7568
7513
  }
7569
7514
  return base;
7570
7515
  }
7516
+ computeBaselines(categoriesToProcess, thresholds, snapshots, firstDate, currentT, horizon) {
7517
+ const baselines = {};
7518
+ for (const category of ALL_CATEGORIES2) {
7519
+ const threshold = thresholds[category];
7520
+ if (!categoriesToProcess.includes(category)) {
7521
+ baselines[category] = this.zeroForecast(category, threshold);
7522
+ continue;
7523
+ }
7524
+ const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
7525
+ baselines[category] = this.forecastCategory(
7526
+ category,
7527
+ timeSeries,
7528
+ currentT,
7529
+ threshold,
7530
+ horizon
7531
+ );
7532
+ }
7533
+ return baselines;
7534
+ }
7535
+ computeAdjustedForecasts(baselines, thresholds, specImpacts, currentT) {
7536
+ const categories = {};
7537
+ for (const category of ALL_CATEGORIES2) {
7538
+ const baseline = baselines[category];
7539
+ categories[category] = this.adjustForecastForCategory(
7540
+ category,
7541
+ baseline,
7542
+ thresholds[category],
7543
+ specImpacts,
7544
+ currentT
7545
+ );
7546
+ }
7547
+ return categories;
7548
+ }
7549
+ adjustForecastForCategory(category, baseline, threshold, specImpacts, currentT) {
7550
+ if (!specImpacts || specImpacts.length === 0) {
7551
+ return { baseline, adjusted: baseline, contributingFeatures: [] };
7552
+ }
7553
+ let totalDelta = 0;
7554
+ const contributing = [];
7555
+ for (const impact of specImpacts) {
7556
+ const delta = impact.deltas?.[category] ?? 0;
7557
+ if (delta !== 0) {
7558
+ totalDelta += delta;
7559
+ contributing.push({ name: impact.featureName, specPath: impact.specPath, delta });
7560
+ }
7561
+ }
7562
+ if (totalDelta === 0) {
7563
+ return { baseline, adjusted: baseline, contributingFeatures: [] };
7564
+ }
7565
+ const adjusted = {
7566
+ ...baseline,
7567
+ projectedValue4w: baseline.projectedValue4w + totalDelta,
7568
+ projectedValue8w: baseline.projectedValue8w + totalDelta,
7569
+ projectedValue12w: baseline.projectedValue12w + totalDelta
7570
+ };
7571
+ const adjustedFit = {
7572
+ slope: baseline.regression.slope,
7573
+ intercept: baseline.regression.intercept + totalDelta,
7574
+ rSquared: baseline.regression.rSquared,
7575
+ dataPoints: baseline.regression.dataPoints
7576
+ };
7577
+ adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
7578
+ adjusted.regression = {
7579
+ slope: adjustedFit.slope,
7580
+ intercept: adjustedFit.intercept,
7581
+ rSquared: adjustedFit.rSquared,
7582
+ dataPoints: adjustedFit.dataPoints
7583
+ };
7584
+ return { baseline, adjusted, contributingFeatures: contributing };
7585
+ }
7571
7586
  /**
7572
7587
  * Extract time series for a single category from snapshots.
7573
7588
  * Returns array of { t (weeks from first), value } sorted oldest first.
@@ -8392,7 +8407,7 @@ async function saveState(projectPath, state, stream, session) {
8392
8407
  }
8393
8408
  }
8394
8409
 
8395
- // src/state/learnings.ts
8410
+ // src/state/learnings-content.ts
8396
8411
  var fs11 = __toESM(require("fs"));
8397
8412
  var path8 = __toESM(require("path"));
8398
8413
  var crypto = __toESM(require("crypto"));
@@ -8403,6 +8418,25 @@ function parseFrontmatter2(line) {
8403
8418
  const tags = match[2] ? match[2].split(",").filter(Boolean) : [];
8404
8419
  return { hash, tags };
8405
8420
  }
8421
+ function parseDateFromEntry(entry) {
8422
+ const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
8423
+ return match ? match[1] ?? null : null;
8424
+ }
8425
+ function extractIndexEntry(entry) {
8426
+ const lines = entry.split("\n");
8427
+ const summary = lines[0] ?? entry;
8428
+ const tags = [];
8429
+ const skillMatch = entry.match(/\[skill:([^\]]+)\]/);
8430
+ if (skillMatch?.[1]) tags.push(skillMatch[1]);
8431
+ const outcomeMatch = entry.match(/\[outcome:([^\]]+)\]/);
8432
+ if (outcomeMatch?.[1]) tags.push(outcomeMatch[1]);
8433
+ return {
8434
+ hash: computeEntryHash(entry),
8435
+ tags,
8436
+ summary,
8437
+ fullText: entry
8438
+ };
8439
+ }
8406
8440
  function computeEntryHash(text) {
8407
8441
  return crypto.createHash("sha256").update(text).digest("hex").slice(0, 8);
8408
8442
  }
@@ -8437,8 +8471,8 @@ function saveContentHashes(stateDir, index) {
8437
8471
  const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
8438
8472
  fs11.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
8439
8473
  }
8440
- function rebuildContentHashes(stateDir) {
8441
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8474
+ function rebuildContentHashes(stateDir, learningsFile) {
8475
+ const learningsPath = path8.join(stateDir, learningsFile);
8442
8476
  if (!fs11.existsSync(learningsPath)) return {};
8443
8477
  const content = fs11.readFileSync(learningsPath, "utf-8");
8444
8478
  const lines = content.split("\n");
@@ -8459,43 +8493,125 @@ function rebuildContentHashes(stateDir) {
8459
8493
  saveContentHashes(stateDir, index);
8460
8494
  return index;
8461
8495
  }
8462
- function extractIndexEntry(entry) {
8463
- const lines = entry.split("\n");
8464
- const summary = lines[0] ?? entry;
8465
- const tags = [];
8466
- const skillMatch = entry.match(/\[skill:([^\]]+)\]/);
8467
- if (skillMatch?.[1]) tags.push(skillMatch[1]);
8468
- const outcomeMatch = entry.match(/\[outcome:([^\]]+)\]/);
8469
- if (outcomeMatch?.[1]) tags.push(outcomeMatch[1]);
8470
- return {
8471
- hash: computeEntryHash(entry),
8472
- tags,
8473
- summary,
8474
- fullText: entry
8475
- };
8496
+ function analyzeLearningPatterns(entries) {
8497
+ const tagGroups = /* @__PURE__ */ new Map();
8498
+ for (const entry of entries) {
8499
+ const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
8500
+ for (const match of tagMatches) {
8501
+ const tag = match[1] ?? match[2];
8502
+ if (tag) {
8503
+ const group = tagGroups.get(tag) ?? [];
8504
+ group.push(entry);
8505
+ tagGroups.set(tag, group);
8506
+ }
8507
+ }
8508
+ }
8509
+ const patterns = [];
8510
+ for (const [tag, groupEntries] of tagGroups) {
8511
+ if (groupEntries.length >= 3) {
8512
+ patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
8513
+ }
8514
+ }
8515
+ return patterns.sort((a, b) => b.count - a.count);
8476
8516
  }
8517
+ function estimateTokens(text) {
8518
+ return Math.ceil(text.length / 4);
8519
+ }
8520
+ function scoreRelevance(entry, intent) {
8521
+ if (!intent || intent.trim() === "") return 0;
8522
+ const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
8523
+ if (intentWords.length === 0) return 0;
8524
+ const entryLower = entry.toLowerCase();
8525
+ const matches = intentWords.filter((word) => entryLower.includes(word));
8526
+ return matches.length / intentWords.length;
8527
+ }
8528
+
8529
+ // src/state/learnings-loader.ts
8530
+ var fs12 = __toESM(require("fs"));
8531
+ var path9 = __toESM(require("path"));
8477
8532
  var learningsCacheMap = /* @__PURE__ */ new Map();
8478
8533
  function clearLearningsCache() {
8479
8534
  learningsCacheMap.clear();
8480
8535
  }
8536
+ function invalidateLearningsCacheEntry(key) {
8537
+ learningsCacheMap.delete(key);
8538
+ }
8539
+ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
8540
+ try {
8541
+ const dirResult = await getStateDir(projectPath, stream, session);
8542
+ if (!dirResult.ok) return dirResult;
8543
+ const stateDir = dirResult.value;
8544
+ const learningsPath = path9.join(stateDir, LEARNINGS_FILE);
8545
+ if (!fs12.existsSync(learningsPath)) {
8546
+ return (0, import_types.Ok)([]);
8547
+ }
8548
+ const stats = fs12.statSync(learningsPath);
8549
+ const cacheKey = learningsPath;
8550
+ const cached = learningsCacheMap.get(cacheKey);
8551
+ let entries;
8552
+ if (cached && cached.mtimeMs === stats.mtimeMs) {
8553
+ entries = cached.entries;
8554
+ } else {
8555
+ const content = fs12.readFileSync(learningsPath, "utf-8");
8556
+ const lines = content.split("\n");
8557
+ entries = [];
8558
+ let currentBlock = [];
8559
+ for (const line of lines) {
8560
+ if (line.startsWith("# ")) continue;
8561
+ if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
8562
+ const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
8563
+ const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
8564
+ if (isDatedBullet || isHeading) {
8565
+ if (currentBlock.length > 0) {
8566
+ entries.push(currentBlock.join("\n"));
8567
+ }
8568
+ currentBlock = [line];
8569
+ } else if (line.trim() !== "" && currentBlock.length > 0) {
8570
+ currentBlock.push(line);
8571
+ }
8572
+ }
8573
+ if (currentBlock.length > 0) {
8574
+ entries.push(currentBlock.join("\n"));
8575
+ }
8576
+ learningsCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
8577
+ evictIfNeeded(learningsCacheMap);
8578
+ }
8579
+ if (!skillName) {
8580
+ return (0, import_types.Ok)(entries);
8581
+ }
8582
+ const filtered = entries.filter((entry) => entry.includes(`[skill:${skillName}]`));
8583
+ return (0, import_types.Ok)(filtered);
8584
+ } catch (error) {
8585
+ return (0, import_types.Err)(
8586
+ new Error(
8587
+ `Failed to load learnings: ${error instanceof Error ? error.message : String(error)}`
8588
+ )
8589
+ );
8590
+ }
8591
+ }
8592
+
8593
+ // src/state/learnings.ts
8594
+ var fs13 = __toESM(require("fs"));
8595
+ var path10 = __toESM(require("path"));
8596
+ var crypto2 = __toESM(require("crypto"));
8481
8597
  async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
8482
8598
  try {
8483
8599
  const dirResult = await getStateDir(projectPath, stream, session);
8484
8600
  if (!dirResult.ok) return dirResult;
8485
8601
  const stateDir = dirResult.value;
8486
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8487
- fs11.mkdirSync(stateDir, { recursive: true });
8602
+ const learningsPath = path10.join(stateDir, LEARNINGS_FILE);
8603
+ fs13.mkdirSync(stateDir, { recursive: true });
8488
8604
  const normalizedContent = normalizeLearningContent(learning);
8489
8605
  const contentHash = computeContentHash(normalizedContent);
8490
- const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
8606
+ const hashesPath = path10.join(stateDir, CONTENT_HASHES_FILE);
8491
8607
  let contentHashes;
8492
- if (fs11.existsSync(hashesPath)) {
8608
+ if (fs13.existsSync(hashesPath)) {
8493
8609
  contentHashes = loadContentHashes(stateDir);
8494
- if (Object.keys(contentHashes).length === 0 && fs11.existsSync(learningsPath)) {
8495
- contentHashes = rebuildContentHashes(stateDir);
8610
+ if (Object.keys(contentHashes).length === 0 && fs13.existsSync(learningsPath)) {
8611
+ contentHashes = rebuildContentHashes(stateDir, LEARNINGS_FILE);
8496
8612
  }
8497
- } else if (fs11.existsSync(learningsPath)) {
8498
- contentHashes = rebuildContentHashes(stateDir);
8613
+ } else if (fs13.existsSync(learningsPath)) {
8614
+ contentHashes = rebuildContentHashes(stateDir, LEARNINGS_FILE);
8499
8615
  } else {
8500
8616
  contentHashes = {};
8501
8617
  }
@@ -8514,7 +8630,7 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
8514
8630
  } else {
8515
8631
  bulletLine = `- **${timestamp}:** ${learning}`;
8516
8632
  }
8517
- const hash = crypto.createHash("sha256").update(bulletLine).digest("hex").slice(0, 8);
8633
+ const hash = crypto2.createHash("sha256").update(bulletLine).digest("hex").slice(0, 8);
8518
8634
  const tagsStr = fmTags.length > 0 ? ` tags:${fmTags.join(",")}` : "";
8519
8635
  const frontmatter = `<!-- hash:${hash}${tagsStr} -->`;
8520
8636
  const entry = `
@@ -8522,19 +8638,19 @@ ${frontmatter}
8522
8638
  ${bulletLine}
8523
8639
  `;
8524
8640
  let existingLineCount;
8525
- if (!fs11.existsSync(learningsPath)) {
8526
- fs11.writeFileSync(learningsPath, `# Learnings
8641
+ if (!fs13.existsSync(learningsPath)) {
8642
+ fs13.writeFileSync(learningsPath, `# Learnings
8527
8643
  ${entry}`);
8528
8644
  existingLineCount = 1;
8529
8645
  } else {
8530
- const existingContent = fs11.readFileSync(learningsPath, "utf-8");
8646
+ const existingContent = fs13.readFileSync(learningsPath, "utf-8");
8531
8647
  existingLineCount = existingContent.split("\n").length;
8532
- fs11.appendFileSync(learningsPath, entry);
8648
+ fs13.appendFileSync(learningsPath, entry);
8533
8649
  }
8534
8650
  const bulletLine_lineNum = existingLineCount + 2;
8535
8651
  contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
8536
8652
  saveContentHashes(stateDir, contentHashes);
8537
- learningsCacheMap.delete(learningsPath);
8653
+ invalidateLearningsCacheEntry(learningsPath);
8538
8654
  return (0, import_types.Ok)(void 0);
8539
8655
  } catch (error) {
8540
8656
  return (0, import_types.Err)(
@@ -8544,42 +8660,6 @@ ${entry}`);
8544
8660
  );
8545
8661
  }
8546
8662
  }
8547
- function estimateTokens(text) {
8548
- return Math.ceil(text.length / 4);
8549
- }
8550
- function scoreRelevance(entry, intent) {
8551
- if (!intent || intent.trim() === "") return 0;
8552
- const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
8553
- if (intentWords.length === 0) return 0;
8554
- const entryLower = entry.toLowerCase();
8555
- const matches = intentWords.filter((word) => entryLower.includes(word));
8556
- return matches.length / intentWords.length;
8557
- }
8558
- function parseDateFromEntry(entry) {
8559
- const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
8560
- return match ? match[1] ?? null : null;
8561
- }
8562
- function analyzeLearningPatterns(entries) {
8563
- const tagGroups = /* @__PURE__ */ new Map();
8564
- for (const entry of entries) {
8565
- const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
8566
- for (const match of tagMatches) {
8567
- const tag = match[1] ?? match[2];
8568
- if (tag) {
8569
- const group = tagGroups.get(tag) ?? [];
8570
- group.push(entry);
8571
- tagGroups.set(tag, group);
8572
- }
8573
- }
8574
- }
8575
- const patterns = [];
8576
- for (const [tag, groupEntries] of tagGroups) {
8577
- if (groupEntries.length >= 3) {
8578
- patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
8579
- }
8580
- }
8581
- return patterns.sort((a, b) => b.count - a.count);
8582
- }
8583
8663
  async function loadBudgetedLearnings(projectPath, options) {
8584
8664
  const { intent, tokenBudget = 1e3, skill, session, stream, depth = "summary" } = options;
8585
8665
  if (depth === "index") {
@@ -8643,11 +8723,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
8643
8723
  const dirResult = await getStateDir(projectPath, stream, session);
8644
8724
  if (!dirResult.ok) return dirResult;
8645
8725
  const stateDir = dirResult.value;
8646
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8647
- if (!fs11.existsSync(learningsPath)) {
8726
+ const learningsPath = path10.join(stateDir, LEARNINGS_FILE);
8727
+ if (!fs13.existsSync(learningsPath)) {
8648
8728
  return (0, import_types.Ok)([]);
8649
8729
  }
8650
- const content = fs11.readFileSync(learningsPath, "utf-8");
8730
+ const content = fs13.readFileSync(learningsPath, "utf-8");
8651
8731
  const lines = content.split("\n");
8652
8732
  const indexEntries = [];
8653
8733
  let pendingFrontmatter = null;
@@ -8700,74 +8780,25 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
8700
8780
  );
8701
8781
  }
8702
8782
  }
8703
- async function loadRelevantLearnings(projectPath, skillName, stream, session) {
8704
- try {
8705
- const dirResult = await getStateDir(projectPath, stream, session);
8706
- if (!dirResult.ok) return dirResult;
8707
- const stateDir = dirResult.value;
8708
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8709
- if (!fs11.existsSync(learningsPath)) {
8710
- return (0, import_types.Ok)([]);
8711
- }
8712
- const stats = fs11.statSync(learningsPath);
8713
- const cacheKey = learningsPath;
8714
- const cached = learningsCacheMap.get(cacheKey);
8715
- let entries;
8716
- if (cached && cached.mtimeMs === stats.mtimeMs) {
8717
- entries = cached.entries;
8718
- } else {
8719
- const content = fs11.readFileSync(learningsPath, "utf-8");
8720
- const lines = content.split("\n");
8721
- entries = [];
8722
- let currentBlock = [];
8723
- for (const line of lines) {
8724
- if (line.startsWith("# ")) continue;
8725
- if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
8726
- const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
8727
- const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
8728
- if (isDatedBullet || isHeading) {
8729
- if (currentBlock.length > 0) {
8730
- entries.push(currentBlock.join("\n"));
8731
- }
8732
- currentBlock = [line];
8733
- } else if (line.trim() !== "" && currentBlock.length > 0) {
8734
- currentBlock.push(line);
8735
- }
8736
- }
8737
- if (currentBlock.length > 0) {
8738
- entries.push(currentBlock.join("\n"));
8739
- }
8740
- learningsCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
8741
- evictIfNeeded(learningsCacheMap);
8742
- }
8743
- if (!skillName) {
8744
- return (0, import_types.Ok)(entries);
8745
- }
8746
- const filtered = entries.filter((entry) => entry.includes(`[skill:${skillName}]`));
8747
- return (0, import_types.Ok)(filtered);
8748
- } catch (error) {
8749
- return (0, import_types.Err)(
8750
- new Error(
8751
- `Failed to load learnings: ${error instanceof Error ? error.message : String(error)}`
8752
- )
8753
- );
8754
- }
8755
- }
8783
+
8784
+ // src/state/learnings-lifecycle.ts
8785
+ var fs14 = __toESM(require("fs"));
8786
+ var path11 = __toESM(require("path"));
8756
8787
  async function archiveLearnings(projectPath, entries, stream) {
8757
8788
  try {
8758
8789
  const dirResult = await getStateDir(projectPath, stream);
8759
8790
  if (!dirResult.ok) return dirResult;
8760
8791
  const stateDir = dirResult.value;
8761
- const archiveDir = path8.join(stateDir, "learnings-archive");
8762
- fs11.mkdirSync(archiveDir, { recursive: true });
8792
+ const archiveDir = path11.join(stateDir, "learnings-archive");
8793
+ fs14.mkdirSync(archiveDir, { recursive: true });
8763
8794
  const now = /* @__PURE__ */ new Date();
8764
8795
  const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
8765
- const archivePath = path8.join(archiveDir, `${yearMonth}.md`);
8796
+ const archivePath = path11.join(archiveDir, `${yearMonth}.md`);
8766
8797
  const archiveContent = entries.join("\n\n") + "\n";
8767
- if (fs11.existsSync(archivePath)) {
8768
- fs11.appendFileSync(archivePath, "\n" + archiveContent);
8798
+ if (fs14.existsSync(archivePath)) {
8799
+ fs14.appendFileSync(archivePath, "\n" + archiveContent);
8769
8800
  } else {
8770
- fs11.writeFileSync(archivePath, `# Learnings Archive
8801
+ fs14.writeFileSync(archivePath, `# Learnings Archive
8771
8802
 
8772
8803
  ${archiveContent}`);
8773
8804
  }
@@ -8785,8 +8816,8 @@ async function pruneLearnings(projectPath, stream) {
8785
8816
  const dirResult = await getStateDir(projectPath, stream);
8786
8817
  if (!dirResult.ok) return dirResult;
8787
8818
  const stateDir = dirResult.value;
8788
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8789
- if (!fs11.existsSync(learningsPath)) {
8819
+ const learningsPath = path11.join(stateDir, LEARNINGS_FILE);
8820
+ if (!fs14.existsSync(learningsPath)) {
8790
8821
  return (0, import_types.Ok)({ kept: 0, archived: 0, patterns: [] });
8791
8822
  }
8792
8823
  const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
@@ -8817,8 +8848,8 @@ async function pruneLearnings(projectPath, stream) {
8817
8848
  if (!archiveResult.ok) return archiveResult;
8818
8849
  }
8819
8850
  const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
8820
- fs11.writeFileSync(learningsPath, newContent);
8821
- learningsCacheMap.delete(learningsPath);
8851
+ fs14.writeFileSync(learningsPath, newContent);
8852
+ invalidateLearningsCacheEntry(learningsPath);
8822
8853
  return (0, import_types.Ok)({
8823
8854
  kept: toKeep.length,
8824
8855
  archived: toArchive.length,
@@ -8862,21 +8893,21 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
8862
8893
  const dirResult = await getStateDir(projectPath, stream);
8863
8894
  if (!dirResult.ok) return dirResult;
8864
8895
  const stateDir = dirResult.value;
8865
- const globalPath = path8.join(stateDir, LEARNINGS_FILE);
8866
- const existingGlobal = fs11.existsSync(globalPath) ? fs11.readFileSync(globalPath, "utf-8") : "";
8896
+ const globalPath = path11.join(stateDir, LEARNINGS_FILE);
8897
+ const existingGlobal = fs14.existsSync(globalPath) ? fs14.readFileSync(globalPath, "utf-8") : "";
8867
8898
  const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
8868
8899
  if (newEntries.length === 0) {
8869
8900
  return (0, import_types.Ok)({ promoted: 0, skipped: skipped + toPromote.length });
8870
8901
  }
8871
8902
  const promotedContent = newEntries.join("\n\n") + "\n";
8872
8903
  if (!existingGlobal) {
8873
- fs11.writeFileSync(globalPath, `# Learnings
8904
+ fs14.writeFileSync(globalPath, `# Learnings
8874
8905
 
8875
8906
  ${promotedContent}`);
8876
8907
  } else {
8877
- fs11.appendFileSync(globalPath, "\n\n" + promotedContent);
8908
+ fs14.appendFileSync(globalPath, "\n\n" + promotedContent);
8878
8909
  }
8879
- learningsCacheMap.delete(globalPath);
8910
+ invalidateLearningsCacheEntry(globalPath);
8880
8911
  return (0, import_types.Ok)({
8881
8912
  promoted: newEntries.length,
8882
8913
  skipped: skipped + (toPromote.length - newEntries.length)
@@ -8896,8 +8927,8 @@ async function countLearningEntries(projectPath, stream) {
8896
8927
  }
8897
8928
 
8898
8929
  // src/state/failures.ts
8899
- var fs12 = __toESM(require("fs"));
8900
- var path9 = __toESM(require("path"));
8930
+ var fs15 = __toESM(require("fs"));
8931
+ var path12 = __toESM(require("path"));
8901
8932
  var failuresCacheMap = /* @__PURE__ */ new Map();
8902
8933
  function clearFailuresCache() {
8903
8934
  failuresCacheMap.clear();
@@ -8908,17 +8939,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
8908
8939
  const dirResult = await getStateDir(projectPath, stream, session);
8909
8940
  if (!dirResult.ok) return dirResult;
8910
8941
  const stateDir = dirResult.value;
8911
- const failuresPath = path9.join(stateDir, FAILURES_FILE);
8912
- fs12.mkdirSync(stateDir, { recursive: true });
8942
+ const failuresPath = path12.join(stateDir, FAILURES_FILE);
8943
+ fs15.mkdirSync(stateDir, { recursive: true });
8913
8944
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8914
8945
  const entry = `
8915
8946
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
8916
8947
  `;
8917
- if (!fs12.existsSync(failuresPath)) {
8918
- fs12.writeFileSync(failuresPath, `# Failures
8948
+ if (!fs15.existsSync(failuresPath)) {
8949
+ fs15.writeFileSync(failuresPath, `# Failures
8919
8950
  ${entry}`);
8920
8951
  } else {
8921
- fs12.appendFileSync(failuresPath, entry);
8952
+ fs15.appendFileSync(failuresPath, entry);
8922
8953
  }
8923
8954
  failuresCacheMap.delete(failuresPath);
8924
8955
  return (0, import_types.Ok)(void 0);
@@ -8935,17 +8966,17 @@ async function loadFailures(projectPath, stream, session) {
8935
8966
  const dirResult = await getStateDir(projectPath, stream, session);
8936
8967
  if (!dirResult.ok) return dirResult;
8937
8968
  const stateDir = dirResult.value;
8938
- const failuresPath = path9.join(stateDir, FAILURES_FILE);
8939
- if (!fs12.existsSync(failuresPath)) {
8969
+ const failuresPath = path12.join(stateDir, FAILURES_FILE);
8970
+ if (!fs15.existsSync(failuresPath)) {
8940
8971
  return (0, import_types.Ok)([]);
8941
8972
  }
8942
- const stats = fs12.statSync(failuresPath);
8973
+ const stats = fs15.statSync(failuresPath);
8943
8974
  const cacheKey = failuresPath;
8944
8975
  const cached = failuresCacheMap.get(cacheKey);
8945
8976
  if (cached && cached.mtimeMs === stats.mtimeMs) {
8946
8977
  return (0, import_types.Ok)(cached.entries);
8947
8978
  }
8948
- const content = fs12.readFileSync(failuresPath, "utf-8");
8979
+ const content = fs15.readFileSync(failuresPath, "utf-8");
8949
8980
  const entries = [];
8950
8981
  for (const line of content.split("\n")) {
8951
8982
  const match = line.match(FAILURE_LINE_REGEX);
@@ -8974,20 +9005,20 @@ async function archiveFailures(projectPath, stream, session) {
8974
9005
  const dirResult = await getStateDir(projectPath, stream, session);
8975
9006
  if (!dirResult.ok) return dirResult;
8976
9007
  const stateDir = dirResult.value;
8977
- const failuresPath = path9.join(stateDir, FAILURES_FILE);
8978
- if (!fs12.existsSync(failuresPath)) {
9008
+ const failuresPath = path12.join(stateDir, FAILURES_FILE);
9009
+ if (!fs15.existsSync(failuresPath)) {
8979
9010
  return (0, import_types.Ok)(void 0);
8980
9011
  }
8981
- const archiveDir = path9.join(stateDir, "archive");
8982
- fs12.mkdirSync(archiveDir, { recursive: true });
9012
+ const archiveDir = path12.join(stateDir, "archive");
9013
+ fs15.mkdirSync(archiveDir, { recursive: true });
8983
9014
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8984
9015
  let archiveName = `failures-${date}.md`;
8985
9016
  let counter = 2;
8986
- while (fs12.existsSync(path9.join(archiveDir, archiveName))) {
9017
+ while (fs15.existsSync(path12.join(archiveDir, archiveName))) {
8987
9018
  archiveName = `failures-${date}-${counter}.md`;
8988
9019
  counter++;
8989
9020
  }
8990
- fs12.renameSync(failuresPath, path9.join(archiveDir, archiveName));
9021
+ fs15.renameSync(failuresPath, path12.join(archiveDir, archiveName));
8991
9022
  failuresCacheMap.delete(failuresPath);
8992
9023
  return (0, import_types.Ok)(void 0);
8993
9024
  } catch (error) {
@@ -9000,16 +9031,16 @@ async function archiveFailures(projectPath, stream, session) {
9000
9031
  }
9001
9032
 
9002
9033
  // src/state/handoff.ts
9003
- var fs13 = __toESM(require("fs"));
9004
- var path10 = __toESM(require("path"));
9034
+ var fs16 = __toESM(require("fs"));
9035
+ var path13 = __toESM(require("path"));
9005
9036
  async function saveHandoff(projectPath, handoff, stream, session) {
9006
9037
  try {
9007
9038
  const dirResult = await getStateDir(projectPath, stream, session);
9008
9039
  if (!dirResult.ok) return dirResult;
9009
9040
  const stateDir = dirResult.value;
9010
- const handoffPath = path10.join(stateDir, HANDOFF_FILE);
9011
- fs13.mkdirSync(stateDir, { recursive: true });
9012
- fs13.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
9041
+ const handoffPath = path13.join(stateDir, HANDOFF_FILE);
9042
+ fs16.mkdirSync(stateDir, { recursive: true });
9043
+ fs16.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
9013
9044
  return (0, import_types.Ok)(void 0);
9014
9045
  } catch (error) {
9015
9046
  return (0, import_types.Err)(
@@ -9022,11 +9053,11 @@ async function loadHandoff(projectPath, stream, session) {
9022
9053
  const dirResult = await getStateDir(projectPath, stream, session);
9023
9054
  if (!dirResult.ok) return dirResult;
9024
9055
  const stateDir = dirResult.value;
9025
- const handoffPath = path10.join(stateDir, HANDOFF_FILE);
9026
- if (!fs13.existsSync(handoffPath)) {
9056
+ const handoffPath = path13.join(stateDir, HANDOFF_FILE);
9057
+ if (!fs16.existsSync(handoffPath)) {
9027
9058
  return (0, import_types.Ok)(null);
9028
9059
  }
9029
- const raw = fs13.readFileSync(handoffPath, "utf-8");
9060
+ const raw = fs16.readFileSync(handoffPath, "utf-8");
9030
9061
  const parsed = JSON.parse(raw);
9031
9062
  const result = HandoffSchema.safeParse(parsed);
9032
9063
  if (!result.success) {
@@ -9041,33 +9072,33 @@ async function loadHandoff(projectPath, stream, session) {
9041
9072
  }
9042
9073
 
9043
9074
  // src/state/mechanical-gate.ts
9044
- var fs14 = __toESM(require("fs"));
9045
- var path11 = __toESM(require("path"));
9075
+ var fs17 = __toESM(require("fs"));
9076
+ var path14 = __toESM(require("path"));
9046
9077
  var import_child_process2 = require("child_process");
9047
9078
  var SAFE_GATE_COMMAND = /^(?:npm|pnpm|yarn)\s+(?:test|run\s+[\w.-]+|run-script\s+[\w.-]+)$|^go\s+(?:test|build|vet|fmt)\s+[\w./ -]+$|^(?:python|python3)\s+-m\s+[\w.-]+$|^make\s+[\w.-]+$|^cargo\s+(?:test|build|check|clippy)(?:\s+[\w./ -]+)?$|^(?:gradle|mvn)\s+[\w:.-]+$/;
9048
9079
  function loadChecksFromConfig(gateConfigPath) {
9049
- if (!fs14.existsSync(gateConfigPath)) return [];
9050
- const raw = JSON.parse(fs14.readFileSync(gateConfigPath, "utf-8"));
9080
+ if (!fs17.existsSync(gateConfigPath)) return [];
9081
+ const raw = JSON.parse(fs17.readFileSync(gateConfigPath, "utf-8"));
9051
9082
  const config = GateConfigSchema.safeParse(raw);
9052
9083
  if (config.success && config.data.checks) return config.data.checks;
9053
9084
  return [];
9054
9085
  }
9055
9086
  function discoverChecksFromProject(projectPath) {
9056
9087
  const checks = [];
9057
- const packageJsonPath = path11.join(projectPath, "package.json");
9058
- if (fs14.existsSync(packageJsonPath)) {
9059
- const pkg = JSON.parse(fs14.readFileSync(packageJsonPath, "utf-8"));
9088
+ const packageJsonPath = path14.join(projectPath, "package.json");
9089
+ if (fs17.existsSync(packageJsonPath)) {
9090
+ const pkg = JSON.parse(fs17.readFileSync(packageJsonPath, "utf-8"));
9060
9091
  const scripts = pkg.scripts || {};
9061
9092
  if (scripts.test) checks.push({ name: "test", command: "npm test" });
9062
9093
  if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
9063
9094
  if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
9064
9095
  if (scripts.build) checks.push({ name: "build", command: "npm run build" });
9065
9096
  }
9066
- if (fs14.existsSync(path11.join(projectPath, "go.mod"))) {
9097
+ if (fs17.existsSync(path14.join(projectPath, "go.mod"))) {
9067
9098
  checks.push({ name: "test", command: "go test ./..." });
9068
9099
  checks.push({ name: "build", command: "go build ./..." });
9069
9100
  }
9070
- if (fs14.existsSync(path11.join(projectPath, "pyproject.toml")) || fs14.existsSync(path11.join(projectPath, "setup.py"))) {
9101
+ if (fs17.existsSync(path14.join(projectPath, "pyproject.toml")) || fs17.existsSync(path14.join(projectPath, "setup.py"))) {
9071
9102
  checks.push({ name: "test", command: "python -m pytest" });
9072
9103
  }
9073
9104
  return checks;
@@ -9107,8 +9138,8 @@ function executeCheck(check, projectPath) {
9107
9138
  }
9108
9139
  }
9109
9140
  async function runMechanicalGate(projectPath) {
9110
- const harnessDir = path11.join(projectPath, HARNESS_DIR);
9111
- const gateConfigPath = path11.join(harnessDir, GATE_CONFIG_FILE);
9141
+ const harnessDir = path14.join(projectPath, HARNESS_DIR);
9142
+ const gateConfigPath = path14.join(harnessDir, GATE_CONFIG_FILE);
9112
9143
  try {
9113
9144
  let checks = loadChecksFromConfig(gateConfigPath);
9114
9145
  if (checks.length === 0) {
@@ -9129,8 +9160,8 @@ async function runMechanicalGate(projectPath) {
9129
9160
  }
9130
9161
 
9131
9162
  // src/state/session-summary.ts
9132
- var fs15 = __toESM(require("fs"));
9133
- var path12 = __toESM(require("path"));
9163
+ var fs18 = __toESM(require("fs"));
9164
+ var path15 = __toESM(require("path"));
9134
9165
  function formatSummary(data) {
9135
9166
  const lines = [
9136
9167
  "## Session Summary",
@@ -9168,9 +9199,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
9168
9199
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
9169
9200
  if (!dirResult.ok) return dirResult;
9170
9201
  const sessionDir = dirResult.value;
9171
- const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
9202
+ const summaryPath = path15.join(sessionDir, SUMMARY_FILE);
9172
9203
  const content = formatSummary(data);
9173
- fs15.writeFileSync(summaryPath, content);
9204
+ fs18.writeFileSync(summaryPath, content);
9174
9205
  const description = deriveIndexDescription(data);
9175
9206
  updateSessionIndex(projectPath, sessionSlug, description);
9176
9207
  return (0, import_types.Ok)(void 0);
@@ -9187,11 +9218,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
9187
9218
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9188
9219
  if (!dirResult.ok) return dirResult;
9189
9220
  const sessionDir = dirResult.value;
9190
- const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
9191
- if (!fs15.existsSync(summaryPath)) {
9221
+ const summaryPath = path15.join(sessionDir, SUMMARY_FILE);
9222
+ if (!fs18.existsSync(summaryPath)) {
9192
9223
  return (0, import_types.Ok)(null);
9193
9224
  }
9194
- const content = fs15.readFileSync(summaryPath, "utf-8");
9225
+ const content = fs18.readFileSync(summaryPath, "utf-8");
9195
9226
  return (0, import_types.Ok)(content);
9196
9227
  } catch (error) {
9197
9228
  return (0, import_types.Err)(
@@ -9203,11 +9234,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
9203
9234
  }
9204
9235
  function listActiveSessions(projectPath) {
9205
9236
  try {
9206
- const indexPath2 = path12.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
9207
- if (!fs15.existsSync(indexPath2)) {
9237
+ const indexPath2 = path15.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
9238
+ if (!fs18.existsSync(indexPath2)) {
9208
9239
  return (0, import_types.Ok)(null);
9209
9240
  }
9210
- const content = fs15.readFileSync(indexPath2, "utf-8");
9241
+ const content = fs18.readFileSync(indexPath2, "utf-8");
9211
9242
  return (0, import_types.Ok)(content);
9212
9243
  } catch (error) {
9213
9244
  return (0, import_types.Err)(
@@ -9219,8 +9250,8 @@ function listActiveSessions(projectPath) {
9219
9250
  }
9220
9251
 
9221
9252
  // src/state/session-sections.ts
9222
- var fs16 = __toESM(require("fs"));
9223
- var path13 = __toESM(require("path"));
9253
+ var fs19 = __toESM(require("fs"));
9254
+ var path16 = __toESM(require("path"));
9224
9255
  var import_types19 = require("@harness-engineering/types");
9225
9256
  function emptySections() {
9226
9257
  const sections = {};
@@ -9233,12 +9264,12 @@ async function loadSessionState(projectPath, sessionSlug) {
9233
9264
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9234
9265
  if (!dirResult.ok) return dirResult;
9235
9266
  const sessionDir = dirResult.value;
9236
- const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
9237
- if (!fs16.existsSync(filePath)) {
9267
+ const filePath = path16.join(sessionDir, SESSION_STATE_FILE);
9268
+ if (!fs19.existsSync(filePath)) {
9238
9269
  return (0, import_types.Ok)(emptySections());
9239
9270
  }
9240
9271
  try {
9241
- const raw = fs16.readFileSync(filePath, "utf-8");
9272
+ const raw = fs19.readFileSync(filePath, "utf-8");
9242
9273
  const parsed = JSON.parse(raw);
9243
9274
  const sections = emptySections();
9244
9275
  for (const name of import_types19.SESSION_SECTION_NAMES) {
@@ -9259,9 +9290,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
9259
9290
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
9260
9291
  if (!dirResult.ok) return dirResult;
9261
9292
  const sessionDir = dirResult.value;
9262
- const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
9293
+ const filePath = path16.join(sessionDir, SESSION_STATE_FILE);
9263
9294
  try {
9264
- fs16.writeFileSync(filePath, JSON.stringify(sections, null, 2));
9295
+ fs19.writeFileSync(filePath, JSON.stringify(sections, null, 2));
9265
9296
  return (0, import_types.Ok)(void 0);
9266
9297
  } catch (error) {
9267
9298
  return (0, import_types.Err)(
@@ -9315,32 +9346,32 @@ function generateEntryId() {
9315
9346
  }
9316
9347
 
9317
9348
  // src/state/session-archive.ts
9318
- var fs17 = __toESM(require("fs"));
9319
- var path14 = __toESM(require("path"));
9349
+ var fs20 = __toESM(require("fs"));
9350
+ var path17 = __toESM(require("path"));
9320
9351
  async function archiveSession(projectPath, sessionSlug) {
9321
9352
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9322
9353
  if (!dirResult.ok) return dirResult;
9323
9354
  const sessionDir = dirResult.value;
9324
- if (!fs17.existsSync(sessionDir)) {
9355
+ if (!fs20.existsSync(sessionDir)) {
9325
9356
  return (0, import_types.Err)(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
9326
9357
  }
9327
- const archiveBase = path14.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
9358
+ const archiveBase = path17.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
9328
9359
  try {
9329
- fs17.mkdirSync(archiveBase, { recursive: true });
9360
+ fs20.mkdirSync(archiveBase, { recursive: true });
9330
9361
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9331
9362
  let archiveName = `${sessionSlug}-${date}`;
9332
9363
  let counter = 1;
9333
- while (fs17.existsSync(path14.join(archiveBase, archiveName))) {
9364
+ while (fs20.existsSync(path17.join(archiveBase, archiveName))) {
9334
9365
  archiveName = `${sessionSlug}-${date}-${counter}`;
9335
9366
  counter++;
9336
9367
  }
9337
- const dest = path14.join(archiveBase, archiveName);
9368
+ const dest = path17.join(archiveBase, archiveName);
9338
9369
  try {
9339
- fs17.renameSync(sessionDir, dest);
9370
+ fs20.renameSync(sessionDir, dest);
9340
9371
  } catch (renameErr) {
9341
9372
  if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
9342
- fs17.cpSync(sessionDir, dest, { recursive: true });
9343
- fs17.rmSync(sessionDir, { recursive: true });
9373
+ fs20.cpSync(sessionDir, dest, { recursive: true });
9374
+ fs20.rmSync(sessionDir, { recursive: true });
9344
9375
  } else {
9345
9376
  throw renameErr;
9346
9377
  }
@@ -9356,8 +9387,8 @@ async function archiveSession(projectPath, sessionSlug) {
9356
9387
  }
9357
9388
 
9358
9389
  // src/state/events.ts
9359
- var fs18 = __toESM(require("fs"));
9360
- var path15 = __toESM(require("path"));
9390
+ var fs21 = __toESM(require("fs"));
9391
+ var path18 = __toESM(require("path"));
9361
9392
  var import_zod8 = require("zod");
9362
9393
  var SkillEventSchema = import_zod8.z.object({
9363
9394
  timestamp: import_zod8.z.string(),
@@ -9378,8 +9409,8 @@ function loadKnownHashes(eventsPath) {
9378
9409
  const cached = knownHashesCache.get(eventsPath);
9379
9410
  if (cached) return cached;
9380
9411
  const hashes = /* @__PURE__ */ new Set();
9381
- if (fs18.existsSync(eventsPath)) {
9382
- const content = fs18.readFileSync(eventsPath, "utf-8");
9412
+ if (fs21.existsSync(eventsPath)) {
9413
+ const content = fs21.readFileSync(eventsPath, "utf-8");
9383
9414
  const lines = content.split("\n").filter((line) => line.trim() !== "");
9384
9415
  for (const line of lines) {
9385
9416
  try {
@@ -9402,8 +9433,8 @@ async function emitEvent(projectPath, event, options) {
9402
9433
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
9403
9434
  if (!dirResult.ok) return dirResult;
9404
9435
  const stateDir = dirResult.value;
9405
- const eventsPath = path15.join(stateDir, EVENTS_FILE);
9406
- fs18.mkdirSync(stateDir, { recursive: true });
9436
+ const eventsPath = path18.join(stateDir, EVENTS_FILE);
9437
+ fs21.mkdirSync(stateDir, { recursive: true });
9407
9438
  const contentHash = computeEventHash(event, options?.session);
9408
9439
  const knownHashes = loadKnownHashes(eventsPath);
9409
9440
  if (knownHashes.has(contentHash)) {
@@ -9417,7 +9448,7 @@ async function emitEvent(projectPath, event, options) {
9417
9448
  if (options?.session) {
9418
9449
  fullEvent.session = options.session;
9419
9450
  }
9420
- fs18.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
9451
+ fs21.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
9421
9452
  knownHashes.add(contentHash);
9422
9453
  return (0, import_types.Ok)({ written: true });
9423
9454
  } catch (error) {
@@ -9431,11 +9462,11 @@ async function loadEvents(projectPath, options) {
9431
9462
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
9432
9463
  if (!dirResult.ok) return dirResult;
9433
9464
  const stateDir = dirResult.value;
9434
- const eventsPath = path15.join(stateDir, EVENTS_FILE);
9435
- if (!fs18.existsSync(eventsPath)) {
9465
+ const eventsPath = path18.join(stateDir, EVENTS_FILE);
9466
+ if (!fs21.existsSync(eventsPath)) {
9436
9467
  return (0, import_types.Ok)([]);
9437
9468
  }
9438
- const content = fs18.readFileSync(eventsPath, "utf-8");
9469
+ const content = fs21.readFileSync(eventsPath, "utf-8");
9439
9470
  const lines = content.split("\n").filter((line) => line.trim() !== "");
9440
9471
  const events = [];
9441
9472
  for (const line of lines) {
@@ -9649,7 +9680,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
9649
9680
  }
9650
9681
 
9651
9682
  // src/security/scanner.ts
9652
- var fs20 = __toESM(require("fs/promises"));
9683
+ var fs23 = __toESM(require("fs/promises"));
9653
9684
  var import_minimatch5 = require("minimatch");
9654
9685
 
9655
9686
  // src/security/rules/registry.ts
@@ -9737,15 +9768,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
9737
9768
  }
9738
9769
 
9739
9770
  // src/security/stack-detector.ts
9740
- var fs19 = __toESM(require("fs"));
9741
- var path16 = __toESM(require("path"));
9771
+ var fs22 = __toESM(require("fs"));
9772
+ var path19 = __toESM(require("path"));
9742
9773
  function detectStack(projectRoot) {
9743
9774
  const stacks = [];
9744
- const pkgJsonPath = path16.join(projectRoot, "package.json");
9745
- if (fs19.existsSync(pkgJsonPath)) {
9775
+ const pkgJsonPath = path19.join(projectRoot, "package.json");
9776
+ if (fs22.existsSync(pkgJsonPath)) {
9746
9777
  stacks.push("node");
9747
9778
  try {
9748
- const pkgJson = JSON.parse(fs19.readFileSync(pkgJsonPath, "utf-8"));
9779
+ const pkgJson = JSON.parse(fs22.readFileSync(pkgJsonPath, "utf-8"));
9749
9780
  const allDeps = {
9750
9781
  ...pkgJson.dependencies,
9751
9782
  ...pkgJson.devDependencies
@@ -9760,13 +9791,13 @@ function detectStack(projectRoot) {
9760
9791
  } catch {
9761
9792
  }
9762
9793
  }
9763
- const goModPath = path16.join(projectRoot, "go.mod");
9764
- if (fs19.existsSync(goModPath)) {
9794
+ const goModPath = path19.join(projectRoot, "go.mod");
9795
+ if (fs22.existsSync(goModPath)) {
9765
9796
  stacks.push("go");
9766
9797
  }
9767
- const requirementsPath = path16.join(projectRoot, "requirements.txt");
9768
- const pyprojectPath = path16.join(projectRoot, "pyproject.toml");
9769
- if (fs19.existsSync(requirementsPath) || fs19.existsSync(pyprojectPath)) {
9798
+ const requirementsPath = path19.join(projectRoot, "requirements.txt");
9799
+ const pyprojectPath = path19.join(projectRoot, "pyproject.toml");
9800
+ if (fs22.existsSync(requirementsPath) || fs22.existsSync(pyprojectPath)) {
9770
9801
  stacks.push("python");
9771
9802
  }
9772
9803
  return stacks;
@@ -10597,7 +10628,7 @@ var SecurityScanner = class {
10597
10628
  }
10598
10629
  async scanFile(filePath) {
10599
10630
  if (!this.config.enabled) return [];
10600
- const content = await fs20.readFile(filePath, "utf-8");
10631
+ const content = await fs23.readFile(filePath, "utf-8");
10601
10632
  return this.scanContentForFile(content, filePath, 1);
10602
10633
  }
10603
10634
  scanContentForFile(content, filePath, startLine = 1) {
@@ -11100,7 +11131,7 @@ function mapSecurityFindings(secFindings, existing) {
11100
11131
  }
11101
11132
 
11102
11133
  // src/ci/check-orchestrator.ts
11103
- var path17 = __toESM(require("path"));
11134
+ var path20 = __toESM(require("path"));
11104
11135
  var import_graph = require("@harness-engineering/graph");
11105
11136
  var ALL_CHECKS = [
11106
11137
  "validate",
@@ -11115,7 +11146,7 @@ var ALL_CHECKS = [
11115
11146
  ];
11116
11147
  async function runValidateCheck(projectRoot, config) {
11117
11148
  const issues = [];
11118
- const agentsPath = path17.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
11149
+ const agentsPath = path20.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
11119
11150
  const result = await validateAgentsMap(agentsPath);
11120
11151
  if (!result.ok) {
11121
11152
  issues.push({ severity: "error", message: result.error.message });
@@ -11172,7 +11203,7 @@ async function runDepsCheck(projectRoot, config) {
11172
11203
  }
11173
11204
  async function runDocsCheck(projectRoot, config) {
11174
11205
  const issues = [];
11175
- const docsDir = path17.join(projectRoot, config.docsDir ?? "docs");
11206
+ const docsDir = path20.join(projectRoot, config.docsDir ?? "docs");
11176
11207
  const entropyConfig = config.entropy || {};
11177
11208
  const result = await checkDocCoverage("project", {
11178
11209
  docsDir,
@@ -11357,7 +11388,7 @@ async function runTraceabilityCheck(projectRoot, config) {
11357
11388
  const issues = [];
11358
11389
  const traceConfig = config.traceability || {};
11359
11390
  if (traceConfig.enabled === false) return issues;
11360
- const graphDir = path17.join(projectRoot, ".harness", "graph");
11391
+ const graphDir = path20.join(projectRoot, ".harness", "graph");
11361
11392
  const store = new import_graph.GraphStore();
11362
11393
  const loaded = await store.load(graphDir);
11363
11394
  if (!loaded) {
@@ -11486,7 +11517,7 @@ async function runCIChecks(input) {
11486
11517
  }
11487
11518
 
11488
11519
  // src/review/mechanical-checks.ts
11489
- var path18 = __toESM(require("path"));
11520
+ var path21 = __toESM(require("path"));
11490
11521
  async function runMechanicalChecks(options) {
11491
11522
  const { projectRoot, config, skip = [], changedFiles } = options;
11492
11523
  const findings = [];
@@ -11498,7 +11529,7 @@ async function runMechanicalChecks(options) {
11498
11529
  };
11499
11530
  if (!skip.includes("validate")) {
11500
11531
  try {
11501
- const agentsPath = path18.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
11532
+ const agentsPath = path21.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
11502
11533
  const result = await validateAgentsMap(agentsPath);
11503
11534
  if (!result.ok) {
11504
11535
  statuses.validate = "fail";
@@ -11535,7 +11566,7 @@ async function runMechanicalChecks(options) {
11535
11566
  statuses.validate = "fail";
11536
11567
  findings.push({
11537
11568
  tool: "validate",
11538
- file: path18.join(projectRoot, "AGENTS.md"),
11569
+ file: path21.join(projectRoot, "AGENTS.md"),
11539
11570
  message: err instanceof Error ? err.message : String(err),
11540
11571
  severity: "error"
11541
11572
  });
@@ -11599,7 +11630,7 @@ async function runMechanicalChecks(options) {
11599
11630
  (async () => {
11600
11631
  const localFindings = [];
11601
11632
  try {
11602
- const docsDir = path18.join(projectRoot, config.docsDir ?? "docs");
11633
+ const docsDir = path21.join(projectRoot, config.docsDir ?? "docs");
11603
11634
  const result = await checkDocCoverage("project", { docsDir });
11604
11635
  if (!result.ok) {
11605
11636
  statuses["check-docs"] = "warn";
@@ -11626,7 +11657,7 @@ async function runMechanicalChecks(options) {
11626
11657
  statuses["check-docs"] = "warn";
11627
11658
  localFindings.push({
11628
11659
  tool: "check-docs",
11629
- file: path18.join(projectRoot, "docs"),
11660
+ file: path21.join(projectRoot, "docs"),
11630
11661
  message: err instanceof Error ? err.message : String(err),
11631
11662
  severity: "warning"
11632
11663
  });
@@ -11774,7 +11805,7 @@ function detectChangeType(commitMessage, diff2) {
11774
11805
  }
11775
11806
 
11776
11807
  // src/review/context-scoper.ts
11777
- var path19 = __toESM(require("path"));
11808
+ var path22 = __toESM(require("path"));
11778
11809
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
11779
11810
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
11780
11811
  function computeContextBudget(diffLines) {
@@ -11782,18 +11813,18 @@ function computeContextBudget(diffLines) {
11782
11813
  return diffLines;
11783
11814
  }
11784
11815
  function isWithinProject(absPath, projectRoot) {
11785
- const resolvedRoot = path19.resolve(projectRoot) + path19.sep;
11786
- const resolvedPath = path19.resolve(absPath);
11787
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path19.resolve(projectRoot);
11816
+ const resolvedRoot = path22.resolve(projectRoot) + path22.sep;
11817
+ const resolvedPath = path22.resolve(absPath);
11818
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path22.resolve(projectRoot);
11788
11819
  }
11789
11820
  async function readContextFile(projectRoot, filePath, reason) {
11790
- const absPath = path19.isAbsolute(filePath) ? filePath : path19.join(projectRoot, filePath);
11821
+ const absPath = path22.isAbsolute(filePath) ? filePath : path22.join(projectRoot, filePath);
11791
11822
  if (!isWithinProject(absPath, projectRoot)) return null;
11792
11823
  const result = await readFileContent(absPath);
11793
11824
  if (!result.ok) return null;
11794
11825
  const content = result.value;
11795
11826
  const lines = content.split("\n").length;
11796
- const relPath = path19.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
11827
+ const relPath = path22.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
11797
11828
  return { path: relPath, content, reason, lines };
11798
11829
  }
11799
11830
  function extractImportSources2(content) {
@@ -11808,18 +11839,18 @@ function extractImportSources2(content) {
11808
11839
  }
11809
11840
  async function resolveImportPath2(projectRoot, fromFile, importSource) {
11810
11841
  if (!importSource.startsWith(".")) return null;
11811
- const fromDir = path19.dirname(path19.join(projectRoot, fromFile));
11812
- const basePath = path19.resolve(fromDir, importSource);
11842
+ const fromDir = path22.dirname(path22.join(projectRoot, fromFile));
11843
+ const basePath = path22.resolve(fromDir, importSource);
11813
11844
  if (!isWithinProject(basePath, projectRoot)) return null;
11814
11845
  const relBase = relativePosix(projectRoot, basePath);
11815
11846
  const candidates = [
11816
11847
  relBase + ".ts",
11817
11848
  relBase + ".tsx",
11818
11849
  relBase + ".mts",
11819
- path19.join(relBase, "index.ts")
11850
+ path22.join(relBase, "index.ts")
11820
11851
  ];
11821
11852
  for (const candidate of candidates) {
11822
- const absCandidate = path19.join(projectRoot, candidate);
11853
+ const absCandidate = path22.join(projectRoot, candidate);
11823
11854
  if (await fileExists(absCandidate)) {
11824
11855
  return candidate;
11825
11856
  }
@@ -11827,7 +11858,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
11827
11858
  return null;
11828
11859
  }
11829
11860
  async function findTestFiles(projectRoot, sourceFile) {
11830
- const baseName = path19.basename(sourceFile, path19.extname(sourceFile));
11861
+ const baseName = path22.basename(sourceFile, path22.extname(sourceFile));
11831
11862
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
11832
11863
  const results = await findFiles(pattern, projectRoot);
11833
11864
  return results.map((f) => relativePosix(projectRoot, f));
@@ -12636,7 +12667,7 @@ async function fanOutReview(options) {
12636
12667
  }
12637
12668
 
12638
12669
  // src/review/validate-findings.ts
12639
- var path20 = __toESM(require("path"));
12670
+ var path23 = __toESM(require("path"));
12640
12671
  var DOWNGRADE_MAP = {
12641
12672
  critical: "important",
12642
12673
  important: "suggestion",
@@ -12657,7 +12688,7 @@ function normalizePath(filePath, projectRoot) {
12657
12688
  let normalized = filePath;
12658
12689
  normalized = normalized.replace(/\\/g, "/");
12659
12690
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
12660
- if (path20.isAbsolute(normalized)) {
12691
+ if (path23.isAbsolute(normalized)) {
12661
12692
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
12662
12693
  if (normalized.startsWith(root)) {
12663
12694
  normalized = normalized.slice(root.length);
@@ -12682,12 +12713,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
12682
12713
  while ((match = importRegex.exec(content)) !== null) {
12683
12714
  const importPath = match[1];
12684
12715
  if (!importPath.startsWith(".")) continue;
12685
- const dir = path20.dirname(current.file);
12686
- let resolved = path20.join(dir, importPath).replace(/\\/g, "/");
12716
+ const dir = path23.dirname(current.file);
12717
+ let resolved = path23.join(dir, importPath).replace(/\\/g, "/");
12687
12718
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
12688
12719
  resolved += ".ts";
12689
12720
  }
12690
- resolved = path20.normalize(resolved).replace(/\\/g, "/");
12721
+ resolved = path23.normalize(resolved).replace(/\\/g, "/");
12691
12722
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
12692
12723
  queue.push({ file: resolved, depth: current.depth + 1 });
12693
12724
  }
@@ -12704,7 +12735,7 @@ async function validateFindings(options) {
12704
12735
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
12705
12736
  continue;
12706
12737
  }
12707
- const absoluteFile = path20.isAbsolute(finding.file) ? finding.file : path20.join(projectRoot, finding.file).replace(/\\/g, "/");
12738
+ const absoluteFile = path23.isAbsolute(finding.file) ? finding.file : path23.join(projectRoot, finding.file).replace(/\\/g, "/");
12708
12739
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
12709
12740
  continue;
12710
12741
  }
@@ -13387,8 +13418,8 @@ function serializeAssignmentHistory(records) {
13387
13418
  }
13388
13419
 
13389
13420
  // src/roadmap/sync.ts
13390
- var fs21 = __toESM(require("fs"));
13391
- var path21 = __toESM(require("path"));
13421
+ var fs24 = __toESM(require("fs"));
13422
+ var path24 = __toESM(require("path"));
13392
13423
  var import_types24 = require("@harness-engineering/types");
13393
13424
 
13394
13425
  // src/roadmap/status-rank.ts
@@ -13418,10 +13449,10 @@ function inferStatus(feature, projectPath, allFeatures) {
13418
13449
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
13419
13450
  const useRootState = featuresWithPlans.length <= 1;
13420
13451
  if (useRootState) {
13421
- const rootStatePath = path21.join(projectPath, ".harness", "state.json");
13422
- if (fs21.existsSync(rootStatePath)) {
13452
+ const rootStatePath = path24.join(projectPath, ".harness", "state.json");
13453
+ if (fs24.existsSync(rootStatePath)) {
13423
13454
  try {
13424
- const raw = fs21.readFileSync(rootStatePath, "utf-8");
13455
+ const raw = fs24.readFileSync(rootStatePath, "utf-8");
13425
13456
  const state = JSON.parse(raw);
13426
13457
  if (state.progress) {
13427
13458
  for (const status of Object.values(state.progress)) {
@@ -13432,16 +13463,16 @@ function inferStatus(feature, projectPath, allFeatures) {
13432
13463
  }
13433
13464
  }
13434
13465
  }
13435
- const sessionsDir = path21.join(projectPath, ".harness", "sessions");
13436
- if (fs21.existsSync(sessionsDir)) {
13466
+ const sessionsDir = path24.join(projectPath, ".harness", "sessions");
13467
+ if (fs24.existsSync(sessionsDir)) {
13437
13468
  try {
13438
- const sessionDirs = fs21.readdirSync(sessionsDir, { withFileTypes: true });
13469
+ const sessionDirs = fs24.readdirSync(sessionsDir, { withFileTypes: true });
13439
13470
  for (const entry of sessionDirs) {
13440
13471
  if (!entry.isDirectory()) continue;
13441
- const autopilotPath = path21.join(sessionsDir, entry.name, "autopilot-state.json");
13442
- if (!fs21.existsSync(autopilotPath)) continue;
13472
+ const autopilotPath = path24.join(sessionsDir, entry.name, "autopilot-state.json");
13473
+ if (!fs24.existsSync(autopilotPath)) continue;
13443
13474
  try {
13444
- const raw = fs21.readFileSync(autopilotPath, "utf-8");
13475
+ const raw = fs24.readFileSync(autopilotPath, "utf-8");
13445
13476
  const autopilot = JSON.parse(raw);
13446
13477
  if (!autopilot.phases) continue;
13447
13478
  const linkedPhases = autopilot.phases.filter(
@@ -13767,6 +13798,7 @@ var GitHubIssuesSyncAdapter = class {
13767
13798
  const data = await response.json();
13768
13799
  return (0, import_types25.Ok)({
13769
13800
  externalId,
13801
+ title: data.title,
13770
13802
  status: data.state,
13771
13803
  labels: data.labels.map((l) => l.name),
13772
13804
  assignee: data.assignee ? `@${data.assignee.login}` : null
@@ -13801,6 +13833,7 @@ var GitHubIssuesSyncAdapter = class {
13801
13833
  for (const issue of issues) {
13802
13834
  tickets.push({
13803
13835
  externalId: buildExternalId(this.owner, this.repo, issue.number),
13836
+ title: issue.title,
13804
13837
  status: issue.state,
13805
13838
  labels: issue.labels.map((l) => l.name),
13806
13839
  assignee: issue.assignee ? `@${issue.assignee.login}` : null
@@ -13841,47 +13874,65 @@ var GitHubIssuesSyncAdapter = class {
13841
13874
  };
13842
13875
 
13843
13876
  // src/roadmap/sync-engine.ts
13844
- var fs22 = __toESM(require("fs"));
13877
+ var fs25 = __toESM(require("fs"));
13845
13878
  function emptySyncResult() {
13846
13879
  return { created: [], updated: [], assignmentChanges: [], errors: [] };
13847
13880
  }
13848
- async function syncToExternal(roadmap, adapter, _config) {
13849
- const result = emptySyncResult();
13850
- let defaultAssignee = null;
13851
- const userResult = await adapter.getAuthenticatedUser();
13852
- if (userResult.ok) {
13853
- defaultAssignee = userResult.value;
13881
+ function buildDedupIndex(tickets, config) {
13882
+ const index = /* @__PURE__ */ new Map();
13883
+ if (!tickets) return index;
13884
+ const configLabels = new Set((config.labels ?? []).map((l) => l.toLowerCase()));
13885
+ for (const ticket of tickets) {
13886
+ const hasConfigLabels = configLabels.size === 0 || ticket.labels.some((l) => configLabels.has(l.toLowerCase()));
13887
+ if (!hasConfigLabels) continue;
13888
+ const key = ticket.title.toLowerCase();
13889
+ const prev = index.get(key);
13890
+ if (!prev || prev.status === "closed" && ticket.status === "open") {
13891
+ index.set(key, ticket);
13892
+ }
13854
13893
  }
13894
+ return index;
13895
+ }
13896
+ async function resolveExternalId(feature, milestone, adapter, dedupIndex, result) {
13897
+ if (feature.externalId) return true;
13898
+ const existing = dedupIndex.get(feature.name.toLowerCase());
13899
+ if (existing) {
13900
+ feature.externalId = existing.externalId;
13901
+ return true;
13902
+ }
13903
+ const createResult = await adapter.createTicket(feature, milestone);
13904
+ if (createResult.ok) {
13905
+ feature.externalId = createResult.value.externalId;
13906
+ result.created.push(createResult.value);
13907
+ } else {
13908
+ result.errors.push({ featureOrId: feature.name, error: createResult.error });
13909
+ }
13910
+ return false;
13911
+ }
13912
+ async function syncToExternal(roadmap, adapter, config, prefetchedTickets) {
13913
+ const result = emptySyncResult();
13914
+ const dedupIndex = buildDedupIndex(prefetchedTickets, config);
13855
13915
  for (const milestone of roadmap.milestones) {
13856
13916
  for (const feature of milestone.features) {
13857
- if (!feature.assignee && defaultAssignee) {
13858
- feature.assignee = defaultAssignee;
13859
- }
13860
- if (!feature.externalId) {
13861
- const createResult = await adapter.createTicket(feature, milestone.name);
13862
- if (createResult.ok) {
13863
- feature.externalId = createResult.value.externalId;
13864
- result.created.push(createResult.value);
13865
- } else {
13866
- result.errors.push({ featureOrId: feature.name, error: createResult.error });
13867
- }
13917
+ const shouldUpdate = await resolveExternalId(
13918
+ feature,
13919
+ milestone.name,
13920
+ adapter,
13921
+ dedupIndex,
13922
+ result
13923
+ );
13924
+ if (!shouldUpdate) continue;
13925
+ const updateResult = await adapter.updateTicket(feature.externalId, feature, milestone.name);
13926
+ if (updateResult.ok) {
13927
+ result.updated.push(feature.externalId);
13868
13928
  } else {
13869
- const updateResult = await adapter.updateTicket(
13870
- feature.externalId,
13871
- feature,
13872
- milestone.name
13873
- );
13874
- if (updateResult.ok) {
13875
- result.updated.push(feature.externalId);
13876
- } else {
13877
- result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
13878
- }
13929
+ result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
13879
13930
  }
13880
13931
  }
13881
13932
  }
13882
13933
  return result;
13883
13934
  }
13884
- async function syncFromExternal(roadmap, adapter, config, options) {
13935
+ async function syncFromExternal(roadmap, adapter, config, options, prefetchedTickets) {
13885
13936
  const result = emptySyncResult();
13886
13937
  const forceSync = options?.forceSync ?? false;
13887
13938
  const featureByExternalId = /* @__PURE__ */ new Map();
@@ -13893,12 +13944,18 @@ async function syncFromExternal(roadmap, adapter, config, options) {
13893
13944
  }
13894
13945
  }
13895
13946
  if (featureByExternalId.size === 0) return result;
13896
- const fetchResult = await adapter.fetchAllTickets();
13897
- if (!fetchResult.ok) {
13898
- result.errors.push({ featureOrId: "*", error: fetchResult.error });
13899
- return result;
13947
+ let tickets;
13948
+ if (prefetchedTickets) {
13949
+ tickets = prefetchedTickets;
13950
+ } else {
13951
+ const fetchResult = await adapter.fetchAllTickets();
13952
+ if (!fetchResult.ok) {
13953
+ result.errors.push({ featureOrId: "*", error: fetchResult.error });
13954
+ return result;
13955
+ }
13956
+ tickets = fetchResult.value;
13900
13957
  }
13901
- for (const ticketState of fetchResult.value) {
13958
+ for (const ticketState of tickets) {
13902
13959
  const feature = featureByExternalId.get(ticketState.externalId);
13903
13960
  if (!feature) continue;
13904
13961
  if (ticketState.assignee !== feature.assignee) {
@@ -13915,6 +13972,9 @@ async function syncFromExternal(roadmap, adapter, config, options) {
13915
13972
  if (!forceSync && isRegression(feature.status, newStatus)) {
13916
13973
  continue;
13917
13974
  }
13975
+ if (!forceSync && feature.status === "blocked" && newStatus === "planned") {
13976
+ continue;
13977
+ }
13918
13978
  feature.status = newStatus;
13919
13979
  }
13920
13980
  }
@@ -13929,7 +13989,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
13929
13989
  });
13930
13990
  await previousSync;
13931
13991
  try {
13932
- const raw = fs22.readFileSync(roadmapPath, "utf-8");
13992
+ const raw = fs25.readFileSync(roadmapPath, "utf-8");
13933
13993
  const parseResult = parseRoadmap(raw);
13934
13994
  if (!parseResult.ok) {
13935
13995
  return {
@@ -13938,9 +13998,11 @@ async function fullSync(roadmapPath, adapter, config, options) {
13938
13998
  };
13939
13999
  }
13940
14000
  const roadmap = parseResult.value;
13941
- const pushResult = await syncToExternal(roadmap, adapter, config);
13942
- const pullResult = await syncFromExternal(roadmap, adapter, config, options);
13943
- fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
14001
+ const fetchResult = await adapter.fetchAllTickets();
14002
+ const tickets = fetchResult.ok ? fetchResult.value : void 0;
14003
+ const pushResult = await syncToExternal(roadmap, adapter, config, tickets);
14004
+ const pullResult = await syncFromExternal(roadmap, adapter, config, options, tickets);
14005
+ fs25.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13944
14006
  return {
13945
14007
  created: pushResult.created,
13946
14008
  updated: pushResult.updated,
@@ -14099,18 +14161,18 @@ var EmitInteractionInputSchema = import_zod10.z.object({
14099
14161
  });
14100
14162
 
14101
14163
  // src/blueprint/scanner.ts
14102
- var fs23 = __toESM(require("fs/promises"));
14103
- var path22 = __toESM(require("path"));
14164
+ var fs26 = __toESM(require("fs/promises"));
14165
+ var path25 = __toESM(require("path"));
14104
14166
  var ProjectScanner = class {
14105
14167
  constructor(rootDir) {
14106
14168
  this.rootDir = rootDir;
14107
14169
  }
14108
14170
  rootDir;
14109
14171
  async scan() {
14110
- let projectName = path22.basename(this.rootDir);
14172
+ let projectName = path25.basename(this.rootDir);
14111
14173
  try {
14112
- const pkgPath = path22.join(this.rootDir, "package.json");
14113
- const pkgRaw = await fs23.readFile(pkgPath, "utf-8");
14174
+ const pkgPath = path25.join(this.rootDir, "package.json");
14175
+ const pkgRaw = await fs26.readFile(pkgPath, "utf-8");
14114
14176
  const pkg = JSON.parse(pkgRaw);
14115
14177
  if (pkg.name) projectName = pkg.name;
14116
14178
  } catch {
@@ -14151,8 +14213,8 @@ var ProjectScanner = class {
14151
14213
  };
14152
14214
 
14153
14215
  // src/blueprint/generator.ts
14154
- var fs24 = __toESM(require("fs/promises"));
14155
- var path23 = __toESM(require("path"));
14216
+ var fs27 = __toESM(require("fs/promises"));
14217
+ var path26 = __toESM(require("path"));
14156
14218
  var ejs = __toESM(require("ejs"));
14157
14219
 
14158
14220
  // src/blueprint/templates.ts
@@ -14236,19 +14298,19 @@ var BlueprintGenerator = class {
14236
14298
  styles: STYLES,
14237
14299
  scripts: SCRIPTS
14238
14300
  });
14239
- await fs24.mkdir(options.outputDir, { recursive: true });
14240
- await fs24.writeFile(path23.join(options.outputDir, "index.html"), html);
14301
+ await fs27.mkdir(options.outputDir, { recursive: true });
14302
+ await fs27.writeFile(path26.join(options.outputDir, "index.html"), html);
14241
14303
  }
14242
14304
  };
14243
14305
 
14244
14306
  // src/update-checker.ts
14245
- var fs25 = __toESM(require("fs"));
14246
- var path24 = __toESM(require("path"));
14307
+ var fs28 = __toESM(require("fs"));
14308
+ var path27 = __toESM(require("path"));
14247
14309
  var os = __toESM(require("os"));
14248
14310
  var import_child_process3 = require("child_process");
14249
14311
  function getStatePath() {
14250
14312
  const home = process.env["HOME"] || os.homedir();
14251
- return path24.join(home, ".harness", "update-check.json");
14313
+ return path27.join(home, ".harness", "update-check.json");
14252
14314
  }
14253
14315
  function isUpdateCheckEnabled(configInterval) {
14254
14316
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -14261,7 +14323,7 @@ function shouldRunCheck(state, intervalMs) {
14261
14323
  }
14262
14324
  function readCheckState() {
14263
14325
  try {
14264
- const raw = fs25.readFileSync(getStatePath(), "utf-8");
14326
+ const raw = fs28.readFileSync(getStatePath(), "utf-8");
14265
14327
  const parsed = JSON.parse(raw);
14266
14328
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
14267
14329
  const state = parsed;
@@ -14278,7 +14340,7 @@ function readCheckState() {
14278
14340
  }
14279
14341
  function spawnBackgroundCheck(currentVersion) {
14280
14342
  const statePath = getStatePath();
14281
- const stateDir = path24.dirname(statePath);
14343
+ const stateDir = path27.dirname(statePath);
14282
14344
  const script = `
14283
14345
  const { execSync } = require('child_process');
14284
14346
  const fs = require('fs');
@@ -14368,9 +14430,9 @@ async function resolveWasmPath(grammarName) {
14368
14430
  const { createRequire } = await import("module");
14369
14431
  const require2 = createRequire(import_meta.url ?? __filename);
14370
14432
  const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
14371
- const path28 = await import("path");
14372
- const pkgDir = path28.dirname(pkgPath);
14373
- return path28.join(pkgDir, "out", `${grammarName}.wasm`);
14433
+ const path31 = await import("path");
14434
+ const pkgDir = path31.dirname(pkgPath);
14435
+ return path31.join(pkgDir, "out", `${grammarName}.wasm`);
14374
14436
  }
14375
14437
  async function loadLanguage(lang) {
14376
14438
  const grammarName = GRAMMAR_MAP[lang];
@@ -14774,8 +14836,8 @@ function getModelPrice(model, dataset) {
14774
14836
  }
14775
14837
 
14776
14838
  // src/pricing/cache.ts
14777
- var fs26 = __toESM(require("fs/promises"));
14778
- var path25 = __toESM(require("path"));
14839
+ var fs29 = __toESM(require("fs/promises"));
14840
+ var path28 = __toESM(require("path"));
14779
14841
 
14780
14842
  // src/pricing/fallback.json
14781
14843
  var fallback_default = {
@@ -14828,14 +14890,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
14828
14890
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
14829
14891
  var STALENESS_WARNING_DAYS = 7;
14830
14892
  function getCachePath(projectRoot) {
14831
- return path25.join(projectRoot, ".harness", "cache", "pricing.json");
14893
+ return path28.join(projectRoot, ".harness", "cache", "pricing.json");
14832
14894
  }
14833
14895
  function getStalenessMarkerPath(projectRoot) {
14834
- return path25.join(projectRoot, ".harness", "cache", "staleness-marker.json");
14896
+ return path28.join(projectRoot, ".harness", "cache", "staleness-marker.json");
14835
14897
  }
14836
14898
  async function readDiskCache(projectRoot) {
14837
14899
  try {
14838
- const raw = await fs26.readFile(getCachePath(projectRoot), "utf-8");
14900
+ const raw = await fs29.readFile(getCachePath(projectRoot), "utf-8");
14839
14901
  return JSON.parse(raw);
14840
14902
  } catch {
14841
14903
  return null;
@@ -14843,8 +14905,8 @@ async function readDiskCache(projectRoot) {
14843
14905
  }
14844
14906
  async function writeDiskCache(projectRoot, data) {
14845
14907
  const cachePath = getCachePath(projectRoot);
14846
- await fs26.mkdir(path25.dirname(cachePath), { recursive: true });
14847
- await fs26.writeFile(cachePath, JSON.stringify(data, null, 2));
14908
+ await fs29.mkdir(path28.dirname(cachePath), { recursive: true });
14909
+ await fs29.writeFile(cachePath, JSON.stringify(data, null, 2));
14848
14910
  }
14849
14911
  async function fetchFromNetwork() {
14850
14912
  try {
@@ -14871,7 +14933,7 @@ function loadFallbackDataset() {
14871
14933
  async function checkAndWarnStaleness(projectRoot) {
14872
14934
  const markerPath = getStalenessMarkerPath(projectRoot);
14873
14935
  try {
14874
- const raw = await fs26.readFile(markerPath, "utf-8");
14936
+ const raw = await fs29.readFile(markerPath, "utf-8");
14875
14937
  const marker = JSON.parse(raw);
14876
14938
  const firstUse = new Date(marker.firstFallbackUse).getTime();
14877
14939
  const now = Date.now();
@@ -14883,8 +14945,8 @@ async function checkAndWarnStaleness(projectRoot) {
14883
14945
  }
14884
14946
  } catch {
14885
14947
  try {
14886
- await fs26.mkdir(path25.dirname(markerPath), { recursive: true });
14887
- await fs26.writeFile(
14948
+ await fs29.mkdir(path28.dirname(markerPath), { recursive: true });
14949
+ await fs29.writeFile(
14888
14950
  markerPath,
14889
14951
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
14890
14952
  );
@@ -14894,7 +14956,7 @@ async function checkAndWarnStaleness(projectRoot) {
14894
14956
  }
14895
14957
  async function clearStalenessMarker(projectRoot) {
14896
14958
  try {
14897
- await fs26.unlink(getStalenessMarkerPath(projectRoot));
14959
+ await fs29.unlink(getStalenessMarkerPath(projectRoot));
14898
14960
  } catch {
14899
14961
  }
14900
14962
  }
@@ -14940,8 +15002,7 @@ function calculateCost(record, dataset) {
14940
15002
  }
14941
15003
 
14942
15004
  // src/usage/aggregator.ts
14943
- function aggregateBySession(records) {
14944
- if (records.length === 0) return [];
15005
+ function bucketRecordsBySession(records) {
14945
15006
  const sessionMap = /* @__PURE__ */ new Map();
14946
15007
  for (const record of records) {
14947
15008
  const tagged = record;
@@ -14957,58 +15018,104 @@ function aggregateBySession(records) {
14957
15018
  }
14958
15019
  bucket.allRecords.push(tagged);
14959
15020
  }
15021
+ return sessionMap;
15022
+ }
15023
+ function accumulateCost(running, recordCost) {
15024
+ if (recordCost != null && running != null) {
15025
+ return running + recordCost;
15026
+ }
15027
+ if (recordCost == null) {
15028
+ return null;
15029
+ }
15030
+ return running;
15031
+ }
15032
+ function sumRecordTokens(tokenSource) {
15033
+ const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
15034
+ let cacheCreation;
15035
+ let cacheRead;
15036
+ let costMicroUSD = 0;
15037
+ let model;
15038
+ for (const r of tokenSource) {
15039
+ tokens.inputTokens += r.tokens.inputTokens;
15040
+ tokens.outputTokens += r.tokens.outputTokens;
15041
+ tokens.totalTokens += r.tokens.totalTokens;
15042
+ if (r.cacheCreationTokens != null) {
15043
+ cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
15044
+ }
15045
+ if (r.cacheReadTokens != null) {
15046
+ cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
15047
+ }
15048
+ costMicroUSD = accumulateCost(costMicroUSD, r.costMicroUSD);
15049
+ if (!model && r.model) {
15050
+ model = r.model;
15051
+ }
15052
+ }
15053
+ return { tokens, cacheCreation, cacheRead, costMicroUSD, model };
15054
+ }
15055
+ function findModel(records) {
15056
+ for (const r of records) {
15057
+ if (r.model) return r.model;
15058
+ }
15059
+ return void 0;
15060
+ }
15061
+ function determineSource(hasHarness, hasCC) {
15062
+ if (hasHarness && hasCC) return "merged";
15063
+ if (hasCC) return "claude-code";
15064
+ return "harness";
15065
+ }
15066
+ function applyOptionalFields(session, totals, model) {
15067
+ if (model) session.model = model;
15068
+ if (totals.cacheCreation != null) session.cacheCreationTokens = totals.cacheCreation;
15069
+ if (totals.cacheRead != null) session.cacheReadTokens = totals.cacheRead;
15070
+ }
15071
+ function buildSessionUsage(sessionId, bucket) {
15072
+ const hasHarness = bucket.harnessRecords.length > 0;
15073
+ const hasCC = bucket.ccRecords.length > 0;
15074
+ const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
15075
+ const totals = sumRecordTokens(tokenSource);
15076
+ const model = totals.model ?? (hasCC ? findModel(bucket.ccRecords) : void 0);
15077
+ const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
15078
+ const session = {
15079
+ sessionId,
15080
+ firstTimestamp: timestamps[0] ?? "",
15081
+ lastTimestamp: timestamps[timestamps.length - 1] ?? "",
15082
+ tokens: totals.tokens,
15083
+ costMicroUSD: totals.costMicroUSD,
15084
+ source: determineSource(hasHarness, hasCC)
15085
+ };
15086
+ applyOptionalFields(session, totals, model);
15087
+ return session;
15088
+ }
15089
+ function accumulateIntoDayBucket(day, record) {
15090
+ day.sessions.add(record.sessionId);
15091
+ day.tokens.inputTokens += record.tokens.inputTokens;
15092
+ day.tokens.outputTokens += record.tokens.outputTokens;
15093
+ day.tokens.totalTokens += record.tokens.totalTokens;
15094
+ if (record.cacheCreationTokens != null) {
15095
+ day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
15096
+ }
15097
+ if (record.cacheReadTokens != null) {
15098
+ day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
15099
+ }
15100
+ day.costMicroUSD = accumulateCost(day.costMicroUSD, record.costMicroUSD);
15101
+ if (record.model) {
15102
+ day.models.add(record.model);
15103
+ }
15104
+ }
15105
+ function createDayBucket() {
15106
+ return {
15107
+ sessions: /* @__PURE__ */ new Set(),
15108
+ tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
15109
+ costMicroUSD: 0,
15110
+ models: /* @__PURE__ */ new Set()
15111
+ };
15112
+ }
15113
+ function aggregateBySession(records) {
15114
+ if (records.length === 0) return [];
15115
+ const sessionMap = bucketRecordsBySession(records);
14960
15116
  const results = [];
14961
15117
  for (const [sessionId, bucket] of sessionMap) {
14962
- const hasHarness = bucket.harnessRecords.length > 0;
14963
- const hasCC = bucket.ccRecords.length > 0;
14964
- const isMerged = hasHarness && hasCC;
14965
- const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
14966
- const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
14967
- let cacheCreation;
14968
- let cacheRead;
14969
- let costMicroUSD = 0;
14970
- let model;
14971
- for (const r of tokenSource) {
14972
- tokens.inputTokens += r.tokens.inputTokens;
14973
- tokens.outputTokens += r.tokens.outputTokens;
14974
- tokens.totalTokens += r.tokens.totalTokens;
14975
- if (r.cacheCreationTokens != null) {
14976
- cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
14977
- }
14978
- if (r.cacheReadTokens != null) {
14979
- cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
14980
- }
14981
- if (r.costMicroUSD != null && costMicroUSD != null) {
14982
- costMicroUSD += r.costMicroUSD;
14983
- } else if (r.costMicroUSD == null) {
14984
- costMicroUSD = null;
14985
- }
14986
- if (!model && r.model) {
14987
- model = r.model;
14988
- }
14989
- }
14990
- if (!model && hasCC) {
14991
- for (const r of bucket.ccRecords) {
14992
- if (r.model) {
14993
- model = r.model;
14994
- break;
14995
- }
14996
- }
14997
- }
14998
- const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
14999
- const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
15000
- const session = {
15001
- sessionId,
15002
- firstTimestamp: timestamps[0] ?? "",
15003
- lastTimestamp: timestamps[timestamps.length - 1] ?? "",
15004
- tokens,
15005
- costMicroUSD,
15006
- source
15007
- };
15008
- if (model) session.model = model;
15009
- if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
15010
- if (cacheRead != null) session.cacheReadTokens = cacheRead;
15011
- results.push(session);
15118
+ results.push(buildSessionUsage(sessionId, bucket));
15012
15119
  }
15013
15120
  results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
15014
15121
  return results;
@@ -15019,32 +15126,9 @@ function aggregateByDay(records) {
15019
15126
  for (const record of records) {
15020
15127
  const date = record.timestamp.slice(0, 10);
15021
15128
  if (!dayMap.has(date)) {
15022
- dayMap.set(date, {
15023
- sessions: /* @__PURE__ */ new Set(),
15024
- tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
15025
- costMicroUSD: 0,
15026
- models: /* @__PURE__ */ new Set()
15027
- });
15028
- }
15029
- const day = dayMap.get(date);
15030
- day.sessions.add(record.sessionId);
15031
- day.tokens.inputTokens += record.tokens.inputTokens;
15032
- day.tokens.outputTokens += record.tokens.outputTokens;
15033
- day.tokens.totalTokens += record.tokens.totalTokens;
15034
- if (record.cacheCreationTokens != null) {
15035
- day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
15036
- }
15037
- if (record.cacheReadTokens != null) {
15038
- day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
15039
- }
15040
- if (record.costMicroUSD != null && day.costMicroUSD != null) {
15041
- day.costMicroUSD += record.costMicroUSD;
15042
- } else if (record.costMicroUSD == null) {
15043
- day.costMicroUSD = null;
15044
- }
15045
- if (record.model) {
15046
- day.models.add(record.model);
15129
+ dayMap.set(date, createDayBucket());
15047
15130
  }
15131
+ accumulateIntoDayBucket(dayMap.get(date), record);
15048
15132
  }
15049
15133
  const results = [];
15050
15134
  for (const [date, day] of dayMap) {
@@ -15064,8 +15148,8 @@ function aggregateByDay(records) {
15064
15148
  }
15065
15149
 
15066
15150
  // src/usage/jsonl-reader.ts
15067
- var fs27 = __toESM(require("fs"));
15068
- var path26 = __toESM(require("path"));
15151
+ var fs30 = __toESM(require("fs"));
15152
+ var path29 = __toESM(require("path"));
15069
15153
  function parseLine(line, lineNumber) {
15070
15154
  let entry;
15071
15155
  try {
@@ -15104,10 +15188,10 @@ function parseLine(line, lineNumber) {
15104
15188
  return record;
15105
15189
  }
15106
15190
  function readCostRecords(projectRoot) {
15107
- const costsFile = path26.join(projectRoot, ".harness", "metrics", "costs.jsonl");
15191
+ const costsFile = path29.join(projectRoot, ".harness", "metrics", "costs.jsonl");
15108
15192
  let raw;
15109
15193
  try {
15110
- raw = fs27.readFileSync(costsFile, "utf-8");
15194
+ raw = fs30.readFileSync(costsFile, "utf-8");
15111
15195
  } catch {
15112
15196
  return [];
15113
15197
  }
@@ -15125,8 +15209,8 @@ function readCostRecords(projectRoot) {
15125
15209
  }
15126
15210
 
15127
15211
  // src/usage/cc-parser.ts
15128
- var fs28 = __toESM(require("fs"));
15129
- var path27 = __toESM(require("path"));
15212
+ var fs31 = __toESM(require("fs"));
15213
+ var path30 = __toESM(require("path"));
15130
15214
  var os2 = __toESM(require("os"));
15131
15215
  function extractUsage(entry) {
15132
15216
  if (entry.type !== "assistant") return null;
@@ -15159,7 +15243,7 @@ function parseCCLine(line, filePath, lineNumber) {
15159
15243
  entry = JSON.parse(line);
15160
15244
  } catch {
15161
15245
  console.warn(
15162
- `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path27.basename(filePath)}`
15246
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path30.basename(filePath)}`
15163
15247
  );
15164
15248
  return null;
15165
15249
  }
@@ -15173,7 +15257,7 @@ function parseCCLine(line, filePath, lineNumber) {
15173
15257
  function readCCFile(filePath) {
15174
15258
  let raw;
15175
15259
  try {
15176
- raw = fs28.readFileSync(filePath, "utf-8");
15260
+ raw = fs31.readFileSync(filePath, "utf-8");
15177
15261
  } catch {
15178
15262
  return [];
15179
15263
  }
@@ -15195,10 +15279,10 @@ function readCCFile(filePath) {
15195
15279
  }
15196
15280
  function parseCCRecords() {
15197
15281
  const homeDir = process.env.HOME ?? os2.homedir();
15198
- const projectsDir = path27.join(homeDir, ".claude", "projects");
15282
+ const projectsDir = path30.join(homeDir, ".claude", "projects");
15199
15283
  let projectDirs;
15200
15284
  try {
15201
- projectDirs = fs28.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path27.join(projectsDir, d.name));
15285
+ projectDirs = fs31.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path30.join(projectsDir, d.name));
15202
15286
  } catch {
15203
15287
  return [];
15204
15288
  }
@@ -15206,7 +15290,7 @@ function parseCCRecords() {
15206
15290
  for (const dir of projectDirs) {
15207
15291
  let files;
15208
15292
  try {
15209
- files = fs28.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path27.join(dir, f));
15293
+ files = fs31.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path30.join(dir, f));
15210
15294
  } catch {
15211
15295
  continue;
15212
15296
  }
@@ -15358,6 +15442,7 @@ var VERSION = "0.15.0";
15358
15442
  clearFailuresCache,
15359
15443
  clearLearningsCache,
15360
15444
  clearTaint,
15445
+ computeContentHash,
15361
15446
  computeOverallSeverity,
15362
15447
  computeScanExitCode,
15363
15448
  configureFeedback,
@@ -15451,6 +15536,7 @@ var VERSION = "0.15.0";
15451
15536
  migrateToStreams,
15452
15537
  networkRules,
15453
15538
  nodeRules,
15539
+ normalizeLearningContent,
15454
15540
  parseCCRecords,
15455
15541
  parseDateFromEntry,
15456
15542
  parseDiff,