@harness-engineering/core 0.20.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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(
@@ -11632,6 +11661,7 @@ var GitHubIssuesSyncAdapter = class {
11632
11661
  const data = await response.json();
11633
11662
  return Ok4({
11634
11663
  externalId,
11664
+ title: data.title,
11635
11665
  status: data.state,
11636
11666
  labels: data.labels.map((l) => l.name),
11637
11667
  assignee: data.assignee ? `@${data.assignee.login}` : null
@@ -11666,6 +11696,7 @@ var GitHubIssuesSyncAdapter = class {
11666
11696
  for (const issue of issues) {
11667
11697
  tickets.push({
11668
11698
  externalId: buildExternalId(this.owner, this.repo, issue.number),
11699
+ title: issue.title,
11669
11700
  status: issue.state,
11670
11701
  labels: issue.labels.map((l) => l.name),
11671
11702
  assignee: issue.assignee ? `@${issue.assignee.login}` : null
@@ -11706,47 +11737,65 @@ var GitHubIssuesSyncAdapter = class {
11706
11737
  };
11707
11738
 
11708
11739
  // src/roadmap/sync-engine.ts
11709
- import * as fs22 from "fs";
11740
+ import * as fs25 from "fs";
11710
11741
  function emptySyncResult() {
11711
11742
  return { created: [], updated: [], assignmentChanges: [], errors: [] };
11712
11743
  }
11713
- async function syncToExternal(roadmap, adapter, _config) {
11714
- const result = emptySyncResult();
11715
- let defaultAssignee = null;
11716
- const userResult = await adapter.getAuthenticatedUser();
11717
- if (userResult.ok) {
11718
- defaultAssignee = userResult.value;
11744
+ function buildDedupIndex(tickets, config) {
11745
+ const index = /* @__PURE__ */ new Map();
11746
+ if (!tickets) return index;
11747
+ const configLabels = new Set((config.labels ?? []).map((l) => l.toLowerCase()));
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);
11755
+ }
11719
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);
11720
11778
  for (const milestone of roadmap.milestones) {
11721
11779
  for (const feature of milestone.features) {
11722
- if (!feature.assignee && defaultAssignee) {
11723
- feature.assignee = defaultAssignee;
11724
- }
11725
- if (!feature.externalId) {
11726
- const createResult = await adapter.createTicket(feature, milestone.name);
11727
- if (createResult.ok) {
11728
- feature.externalId = createResult.value.externalId;
11729
- result.created.push(createResult.value);
11730
- } else {
11731
- result.errors.push({ featureOrId: feature.name, error: createResult.error });
11732
- }
11780
+ const shouldUpdate = await resolveExternalId(
11781
+ feature,
11782
+ milestone.name,
11783
+ adapter,
11784
+ dedupIndex,
11785
+ result
11786
+ );
11787
+ if (!shouldUpdate) continue;
11788
+ const updateResult = await adapter.updateTicket(feature.externalId, feature, milestone.name);
11789
+ if (updateResult.ok) {
11790
+ result.updated.push(feature.externalId);
11733
11791
  } else {
11734
- const updateResult = await adapter.updateTicket(
11735
- feature.externalId,
11736
- feature,
11737
- milestone.name
11738
- );
11739
- if (updateResult.ok) {
11740
- result.updated.push(feature.externalId);
11741
- } else {
11742
- result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
11743
- }
11792
+ result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
11744
11793
  }
11745
11794
  }
11746
11795
  }
11747
11796
  return result;
11748
11797
  }
11749
- async function syncFromExternal(roadmap, adapter, config, options) {
11798
+ async function syncFromExternal(roadmap, adapter, config, options, prefetchedTickets) {
11750
11799
  const result = emptySyncResult();
11751
11800
  const forceSync = options?.forceSync ?? false;
11752
11801
  const featureByExternalId = /* @__PURE__ */ new Map();
@@ -11758,12 +11807,18 @@ async function syncFromExternal(roadmap, adapter, config, options) {
11758
11807
  }
11759
11808
  }
11760
11809
  if (featureByExternalId.size === 0) return result;
11761
- const fetchResult = await adapter.fetchAllTickets();
11762
- if (!fetchResult.ok) {
11763
- result.errors.push({ featureOrId: "*", error: fetchResult.error });
11764
- return result;
11810
+ let tickets;
11811
+ if (prefetchedTickets) {
11812
+ tickets = prefetchedTickets;
11813
+ } else {
11814
+ const fetchResult = await adapter.fetchAllTickets();
11815
+ if (!fetchResult.ok) {
11816
+ result.errors.push({ featureOrId: "*", error: fetchResult.error });
11817
+ return result;
11818
+ }
11819
+ tickets = fetchResult.value;
11765
11820
  }
11766
- for (const ticketState of fetchResult.value) {
11821
+ for (const ticketState of tickets) {
11767
11822
  const feature = featureByExternalId.get(ticketState.externalId);
11768
11823
  if (!feature) continue;
11769
11824
  if (ticketState.assignee !== feature.assignee) {
@@ -11780,6 +11835,9 @@ async function syncFromExternal(roadmap, adapter, config, options) {
11780
11835
  if (!forceSync && isRegression(feature.status, newStatus)) {
11781
11836
  continue;
11782
11837
  }
11838
+ if (!forceSync && feature.status === "blocked" && newStatus === "planned") {
11839
+ continue;
11840
+ }
11783
11841
  feature.status = newStatus;
11784
11842
  }
11785
11843
  }
@@ -11794,7 +11852,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
11794
11852
  });
11795
11853
  await previousSync;
11796
11854
  try {
11797
- const raw = fs22.readFileSync(roadmapPath, "utf-8");
11855
+ const raw = fs25.readFileSync(roadmapPath, "utf-8");
11798
11856
  const parseResult = parseRoadmap(raw);
11799
11857
  if (!parseResult.ok) {
11800
11858
  return {
@@ -11803,9 +11861,11 @@ async function fullSync(roadmapPath, adapter, config, options) {
11803
11861
  };
11804
11862
  }
11805
11863
  const roadmap = parseResult.value;
11806
- const pushResult = await syncToExternal(roadmap, adapter, config);
11807
- const pullResult = await syncFromExternal(roadmap, adapter, config, options);
11808
- fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
11864
+ const fetchResult = await adapter.fetchAllTickets();
11865
+ const tickets = fetchResult.ok ? fetchResult.value : void 0;
11866
+ const pushResult = await syncToExternal(roadmap, adapter, config, tickets);
11867
+ const pullResult = await syncFromExternal(roadmap, adapter, config, options, tickets);
11868
+ fs25.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
11809
11869
  return {
11810
11870
  created: pushResult.created,
11811
11871
  updated: pushResult.updated,
@@ -11964,18 +12024,18 @@ var EmitInteractionInputSchema = z9.object({
11964
12024
  });
11965
12025
 
11966
12026
  // src/blueprint/scanner.ts
11967
- import * as fs23 from "fs/promises";
11968
- import * as path22 from "path";
12027
+ import * as fs26 from "fs/promises";
12028
+ import * as path25 from "path";
11969
12029
  var ProjectScanner = class {
11970
12030
  constructor(rootDir) {
11971
12031
  this.rootDir = rootDir;
11972
12032
  }
11973
12033
  rootDir;
11974
12034
  async scan() {
11975
- let projectName = path22.basename(this.rootDir);
12035
+ let projectName = path25.basename(this.rootDir);
11976
12036
  try {
11977
- const pkgPath = path22.join(this.rootDir, "package.json");
11978
- 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");
11979
12039
  const pkg = JSON.parse(pkgRaw);
11980
12040
  if (pkg.name) projectName = pkg.name;
11981
12041
  } catch {
@@ -12016,8 +12076,8 @@ var ProjectScanner = class {
12016
12076
  };
12017
12077
 
12018
12078
  // src/blueprint/generator.ts
12019
- import * as fs24 from "fs/promises";
12020
- import * as path23 from "path";
12079
+ import * as fs27 from "fs/promises";
12080
+ import * as path26 from "path";
12021
12081
  import * as ejs from "ejs";
12022
12082
 
12023
12083
  // src/blueprint/templates.ts
@@ -12101,19 +12161,19 @@ var BlueprintGenerator = class {
12101
12161
  styles: STYLES,
12102
12162
  scripts: SCRIPTS
12103
12163
  });
12104
- await fs24.mkdir(options.outputDir, { recursive: true });
12105
- 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);
12106
12166
  }
12107
12167
  };
12108
12168
 
12109
12169
  // src/update-checker.ts
12110
- import * as fs25 from "fs";
12111
- import * as path24 from "path";
12170
+ import * as fs28 from "fs";
12171
+ import * as path27 from "path";
12112
12172
  import * as os from "os";
12113
12173
  import { spawn } from "child_process";
12114
12174
  function getStatePath() {
12115
12175
  const home = process.env["HOME"] || os.homedir();
12116
- return path24.join(home, ".harness", "update-check.json");
12176
+ return path27.join(home, ".harness", "update-check.json");
12117
12177
  }
12118
12178
  function isUpdateCheckEnabled(configInterval) {
12119
12179
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -12126,7 +12186,7 @@ function shouldRunCheck(state, intervalMs) {
12126
12186
  }
12127
12187
  function readCheckState() {
12128
12188
  try {
12129
- const raw = fs25.readFileSync(getStatePath(), "utf-8");
12189
+ const raw = fs28.readFileSync(getStatePath(), "utf-8");
12130
12190
  const parsed = JSON.parse(raw);
12131
12191
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
12132
12192
  const state = parsed;
@@ -12143,7 +12203,7 @@ function readCheckState() {
12143
12203
  }
12144
12204
  function spawnBackgroundCheck(currentVersion) {
12145
12205
  const statePath = getStatePath();
12146
- const stateDir = path24.dirname(statePath);
12206
+ const stateDir = path27.dirname(statePath);
12147
12207
  const script = `
12148
12208
  const { execSync } = require('child_process');
12149
12209
  const fs = require('fs');
@@ -12232,9 +12292,9 @@ async function resolveWasmPath(grammarName) {
12232
12292
  const { createRequire } = await import("module");
12233
12293
  const require2 = createRequire(import.meta.url ?? __filename);
12234
12294
  const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
12235
- const path28 = await import("path");
12236
- const pkgDir = path28.dirname(pkgPath);
12237
- 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`);
12238
12298
  }
12239
12299
  async function loadLanguage(lang) {
12240
12300
  const grammarName = GRAMMAR_MAP[lang];
@@ -12638,8 +12698,8 @@ function getModelPrice(model, dataset) {
12638
12698
  }
12639
12699
 
12640
12700
  // src/pricing/cache.ts
12641
- import * as fs26 from "fs/promises";
12642
- import * as path25 from "path";
12701
+ import * as fs29 from "fs/promises";
12702
+ import * as path28 from "path";
12643
12703
 
12644
12704
  // src/pricing/fallback.json
12645
12705
  var fallback_default = {
@@ -12692,14 +12752,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
12692
12752
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
12693
12753
  var STALENESS_WARNING_DAYS = 7;
12694
12754
  function getCachePath(projectRoot) {
12695
- return path25.join(projectRoot, ".harness", "cache", "pricing.json");
12755
+ return path28.join(projectRoot, ".harness", "cache", "pricing.json");
12696
12756
  }
12697
12757
  function getStalenessMarkerPath(projectRoot) {
12698
- return path25.join(projectRoot, ".harness", "cache", "staleness-marker.json");
12758
+ return path28.join(projectRoot, ".harness", "cache", "staleness-marker.json");
12699
12759
  }
12700
12760
  async function readDiskCache(projectRoot) {
12701
12761
  try {
12702
- const raw = await fs26.readFile(getCachePath(projectRoot), "utf-8");
12762
+ const raw = await fs29.readFile(getCachePath(projectRoot), "utf-8");
12703
12763
  return JSON.parse(raw);
12704
12764
  } catch {
12705
12765
  return null;
@@ -12707,8 +12767,8 @@ async function readDiskCache(projectRoot) {
12707
12767
  }
12708
12768
  async function writeDiskCache(projectRoot, data) {
12709
12769
  const cachePath = getCachePath(projectRoot);
12710
- await fs26.mkdir(path25.dirname(cachePath), { recursive: true });
12711
- 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));
12712
12772
  }
12713
12773
  async function fetchFromNetwork() {
12714
12774
  try {
@@ -12735,7 +12795,7 @@ function loadFallbackDataset() {
12735
12795
  async function checkAndWarnStaleness(projectRoot) {
12736
12796
  const markerPath = getStalenessMarkerPath(projectRoot);
12737
12797
  try {
12738
- const raw = await fs26.readFile(markerPath, "utf-8");
12798
+ const raw = await fs29.readFile(markerPath, "utf-8");
12739
12799
  const marker = JSON.parse(raw);
12740
12800
  const firstUse = new Date(marker.firstFallbackUse).getTime();
12741
12801
  const now = Date.now();
@@ -12747,8 +12807,8 @@ async function checkAndWarnStaleness(projectRoot) {
12747
12807
  }
12748
12808
  } catch {
12749
12809
  try {
12750
- await fs26.mkdir(path25.dirname(markerPath), { recursive: true });
12751
- await fs26.writeFile(
12810
+ await fs29.mkdir(path28.dirname(markerPath), { recursive: true });
12811
+ await fs29.writeFile(
12752
12812
  markerPath,
12753
12813
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
12754
12814
  );
@@ -12758,7 +12818,7 @@ async function checkAndWarnStaleness(projectRoot) {
12758
12818
  }
12759
12819
  async function clearStalenessMarker(projectRoot) {
12760
12820
  try {
12761
- await fs26.unlink(getStalenessMarkerPath(projectRoot));
12821
+ await fs29.unlink(getStalenessMarkerPath(projectRoot));
12762
12822
  } catch {
12763
12823
  }
12764
12824
  }
@@ -12804,8 +12864,7 @@ function calculateCost(record, dataset) {
12804
12864
  }
12805
12865
 
12806
12866
  // src/usage/aggregator.ts
12807
- function aggregateBySession(records) {
12808
- if (records.length === 0) return [];
12867
+ function bucketRecordsBySession(records) {
12809
12868
  const sessionMap = /* @__PURE__ */ new Map();
12810
12869
  for (const record of records) {
12811
12870
  const tagged = record;
@@ -12821,58 +12880,104 @@ function aggregateBySession(records) {
12821
12880
  }
12822
12881
  bucket.allRecords.push(tagged);
12823
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);
12824
12978
  const results = [];
12825
12979
  for (const [sessionId, bucket] of sessionMap) {
12826
- const hasHarness = bucket.harnessRecords.length > 0;
12827
- const hasCC = bucket.ccRecords.length > 0;
12828
- const isMerged = hasHarness && hasCC;
12829
- const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
12830
- const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
12831
- let cacheCreation;
12832
- let cacheRead;
12833
- let costMicroUSD = 0;
12834
- let model;
12835
- for (const r of tokenSource) {
12836
- tokens.inputTokens += r.tokens.inputTokens;
12837
- tokens.outputTokens += r.tokens.outputTokens;
12838
- tokens.totalTokens += r.tokens.totalTokens;
12839
- if (r.cacheCreationTokens != null) {
12840
- cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
12841
- }
12842
- if (r.cacheReadTokens != null) {
12843
- cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
12844
- }
12845
- if (r.costMicroUSD != null && costMicroUSD != null) {
12846
- costMicroUSD += r.costMicroUSD;
12847
- } else if (r.costMicroUSD == null) {
12848
- costMicroUSD = null;
12849
- }
12850
- if (!model && r.model) {
12851
- model = r.model;
12852
- }
12853
- }
12854
- if (!model && hasCC) {
12855
- for (const r of bucket.ccRecords) {
12856
- if (r.model) {
12857
- model = r.model;
12858
- break;
12859
- }
12860
- }
12861
- }
12862
- const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
12863
- const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
12864
- const session = {
12865
- sessionId,
12866
- firstTimestamp: timestamps[0] ?? "",
12867
- lastTimestamp: timestamps[timestamps.length - 1] ?? "",
12868
- tokens,
12869
- costMicroUSD,
12870
- source
12871
- };
12872
- if (model) session.model = model;
12873
- if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
12874
- if (cacheRead != null) session.cacheReadTokens = cacheRead;
12875
- results.push(session);
12980
+ results.push(buildSessionUsage(sessionId, bucket));
12876
12981
  }
12877
12982
  results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
12878
12983
  return results;
@@ -12883,32 +12988,9 @@ function aggregateByDay(records) {
12883
12988
  for (const record of records) {
12884
12989
  const date = record.timestamp.slice(0, 10);
12885
12990
  if (!dayMap.has(date)) {
12886
- dayMap.set(date, {
12887
- sessions: /* @__PURE__ */ new Set(),
12888
- tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
12889
- costMicroUSD: 0,
12890
- models: /* @__PURE__ */ new Set()
12891
- });
12892
- }
12893
- const day = dayMap.get(date);
12894
- day.sessions.add(record.sessionId);
12895
- day.tokens.inputTokens += record.tokens.inputTokens;
12896
- day.tokens.outputTokens += record.tokens.outputTokens;
12897
- day.tokens.totalTokens += record.tokens.totalTokens;
12898
- if (record.cacheCreationTokens != null) {
12899
- day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
12900
- }
12901
- if (record.cacheReadTokens != null) {
12902
- day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
12903
- }
12904
- if (record.costMicroUSD != null && day.costMicroUSD != null) {
12905
- day.costMicroUSD += record.costMicroUSD;
12906
- } else if (record.costMicroUSD == null) {
12907
- day.costMicroUSD = null;
12908
- }
12909
- if (record.model) {
12910
- day.models.add(record.model);
12991
+ dayMap.set(date, createDayBucket());
12911
12992
  }
12993
+ accumulateIntoDayBucket(dayMap.get(date), record);
12912
12994
  }
12913
12995
  const results = [];
12914
12996
  for (const [date, day] of dayMap) {
@@ -12928,8 +13010,8 @@ function aggregateByDay(records) {
12928
13010
  }
12929
13011
 
12930
13012
  // src/usage/jsonl-reader.ts
12931
- import * as fs27 from "fs";
12932
- import * as path26 from "path";
13013
+ import * as fs30 from "fs";
13014
+ import * as path29 from "path";
12933
13015
  function parseLine(line, lineNumber) {
12934
13016
  let entry;
12935
13017
  try {
@@ -12968,10 +13050,10 @@ function parseLine(line, lineNumber) {
12968
13050
  return record;
12969
13051
  }
12970
13052
  function readCostRecords(projectRoot) {
12971
- const costsFile = path26.join(projectRoot, ".harness", "metrics", "costs.jsonl");
13053
+ const costsFile = path29.join(projectRoot, ".harness", "metrics", "costs.jsonl");
12972
13054
  let raw;
12973
13055
  try {
12974
- raw = fs27.readFileSync(costsFile, "utf-8");
13056
+ raw = fs30.readFileSync(costsFile, "utf-8");
12975
13057
  } catch {
12976
13058
  return [];
12977
13059
  }
@@ -12989,8 +13071,8 @@ function readCostRecords(projectRoot) {
12989
13071
  }
12990
13072
 
12991
13073
  // src/usage/cc-parser.ts
12992
- import * as fs28 from "fs";
12993
- import * as path27 from "path";
13074
+ import * as fs31 from "fs";
13075
+ import * as path30 from "path";
12994
13076
  import * as os2 from "os";
12995
13077
  function extractUsage(entry) {
12996
13078
  if (entry.type !== "assistant") return null;
@@ -13023,7 +13105,7 @@ function parseCCLine(line, filePath, lineNumber) {
13023
13105
  entry = JSON.parse(line);
13024
13106
  } catch {
13025
13107
  console.warn(
13026
- `[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)}`
13027
13109
  );
13028
13110
  return null;
13029
13111
  }
@@ -13037,7 +13119,7 @@ function parseCCLine(line, filePath, lineNumber) {
13037
13119
  function readCCFile(filePath) {
13038
13120
  let raw;
13039
13121
  try {
13040
- raw = fs28.readFileSync(filePath, "utf-8");
13122
+ raw = fs31.readFileSync(filePath, "utf-8");
13041
13123
  } catch {
13042
13124
  return [];
13043
13125
  }
@@ -13059,10 +13141,10 @@ function readCCFile(filePath) {
13059
13141
  }
13060
13142
  function parseCCRecords() {
13061
13143
  const homeDir = process.env.HOME ?? os2.homedir();
13062
- const projectsDir = path27.join(homeDir, ".claude", "projects");
13144
+ const projectsDir = path30.join(homeDir, ".claude", "projects");
13063
13145
  let projectDirs;
13064
13146
  try {
13065
- 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));
13066
13148
  } catch {
13067
13149
  return [];
13068
13150
  }
@@ -13070,7 +13152,7 @@ function parseCCRecords() {
13070
13152
  for (const dir of projectDirs) {
13071
13153
  let files;
13072
13154
  try {
13073
- 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));
13074
13156
  } catch {
13075
13157
  continue;
13076
13158
  }
@@ -13221,6 +13303,7 @@ export {
13221
13303
  clearFailuresCache,
13222
13304
  clearLearningsCache,
13223
13305
  clearTaint,
13306
+ computeContentHash,
13224
13307
  computeOverallSeverity,
13225
13308
  computeScanExitCode,
13226
13309
  configureFeedback,
@@ -13314,6 +13397,7 @@ export {
13314
13397
  migrateToStreams,
13315
13398
  networkRules,
13316
13399
  nodeRules,
13400
+ normalizeLearningContent,
13317
13401
  parseCCRecords,
13318
13402
  parseDateFromEntry,
13319
13403
  parseDiff,