@harness-engineering/core 0.21.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(
@@ -13843,42 +13874,54 @@ var GitHubIssuesSyncAdapter = class {
13843
13874
  };
13844
13875
 
13845
13876
  // src/roadmap/sync-engine.ts
13846
- var fs22 = __toESM(require("fs"));
13877
+ var fs25 = __toESM(require("fs"));
13847
13878
  function emptySyncResult() {
13848
13879
  return { created: [], updated: [], assignmentChanges: [], errors: [] };
13849
13880
  }
13850
- async function syncToExternal(roadmap, adapter, config, prefetchedTickets) {
13851
- const result = emptySyncResult();
13852
- const existingByTitle = /* @__PURE__ */ new Map();
13881
+ function buildDedupIndex(tickets, config) {
13882
+ const index = /* @__PURE__ */ new Map();
13883
+ if (!tickets) return index;
13853
13884
  const configLabels = new Set((config.labels ?? []).map((l) => l.toLowerCase()));
13854
- if (prefetchedTickets) {
13855
- for (const ticket of prefetchedTickets) {
13856
- const hasConfigLabels = configLabels.size === 0 || ticket.labels.some((l) => configLabels.has(l.toLowerCase()));
13857
- if (!hasConfigLabels) continue;
13858
- const key = ticket.title.toLowerCase();
13859
- const prev = existingByTitle.get(key);
13860
- if (!prev || prev.status === "closed" && ticket.status === "open") {
13861
- existingByTitle.set(key, ticket);
13862
- }
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);
13863
13892
  }
13864
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);
13865
13915
  for (const milestone of roadmap.milestones) {
13866
13916
  for (const feature of milestone.features) {
13867
- if (!feature.externalId) {
13868
- const existing = existingByTitle.get(feature.name.toLowerCase());
13869
- if (existing) {
13870
- feature.externalId = existing.externalId;
13871
- } else {
13872
- const createResult = await adapter.createTicket(feature, milestone.name);
13873
- if (createResult.ok) {
13874
- feature.externalId = createResult.value.externalId;
13875
- result.created.push(createResult.value);
13876
- } else {
13877
- result.errors.push({ featureOrId: feature.name, error: createResult.error });
13878
- }
13879
- continue;
13880
- }
13881
- }
13917
+ const shouldUpdate = await resolveExternalId(
13918
+ feature,
13919
+ milestone.name,
13920
+ adapter,
13921
+ dedupIndex,
13922
+ result
13923
+ );
13924
+ if (!shouldUpdate) continue;
13882
13925
  const updateResult = await adapter.updateTicket(feature.externalId, feature, milestone.name);
13883
13926
  if (updateResult.ok) {
13884
13927
  result.updated.push(feature.externalId);
@@ -13929,6 +13972,9 @@ async function syncFromExternal(roadmap, adapter, config, options, prefetchedTic
13929
13972
  if (!forceSync && isRegression(feature.status, newStatus)) {
13930
13973
  continue;
13931
13974
  }
13975
+ if (!forceSync && feature.status === "blocked" && newStatus === "planned") {
13976
+ continue;
13977
+ }
13932
13978
  feature.status = newStatus;
13933
13979
  }
13934
13980
  }
@@ -13943,7 +13989,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
13943
13989
  });
13944
13990
  await previousSync;
13945
13991
  try {
13946
- const raw = fs22.readFileSync(roadmapPath, "utf-8");
13992
+ const raw = fs25.readFileSync(roadmapPath, "utf-8");
13947
13993
  const parseResult = parseRoadmap(raw);
13948
13994
  if (!parseResult.ok) {
13949
13995
  return {
@@ -13956,7 +14002,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
13956
14002
  const tickets = fetchResult.ok ? fetchResult.value : void 0;
13957
14003
  const pushResult = await syncToExternal(roadmap, adapter, config, tickets);
13958
14004
  const pullResult = await syncFromExternal(roadmap, adapter, config, options, tickets);
13959
- fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
14005
+ fs25.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13960
14006
  return {
13961
14007
  created: pushResult.created,
13962
14008
  updated: pushResult.updated,
@@ -14115,18 +14161,18 @@ var EmitInteractionInputSchema = import_zod10.z.object({
14115
14161
  });
14116
14162
 
14117
14163
  // src/blueprint/scanner.ts
14118
- var fs23 = __toESM(require("fs/promises"));
14119
- var path22 = __toESM(require("path"));
14164
+ var fs26 = __toESM(require("fs/promises"));
14165
+ var path25 = __toESM(require("path"));
14120
14166
  var ProjectScanner = class {
14121
14167
  constructor(rootDir) {
14122
14168
  this.rootDir = rootDir;
14123
14169
  }
14124
14170
  rootDir;
14125
14171
  async scan() {
14126
- let projectName = path22.basename(this.rootDir);
14172
+ let projectName = path25.basename(this.rootDir);
14127
14173
  try {
14128
- const pkgPath = path22.join(this.rootDir, "package.json");
14129
- 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");
14130
14176
  const pkg = JSON.parse(pkgRaw);
14131
14177
  if (pkg.name) projectName = pkg.name;
14132
14178
  } catch {
@@ -14167,8 +14213,8 @@ var ProjectScanner = class {
14167
14213
  };
14168
14214
 
14169
14215
  // src/blueprint/generator.ts
14170
- var fs24 = __toESM(require("fs/promises"));
14171
- var path23 = __toESM(require("path"));
14216
+ var fs27 = __toESM(require("fs/promises"));
14217
+ var path26 = __toESM(require("path"));
14172
14218
  var ejs = __toESM(require("ejs"));
14173
14219
 
14174
14220
  // src/blueprint/templates.ts
@@ -14252,19 +14298,19 @@ var BlueprintGenerator = class {
14252
14298
  styles: STYLES,
14253
14299
  scripts: SCRIPTS
14254
14300
  });
14255
- await fs24.mkdir(options.outputDir, { recursive: true });
14256
- 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);
14257
14303
  }
14258
14304
  };
14259
14305
 
14260
14306
  // src/update-checker.ts
14261
- var fs25 = __toESM(require("fs"));
14262
- var path24 = __toESM(require("path"));
14307
+ var fs28 = __toESM(require("fs"));
14308
+ var path27 = __toESM(require("path"));
14263
14309
  var os = __toESM(require("os"));
14264
14310
  var import_child_process3 = require("child_process");
14265
14311
  function getStatePath() {
14266
14312
  const home = process.env["HOME"] || os.homedir();
14267
- return path24.join(home, ".harness", "update-check.json");
14313
+ return path27.join(home, ".harness", "update-check.json");
14268
14314
  }
14269
14315
  function isUpdateCheckEnabled(configInterval) {
14270
14316
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -14277,7 +14323,7 @@ function shouldRunCheck(state, intervalMs) {
14277
14323
  }
14278
14324
  function readCheckState() {
14279
14325
  try {
14280
- const raw = fs25.readFileSync(getStatePath(), "utf-8");
14326
+ const raw = fs28.readFileSync(getStatePath(), "utf-8");
14281
14327
  const parsed = JSON.parse(raw);
14282
14328
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
14283
14329
  const state = parsed;
@@ -14294,7 +14340,7 @@ function readCheckState() {
14294
14340
  }
14295
14341
  function spawnBackgroundCheck(currentVersion) {
14296
14342
  const statePath = getStatePath();
14297
- const stateDir = path24.dirname(statePath);
14343
+ const stateDir = path27.dirname(statePath);
14298
14344
  const script = `
14299
14345
  const { execSync } = require('child_process');
14300
14346
  const fs = require('fs');
@@ -14384,9 +14430,9 @@ async function resolveWasmPath(grammarName) {
14384
14430
  const { createRequire } = await import("module");
14385
14431
  const require2 = createRequire(import_meta.url ?? __filename);
14386
14432
  const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
14387
- const path28 = await import("path");
14388
- const pkgDir = path28.dirname(pkgPath);
14389
- 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`);
14390
14436
  }
14391
14437
  async function loadLanguage(lang) {
14392
14438
  const grammarName = GRAMMAR_MAP[lang];
@@ -14790,8 +14836,8 @@ function getModelPrice(model, dataset) {
14790
14836
  }
14791
14837
 
14792
14838
  // src/pricing/cache.ts
14793
- var fs26 = __toESM(require("fs/promises"));
14794
- var path25 = __toESM(require("path"));
14839
+ var fs29 = __toESM(require("fs/promises"));
14840
+ var path28 = __toESM(require("path"));
14795
14841
 
14796
14842
  // src/pricing/fallback.json
14797
14843
  var fallback_default = {
@@ -14844,14 +14890,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
14844
14890
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
14845
14891
  var STALENESS_WARNING_DAYS = 7;
14846
14892
  function getCachePath(projectRoot) {
14847
- return path25.join(projectRoot, ".harness", "cache", "pricing.json");
14893
+ return path28.join(projectRoot, ".harness", "cache", "pricing.json");
14848
14894
  }
14849
14895
  function getStalenessMarkerPath(projectRoot) {
14850
- return path25.join(projectRoot, ".harness", "cache", "staleness-marker.json");
14896
+ return path28.join(projectRoot, ".harness", "cache", "staleness-marker.json");
14851
14897
  }
14852
14898
  async function readDiskCache(projectRoot) {
14853
14899
  try {
14854
- const raw = await fs26.readFile(getCachePath(projectRoot), "utf-8");
14900
+ const raw = await fs29.readFile(getCachePath(projectRoot), "utf-8");
14855
14901
  return JSON.parse(raw);
14856
14902
  } catch {
14857
14903
  return null;
@@ -14859,8 +14905,8 @@ async function readDiskCache(projectRoot) {
14859
14905
  }
14860
14906
  async function writeDiskCache(projectRoot, data) {
14861
14907
  const cachePath = getCachePath(projectRoot);
14862
- await fs26.mkdir(path25.dirname(cachePath), { recursive: true });
14863
- 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));
14864
14910
  }
14865
14911
  async function fetchFromNetwork() {
14866
14912
  try {
@@ -14887,7 +14933,7 @@ function loadFallbackDataset() {
14887
14933
  async function checkAndWarnStaleness(projectRoot) {
14888
14934
  const markerPath = getStalenessMarkerPath(projectRoot);
14889
14935
  try {
14890
- const raw = await fs26.readFile(markerPath, "utf-8");
14936
+ const raw = await fs29.readFile(markerPath, "utf-8");
14891
14937
  const marker = JSON.parse(raw);
14892
14938
  const firstUse = new Date(marker.firstFallbackUse).getTime();
14893
14939
  const now = Date.now();
@@ -14899,8 +14945,8 @@ async function checkAndWarnStaleness(projectRoot) {
14899
14945
  }
14900
14946
  } catch {
14901
14947
  try {
14902
- await fs26.mkdir(path25.dirname(markerPath), { recursive: true });
14903
- await fs26.writeFile(
14948
+ await fs29.mkdir(path28.dirname(markerPath), { recursive: true });
14949
+ await fs29.writeFile(
14904
14950
  markerPath,
14905
14951
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
14906
14952
  );
@@ -14910,7 +14956,7 @@ async function checkAndWarnStaleness(projectRoot) {
14910
14956
  }
14911
14957
  async function clearStalenessMarker(projectRoot) {
14912
14958
  try {
14913
- await fs26.unlink(getStalenessMarkerPath(projectRoot));
14959
+ await fs29.unlink(getStalenessMarkerPath(projectRoot));
14914
14960
  } catch {
14915
14961
  }
14916
14962
  }
@@ -14956,8 +15002,7 @@ function calculateCost(record, dataset) {
14956
15002
  }
14957
15003
 
14958
15004
  // src/usage/aggregator.ts
14959
- function aggregateBySession(records) {
14960
- if (records.length === 0) return [];
15005
+ function bucketRecordsBySession(records) {
14961
15006
  const sessionMap = /* @__PURE__ */ new Map();
14962
15007
  for (const record of records) {
14963
15008
  const tagged = record;
@@ -14973,58 +15018,104 @@ function aggregateBySession(records) {
14973
15018
  }
14974
15019
  bucket.allRecords.push(tagged);
14975
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);
14976
15116
  const results = [];
14977
15117
  for (const [sessionId, bucket] of sessionMap) {
14978
- const hasHarness = bucket.harnessRecords.length > 0;
14979
- const hasCC = bucket.ccRecords.length > 0;
14980
- const isMerged = hasHarness && hasCC;
14981
- const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
14982
- const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
14983
- let cacheCreation;
14984
- let cacheRead;
14985
- let costMicroUSD = 0;
14986
- let model;
14987
- for (const r of tokenSource) {
14988
- tokens.inputTokens += r.tokens.inputTokens;
14989
- tokens.outputTokens += r.tokens.outputTokens;
14990
- tokens.totalTokens += r.tokens.totalTokens;
14991
- if (r.cacheCreationTokens != null) {
14992
- cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
14993
- }
14994
- if (r.cacheReadTokens != null) {
14995
- cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
14996
- }
14997
- if (r.costMicroUSD != null && costMicroUSD != null) {
14998
- costMicroUSD += r.costMicroUSD;
14999
- } else if (r.costMicroUSD == null) {
15000
- costMicroUSD = null;
15001
- }
15002
- if (!model && r.model) {
15003
- model = r.model;
15004
- }
15005
- }
15006
- if (!model && hasCC) {
15007
- for (const r of bucket.ccRecords) {
15008
- if (r.model) {
15009
- model = r.model;
15010
- break;
15011
- }
15012
- }
15013
- }
15014
- const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
15015
- const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
15016
- const session = {
15017
- sessionId,
15018
- firstTimestamp: timestamps[0] ?? "",
15019
- lastTimestamp: timestamps[timestamps.length - 1] ?? "",
15020
- tokens,
15021
- costMicroUSD,
15022
- source
15023
- };
15024
- if (model) session.model = model;
15025
- if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
15026
- if (cacheRead != null) session.cacheReadTokens = cacheRead;
15027
- results.push(session);
15118
+ results.push(buildSessionUsage(sessionId, bucket));
15028
15119
  }
15029
15120
  results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
15030
15121
  return results;
@@ -15035,32 +15126,9 @@ function aggregateByDay(records) {
15035
15126
  for (const record of records) {
15036
15127
  const date = record.timestamp.slice(0, 10);
15037
15128
  if (!dayMap.has(date)) {
15038
- dayMap.set(date, {
15039
- sessions: /* @__PURE__ */ new Set(),
15040
- tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
15041
- costMicroUSD: 0,
15042
- models: /* @__PURE__ */ new Set()
15043
- });
15044
- }
15045
- const day = dayMap.get(date);
15046
- day.sessions.add(record.sessionId);
15047
- day.tokens.inputTokens += record.tokens.inputTokens;
15048
- day.tokens.outputTokens += record.tokens.outputTokens;
15049
- day.tokens.totalTokens += record.tokens.totalTokens;
15050
- if (record.cacheCreationTokens != null) {
15051
- day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
15052
- }
15053
- if (record.cacheReadTokens != null) {
15054
- day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
15055
- }
15056
- if (record.costMicroUSD != null && day.costMicroUSD != null) {
15057
- day.costMicroUSD += record.costMicroUSD;
15058
- } else if (record.costMicroUSD == null) {
15059
- day.costMicroUSD = null;
15060
- }
15061
- if (record.model) {
15062
- day.models.add(record.model);
15129
+ dayMap.set(date, createDayBucket());
15063
15130
  }
15131
+ accumulateIntoDayBucket(dayMap.get(date), record);
15064
15132
  }
15065
15133
  const results = [];
15066
15134
  for (const [date, day] of dayMap) {
@@ -15080,8 +15148,8 @@ function aggregateByDay(records) {
15080
15148
  }
15081
15149
 
15082
15150
  // src/usage/jsonl-reader.ts
15083
- var fs27 = __toESM(require("fs"));
15084
- var path26 = __toESM(require("path"));
15151
+ var fs30 = __toESM(require("fs"));
15152
+ var path29 = __toESM(require("path"));
15085
15153
  function parseLine(line, lineNumber) {
15086
15154
  let entry;
15087
15155
  try {
@@ -15120,10 +15188,10 @@ function parseLine(line, lineNumber) {
15120
15188
  return record;
15121
15189
  }
15122
15190
  function readCostRecords(projectRoot) {
15123
- const costsFile = path26.join(projectRoot, ".harness", "metrics", "costs.jsonl");
15191
+ const costsFile = path29.join(projectRoot, ".harness", "metrics", "costs.jsonl");
15124
15192
  let raw;
15125
15193
  try {
15126
- raw = fs27.readFileSync(costsFile, "utf-8");
15194
+ raw = fs30.readFileSync(costsFile, "utf-8");
15127
15195
  } catch {
15128
15196
  return [];
15129
15197
  }
@@ -15141,8 +15209,8 @@ function readCostRecords(projectRoot) {
15141
15209
  }
15142
15210
 
15143
15211
  // src/usage/cc-parser.ts
15144
- var fs28 = __toESM(require("fs"));
15145
- var path27 = __toESM(require("path"));
15212
+ var fs31 = __toESM(require("fs"));
15213
+ var path30 = __toESM(require("path"));
15146
15214
  var os2 = __toESM(require("os"));
15147
15215
  function extractUsage(entry) {
15148
15216
  if (entry.type !== "assistant") return null;
@@ -15175,7 +15243,7 @@ function parseCCLine(line, filePath, lineNumber) {
15175
15243
  entry = JSON.parse(line);
15176
15244
  } catch {
15177
15245
  console.warn(
15178
- `[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)}`
15179
15247
  );
15180
15248
  return null;
15181
15249
  }
@@ -15189,7 +15257,7 @@ function parseCCLine(line, filePath, lineNumber) {
15189
15257
  function readCCFile(filePath) {
15190
15258
  let raw;
15191
15259
  try {
15192
- raw = fs28.readFileSync(filePath, "utf-8");
15260
+ raw = fs31.readFileSync(filePath, "utf-8");
15193
15261
  } catch {
15194
15262
  return [];
15195
15263
  }
@@ -15211,10 +15279,10 @@ function readCCFile(filePath) {
15211
15279
  }
15212
15280
  function parseCCRecords() {
15213
15281
  const homeDir = process.env.HOME ?? os2.homedir();
15214
- const projectsDir = path27.join(homeDir, ".claude", "projects");
15282
+ const projectsDir = path30.join(homeDir, ".claude", "projects");
15215
15283
  let projectDirs;
15216
15284
  try {
15217
- 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));
15218
15286
  } catch {
15219
15287
  return [];
15220
15288
  }
@@ -15222,7 +15290,7 @@ function parseCCRecords() {
15222
15290
  for (const dir of projectDirs) {
15223
15291
  let files;
15224
15292
  try {
15225
- 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));
15226
15294
  } catch {
15227
15295
  continue;
15228
15296
  }
@@ -15374,6 +15442,7 @@ var VERSION = "0.15.0";
15374
15442
  clearFailuresCache,
15375
15443
  clearLearningsCache,
15376
15444
  clearTaint,
15445
+ computeContentHash,
15377
15446
  computeOverallSeverity,
15378
15447
  computeScanExitCode,
15379
15448
  configureFeedback,
@@ -15467,6 +15536,7 @@ var VERSION = "0.15.0";
15467
15536
  migrateToStreams,
15468
15537
  networkRules,
15469
15538
  nodeRules,
15539
+ normalizeLearningContent,
15470
15540
  parseCCRecords,
15471
15541
  parseDateFromEntry,
15472
15542
  parseDiff,