@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.mjs CHANGED
@@ -84,15 +84,15 @@ function validateConfig(data, schema) {
84
84
  let message = "Configuration validation failed";
85
85
  const suggestions = [];
86
86
  if (firstError) {
87
- const path28 = firstError.path.join(".");
88
- const pathDisplay = path28 ? ` at "${path28}"` : "";
87
+ const path31 = firstError.path.join(".");
88
+ const pathDisplay = path31 ? ` at "${path31}"` : "";
89
89
  if (firstError.code === "invalid_type") {
90
90
  const received = firstError.received;
91
91
  const expected = firstError.expected;
92
92
  if (received === "undefined") {
93
93
  code = "MISSING_FIELD";
94
94
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
95
- suggestions.push(`Field "${path28}" is required and must be of type "${expected}"`);
95
+ suggestions.push(`Field "${path31}" is required and must be of type "${expected}"`);
96
96
  } else {
97
97
  code = "INVALID_TYPE";
98
98
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -308,27 +308,27 @@ function extractSections(content) {
308
308
  }
309
309
  return sections.map((section) => buildAgentMapSection(section, lines));
310
310
  }
311
- function isExternalLink(path28) {
312
- return path28.startsWith("http://") || path28.startsWith("https://") || path28.startsWith("#") || path28.startsWith("mailto:");
311
+ function isExternalLink(path31) {
312
+ return path31.startsWith("http://") || path31.startsWith("https://") || path31.startsWith("#") || path31.startsWith("mailto:");
313
313
  }
314
314
  function resolveLinkPath(linkPath, baseDir) {
315
315
  return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
316
316
  }
317
- async function validateAgentsMap(path28 = "./AGENTS.md") {
318
- const contentResult = await readFileContent(path28);
317
+ async function validateAgentsMap(path31 = "./AGENTS.md") {
318
+ const contentResult = await readFileContent(path31);
319
319
  if (!contentResult.ok) {
320
320
  return Err(
321
321
  createError(
322
322
  "PARSE_ERROR",
323
323
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
324
- { path: path28 },
324
+ { path: path31 },
325
325
  ["Ensure the file exists", "Check file permissions"]
326
326
  )
327
327
  );
328
328
  }
329
329
  const content = contentResult.value;
330
330
  const sections = extractSections(content);
331
- const baseDir = dirname(path28);
331
+ const baseDir = dirname(path31);
332
332
  const sectionTitles = sections.map((s) => s.title);
333
333
  const missingSections = REQUIRED_SECTIONS.filter(
334
334
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -469,8 +469,8 @@ async function checkDocCoverage(domain, options = {}) {
469
469
 
470
470
  // src/context/knowledge-map.ts
471
471
  import { join as join2, basename as basename2 } from "path";
472
- function suggestFix(path28, existingFiles) {
473
- const targetName = basename2(path28).toLowerCase();
472
+ function suggestFix(path31, existingFiles) {
473
+ const targetName = basename2(path31).toLowerCase();
474
474
  const similar = existingFiles.find((file) => {
475
475
  const fileName = basename2(file).toLowerCase();
476
476
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -478,7 +478,7 @@ function suggestFix(path28, existingFiles) {
478
478
  if (similar) {
479
479
  return `Did you mean "${similar}"?`;
480
480
  }
481
- return `Create the file "${path28}" or remove the link`;
481
+ return `Create the file "${path31}" or remove the link`;
482
482
  }
483
483
  async function validateKnowledgeMap(rootDir = process.cwd()) {
484
484
  const agentsPath = join2(rootDir, "AGENTS.md");
@@ -830,8 +830,8 @@ function createBoundaryValidator(schema, name) {
830
830
  return Ok(result.data);
831
831
  }
832
832
  const suggestions = result.error.issues.map((issue) => {
833
- const path28 = issue.path.join(".");
834
- return path28 ? `${path28}: ${issue.message}` : issue.message;
833
+ const path31 = issue.path.join(".");
834
+ return path31 ? `${path31}: ${issue.message}` : issue.message;
835
835
  });
836
836
  return Err(
837
837
  createError(
@@ -1463,11 +1463,11 @@ function processExportListSpecifiers(exportDecl, exports) {
1463
1463
  var TypeScriptParser = class {
1464
1464
  name = "typescript";
1465
1465
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1466
- async parseFile(path28) {
1467
- const contentResult = await readFileContent(path28);
1466
+ async parseFile(path31) {
1467
+ const contentResult = await readFileContent(path31);
1468
1468
  if (!contentResult.ok) {
1469
1469
  return Err(
1470
- createParseError("NOT_FOUND", `File not found: ${path28}`, { path: path28 }, [
1470
+ createParseError("NOT_FOUND", `File not found: ${path31}`, { path: path31 }, [
1471
1471
  "Check that the file exists",
1472
1472
  "Verify the path is correct"
1473
1473
  ])
@@ -1477,7 +1477,7 @@ var TypeScriptParser = class {
1477
1477
  const ast = parse(contentResult.value, {
1478
1478
  loc: true,
1479
1479
  range: true,
1480
- jsx: path28.endsWith(".tsx"),
1480
+ jsx: path31.endsWith(".tsx"),
1481
1481
  errorOnUnknownASTType: false
1482
1482
  });
1483
1483
  return Ok({
@@ -1488,7 +1488,7 @@ var TypeScriptParser = class {
1488
1488
  } catch (e) {
1489
1489
  const error = e;
1490
1490
  return Err(
1491
- createParseError("SYNTAX_ERROR", `Failed to parse ${path28}: ${error.message}`, { path: path28 }, [
1491
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path31}: ${error.message}`, { path: path31 }, [
1492
1492
  "Check for syntax errors in the file",
1493
1493
  "Ensure valid TypeScript syntax"
1494
1494
  ])
@@ -1673,22 +1673,22 @@ function extractInlineRefs(content) {
1673
1673
  }
1674
1674
  return refs;
1675
1675
  }
1676
- async function parseDocumentationFile(path28) {
1677
- const contentResult = await readFileContent(path28);
1676
+ async function parseDocumentationFile(path31) {
1677
+ const contentResult = await readFileContent(path31);
1678
1678
  if (!contentResult.ok) {
1679
1679
  return Err(
1680
1680
  createEntropyError(
1681
1681
  "PARSE_ERROR",
1682
- `Failed to read documentation file: ${path28}`,
1683
- { file: path28 },
1682
+ `Failed to read documentation file: ${path31}`,
1683
+ { file: path31 },
1684
1684
  ["Check that the file exists"]
1685
1685
  )
1686
1686
  );
1687
1687
  }
1688
1688
  const content = contentResult.value;
1689
- const type = path28.endsWith(".md") ? "markdown" : "text";
1689
+ const type = path31.endsWith(".md") ? "markdown" : "text";
1690
1690
  return Ok({
1691
- path: path28,
1691
+ path: path31,
1692
1692
  type,
1693
1693
  content,
1694
1694
  codeBlocks: extractCodeBlocks(content),
@@ -5218,8 +5218,7 @@ function parseListField(fieldMap, ...keys) {
5218
5218
  if (raw === EM_DASH || raw === "none") return [];
5219
5219
  return raw.split(",").map((s) => s.trim());
5220
5220
  }
5221
- function parseFeatureFields(name, body) {
5222
- const fieldMap = extractFieldMap(body);
5221
+ function validateStatus(name, fieldMap) {
5223
5222
  const statusRaw = fieldMap.get("Status");
5224
5223
  if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
5225
5224
  return Err2(
@@ -5228,12 +5227,10 @@ function parseFeatureFields(name, body) {
5228
5227
  )
5229
5228
  );
5230
5229
  }
5231
- const specRaw = fieldMap.get("Spec") ?? EM_DASH;
5232
- const plans = parseListField(fieldMap, "Plans", "Plan");
5233
- const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
5234
- const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
5230
+ return Ok2(statusRaw);
5231
+ }
5232
+ function validatePriority(name, fieldMap) {
5235
5233
  const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
5236
- const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
5237
5234
  if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
5238
5235
  return Err2(
5239
5236
  new Error(
@@ -5241,16 +5238,28 @@ function parseFeatureFields(name, body) {
5241
5238
  )
5242
5239
  );
5243
5240
  }
5241
+ return Ok2(priorityRaw === EM_DASH ? null : priorityRaw);
5242
+ }
5243
+ function optionalField(fieldMap, key) {
5244
+ const raw = fieldMap.get(key) ?? EM_DASH;
5245
+ return raw === EM_DASH ? null : raw;
5246
+ }
5247
+ function parseFeatureFields(name, body) {
5248
+ const fieldMap = extractFieldMap(body);
5249
+ const statusResult = validateStatus(name, fieldMap);
5250
+ if (!statusResult.ok) return statusResult;
5251
+ const priorityResult = validatePriority(name, fieldMap);
5252
+ if (!priorityResult.ok) return priorityResult;
5244
5253
  return Ok2({
5245
5254
  name,
5246
- status: statusRaw,
5247
- spec: specRaw === EM_DASH ? null : specRaw,
5248
- plans,
5249
- blockedBy,
5255
+ status: statusResult.value,
5256
+ spec: optionalField(fieldMap, "Spec"),
5257
+ plans: parseListField(fieldMap, "Plans", "Plan"),
5258
+ blockedBy: parseListField(fieldMap, "Blocked by", "Blockers"),
5250
5259
  summary: fieldMap.get("Summary") ?? "",
5251
- assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
5252
- priority: priorityRaw === EM_DASH ? null : priorityRaw,
5253
- externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
5260
+ assignee: optionalField(fieldMap, "Assignee"),
5261
+ priority: priorityResult.value,
5262
+ externalId: optionalField(fieldMap, "External-ID")
5254
5263
  });
5255
5264
  }
5256
5265
  function parseAssignmentHistory(body) {
@@ -5316,82 +5325,16 @@ var PredictionEngine = class {
5316
5325
  const firstDate = new Date(snapshots[0].capturedAt).getTime();
5317
5326
  const lastSnapshot = snapshots[snapshots.length - 1];
5318
5327
  const currentT = (new Date(lastSnapshot.capturedAt).getTime() - firstDate) / (7 * 24 * 60 * 60 * 1e3);
5319
- const baselines = {};
5320
- for (const category of ALL_CATEGORIES2) {
5321
- const threshold = thresholds[category];
5322
- const shouldProcess = categoriesToProcess.includes(category);
5323
- if (!shouldProcess) {
5324
- baselines[category] = this.zeroForecast(category, threshold);
5325
- continue;
5326
- }
5327
- const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
5328
- baselines[category] = this.forecastCategory(
5329
- category,
5330
- timeSeries,
5331
- currentT,
5332
- threshold,
5333
- opts.horizon
5334
- );
5335
- }
5328
+ const baselines = this.computeBaselines(
5329
+ categoriesToProcess,
5330
+ thresholds,
5331
+ snapshots,
5332
+ firstDate,
5333
+ currentT,
5334
+ opts.horizon
5335
+ );
5336
5336
  const specImpacts = this.computeSpecImpacts(opts);
5337
- const categories = {};
5338
- for (const category of ALL_CATEGORIES2) {
5339
- const baseline = baselines[category];
5340
- const threshold = thresholds[category];
5341
- if (!specImpacts || specImpacts.length === 0) {
5342
- categories[category] = {
5343
- baseline,
5344
- adjusted: baseline,
5345
- contributingFeatures: []
5346
- };
5347
- continue;
5348
- }
5349
- let totalDelta = 0;
5350
- const contributing = [];
5351
- for (const impact of specImpacts) {
5352
- const delta = impact.deltas?.[category] ?? 0;
5353
- if (delta !== 0) {
5354
- totalDelta += delta;
5355
- contributing.push({
5356
- name: impact.featureName,
5357
- specPath: impact.specPath,
5358
- delta
5359
- });
5360
- }
5361
- }
5362
- if (totalDelta === 0) {
5363
- categories[category] = {
5364
- baseline,
5365
- adjusted: baseline,
5366
- contributingFeatures: []
5367
- };
5368
- continue;
5369
- }
5370
- const adjusted = {
5371
- ...baseline,
5372
- projectedValue4w: baseline.projectedValue4w + totalDelta,
5373
- projectedValue8w: baseline.projectedValue8w + totalDelta,
5374
- projectedValue12w: baseline.projectedValue12w + totalDelta
5375
- };
5376
- const adjustedFit = {
5377
- slope: baseline.regression.slope,
5378
- intercept: baseline.regression.intercept + totalDelta,
5379
- rSquared: baseline.regression.rSquared,
5380
- dataPoints: baseline.regression.dataPoints
5381
- };
5382
- adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
5383
- adjusted.regression = {
5384
- slope: adjustedFit.slope,
5385
- intercept: adjustedFit.intercept,
5386
- rSquared: adjustedFit.rSquared,
5387
- dataPoints: adjustedFit.dataPoints
5388
- };
5389
- categories[category] = {
5390
- baseline,
5391
- adjusted,
5392
- contributingFeatures: contributing
5393
- };
5394
- }
5337
+ const categories = this.computeAdjustedForecasts(baselines, thresholds, specImpacts, currentT);
5395
5338
  const warnings = this.generateWarnings(
5396
5339
  categories,
5397
5340
  opts.horizon
@@ -5433,6 +5376,76 @@ var PredictionEngine = class {
5433
5376
  }
5434
5377
  return base;
5435
5378
  }
5379
+ computeBaselines(categoriesToProcess, thresholds, snapshots, firstDate, currentT, horizon) {
5380
+ const baselines = {};
5381
+ for (const category of ALL_CATEGORIES2) {
5382
+ const threshold = thresholds[category];
5383
+ if (!categoriesToProcess.includes(category)) {
5384
+ baselines[category] = this.zeroForecast(category, threshold);
5385
+ continue;
5386
+ }
5387
+ const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
5388
+ baselines[category] = this.forecastCategory(
5389
+ category,
5390
+ timeSeries,
5391
+ currentT,
5392
+ threshold,
5393
+ horizon
5394
+ );
5395
+ }
5396
+ return baselines;
5397
+ }
5398
+ computeAdjustedForecasts(baselines, thresholds, specImpacts, currentT) {
5399
+ const categories = {};
5400
+ for (const category of ALL_CATEGORIES2) {
5401
+ const baseline = baselines[category];
5402
+ categories[category] = this.adjustForecastForCategory(
5403
+ category,
5404
+ baseline,
5405
+ thresholds[category],
5406
+ specImpacts,
5407
+ currentT
5408
+ );
5409
+ }
5410
+ return categories;
5411
+ }
5412
+ adjustForecastForCategory(category, baseline, threshold, specImpacts, currentT) {
5413
+ if (!specImpacts || specImpacts.length === 0) {
5414
+ return { baseline, adjusted: baseline, contributingFeatures: [] };
5415
+ }
5416
+ let totalDelta = 0;
5417
+ const contributing = [];
5418
+ for (const impact of specImpacts) {
5419
+ const delta = impact.deltas?.[category] ?? 0;
5420
+ if (delta !== 0) {
5421
+ totalDelta += delta;
5422
+ contributing.push({ name: impact.featureName, specPath: impact.specPath, delta });
5423
+ }
5424
+ }
5425
+ if (totalDelta === 0) {
5426
+ return { baseline, adjusted: baseline, contributingFeatures: [] };
5427
+ }
5428
+ const adjusted = {
5429
+ ...baseline,
5430
+ projectedValue4w: baseline.projectedValue4w + totalDelta,
5431
+ projectedValue8w: baseline.projectedValue8w + totalDelta,
5432
+ projectedValue12w: baseline.projectedValue12w + totalDelta
5433
+ };
5434
+ const adjustedFit = {
5435
+ slope: baseline.regression.slope,
5436
+ intercept: baseline.regression.intercept + totalDelta,
5437
+ rSquared: baseline.regression.rSquared,
5438
+ dataPoints: baseline.regression.dataPoints
5439
+ };
5440
+ adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
5441
+ adjusted.regression = {
5442
+ slope: adjustedFit.slope,
5443
+ intercept: adjustedFit.intercept,
5444
+ rSquared: adjustedFit.rSquared,
5445
+ dataPoints: adjustedFit.dataPoints
5446
+ };
5447
+ return { baseline, adjusted, contributingFeatures: contributing };
5448
+ }
5436
5449
  /**
5437
5450
  * Extract time series for a single category from snapshots.
5438
5451
  * Returns array of { t (weeks from first), value } sorted oldest first.
@@ -6257,7 +6270,7 @@ async function saveState(projectPath, state, stream, session) {
6257
6270
  }
6258
6271
  }
6259
6272
 
6260
- // src/state/learnings.ts
6273
+ // src/state/learnings-content.ts
6261
6274
  import * as fs11 from "fs";
6262
6275
  import * as path8 from "path";
6263
6276
  import * as crypto from "crypto";
@@ -6268,6 +6281,25 @@ function parseFrontmatter2(line) {
6268
6281
  const tags = match[2] ? match[2].split(",").filter(Boolean) : [];
6269
6282
  return { hash, tags };
6270
6283
  }
6284
+ function parseDateFromEntry(entry) {
6285
+ const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
6286
+ return match ? match[1] ?? null : null;
6287
+ }
6288
+ function extractIndexEntry(entry) {
6289
+ const lines = entry.split("\n");
6290
+ const summary = lines[0] ?? entry;
6291
+ const tags = [];
6292
+ const skillMatch = entry.match(/\[skill:([^\]]+)\]/);
6293
+ if (skillMatch?.[1]) tags.push(skillMatch[1]);
6294
+ const outcomeMatch = entry.match(/\[outcome:([^\]]+)\]/);
6295
+ if (outcomeMatch?.[1]) tags.push(outcomeMatch[1]);
6296
+ return {
6297
+ hash: computeEntryHash(entry),
6298
+ tags,
6299
+ summary,
6300
+ fullText: entry
6301
+ };
6302
+ }
6271
6303
  function computeEntryHash(text) {
6272
6304
  return crypto.createHash("sha256").update(text).digest("hex").slice(0, 8);
6273
6305
  }
@@ -6302,8 +6334,8 @@ function saveContentHashes(stateDir, index) {
6302
6334
  const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
6303
6335
  fs11.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
6304
6336
  }
6305
- function rebuildContentHashes(stateDir) {
6306
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
6337
+ function rebuildContentHashes(stateDir, learningsFile) {
6338
+ const learningsPath = path8.join(stateDir, learningsFile);
6307
6339
  if (!fs11.existsSync(learningsPath)) return {};
6308
6340
  const content = fs11.readFileSync(learningsPath, "utf-8");
6309
6341
  const lines = content.split("\n");
@@ -6324,43 +6356,125 @@ function rebuildContentHashes(stateDir) {
6324
6356
  saveContentHashes(stateDir, index);
6325
6357
  return index;
6326
6358
  }
6327
- function extractIndexEntry(entry) {
6328
- const lines = entry.split("\n");
6329
- const summary = lines[0] ?? entry;
6330
- const tags = [];
6331
- const skillMatch = entry.match(/\[skill:([^\]]+)\]/);
6332
- if (skillMatch?.[1]) tags.push(skillMatch[1]);
6333
- const outcomeMatch = entry.match(/\[outcome:([^\]]+)\]/);
6334
- if (outcomeMatch?.[1]) tags.push(outcomeMatch[1]);
6335
- return {
6336
- hash: computeEntryHash(entry),
6337
- tags,
6338
- summary,
6339
- fullText: entry
6340
- };
6359
+ function analyzeLearningPatterns(entries) {
6360
+ const tagGroups = /* @__PURE__ */ new Map();
6361
+ for (const entry of entries) {
6362
+ const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
6363
+ for (const match of tagMatches) {
6364
+ const tag = match[1] ?? match[2];
6365
+ if (tag) {
6366
+ const group = tagGroups.get(tag) ?? [];
6367
+ group.push(entry);
6368
+ tagGroups.set(tag, group);
6369
+ }
6370
+ }
6371
+ }
6372
+ const patterns = [];
6373
+ for (const [tag, groupEntries] of tagGroups) {
6374
+ if (groupEntries.length >= 3) {
6375
+ patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
6376
+ }
6377
+ }
6378
+ return patterns.sort((a, b) => b.count - a.count);
6379
+ }
6380
+ function estimateTokens(text) {
6381
+ return Math.ceil(text.length / 4);
6341
6382
  }
6383
+ function scoreRelevance(entry, intent) {
6384
+ if (!intent || intent.trim() === "") return 0;
6385
+ const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
6386
+ if (intentWords.length === 0) return 0;
6387
+ const entryLower = entry.toLowerCase();
6388
+ const matches = intentWords.filter((word) => entryLower.includes(word));
6389
+ return matches.length / intentWords.length;
6390
+ }
6391
+
6392
+ // src/state/learnings-loader.ts
6393
+ import * as fs12 from "fs";
6394
+ import * as path9 from "path";
6342
6395
  var learningsCacheMap = /* @__PURE__ */ new Map();
6343
6396
  function clearLearningsCache() {
6344
6397
  learningsCacheMap.clear();
6345
6398
  }
6399
+ function invalidateLearningsCacheEntry(key) {
6400
+ learningsCacheMap.delete(key);
6401
+ }
6402
+ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
6403
+ try {
6404
+ const dirResult = await getStateDir(projectPath, stream, session);
6405
+ if (!dirResult.ok) return dirResult;
6406
+ const stateDir = dirResult.value;
6407
+ const learningsPath = path9.join(stateDir, LEARNINGS_FILE);
6408
+ if (!fs12.existsSync(learningsPath)) {
6409
+ return Ok([]);
6410
+ }
6411
+ const stats = fs12.statSync(learningsPath);
6412
+ const cacheKey = learningsPath;
6413
+ const cached = learningsCacheMap.get(cacheKey);
6414
+ let entries;
6415
+ if (cached && cached.mtimeMs === stats.mtimeMs) {
6416
+ entries = cached.entries;
6417
+ } else {
6418
+ const content = fs12.readFileSync(learningsPath, "utf-8");
6419
+ const lines = content.split("\n");
6420
+ entries = [];
6421
+ let currentBlock = [];
6422
+ for (const line of lines) {
6423
+ if (line.startsWith("# ")) continue;
6424
+ if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
6425
+ const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
6426
+ const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
6427
+ if (isDatedBullet || isHeading) {
6428
+ if (currentBlock.length > 0) {
6429
+ entries.push(currentBlock.join("\n"));
6430
+ }
6431
+ currentBlock = [line];
6432
+ } else if (line.trim() !== "" && currentBlock.length > 0) {
6433
+ currentBlock.push(line);
6434
+ }
6435
+ }
6436
+ if (currentBlock.length > 0) {
6437
+ entries.push(currentBlock.join("\n"));
6438
+ }
6439
+ learningsCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
6440
+ evictIfNeeded(learningsCacheMap);
6441
+ }
6442
+ if (!skillName) {
6443
+ return Ok(entries);
6444
+ }
6445
+ const filtered = entries.filter((entry) => entry.includes(`[skill:${skillName}]`));
6446
+ return Ok(filtered);
6447
+ } catch (error) {
6448
+ return Err(
6449
+ new Error(
6450
+ `Failed to load learnings: ${error instanceof Error ? error.message : String(error)}`
6451
+ )
6452
+ );
6453
+ }
6454
+ }
6455
+
6456
+ // src/state/learnings.ts
6457
+ import * as fs13 from "fs";
6458
+ import * as path10 from "path";
6459
+ import * as crypto2 from "crypto";
6346
6460
  async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
6347
6461
  try {
6348
6462
  const dirResult = await getStateDir(projectPath, stream, session);
6349
6463
  if (!dirResult.ok) return dirResult;
6350
6464
  const stateDir = dirResult.value;
6351
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
6352
- fs11.mkdirSync(stateDir, { recursive: true });
6465
+ const learningsPath = path10.join(stateDir, LEARNINGS_FILE);
6466
+ fs13.mkdirSync(stateDir, { recursive: true });
6353
6467
  const normalizedContent = normalizeLearningContent(learning);
6354
6468
  const contentHash = computeContentHash(normalizedContent);
6355
- const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
6469
+ const hashesPath = path10.join(stateDir, CONTENT_HASHES_FILE);
6356
6470
  let contentHashes;
6357
- if (fs11.existsSync(hashesPath)) {
6471
+ if (fs13.existsSync(hashesPath)) {
6358
6472
  contentHashes = loadContentHashes(stateDir);
6359
- if (Object.keys(contentHashes).length === 0 && fs11.existsSync(learningsPath)) {
6360
- contentHashes = rebuildContentHashes(stateDir);
6473
+ if (Object.keys(contentHashes).length === 0 && fs13.existsSync(learningsPath)) {
6474
+ contentHashes = rebuildContentHashes(stateDir, LEARNINGS_FILE);
6361
6475
  }
6362
- } else if (fs11.existsSync(learningsPath)) {
6363
- contentHashes = rebuildContentHashes(stateDir);
6476
+ } else if (fs13.existsSync(learningsPath)) {
6477
+ contentHashes = rebuildContentHashes(stateDir, LEARNINGS_FILE);
6364
6478
  } else {
6365
6479
  contentHashes = {};
6366
6480
  }
@@ -6379,7 +6493,7 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
6379
6493
  } else {
6380
6494
  bulletLine = `- **${timestamp}:** ${learning}`;
6381
6495
  }
6382
- const hash = crypto.createHash("sha256").update(bulletLine).digest("hex").slice(0, 8);
6496
+ const hash = crypto2.createHash("sha256").update(bulletLine).digest("hex").slice(0, 8);
6383
6497
  const tagsStr = fmTags.length > 0 ? ` tags:${fmTags.join(",")}` : "";
6384
6498
  const frontmatter = `<!-- hash:${hash}${tagsStr} -->`;
6385
6499
  const entry = `
@@ -6387,19 +6501,19 @@ ${frontmatter}
6387
6501
  ${bulletLine}
6388
6502
  `;
6389
6503
  let existingLineCount;
6390
- if (!fs11.existsSync(learningsPath)) {
6391
- fs11.writeFileSync(learningsPath, `# Learnings
6504
+ if (!fs13.existsSync(learningsPath)) {
6505
+ fs13.writeFileSync(learningsPath, `# Learnings
6392
6506
  ${entry}`);
6393
6507
  existingLineCount = 1;
6394
6508
  } else {
6395
- const existingContent = fs11.readFileSync(learningsPath, "utf-8");
6509
+ const existingContent = fs13.readFileSync(learningsPath, "utf-8");
6396
6510
  existingLineCount = existingContent.split("\n").length;
6397
- fs11.appendFileSync(learningsPath, entry);
6511
+ fs13.appendFileSync(learningsPath, entry);
6398
6512
  }
6399
6513
  const bulletLine_lineNum = existingLineCount + 2;
6400
6514
  contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
6401
6515
  saveContentHashes(stateDir, contentHashes);
6402
- learningsCacheMap.delete(learningsPath);
6516
+ invalidateLearningsCacheEntry(learningsPath);
6403
6517
  return Ok(void 0);
6404
6518
  } catch (error) {
6405
6519
  return Err(
@@ -6409,42 +6523,6 @@ ${entry}`);
6409
6523
  );
6410
6524
  }
6411
6525
  }
6412
- function estimateTokens(text) {
6413
- return Math.ceil(text.length / 4);
6414
- }
6415
- function scoreRelevance(entry, intent) {
6416
- if (!intent || intent.trim() === "") return 0;
6417
- const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
6418
- if (intentWords.length === 0) return 0;
6419
- const entryLower = entry.toLowerCase();
6420
- const matches = intentWords.filter((word) => entryLower.includes(word));
6421
- return matches.length / intentWords.length;
6422
- }
6423
- function parseDateFromEntry(entry) {
6424
- const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
6425
- return match ? match[1] ?? null : null;
6426
- }
6427
- function analyzeLearningPatterns(entries) {
6428
- const tagGroups = /* @__PURE__ */ new Map();
6429
- for (const entry of entries) {
6430
- const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
6431
- for (const match of tagMatches) {
6432
- const tag = match[1] ?? match[2];
6433
- if (tag) {
6434
- const group = tagGroups.get(tag) ?? [];
6435
- group.push(entry);
6436
- tagGroups.set(tag, group);
6437
- }
6438
- }
6439
- }
6440
- const patterns = [];
6441
- for (const [tag, groupEntries] of tagGroups) {
6442
- if (groupEntries.length >= 3) {
6443
- patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
6444
- }
6445
- }
6446
- return patterns.sort((a, b) => b.count - a.count);
6447
- }
6448
6526
  async function loadBudgetedLearnings(projectPath, options) {
6449
6527
  const { intent, tokenBudget = 1e3, skill, session, stream, depth = "summary" } = options;
6450
6528
  if (depth === "index") {
@@ -6508,11 +6586,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
6508
6586
  const dirResult = await getStateDir(projectPath, stream, session);
6509
6587
  if (!dirResult.ok) return dirResult;
6510
6588
  const stateDir = dirResult.value;
6511
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
6512
- if (!fs11.existsSync(learningsPath)) {
6589
+ const learningsPath = path10.join(stateDir, LEARNINGS_FILE);
6590
+ if (!fs13.existsSync(learningsPath)) {
6513
6591
  return Ok([]);
6514
6592
  }
6515
- const content = fs11.readFileSync(learningsPath, "utf-8");
6593
+ const content = fs13.readFileSync(learningsPath, "utf-8");
6516
6594
  const lines = content.split("\n");
6517
6595
  const indexEntries = [];
6518
6596
  let pendingFrontmatter = null;
@@ -6565,74 +6643,25 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
6565
6643
  );
6566
6644
  }
6567
6645
  }
6568
- async function loadRelevantLearnings(projectPath, skillName, stream, session) {
6569
- try {
6570
- const dirResult = await getStateDir(projectPath, stream, session);
6571
- if (!dirResult.ok) return dirResult;
6572
- const stateDir = dirResult.value;
6573
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
6574
- if (!fs11.existsSync(learningsPath)) {
6575
- return Ok([]);
6576
- }
6577
- const stats = fs11.statSync(learningsPath);
6578
- const cacheKey = learningsPath;
6579
- const cached = learningsCacheMap.get(cacheKey);
6580
- let entries;
6581
- if (cached && cached.mtimeMs === stats.mtimeMs) {
6582
- entries = cached.entries;
6583
- } else {
6584
- const content = fs11.readFileSync(learningsPath, "utf-8");
6585
- const lines = content.split("\n");
6586
- entries = [];
6587
- let currentBlock = [];
6588
- for (const line of lines) {
6589
- if (line.startsWith("# ")) continue;
6590
- if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
6591
- const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
6592
- const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
6593
- if (isDatedBullet || isHeading) {
6594
- if (currentBlock.length > 0) {
6595
- entries.push(currentBlock.join("\n"));
6596
- }
6597
- currentBlock = [line];
6598
- } else if (line.trim() !== "" && currentBlock.length > 0) {
6599
- currentBlock.push(line);
6600
- }
6601
- }
6602
- if (currentBlock.length > 0) {
6603
- entries.push(currentBlock.join("\n"));
6604
- }
6605
- learningsCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
6606
- evictIfNeeded(learningsCacheMap);
6607
- }
6608
- if (!skillName) {
6609
- return Ok(entries);
6610
- }
6611
- const filtered = entries.filter((entry) => entry.includes(`[skill:${skillName}]`));
6612
- return Ok(filtered);
6613
- } catch (error) {
6614
- return Err(
6615
- new Error(
6616
- `Failed to load learnings: ${error instanceof Error ? error.message : String(error)}`
6617
- )
6618
- );
6619
- }
6620
- }
6646
+
6647
+ // src/state/learnings-lifecycle.ts
6648
+ import * as fs14 from "fs";
6649
+ import * as path11 from "path";
6621
6650
  async function archiveLearnings(projectPath, entries, stream) {
6622
6651
  try {
6623
6652
  const dirResult = await getStateDir(projectPath, stream);
6624
6653
  if (!dirResult.ok) return dirResult;
6625
6654
  const stateDir = dirResult.value;
6626
- const archiveDir = path8.join(stateDir, "learnings-archive");
6627
- fs11.mkdirSync(archiveDir, { recursive: true });
6655
+ const archiveDir = path11.join(stateDir, "learnings-archive");
6656
+ fs14.mkdirSync(archiveDir, { recursive: true });
6628
6657
  const now = /* @__PURE__ */ new Date();
6629
6658
  const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
6630
- const archivePath = path8.join(archiveDir, `${yearMonth}.md`);
6659
+ const archivePath = path11.join(archiveDir, `${yearMonth}.md`);
6631
6660
  const archiveContent = entries.join("\n\n") + "\n";
6632
- if (fs11.existsSync(archivePath)) {
6633
- fs11.appendFileSync(archivePath, "\n" + archiveContent);
6661
+ if (fs14.existsSync(archivePath)) {
6662
+ fs14.appendFileSync(archivePath, "\n" + archiveContent);
6634
6663
  } else {
6635
- fs11.writeFileSync(archivePath, `# Learnings Archive
6664
+ fs14.writeFileSync(archivePath, `# Learnings Archive
6636
6665
 
6637
6666
  ${archiveContent}`);
6638
6667
  }
@@ -6650,8 +6679,8 @@ async function pruneLearnings(projectPath, stream) {
6650
6679
  const dirResult = await getStateDir(projectPath, stream);
6651
6680
  if (!dirResult.ok) return dirResult;
6652
6681
  const stateDir = dirResult.value;
6653
- const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
6654
- if (!fs11.existsSync(learningsPath)) {
6682
+ const learningsPath = path11.join(stateDir, LEARNINGS_FILE);
6683
+ if (!fs14.existsSync(learningsPath)) {
6655
6684
  return Ok({ kept: 0, archived: 0, patterns: [] });
6656
6685
  }
6657
6686
  const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
@@ -6682,8 +6711,8 @@ async function pruneLearnings(projectPath, stream) {
6682
6711
  if (!archiveResult.ok) return archiveResult;
6683
6712
  }
6684
6713
  const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
6685
- fs11.writeFileSync(learningsPath, newContent);
6686
- learningsCacheMap.delete(learningsPath);
6714
+ fs14.writeFileSync(learningsPath, newContent);
6715
+ invalidateLearningsCacheEntry(learningsPath);
6687
6716
  return Ok({
6688
6717
  kept: toKeep.length,
6689
6718
  archived: toArchive.length,
@@ -6727,21 +6756,21 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
6727
6756
  const dirResult = await getStateDir(projectPath, stream);
6728
6757
  if (!dirResult.ok) return dirResult;
6729
6758
  const stateDir = dirResult.value;
6730
- const globalPath = path8.join(stateDir, LEARNINGS_FILE);
6731
- const existingGlobal = fs11.existsSync(globalPath) ? fs11.readFileSync(globalPath, "utf-8") : "";
6759
+ const globalPath = path11.join(stateDir, LEARNINGS_FILE);
6760
+ const existingGlobal = fs14.existsSync(globalPath) ? fs14.readFileSync(globalPath, "utf-8") : "";
6732
6761
  const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
6733
6762
  if (newEntries.length === 0) {
6734
6763
  return Ok({ promoted: 0, skipped: skipped + toPromote.length });
6735
6764
  }
6736
6765
  const promotedContent = newEntries.join("\n\n") + "\n";
6737
6766
  if (!existingGlobal) {
6738
- fs11.writeFileSync(globalPath, `# Learnings
6767
+ fs14.writeFileSync(globalPath, `# Learnings
6739
6768
 
6740
6769
  ${promotedContent}`);
6741
6770
  } else {
6742
- fs11.appendFileSync(globalPath, "\n\n" + promotedContent);
6771
+ fs14.appendFileSync(globalPath, "\n\n" + promotedContent);
6743
6772
  }
6744
- learningsCacheMap.delete(globalPath);
6773
+ invalidateLearningsCacheEntry(globalPath);
6745
6774
  return Ok({
6746
6775
  promoted: newEntries.length,
6747
6776
  skipped: skipped + (toPromote.length - newEntries.length)
@@ -6761,8 +6790,8 @@ async function countLearningEntries(projectPath, stream) {
6761
6790
  }
6762
6791
 
6763
6792
  // src/state/failures.ts
6764
- import * as fs12 from "fs";
6765
- import * as path9 from "path";
6793
+ import * as fs15 from "fs";
6794
+ import * as path12 from "path";
6766
6795
  var failuresCacheMap = /* @__PURE__ */ new Map();
6767
6796
  function clearFailuresCache() {
6768
6797
  failuresCacheMap.clear();
@@ -6773,17 +6802,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
6773
6802
  const dirResult = await getStateDir(projectPath, stream, session);
6774
6803
  if (!dirResult.ok) return dirResult;
6775
6804
  const stateDir = dirResult.value;
6776
- const failuresPath = path9.join(stateDir, FAILURES_FILE);
6777
- fs12.mkdirSync(stateDir, { recursive: true });
6805
+ const failuresPath = path12.join(stateDir, FAILURES_FILE);
6806
+ fs15.mkdirSync(stateDir, { recursive: true });
6778
6807
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6779
6808
  const entry = `
6780
6809
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
6781
6810
  `;
6782
- if (!fs12.existsSync(failuresPath)) {
6783
- fs12.writeFileSync(failuresPath, `# Failures
6811
+ if (!fs15.existsSync(failuresPath)) {
6812
+ fs15.writeFileSync(failuresPath, `# Failures
6784
6813
  ${entry}`);
6785
6814
  } else {
6786
- fs12.appendFileSync(failuresPath, entry);
6815
+ fs15.appendFileSync(failuresPath, entry);
6787
6816
  }
6788
6817
  failuresCacheMap.delete(failuresPath);
6789
6818
  return Ok(void 0);
@@ -6800,17 +6829,17 @@ async function loadFailures(projectPath, stream, session) {
6800
6829
  const dirResult = await getStateDir(projectPath, stream, session);
6801
6830
  if (!dirResult.ok) return dirResult;
6802
6831
  const stateDir = dirResult.value;
6803
- const failuresPath = path9.join(stateDir, FAILURES_FILE);
6804
- if (!fs12.existsSync(failuresPath)) {
6832
+ const failuresPath = path12.join(stateDir, FAILURES_FILE);
6833
+ if (!fs15.existsSync(failuresPath)) {
6805
6834
  return Ok([]);
6806
6835
  }
6807
- const stats = fs12.statSync(failuresPath);
6836
+ const stats = fs15.statSync(failuresPath);
6808
6837
  const cacheKey = failuresPath;
6809
6838
  const cached = failuresCacheMap.get(cacheKey);
6810
6839
  if (cached && cached.mtimeMs === stats.mtimeMs) {
6811
6840
  return Ok(cached.entries);
6812
6841
  }
6813
- const content = fs12.readFileSync(failuresPath, "utf-8");
6842
+ const content = fs15.readFileSync(failuresPath, "utf-8");
6814
6843
  const entries = [];
6815
6844
  for (const line of content.split("\n")) {
6816
6845
  const match = line.match(FAILURE_LINE_REGEX);
@@ -6839,20 +6868,20 @@ async function archiveFailures(projectPath, stream, session) {
6839
6868
  const dirResult = await getStateDir(projectPath, stream, session);
6840
6869
  if (!dirResult.ok) return dirResult;
6841
6870
  const stateDir = dirResult.value;
6842
- const failuresPath = path9.join(stateDir, FAILURES_FILE);
6843
- if (!fs12.existsSync(failuresPath)) {
6871
+ const failuresPath = path12.join(stateDir, FAILURES_FILE);
6872
+ if (!fs15.existsSync(failuresPath)) {
6844
6873
  return Ok(void 0);
6845
6874
  }
6846
- const archiveDir = path9.join(stateDir, "archive");
6847
- fs12.mkdirSync(archiveDir, { recursive: true });
6875
+ const archiveDir = path12.join(stateDir, "archive");
6876
+ fs15.mkdirSync(archiveDir, { recursive: true });
6848
6877
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6849
6878
  let archiveName = `failures-${date}.md`;
6850
6879
  let counter = 2;
6851
- while (fs12.existsSync(path9.join(archiveDir, archiveName))) {
6880
+ while (fs15.existsSync(path12.join(archiveDir, archiveName))) {
6852
6881
  archiveName = `failures-${date}-${counter}.md`;
6853
6882
  counter++;
6854
6883
  }
6855
- fs12.renameSync(failuresPath, path9.join(archiveDir, archiveName));
6884
+ fs15.renameSync(failuresPath, path12.join(archiveDir, archiveName));
6856
6885
  failuresCacheMap.delete(failuresPath);
6857
6886
  return Ok(void 0);
6858
6887
  } catch (error) {
@@ -6865,16 +6894,16 @@ async function archiveFailures(projectPath, stream, session) {
6865
6894
  }
6866
6895
 
6867
6896
  // src/state/handoff.ts
6868
- import * as fs13 from "fs";
6869
- import * as path10 from "path";
6897
+ import * as fs16 from "fs";
6898
+ import * as path13 from "path";
6870
6899
  async function saveHandoff(projectPath, handoff, stream, session) {
6871
6900
  try {
6872
6901
  const dirResult = await getStateDir(projectPath, stream, session);
6873
6902
  if (!dirResult.ok) return dirResult;
6874
6903
  const stateDir = dirResult.value;
6875
- const handoffPath = path10.join(stateDir, HANDOFF_FILE);
6876
- fs13.mkdirSync(stateDir, { recursive: true });
6877
- fs13.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
6904
+ const handoffPath = path13.join(stateDir, HANDOFF_FILE);
6905
+ fs16.mkdirSync(stateDir, { recursive: true });
6906
+ fs16.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
6878
6907
  return Ok(void 0);
6879
6908
  } catch (error) {
6880
6909
  return Err(
@@ -6887,11 +6916,11 @@ async function loadHandoff(projectPath, stream, session) {
6887
6916
  const dirResult = await getStateDir(projectPath, stream, session);
6888
6917
  if (!dirResult.ok) return dirResult;
6889
6918
  const stateDir = dirResult.value;
6890
- const handoffPath = path10.join(stateDir, HANDOFF_FILE);
6891
- if (!fs13.existsSync(handoffPath)) {
6919
+ const handoffPath = path13.join(stateDir, HANDOFF_FILE);
6920
+ if (!fs16.existsSync(handoffPath)) {
6892
6921
  return Ok(null);
6893
6922
  }
6894
- const raw = fs13.readFileSync(handoffPath, "utf-8");
6923
+ const raw = fs16.readFileSync(handoffPath, "utf-8");
6895
6924
  const parsed = JSON.parse(raw);
6896
6925
  const result = HandoffSchema.safeParse(parsed);
6897
6926
  if (!result.success) {
@@ -6906,33 +6935,33 @@ async function loadHandoff(projectPath, stream, session) {
6906
6935
  }
6907
6936
 
6908
6937
  // src/state/mechanical-gate.ts
6909
- import * as fs14 from "fs";
6910
- import * as path11 from "path";
6938
+ import * as fs17 from "fs";
6939
+ import * as path14 from "path";
6911
6940
  import { execSync as execSync2 } from "child_process";
6912
6941
  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:.-]+$/;
6913
6942
  function loadChecksFromConfig(gateConfigPath) {
6914
- if (!fs14.existsSync(gateConfigPath)) return [];
6915
- const raw = JSON.parse(fs14.readFileSync(gateConfigPath, "utf-8"));
6943
+ if (!fs17.existsSync(gateConfigPath)) return [];
6944
+ const raw = JSON.parse(fs17.readFileSync(gateConfigPath, "utf-8"));
6916
6945
  const config = GateConfigSchema.safeParse(raw);
6917
6946
  if (config.success && config.data.checks) return config.data.checks;
6918
6947
  return [];
6919
6948
  }
6920
6949
  function discoverChecksFromProject(projectPath) {
6921
6950
  const checks = [];
6922
- const packageJsonPath = path11.join(projectPath, "package.json");
6923
- if (fs14.existsSync(packageJsonPath)) {
6924
- const pkg = JSON.parse(fs14.readFileSync(packageJsonPath, "utf-8"));
6951
+ const packageJsonPath = path14.join(projectPath, "package.json");
6952
+ if (fs17.existsSync(packageJsonPath)) {
6953
+ const pkg = JSON.parse(fs17.readFileSync(packageJsonPath, "utf-8"));
6925
6954
  const scripts = pkg.scripts || {};
6926
6955
  if (scripts.test) checks.push({ name: "test", command: "npm test" });
6927
6956
  if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
6928
6957
  if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
6929
6958
  if (scripts.build) checks.push({ name: "build", command: "npm run build" });
6930
6959
  }
6931
- if (fs14.existsSync(path11.join(projectPath, "go.mod"))) {
6960
+ if (fs17.existsSync(path14.join(projectPath, "go.mod"))) {
6932
6961
  checks.push({ name: "test", command: "go test ./..." });
6933
6962
  checks.push({ name: "build", command: "go build ./..." });
6934
6963
  }
6935
- if (fs14.existsSync(path11.join(projectPath, "pyproject.toml")) || fs14.existsSync(path11.join(projectPath, "setup.py"))) {
6964
+ if (fs17.existsSync(path14.join(projectPath, "pyproject.toml")) || fs17.existsSync(path14.join(projectPath, "setup.py"))) {
6936
6965
  checks.push({ name: "test", command: "python -m pytest" });
6937
6966
  }
6938
6967
  return checks;
@@ -6972,8 +7001,8 @@ function executeCheck(check, projectPath) {
6972
7001
  }
6973
7002
  }
6974
7003
  async function runMechanicalGate(projectPath) {
6975
- const harnessDir = path11.join(projectPath, HARNESS_DIR);
6976
- const gateConfigPath = path11.join(harnessDir, GATE_CONFIG_FILE);
7004
+ const harnessDir = path14.join(projectPath, HARNESS_DIR);
7005
+ const gateConfigPath = path14.join(harnessDir, GATE_CONFIG_FILE);
6977
7006
  try {
6978
7007
  let checks = loadChecksFromConfig(gateConfigPath);
6979
7008
  if (checks.length === 0) {
@@ -6994,8 +7023,8 @@ async function runMechanicalGate(projectPath) {
6994
7023
  }
6995
7024
 
6996
7025
  // src/state/session-summary.ts
6997
- import * as fs15 from "fs";
6998
- import * as path12 from "path";
7026
+ import * as fs18 from "fs";
7027
+ import * as path15 from "path";
6999
7028
  function formatSummary(data) {
7000
7029
  const lines = [
7001
7030
  "## Session Summary",
@@ -7033,9 +7062,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
7033
7062
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
7034
7063
  if (!dirResult.ok) return dirResult;
7035
7064
  const sessionDir = dirResult.value;
7036
- const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
7065
+ const summaryPath = path15.join(sessionDir, SUMMARY_FILE);
7037
7066
  const content = formatSummary(data);
7038
- fs15.writeFileSync(summaryPath, content);
7067
+ fs18.writeFileSync(summaryPath, content);
7039
7068
  const description = deriveIndexDescription(data);
7040
7069
  updateSessionIndex(projectPath, sessionSlug, description);
7041
7070
  return Ok(void 0);
@@ -7052,11 +7081,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
7052
7081
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7053
7082
  if (!dirResult.ok) return dirResult;
7054
7083
  const sessionDir = dirResult.value;
7055
- const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
7056
- if (!fs15.existsSync(summaryPath)) {
7084
+ const summaryPath = path15.join(sessionDir, SUMMARY_FILE);
7085
+ if (!fs18.existsSync(summaryPath)) {
7057
7086
  return Ok(null);
7058
7087
  }
7059
- const content = fs15.readFileSync(summaryPath, "utf-8");
7088
+ const content = fs18.readFileSync(summaryPath, "utf-8");
7060
7089
  return Ok(content);
7061
7090
  } catch (error) {
7062
7091
  return Err(
@@ -7068,11 +7097,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
7068
7097
  }
7069
7098
  function listActiveSessions(projectPath) {
7070
7099
  try {
7071
- const indexPath2 = path12.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
7072
- if (!fs15.existsSync(indexPath2)) {
7100
+ const indexPath2 = path15.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
7101
+ if (!fs18.existsSync(indexPath2)) {
7073
7102
  return Ok(null);
7074
7103
  }
7075
- const content = fs15.readFileSync(indexPath2, "utf-8");
7104
+ const content = fs18.readFileSync(indexPath2, "utf-8");
7076
7105
  return Ok(content);
7077
7106
  } catch (error) {
7078
7107
  return Err(
@@ -7084,8 +7113,8 @@ function listActiveSessions(projectPath) {
7084
7113
  }
7085
7114
 
7086
7115
  // src/state/session-sections.ts
7087
- import * as fs16 from "fs";
7088
- import * as path13 from "path";
7116
+ import * as fs19 from "fs";
7117
+ import * as path16 from "path";
7089
7118
  import { SESSION_SECTION_NAMES } from "@harness-engineering/types";
7090
7119
  function emptySections() {
7091
7120
  const sections = {};
@@ -7098,12 +7127,12 @@ async function loadSessionState(projectPath, sessionSlug) {
7098
7127
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7099
7128
  if (!dirResult.ok) return dirResult;
7100
7129
  const sessionDir = dirResult.value;
7101
- const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
7102
- if (!fs16.existsSync(filePath)) {
7130
+ const filePath = path16.join(sessionDir, SESSION_STATE_FILE);
7131
+ if (!fs19.existsSync(filePath)) {
7103
7132
  return Ok(emptySections());
7104
7133
  }
7105
7134
  try {
7106
- const raw = fs16.readFileSync(filePath, "utf-8");
7135
+ const raw = fs19.readFileSync(filePath, "utf-8");
7107
7136
  const parsed = JSON.parse(raw);
7108
7137
  const sections = emptySections();
7109
7138
  for (const name of SESSION_SECTION_NAMES) {
@@ -7124,9 +7153,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
7124
7153
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
7125
7154
  if (!dirResult.ok) return dirResult;
7126
7155
  const sessionDir = dirResult.value;
7127
- const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
7156
+ const filePath = path16.join(sessionDir, SESSION_STATE_FILE);
7128
7157
  try {
7129
- fs16.writeFileSync(filePath, JSON.stringify(sections, null, 2));
7158
+ fs19.writeFileSync(filePath, JSON.stringify(sections, null, 2));
7130
7159
  return Ok(void 0);
7131
7160
  } catch (error) {
7132
7161
  return Err(
@@ -7180,32 +7209,32 @@ function generateEntryId() {
7180
7209
  }
7181
7210
 
7182
7211
  // src/state/session-archive.ts
7183
- import * as fs17 from "fs";
7184
- import * as path14 from "path";
7212
+ import * as fs20 from "fs";
7213
+ import * as path17 from "path";
7185
7214
  async function archiveSession(projectPath, sessionSlug) {
7186
7215
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7187
7216
  if (!dirResult.ok) return dirResult;
7188
7217
  const sessionDir = dirResult.value;
7189
- if (!fs17.existsSync(sessionDir)) {
7218
+ if (!fs20.existsSync(sessionDir)) {
7190
7219
  return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
7191
7220
  }
7192
- const archiveBase = path14.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
7221
+ const archiveBase = path17.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
7193
7222
  try {
7194
- fs17.mkdirSync(archiveBase, { recursive: true });
7223
+ fs20.mkdirSync(archiveBase, { recursive: true });
7195
7224
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7196
7225
  let archiveName = `${sessionSlug}-${date}`;
7197
7226
  let counter = 1;
7198
- while (fs17.existsSync(path14.join(archiveBase, archiveName))) {
7227
+ while (fs20.existsSync(path17.join(archiveBase, archiveName))) {
7199
7228
  archiveName = `${sessionSlug}-${date}-${counter}`;
7200
7229
  counter++;
7201
7230
  }
7202
- const dest = path14.join(archiveBase, archiveName);
7231
+ const dest = path17.join(archiveBase, archiveName);
7203
7232
  try {
7204
- fs17.renameSync(sessionDir, dest);
7233
+ fs20.renameSync(sessionDir, dest);
7205
7234
  } catch (renameErr) {
7206
7235
  if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
7207
- fs17.cpSync(sessionDir, dest, { recursive: true });
7208
- fs17.rmSync(sessionDir, { recursive: true });
7236
+ fs20.cpSync(sessionDir, dest, { recursive: true });
7237
+ fs20.rmSync(sessionDir, { recursive: true });
7209
7238
  } else {
7210
7239
  throw renameErr;
7211
7240
  }
@@ -7221,8 +7250,8 @@ async function archiveSession(projectPath, sessionSlug) {
7221
7250
  }
7222
7251
 
7223
7252
  // src/state/events.ts
7224
- import * as fs18 from "fs";
7225
- import * as path15 from "path";
7253
+ import * as fs21 from "fs";
7254
+ import * as path18 from "path";
7226
7255
  import { z as z7 } from "zod";
7227
7256
  var SkillEventSchema = z7.object({
7228
7257
  timestamp: z7.string(),
@@ -7243,8 +7272,8 @@ function loadKnownHashes(eventsPath) {
7243
7272
  const cached = knownHashesCache.get(eventsPath);
7244
7273
  if (cached) return cached;
7245
7274
  const hashes = /* @__PURE__ */ new Set();
7246
- if (fs18.existsSync(eventsPath)) {
7247
- const content = fs18.readFileSync(eventsPath, "utf-8");
7275
+ if (fs21.existsSync(eventsPath)) {
7276
+ const content = fs21.readFileSync(eventsPath, "utf-8");
7248
7277
  const lines = content.split("\n").filter((line) => line.trim() !== "");
7249
7278
  for (const line of lines) {
7250
7279
  try {
@@ -7267,8 +7296,8 @@ async function emitEvent(projectPath, event, options) {
7267
7296
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
7268
7297
  if (!dirResult.ok) return dirResult;
7269
7298
  const stateDir = dirResult.value;
7270
- const eventsPath = path15.join(stateDir, EVENTS_FILE);
7271
- fs18.mkdirSync(stateDir, { recursive: true });
7299
+ const eventsPath = path18.join(stateDir, EVENTS_FILE);
7300
+ fs21.mkdirSync(stateDir, { recursive: true });
7272
7301
  const contentHash = computeEventHash(event, options?.session);
7273
7302
  const knownHashes = loadKnownHashes(eventsPath);
7274
7303
  if (knownHashes.has(contentHash)) {
@@ -7282,7 +7311,7 @@ async function emitEvent(projectPath, event, options) {
7282
7311
  if (options?.session) {
7283
7312
  fullEvent.session = options.session;
7284
7313
  }
7285
- fs18.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
7314
+ fs21.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
7286
7315
  knownHashes.add(contentHash);
7287
7316
  return Ok({ written: true });
7288
7317
  } catch (error) {
@@ -7296,11 +7325,11 @@ async function loadEvents(projectPath, options) {
7296
7325
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
7297
7326
  if (!dirResult.ok) return dirResult;
7298
7327
  const stateDir = dirResult.value;
7299
- const eventsPath = path15.join(stateDir, EVENTS_FILE);
7300
- if (!fs18.existsSync(eventsPath)) {
7328
+ const eventsPath = path18.join(stateDir, EVENTS_FILE);
7329
+ if (!fs21.existsSync(eventsPath)) {
7301
7330
  return Ok([]);
7302
7331
  }
7303
- const content = fs18.readFileSync(eventsPath, "utf-8");
7332
+ const content = fs21.readFileSync(eventsPath, "utf-8");
7304
7333
  const lines = content.split("\n").filter((line) => line.trim() !== "");
7305
7334
  const events = [];
7306
7335
  for (const line of lines) {
@@ -7514,7 +7543,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
7514
7543
  }
7515
7544
 
7516
7545
  // src/security/scanner.ts
7517
- import * as fs20 from "fs/promises";
7546
+ import * as fs23 from "fs/promises";
7518
7547
  import { minimatch as minimatch4 } from "minimatch";
7519
7548
 
7520
7549
  // src/security/rules/registry.ts
@@ -7602,15 +7631,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
7602
7631
  }
7603
7632
 
7604
7633
  // src/security/stack-detector.ts
7605
- import * as fs19 from "fs";
7606
- import * as path16 from "path";
7634
+ import * as fs22 from "fs";
7635
+ import * as path19 from "path";
7607
7636
  function detectStack(projectRoot) {
7608
7637
  const stacks = [];
7609
- const pkgJsonPath = path16.join(projectRoot, "package.json");
7610
- if (fs19.existsSync(pkgJsonPath)) {
7638
+ const pkgJsonPath = path19.join(projectRoot, "package.json");
7639
+ if (fs22.existsSync(pkgJsonPath)) {
7611
7640
  stacks.push("node");
7612
7641
  try {
7613
- const pkgJson = JSON.parse(fs19.readFileSync(pkgJsonPath, "utf-8"));
7642
+ const pkgJson = JSON.parse(fs22.readFileSync(pkgJsonPath, "utf-8"));
7614
7643
  const allDeps = {
7615
7644
  ...pkgJson.dependencies,
7616
7645
  ...pkgJson.devDependencies
@@ -7625,13 +7654,13 @@ function detectStack(projectRoot) {
7625
7654
  } catch {
7626
7655
  }
7627
7656
  }
7628
- const goModPath = path16.join(projectRoot, "go.mod");
7629
- if (fs19.existsSync(goModPath)) {
7657
+ const goModPath = path19.join(projectRoot, "go.mod");
7658
+ if (fs22.existsSync(goModPath)) {
7630
7659
  stacks.push("go");
7631
7660
  }
7632
- const requirementsPath = path16.join(projectRoot, "requirements.txt");
7633
- const pyprojectPath = path16.join(projectRoot, "pyproject.toml");
7634
- if (fs19.existsSync(requirementsPath) || fs19.existsSync(pyprojectPath)) {
7661
+ const requirementsPath = path19.join(projectRoot, "requirements.txt");
7662
+ const pyprojectPath = path19.join(projectRoot, "pyproject.toml");
7663
+ if (fs22.existsSync(requirementsPath) || fs22.existsSync(pyprojectPath)) {
7635
7664
  stacks.push("python");
7636
7665
  }
7637
7666
  return stacks;
@@ -8462,7 +8491,7 @@ var SecurityScanner = class {
8462
8491
  }
8463
8492
  async scanFile(filePath) {
8464
8493
  if (!this.config.enabled) return [];
8465
- const content = await fs20.readFile(filePath, "utf-8");
8494
+ const content = await fs23.readFile(filePath, "utf-8");
8466
8495
  return this.scanContentForFile(content, filePath, 1);
8467
8496
  }
8468
8497
  scanContentForFile(content, filePath, startLine = 1) {
@@ -8789,19 +8818,19 @@ var DESTRUCTIVE_BASH = [
8789
8818
  ];
8790
8819
 
8791
8820
  // src/security/taint.ts
8792
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync12, unlinkSync, mkdirSync as mkdirSync12, readdirSync as readdirSync3 } from "fs";
8793
- import { join as join24, dirname as dirname9 } from "path";
8821
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, unlinkSync, mkdirSync as mkdirSync13, readdirSync as readdirSync3 } from "fs";
8822
+ import { join as join27, dirname as dirname9 } from "path";
8794
8823
  var TAINT_DURATION_MS = 30 * 60 * 1e3;
8795
8824
  var DEFAULT_SESSION_ID = "default";
8796
8825
  function getTaintFilePath(projectRoot, sessionId) {
8797
8826
  const id = sessionId || DEFAULT_SESSION_ID;
8798
- return join24(projectRoot, ".harness", `session-taint-${id}.json`);
8827
+ return join27(projectRoot, ".harness", `session-taint-${id}.json`);
8799
8828
  }
8800
8829
  function readTaint(projectRoot, sessionId) {
8801
8830
  const filePath = getTaintFilePath(projectRoot, sessionId);
8802
8831
  let content;
8803
8832
  try {
8804
- content = readFileSync17(filePath, "utf8");
8833
+ content = readFileSync20(filePath, "utf8");
8805
8834
  } catch {
8806
8835
  return null;
8807
8836
  }
@@ -8846,7 +8875,7 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
8846
8875
  const filePath = getTaintFilePath(projectRoot, id);
8847
8876
  const now = (/* @__PURE__ */ new Date()).toISOString();
8848
8877
  const dir = dirname9(filePath);
8849
- mkdirSync12(dir, { recursive: true });
8878
+ mkdirSync13(dir, { recursive: true });
8850
8879
  const existing = readTaint(projectRoot, id);
8851
8880
  const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
8852
8881
  const taintFindings = findings.map((f) => ({
@@ -8864,7 +8893,7 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
8864
8893
  severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
8865
8894
  findings: [...existing?.findings || [], ...taintFindings]
8866
8895
  };
8867
- writeFileSync12(filePath, JSON.stringify(state, null, 2) + "\n");
8896
+ writeFileSync14(filePath, JSON.stringify(state, null, 2) + "\n");
8868
8897
  return state;
8869
8898
  }
8870
8899
  function clearTaint(projectRoot, sessionId) {
@@ -8877,14 +8906,14 @@ function clearTaint(projectRoot, sessionId) {
8877
8906
  return 0;
8878
8907
  }
8879
8908
  }
8880
- const harnessDir = join24(projectRoot, ".harness");
8909
+ const harnessDir = join27(projectRoot, ".harness");
8881
8910
  let count = 0;
8882
8911
  try {
8883
8912
  const files = readdirSync3(harnessDir);
8884
8913
  for (const file of files) {
8885
8914
  if (file.startsWith("session-taint-") && file.endsWith(".json")) {
8886
8915
  try {
8887
- unlinkSync(join24(harnessDir, file));
8916
+ unlinkSync(join27(harnessDir, file));
8888
8917
  count++;
8889
8918
  } catch {
8890
8919
  }
@@ -8895,7 +8924,7 @@ function clearTaint(projectRoot, sessionId) {
8895
8924
  return count;
8896
8925
  }
8897
8926
  function listTaintedSessions(projectRoot) {
8898
- const harnessDir = join24(projectRoot, ".harness");
8927
+ const harnessDir = join27(projectRoot, ".harness");
8899
8928
  const sessions = [];
8900
8929
  try {
8901
8930
  const files = readdirSync3(harnessDir);
@@ -8965,7 +8994,7 @@ function mapSecurityFindings(secFindings, existing) {
8965
8994
  }
8966
8995
 
8967
8996
  // src/ci/check-orchestrator.ts
8968
- import * as path17 from "path";
8997
+ import * as path20 from "path";
8969
8998
  import { GraphStore, queryTraceability } from "@harness-engineering/graph";
8970
8999
  var ALL_CHECKS = [
8971
9000
  "validate",
@@ -8980,7 +9009,7 @@ var ALL_CHECKS = [
8980
9009
  ];
8981
9010
  async function runValidateCheck(projectRoot, config) {
8982
9011
  const issues = [];
8983
- const agentsPath = path17.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9012
+ const agentsPath = path20.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8984
9013
  const result = await validateAgentsMap(agentsPath);
8985
9014
  if (!result.ok) {
8986
9015
  issues.push({ severity: "error", message: result.error.message });
@@ -9037,7 +9066,7 @@ async function runDepsCheck(projectRoot, config) {
9037
9066
  }
9038
9067
  async function runDocsCheck(projectRoot, config) {
9039
9068
  const issues = [];
9040
- const docsDir = path17.join(projectRoot, config.docsDir ?? "docs");
9069
+ const docsDir = path20.join(projectRoot, config.docsDir ?? "docs");
9041
9070
  const entropyConfig = config.entropy || {};
9042
9071
  const result = await checkDocCoverage("project", {
9043
9072
  docsDir,
@@ -9222,7 +9251,7 @@ async function runTraceabilityCheck(projectRoot, config) {
9222
9251
  const issues = [];
9223
9252
  const traceConfig = config.traceability || {};
9224
9253
  if (traceConfig.enabled === false) return issues;
9225
- const graphDir = path17.join(projectRoot, ".harness", "graph");
9254
+ const graphDir = path20.join(projectRoot, ".harness", "graph");
9226
9255
  const store = new GraphStore();
9227
9256
  const loaded = await store.load(graphDir);
9228
9257
  if (!loaded) {
@@ -9351,7 +9380,7 @@ async function runCIChecks(input) {
9351
9380
  }
9352
9381
 
9353
9382
  // src/review/mechanical-checks.ts
9354
- import * as path18 from "path";
9383
+ import * as path21 from "path";
9355
9384
  async function runMechanicalChecks(options) {
9356
9385
  const { projectRoot, config, skip = [], changedFiles } = options;
9357
9386
  const findings = [];
@@ -9363,7 +9392,7 @@ async function runMechanicalChecks(options) {
9363
9392
  };
9364
9393
  if (!skip.includes("validate")) {
9365
9394
  try {
9366
- const agentsPath = path18.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9395
+ const agentsPath = path21.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9367
9396
  const result = await validateAgentsMap(agentsPath);
9368
9397
  if (!result.ok) {
9369
9398
  statuses.validate = "fail";
@@ -9400,7 +9429,7 @@ async function runMechanicalChecks(options) {
9400
9429
  statuses.validate = "fail";
9401
9430
  findings.push({
9402
9431
  tool: "validate",
9403
- file: path18.join(projectRoot, "AGENTS.md"),
9432
+ file: path21.join(projectRoot, "AGENTS.md"),
9404
9433
  message: err instanceof Error ? err.message : String(err),
9405
9434
  severity: "error"
9406
9435
  });
@@ -9464,7 +9493,7 @@ async function runMechanicalChecks(options) {
9464
9493
  (async () => {
9465
9494
  const localFindings = [];
9466
9495
  try {
9467
- const docsDir = path18.join(projectRoot, config.docsDir ?? "docs");
9496
+ const docsDir = path21.join(projectRoot, config.docsDir ?? "docs");
9468
9497
  const result = await checkDocCoverage("project", { docsDir });
9469
9498
  if (!result.ok) {
9470
9499
  statuses["check-docs"] = "warn";
@@ -9491,7 +9520,7 @@ async function runMechanicalChecks(options) {
9491
9520
  statuses["check-docs"] = "warn";
9492
9521
  localFindings.push({
9493
9522
  tool: "check-docs",
9494
- file: path18.join(projectRoot, "docs"),
9523
+ file: path21.join(projectRoot, "docs"),
9495
9524
  message: err instanceof Error ? err.message : String(err),
9496
9525
  severity: "warning"
9497
9526
  });
@@ -9639,7 +9668,7 @@ function detectChangeType(commitMessage, diff2) {
9639
9668
  }
9640
9669
 
9641
9670
  // src/review/context-scoper.ts
9642
- import * as path19 from "path";
9671
+ import * as path22 from "path";
9643
9672
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
9644
9673
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
9645
9674
  function computeContextBudget(diffLines) {
@@ -9647,18 +9676,18 @@ function computeContextBudget(diffLines) {
9647
9676
  return diffLines;
9648
9677
  }
9649
9678
  function isWithinProject(absPath, projectRoot) {
9650
- const resolvedRoot = path19.resolve(projectRoot) + path19.sep;
9651
- const resolvedPath = path19.resolve(absPath);
9652
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path19.resolve(projectRoot);
9679
+ const resolvedRoot = path22.resolve(projectRoot) + path22.sep;
9680
+ const resolvedPath = path22.resolve(absPath);
9681
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path22.resolve(projectRoot);
9653
9682
  }
9654
9683
  async function readContextFile(projectRoot, filePath, reason) {
9655
- const absPath = path19.isAbsolute(filePath) ? filePath : path19.join(projectRoot, filePath);
9684
+ const absPath = path22.isAbsolute(filePath) ? filePath : path22.join(projectRoot, filePath);
9656
9685
  if (!isWithinProject(absPath, projectRoot)) return null;
9657
9686
  const result = await readFileContent(absPath);
9658
9687
  if (!result.ok) return null;
9659
9688
  const content = result.value;
9660
9689
  const lines = content.split("\n").length;
9661
- const relPath = path19.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
9690
+ const relPath = path22.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
9662
9691
  return { path: relPath, content, reason, lines };
9663
9692
  }
9664
9693
  function extractImportSources(content) {
@@ -9673,18 +9702,18 @@ function extractImportSources(content) {
9673
9702
  }
9674
9703
  async function resolveImportPath(projectRoot, fromFile, importSource) {
9675
9704
  if (!importSource.startsWith(".")) return null;
9676
- const fromDir = path19.dirname(path19.join(projectRoot, fromFile));
9677
- const basePath = path19.resolve(fromDir, importSource);
9705
+ const fromDir = path22.dirname(path22.join(projectRoot, fromFile));
9706
+ const basePath = path22.resolve(fromDir, importSource);
9678
9707
  if (!isWithinProject(basePath, projectRoot)) return null;
9679
9708
  const relBase = relativePosix(projectRoot, basePath);
9680
9709
  const candidates = [
9681
9710
  relBase + ".ts",
9682
9711
  relBase + ".tsx",
9683
9712
  relBase + ".mts",
9684
- path19.join(relBase, "index.ts")
9713
+ path22.join(relBase, "index.ts")
9685
9714
  ];
9686
9715
  for (const candidate of candidates) {
9687
- const absCandidate = path19.join(projectRoot, candidate);
9716
+ const absCandidate = path22.join(projectRoot, candidate);
9688
9717
  if (await fileExists(absCandidate)) {
9689
9718
  return candidate;
9690
9719
  }
@@ -9692,7 +9721,7 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
9692
9721
  return null;
9693
9722
  }
9694
9723
  async function findTestFiles(projectRoot, sourceFile) {
9695
- const baseName = path19.basename(sourceFile, path19.extname(sourceFile));
9724
+ const baseName = path22.basename(sourceFile, path22.extname(sourceFile));
9696
9725
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
9697
9726
  const results = await findFiles(pattern, projectRoot);
9698
9727
  return results.map((f) => relativePosix(projectRoot, f));
@@ -10501,7 +10530,7 @@ async function fanOutReview(options) {
10501
10530
  }
10502
10531
 
10503
10532
  // src/review/validate-findings.ts
10504
- import * as path20 from "path";
10533
+ import * as path23 from "path";
10505
10534
  var DOWNGRADE_MAP = {
10506
10535
  critical: "important",
10507
10536
  important: "suggestion",
@@ -10522,7 +10551,7 @@ function normalizePath(filePath, projectRoot) {
10522
10551
  let normalized = filePath;
10523
10552
  normalized = normalized.replace(/\\/g, "/");
10524
10553
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
10525
- if (path20.isAbsolute(normalized)) {
10554
+ if (path23.isAbsolute(normalized)) {
10526
10555
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
10527
10556
  if (normalized.startsWith(root)) {
10528
10557
  normalized = normalized.slice(root.length);
@@ -10547,12 +10576,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
10547
10576
  while ((match = importRegex.exec(content)) !== null) {
10548
10577
  const importPath = match[1];
10549
10578
  if (!importPath.startsWith(".")) continue;
10550
- const dir = path20.dirname(current.file);
10551
- let resolved = path20.join(dir, importPath).replace(/\\/g, "/");
10579
+ const dir = path23.dirname(current.file);
10580
+ let resolved = path23.join(dir, importPath).replace(/\\/g, "/");
10552
10581
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
10553
10582
  resolved += ".ts";
10554
10583
  }
10555
- resolved = path20.normalize(resolved).replace(/\\/g, "/");
10584
+ resolved = path23.normalize(resolved).replace(/\\/g, "/");
10556
10585
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
10557
10586
  queue.push({ file: resolved, depth: current.depth + 1 });
10558
10587
  }
@@ -10569,7 +10598,7 @@ async function validateFindings(options) {
10569
10598
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
10570
10599
  continue;
10571
10600
  }
10572
- const absoluteFile = path20.isAbsolute(finding.file) ? finding.file : path20.join(projectRoot, finding.file).replace(/\\/g, "/");
10601
+ const absoluteFile = path23.isAbsolute(finding.file) ? finding.file : path23.join(projectRoot, finding.file).replace(/\\/g, "/");
10573
10602
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
10574
10603
  continue;
10575
10604
  }
@@ -11252,8 +11281,8 @@ function serializeAssignmentHistory(records) {
11252
11281
  }
11253
11282
 
11254
11283
  // src/roadmap/sync.ts
11255
- import * as fs21 from "fs";
11256
- import * as path21 from "path";
11284
+ import * as fs24 from "fs";
11285
+ import * as path24 from "path";
11257
11286
  import { Ok as Ok3 } from "@harness-engineering/types";
11258
11287
 
11259
11288
  // src/roadmap/status-rank.ts
@@ -11283,10 +11312,10 @@ function inferStatus(feature, projectPath, allFeatures) {
11283
11312
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
11284
11313
  const useRootState = featuresWithPlans.length <= 1;
11285
11314
  if (useRootState) {
11286
- const rootStatePath = path21.join(projectPath, ".harness", "state.json");
11287
- if (fs21.existsSync(rootStatePath)) {
11315
+ const rootStatePath = path24.join(projectPath, ".harness", "state.json");
11316
+ if (fs24.existsSync(rootStatePath)) {
11288
11317
  try {
11289
- const raw = fs21.readFileSync(rootStatePath, "utf-8");
11318
+ const raw = fs24.readFileSync(rootStatePath, "utf-8");
11290
11319
  const state = JSON.parse(raw);
11291
11320
  if (state.progress) {
11292
11321
  for (const status of Object.values(state.progress)) {
@@ -11297,16 +11326,16 @@ function inferStatus(feature, projectPath, allFeatures) {
11297
11326
  }
11298
11327
  }
11299
11328
  }
11300
- const sessionsDir = path21.join(projectPath, ".harness", "sessions");
11301
- if (fs21.existsSync(sessionsDir)) {
11329
+ const sessionsDir = path24.join(projectPath, ".harness", "sessions");
11330
+ if (fs24.existsSync(sessionsDir)) {
11302
11331
  try {
11303
- const sessionDirs = fs21.readdirSync(sessionsDir, { withFileTypes: true });
11332
+ const sessionDirs = fs24.readdirSync(sessionsDir, { withFileTypes: true });
11304
11333
  for (const entry of sessionDirs) {
11305
11334
  if (!entry.isDirectory()) continue;
11306
- const autopilotPath = path21.join(sessionsDir, entry.name, "autopilot-state.json");
11307
- if (!fs21.existsSync(autopilotPath)) continue;
11335
+ const autopilotPath = path24.join(sessionsDir, entry.name, "autopilot-state.json");
11336
+ if (!fs24.existsSync(autopilotPath)) continue;
11308
11337
  try {
11309
- const raw = fs21.readFileSync(autopilotPath, "utf-8");
11338
+ const raw = fs24.readFileSync(autopilotPath, "utf-8");
11310
11339
  const autopilot = JSON.parse(raw);
11311
11340
  if (!autopilot.phases) continue;
11312
11341
  const linkedPhases = autopilot.phases.filter(
@@ -11708,42 +11737,54 @@ var GitHubIssuesSyncAdapter = class {
11708
11737
  };
11709
11738
 
11710
11739
  // src/roadmap/sync-engine.ts
11711
- import * as fs22 from "fs";
11740
+ import * as fs25 from "fs";
11712
11741
  function emptySyncResult() {
11713
11742
  return { created: [], updated: [], assignmentChanges: [], errors: [] };
11714
11743
  }
11715
- async function syncToExternal(roadmap, adapter, config, prefetchedTickets) {
11716
- const result = emptySyncResult();
11717
- const existingByTitle = /* @__PURE__ */ new Map();
11744
+ function buildDedupIndex(tickets, config) {
11745
+ const index = /* @__PURE__ */ new Map();
11746
+ if (!tickets) return index;
11718
11747
  const configLabels = new Set((config.labels ?? []).map((l) => l.toLowerCase()));
11719
- if (prefetchedTickets) {
11720
- for (const ticket of prefetchedTickets) {
11721
- const hasConfigLabels = configLabels.size === 0 || ticket.labels.some((l) => configLabels.has(l.toLowerCase()));
11722
- if (!hasConfigLabels) continue;
11723
- const key = ticket.title.toLowerCase();
11724
- const prev = existingByTitle.get(key);
11725
- if (!prev || prev.status === "closed" && ticket.status === "open") {
11726
- existingByTitle.set(key, ticket);
11727
- }
11748
+ for (const ticket of tickets) {
11749
+ const hasConfigLabels = configLabels.size === 0 || ticket.labels.some((l) => configLabels.has(l.toLowerCase()));
11750
+ if (!hasConfigLabels) continue;
11751
+ const key = ticket.title.toLowerCase();
11752
+ const prev = index.get(key);
11753
+ if (!prev || prev.status === "closed" && ticket.status === "open") {
11754
+ index.set(key, ticket);
11728
11755
  }
11729
11756
  }
11757
+ return index;
11758
+ }
11759
+ async function resolveExternalId(feature, milestone, adapter, dedupIndex, result) {
11760
+ if (feature.externalId) return true;
11761
+ const existing = dedupIndex.get(feature.name.toLowerCase());
11762
+ if (existing) {
11763
+ feature.externalId = existing.externalId;
11764
+ return true;
11765
+ }
11766
+ const createResult = await adapter.createTicket(feature, milestone);
11767
+ if (createResult.ok) {
11768
+ feature.externalId = createResult.value.externalId;
11769
+ result.created.push(createResult.value);
11770
+ } else {
11771
+ result.errors.push({ featureOrId: feature.name, error: createResult.error });
11772
+ }
11773
+ return false;
11774
+ }
11775
+ async function syncToExternal(roadmap, adapter, config, prefetchedTickets) {
11776
+ const result = emptySyncResult();
11777
+ const dedupIndex = buildDedupIndex(prefetchedTickets, config);
11730
11778
  for (const milestone of roadmap.milestones) {
11731
11779
  for (const feature of milestone.features) {
11732
- if (!feature.externalId) {
11733
- const existing = existingByTitle.get(feature.name.toLowerCase());
11734
- if (existing) {
11735
- feature.externalId = existing.externalId;
11736
- } else {
11737
- const createResult = await adapter.createTicket(feature, milestone.name);
11738
- if (createResult.ok) {
11739
- feature.externalId = createResult.value.externalId;
11740
- result.created.push(createResult.value);
11741
- } else {
11742
- result.errors.push({ featureOrId: feature.name, error: createResult.error });
11743
- }
11744
- continue;
11745
- }
11746
- }
11780
+ const shouldUpdate = await resolveExternalId(
11781
+ feature,
11782
+ milestone.name,
11783
+ adapter,
11784
+ dedupIndex,
11785
+ result
11786
+ );
11787
+ if (!shouldUpdate) continue;
11747
11788
  const updateResult = await adapter.updateTicket(feature.externalId, feature, milestone.name);
11748
11789
  if (updateResult.ok) {
11749
11790
  result.updated.push(feature.externalId);
@@ -11794,6 +11835,9 @@ async function syncFromExternal(roadmap, adapter, config, options, prefetchedTic
11794
11835
  if (!forceSync && isRegression(feature.status, newStatus)) {
11795
11836
  continue;
11796
11837
  }
11838
+ if (!forceSync && feature.status === "blocked" && newStatus === "planned") {
11839
+ continue;
11840
+ }
11797
11841
  feature.status = newStatus;
11798
11842
  }
11799
11843
  }
@@ -11808,7 +11852,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
11808
11852
  });
11809
11853
  await previousSync;
11810
11854
  try {
11811
- const raw = fs22.readFileSync(roadmapPath, "utf-8");
11855
+ const raw = fs25.readFileSync(roadmapPath, "utf-8");
11812
11856
  const parseResult = parseRoadmap(raw);
11813
11857
  if (!parseResult.ok) {
11814
11858
  return {
@@ -11821,7 +11865,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
11821
11865
  const tickets = fetchResult.ok ? fetchResult.value : void 0;
11822
11866
  const pushResult = await syncToExternal(roadmap, adapter, config, tickets);
11823
11867
  const pullResult = await syncFromExternal(roadmap, adapter, config, options, tickets);
11824
- fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
11868
+ fs25.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
11825
11869
  return {
11826
11870
  created: pushResult.created,
11827
11871
  updated: pushResult.updated,
@@ -11980,18 +12024,18 @@ var EmitInteractionInputSchema = z9.object({
11980
12024
  });
11981
12025
 
11982
12026
  // src/blueprint/scanner.ts
11983
- import * as fs23 from "fs/promises";
11984
- import * as path22 from "path";
12027
+ import * as fs26 from "fs/promises";
12028
+ import * as path25 from "path";
11985
12029
  var ProjectScanner = class {
11986
12030
  constructor(rootDir) {
11987
12031
  this.rootDir = rootDir;
11988
12032
  }
11989
12033
  rootDir;
11990
12034
  async scan() {
11991
- let projectName = path22.basename(this.rootDir);
12035
+ let projectName = path25.basename(this.rootDir);
11992
12036
  try {
11993
- const pkgPath = path22.join(this.rootDir, "package.json");
11994
- const pkgRaw = await fs23.readFile(pkgPath, "utf-8");
12037
+ const pkgPath = path25.join(this.rootDir, "package.json");
12038
+ const pkgRaw = await fs26.readFile(pkgPath, "utf-8");
11995
12039
  const pkg = JSON.parse(pkgRaw);
11996
12040
  if (pkg.name) projectName = pkg.name;
11997
12041
  } catch {
@@ -12032,8 +12076,8 @@ var ProjectScanner = class {
12032
12076
  };
12033
12077
 
12034
12078
  // src/blueprint/generator.ts
12035
- import * as fs24 from "fs/promises";
12036
- import * as path23 from "path";
12079
+ import * as fs27 from "fs/promises";
12080
+ import * as path26 from "path";
12037
12081
  import * as ejs from "ejs";
12038
12082
 
12039
12083
  // src/blueprint/templates.ts
@@ -12117,19 +12161,19 @@ var BlueprintGenerator = class {
12117
12161
  styles: STYLES,
12118
12162
  scripts: SCRIPTS
12119
12163
  });
12120
- await fs24.mkdir(options.outputDir, { recursive: true });
12121
- await fs24.writeFile(path23.join(options.outputDir, "index.html"), html);
12164
+ await fs27.mkdir(options.outputDir, { recursive: true });
12165
+ await fs27.writeFile(path26.join(options.outputDir, "index.html"), html);
12122
12166
  }
12123
12167
  };
12124
12168
 
12125
12169
  // src/update-checker.ts
12126
- import * as fs25 from "fs";
12127
- import * as path24 from "path";
12170
+ import * as fs28 from "fs";
12171
+ import * as path27 from "path";
12128
12172
  import * as os from "os";
12129
12173
  import { spawn } from "child_process";
12130
12174
  function getStatePath() {
12131
12175
  const home = process.env["HOME"] || os.homedir();
12132
- return path24.join(home, ".harness", "update-check.json");
12176
+ return path27.join(home, ".harness", "update-check.json");
12133
12177
  }
12134
12178
  function isUpdateCheckEnabled(configInterval) {
12135
12179
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -12142,7 +12186,7 @@ function shouldRunCheck(state, intervalMs) {
12142
12186
  }
12143
12187
  function readCheckState() {
12144
12188
  try {
12145
- const raw = fs25.readFileSync(getStatePath(), "utf-8");
12189
+ const raw = fs28.readFileSync(getStatePath(), "utf-8");
12146
12190
  const parsed = JSON.parse(raw);
12147
12191
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
12148
12192
  const state = parsed;
@@ -12159,7 +12203,7 @@ function readCheckState() {
12159
12203
  }
12160
12204
  function spawnBackgroundCheck(currentVersion) {
12161
12205
  const statePath = getStatePath();
12162
- const stateDir = path24.dirname(statePath);
12206
+ const stateDir = path27.dirname(statePath);
12163
12207
  const script = `
12164
12208
  const { execSync } = require('child_process');
12165
12209
  const fs = require('fs');
@@ -12248,9 +12292,9 @@ async function resolveWasmPath(grammarName) {
12248
12292
  const { createRequire } = await import("module");
12249
12293
  const require2 = createRequire(import.meta.url ?? __filename);
12250
12294
  const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
12251
- const path28 = await import("path");
12252
- const pkgDir = path28.dirname(pkgPath);
12253
- return path28.join(pkgDir, "out", `${grammarName}.wasm`);
12295
+ const path31 = await import("path");
12296
+ const pkgDir = path31.dirname(pkgPath);
12297
+ return path31.join(pkgDir, "out", `${grammarName}.wasm`);
12254
12298
  }
12255
12299
  async function loadLanguage(lang) {
12256
12300
  const grammarName = GRAMMAR_MAP[lang];
@@ -12654,8 +12698,8 @@ function getModelPrice(model, dataset) {
12654
12698
  }
12655
12699
 
12656
12700
  // src/pricing/cache.ts
12657
- import * as fs26 from "fs/promises";
12658
- import * as path25 from "path";
12701
+ import * as fs29 from "fs/promises";
12702
+ import * as path28 from "path";
12659
12703
 
12660
12704
  // src/pricing/fallback.json
12661
12705
  var fallback_default = {
@@ -12708,14 +12752,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
12708
12752
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
12709
12753
  var STALENESS_WARNING_DAYS = 7;
12710
12754
  function getCachePath(projectRoot) {
12711
- return path25.join(projectRoot, ".harness", "cache", "pricing.json");
12755
+ return path28.join(projectRoot, ".harness", "cache", "pricing.json");
12712
12756
  }
12713
12757
  function getStalenessMarkerPath(projectRoot) {
12714
- return path25.join(projectRoot, ".harness", "cache", "staleness-marker.json");
12758
+ return path28.join(projectRoot, ".harness", "cache", "staleness-marker.json");
12715
12759
  }
12716
12760
  async function readDiskCache(projectRoot) {
12717
12761
  try {
12718
- const raw = await fs26.readFile(getCachePath(projectRoot), "utf-8");
12762
+ const raw = await fs29.readFile(getCachePath(projectRoot), "utf-8");
12719
12763
  return JSON.parse(raw);
12720
12764
  } catch {
12721
12765
  return null;
@@ -12723,8 +12767,8 @@ async function readDiskCache(projectRoot) {
12723
12767
  }
12724
12768
  async function writeDiskCache(projectRoot, data) {
12725
12769
  const cachePath = getCachePath(projectRoot);
12726
- await fs26.mkdir(path25.dirname(cachePath), { recursive: true });
12727
- await fs26.writeFile(cachePath, JSON.stringify(data, null, 2));
12770
+ await fs29.mkdir(path28.dirname(cachePath), { recursive: true });
12771
+ await fs29.writeFile(cachePath, JSON.stringify(data, null, 2));
12728
12772
  }
12729
12773
  async function fetchFromNetwork() {
12730
12774
  try {
@@ -12751,7 +12795,7 @@ function loadFallbackDataset() {
12751
12795
  async function checkAndWarnStaleness(projectRoot) {
12752
12796
  const markerPath = getStalenessMarkerPath(projectRoot);
12753
12797
  try {
12754
- const raw = await fs26.readFile(markerPath, "utf-8");
12798
+ const raw = await fs29.readFile(markerPath, "utf-8");
12755
12799
  const marker = JSON.parse(raw);
12756
12800
  const firstUse = new Date(marker.firstFallbackUse).getTime();
12757
12801
  const now = Date.now();
@@ -12763,8 +12807,8 @@ async function checkAndWarnStaleness(projectRoot) {
12763
12807
  }
12764
12808
  } catch {
12765
12809
  try {
12766
- await fs26.mkdir(path25.dirname(markerPath), { recursive: true });
12767
- await fs26.writeFile(
12810
+ await fs29.mkdir(path28.dirname(markerPath), { recursive: true });
12811
+ await fs29.writeFile(
12768
12812
  markerPath,
12769
12813
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
12770
12814
  );
@@ -12774,7 +12818,7 @@ async function checkAndWarnStaleness(projectRoot) {
12774
12818
  }
12775
12819
  async function clearStalenessMarker(projectRoot) {
12776
12820
  try {
12777
- await fs26.unlink(getStalenessMarkerPath(projectRoot));
12821
+ await fs29.unlink(getStalenessMarkerPath(projectRoot));
12778
12822
  } catch {
12779
12823
  }
12780
12824
  }
@@ -12820,8 +12864,7 @@ function calculateCost(record, dataset) {
12820
12864
  }
12821
12865
 
12822
12866
  // src/usage/aggregator.ts
12823
- function aggregateBySession(records) {
12824
- if (records.length === 0) return [];
12867
+ function bucketRecordsBySession(records) {
12825
12868
  const sessionMap = /* @__PURE__ */ new Map();
12826
12869
  for (const record of records) {
12827
12870
  const tagged = record;
@@ -12837,58 +12880,104 @@ function aggregateBySession(records) {
12837
12880
  }
12838
12881
  bucket.allRecords.push(tagged);
12839
12882
  }
12883
+ return sessionMap;
12884
+ }
12885
+ function accumulateCost(running, recordCost) {
12886
+ if (recordCost != null && running != null) {
12887
+ return running + recordCost;
12888
+ }
12889
+ if (recordCost == null) {
12890
+ return null;
12891
+ }
12892
+ return running;
12893
+ }
12894
+ function sumRecordTokens(tokenSource) {
12895
+ const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
12896
+ let cacheCreation;
12897
+ let cacheRead;
12898
+ let costMicroUSD = 0;
12899
+ let model;
12900
+ for (const r of tokenSource) {
12901
+ tokens.inputTokens += r.tokens.inputTokens;
12902
+ tokens.outputTokens += r.tokens.outputTokens;
12903
+ tokens.totalTokens += r.tokens.totalTokens;
12904
+ if (r.cacheCreationTokens != null) {
12905
+ cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
12906
+ }
12907
+ if (r.cacheReadTokens != null) {
12908
+ cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
12909
+ }
12910
+ costMicroUSD = accumulateCost(costMicroUSD, r.costMicroUSD);
12911
+ if (!model && r.model) {
12912
+ model = r.model;
12913
+ }
12914
+ }
12915
+ return { tokens, cacheCreation, cacheRead, costMicroUSD, model };
12916
+ }
12917
+ function findModel(records) {
12918
+ for (const r of records) {
12919
+ if (r.model) return r.model;
12920
+ }
12921
+ return void 0;
12922
+ }
12923
+ function determineSource(hasHarness, hasCC) {
12924
+ if (hasHarness && hasCC) return "merged";
12925
+ if (hasCC) return "claude-code";
12926
+ return "harness";
12927
+ }
12928
+ function applyOptionalFields(session, totals, model) {
12929
+ if (model) session.model = model;
12930
+ if (totals.cacheCreation != null) session.cacheCreationTokens = totals.cacheCreation;
12931
+ if (totals.cacheRead != null) session.cacheReadTokens = totals.cacheRead;
12932
+ }
12933
+ function buildSessionUsage(sessionId, bucket) {
12934
+ const hasHarness = bucket.harnessRecords.length > 0;
12935
+ const hasCC = bucket.ccRecords.length > 0;
12936
+ const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
12937
+ const totals = sumRecordTokens(tokenSource);
12938
+ const model = totals.model ?? (hasCC ? findModel(bucket.ccRecords) : void 0);
12939
+ const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
12940
+ const session = {
12941
+ sessionId,
12942
+ firstTimestamp: timestamps[0] ?? "",
12943
+ lastTimestamp: timestamps[timestamps.length - 1] ?? "",
12944
+ tokens: totals.tokens,
12945
+ costMicroUSD: totals.costMicroUSD,
12946
+ source: determineSource(hasHarness, hasCC)
12947
+ };
12948
+ applyOptionalFields(session, totals, model);
12949
+ return session;
12950
+ }
12951
+ function accumulateIntoDayBucket(day, record) {
12952
+ day.sessions.add(record.sessionId);
12953
+ day.tokens.inputTokens += record.tokens.inputTokens;
12954
+ day.tokens.outputTokens += record.tokens.outputTokens;
12955
+ day.tokens.totalTokens += record.tokens.totalTokens;
12956
+ if (record.cacheCreationTokens != null) {
12957
+ day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
12958
+ }
12959
+ if (record.cacheReadTokens != null) {
12960
+ day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
12961
+ }
12962
+ day.costMicroUSD = accumulateCost(day.costMicroUSD, record.costMicroUSD);
12963
+ if (record.model) {
12964
+ day.models.add(record.model);
12965
+ }
12966
+ }
12967
+ function createDayBucket() {
12968
+ return {
12969
+ sessions: /* @__PURE__ */ new Set(),
12970
+ tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
12971
+ costMicroUSD: 0,
12972
+ models: /* @__PURE__ */ new Set()
12973
+ };
12974
+ }
12975
+ function aggregateBySession(records) {
12976
+ if (records.length === 0) return [];
12977
+ const sessionMap = bucketRecordsBySession(records);
12840
12978
  const results = [];
12841
12979
  for (const [sessionId, bucket] of sessionMap) {
12842
- const hasHarness = bucket.harnessRecords.length > 0;
12843
- const hasCC = bucket.ccRecords.length > 0;
12844
- const isMerged = hasHarness && hasCC;
12845
- const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
12846
- const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
12847
- let cacheCreation;
12848
- let cacheRead;
12849
- let costMicroUSD = 0;
12850
- let model;
12851
- for (const r of tokenSource) {
12852
- tokens.inputTokens += r.tokens.inputTokens;
12853
- tokens.outputTokens += r.tokens.outputTokens;
12854
- tokens.totalTokens += r.tokens.totalTokens;
12855
- if (r.cacheCreationTokens != null) {
12856
- cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
12857
- }
12858
- if (r.cacheReadTokens != null) {
12859
- cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
12860
- }
12861
- if (r.costMicroUSD != null && costMicroUSD != null) {
12862
- costMicroUSD += r.costMicroUSD;
12863
- } else if (r.costMicroUSD == null) {
12864
- costMicroUSD = null;
12865
- }
12866
- if (!model && r.model) {
12867
- model = r.model;
12868
- }
12869
- }
12870
- if (!model && hasCC) {
12871
- for (const r of bucket.ccRecords) {
12872
- if (r.model) {
12873
- model = r.model;
12874
- break;
12875
- }
12876
- }
12877
- }
12878
- const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
12879
- const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
12880
- const session = {
12881
- sessionId,
12882
- firstTimestamp: timestamps[0] ?? "",
12883
- lastTimestamp: timestamps[timestamps.length - 1] ?? "",
12884
- tokens,
12885
- costMicroUSD,
12886
- source
12887
- };
12888
- if (model) session.model = model;
12889
- if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
12890
- if (cacheRead != null) session.cacheReadTokens = cacheRead;
12891
- results.push(session);
12980
+ results.push(buildSessionUsage(sessionId, bucket));
12892
12981
  }
12893
12982
  results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
12894
12983
  return results;
@@ -12899,32 +12988,9 @@ function aggregateByDay(records) {
12899
12988
  for (const record of records) {
12900
12989
  const date = record.timestamp.slice(0, 10);
12901
12990
  if (!dayMap.has(date)) {
12902
- dayMap.set(date, {
12903
- sessions: /* @__PURE__ */ new Set(),
12904
- tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
12905
- costMicroUSD: 0,
12906
- models: /* @__PURE__ */ new Set()
12907
- });
12908
- }
12909
- const day = dayMap.get(date);
12910
- day.sessions.add(record.sessionId);
12911
- day.tokens.inputTokens += record.tokens.inputTokens;
12912
- day.tokens.outputTokens += record.tokens.outputTokens;
12913
- day.tokens.totalTokens += record.tokens.totalTokens;
12914
- if (record.cacheCreationTokens != null) {
12915
- day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
12916
- }
12917
- if (record.cacheReadTokens != null) {
12918
- day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
12919
- }
12920
- if (record.costMicroUSD != null && day.costMicroUSD != null) {
12921
- day.costMicroUSD += record.costMicroUSD;
12922
- } else if (record.costMicroUSD == null) {
12923
- day.costMicroUSD = null;
12924
- }
12925
- if (record.model) {
12926
- day.models.add(record.model);
12991
+ dayMap.set(date, createDayBucket());
12927
12992
  }
12993
+ accumulateIntoDayBucket(dayMap.get(date), record);
12928
12994
  }
12929
12995
  const results = [];
12930
12996
  for (const [date, day] of dayMap) {
@@ -12944,8 +13010,8 @@ function aggregateByDay(records) {
12944
13010
  }
12945
13011
 
12946
13012
  // src/usage/jsonl-reader.ts
12947
- import * as fs27 from "fs";
12948
- import * as path26 from "path";
13013
+ import * as fs30 from "fs";
13014
+ import * as path29 from "path";
12949
13015
  function parseLine(line, lineNumber) {
12950
13016
  let entry;
12951
13017
  try {
@@ -12984,10 +13050,10 @@ function parseLine(line, lineNumber) {
12984
13050
  return record;
12985
13051
  }
12986
13052
  function readCostRecords(projectRoot) {
12987
- const costsFile = path26.join(projectRoot, ".harness", "metrics", "costs.jsonl");
13053
+ const costsFile = path29.join(projectRoot, ".harness", "metrics", "costs.jsonl");
12988
13054
  let raw;
12989
13055
  try {
12990
- raw = fs27.readFileSync(costsFile, "utf-8");
13056
+ raw = fs30.readFileSync(costsFile, "utf-8");
12991
13057
  } catch {
12992
13058
  return [];
12993
13059
  }
@@ -13005,8 +13071,8 @@ function readCostRecords(projectRoot) {
13005
13071
  }
13006
13072
 
13007
13073
  // src/usage/cc-parser.ts
13008
- import * as fs28 from "fs";
13009
- import * as path27 from "path";
13074
+ import * as fs31 from "fs";
13075
+ import * as path30 from "path";
13010
13076
  import * as os2 from "os";
13011
13077
  function extractUsage(entry) {
13012
13078
  if (entry.type !== "assistant") return null;
@@ -13039,7 +13105,7 @@ function parseCCLine(line, filePath, lineNumber) {
13039
13105
  entry = JSON.parse(line);
13040
13106
  } catch {
13041
13107
  console.warn(
13042
- `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path27.basename(filePath)}`
13108
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path30.basename(filePath)}`
13043
13109
  );
13044
13110
  return null;
13045
13111
  }
@@ -13053,7 +13119,7 @@ function parseCCLine(line, filePath, lineNumber) {
13053
13119
  function readCCFile(filePath) {
13054
13120
  let raw;
13055
13121
  try {
13056
- raw = fs28.readFileSync(filePath, "utf-8");
13122
+ raw = fs31.readFileSync(filePath, "utf-8");
13057
13123
  } catch {
13058
13124
  return [];
13059
13125
  }
@@ -13075,10 +13141,10 @@ function readCCFile(filePath) {
13075
13141
  }
13076
13142
  function parseCCRecords() {
13077
13143
  const homeDir = process.env.HOME ?? os2.homedir();
13078
- const projectsDir = path27.join(homeDir, ".claude", "projects");
13144
+ const projectsDir = path30.join(homeDir, ".claude", "projects");
13079
13145
  let projectDirs;
13080
13146
  try {
13081
- projectDirs = fs28.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path27.join(projectsDir, d.name));
13147
+ projectDirs = fs31.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path30.join(projectsDir, d.name));
13082
13148
  } catch {
13083
13149
  return [];
13084
13150
  }
@@ -13086,7 +13152,7 @@ function parseCCRecords() {
13086
13152
  for (const dir of projectDirs) {
13087
13153
  let files;
13088
13154
  try {
13089
- files = fs28.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path27.join(dir, f));
13155
+ files = fs31.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path30.join(dir, f));
13090
13156
  } catch {
13091
13157
  continue;
13092
13158
  }
@@ -13237,6 +13303,7 @@ export {
13237
13303
  clearFailuresCache,
13238
13304
  clearLearningsCache,
13239
13305
  clearTaint,
13306
+ computeContentHash,
13240
13307
  computeOverallSeverity,
13241
13308
  computeScanExitCode,
13242
13309
  configureFeedback,
@@ -13330,6 +13397,7 @@ export {
13330
13397
  migrateToStreams,
13331
13398
  networkRules,
13332
13399
  nodeRules,
13400
+ normalizeLearningContent,
13333
13401
  parseCCRecords,
13334
13402
  parseDateFromEntry,
13335
13403
  parseDiff,