@harness-engineering/core 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -83,15 +83,15 @@ function validateConfig(data, schema) {
83
83
  let message = "Configuration validation failed";
84
84
  const suggestions = [];
85
85
  if (firstError) {
86
- const path13 = firstError.path.join(".");
87
- const pathDisplay = path13 ? ` at "${path13}"` : "";
86
+ const path20 = firstError.path.join(".");
87
+ const pathDisplay = path20 ? ` at "${path20}"` : "";
88
88
  if (firstError.code === "invalid_type") {
89
89
  const received = firstError.received;
90
90
  const expected = firstError.expected;
91
91
  if (received === "undefined") {
92
92
  code = "MISSING_FIELD";
93
93
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
94
- suggestions.push(`Field "${path13}" is required and must be of type "${expected}"`);
94
+ suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
95
95
  } else {
96
96
  code = "INVALID_TYPE";
97
97
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -304,30 +304,27 @@ function extractSections(content) {
304
304
  return result;
305
305
  });
306
306
  }
307
- function isExternalLink(path13) {
308
- return path13.startsWith("http://") || path13.startsWith("https://") || path13.startsWith("#") || path13.startsWith("mailto:");
307
+ function isExternalLink(path20) {
308
+ return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
309
309
  }
310
310
  function resolveLinkPath(linkPath, baseDir) {
311
311
  return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
312
312
  }
313
- async function validateAgentsMap(path13 = "./AGENTS.md") {
314
- console.warn(
315
- "[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
316
- );
317
- const contentResult = await readFileContent(path13);
313
+ async function validateAgentsMap(path20 = "./AGENTS.md") {
314
+ const contentResult = await readFileContent(path20);
318
315
  if (!contentResult.ok) {
319
316
  return Err(
320
317
  createError(
321
318
  "PARSE_ERROR",
322
319
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
323
- { path: path13 },
320
+ { path: path20 },
324
321
  ["Ensure the file exists", "Check file permissions"]
325
322
  )
326
323
  );
327
324
  }
328
325
  const content = contentResult.value;
329
326
  const sections = extractSections(content);
330
- const baseDir = dirname(path13);
327
+ const baseDir = dirname(path20);
331
328
  const sectionTitles = sections.map((s) => s.title);
332
329
  const missingSections = REQUIRED_SECTIONS.filter(
333
330
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -468,8 +465,8 @@ async function checkDocCoverage(domain, options = {}) {
468
465
 
469
466
  // src/context/knowledge-map.ts
470
467
  import { join as join2, basename as basename2, relative as relative2 } from "path";
471
- function suggestFix(path13, existingFiles) {
472
- const targetName = basename2(path13).toLowerCase();
468
+ function suggestFix(path20, existingFiles) {
469
+ const targetName = basename2(path20).toLowerCase();
473
470
  const similar = existingFiles.find((file) => {
474
471
  const fileName = basename2(file).toLowerCase();
475
472
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -477,12 +474,9 @@ function suggestFix(path13, existingFiles) {
477
474
  if (similar) {
478
475
  return `Did you mean "${similar}"?`;
479
476
  }
480
- return `Create the file "${path13}" or remove the link`;
477
+ return `Create the file "${path20}" or remove the link`;
481
478
  }
482
479
  async function validateKnowledgeMap(rootDir = process.cwd()) {
483
- console.warn(
484
- "[harness] validateKnowledgeMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
485
- );
486
480
  const agentsPath = join2(rootDir, "AGENTS.md");
487
481
  const agentsResult = await validateAgentsMap(agentsPath);
488
482
  if (!agentsResult.ok) {
@@ -832,8 +826,8 @@ function createBoundaryValidator(schema, name) {
832
826
  return Ok(result.data);
833
827
  }
834
828
  const suggestions = result.error.issues.map((issue) => {
835
- const path13 = issue.path.join(".");
836
- return path13 ? `${path13}: ${issue.message}` : issue.message;
829
+ const path20 = issue.path.join(".");
830
+ return path20 ? `${path20}: ${issue.message}` : issue.message;
837
831
  });
838
832
  return Err(
839
833
  createError(
@@ -1387,11 +1381,11 @@ function walk(node, visitor) {
1387
1381
  var TypeScriptParser = class {
1388
1382
  name = "typescript";
1389
1383
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1390
- async parseFile(path13) {
1391
- const contentResult = await readFileContent(path13);
1384
+ async parseFile(path20) {
1385
+ const contentResult = await readFileContent(path20);
1392
1386
  if (!contentResult.ok) {
1393
1387
  return Err(
1394
- createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
1388
+ createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
1395
1389
  "Check that the file exists",
1396
1390
  "Verify the path is correct"
1397
1391
  ])
@@ -1401,7 +1395,7 @@ var TypeScriptParser = class {
1401
1395
  const ast = parse(contentResult.value, {
1402
1396
  loc: true,
1403
1397
  range: true,
1404
- jsx: path13.endsWith(".tsx"),
1398
+ jsx: path20.endsWith(".tsx"),
1405
1399
  errorOnUnknownASTType: false
1406
1400
  });
1407
1401
  return Ok({
@@ -1412,7 +1406,7 @@ var TypeScriptParser = class {
1412
1406
  } catch (e) {
1413
1407
  const error = e;
1414
1408
  return Err(
1415
- createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
1409
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
1416
1410
  "Check for syntax errors in the file",
1417
1411
  "Ensure valid TypeScript syntax"
1418
1412
  ])
@@ -1696,22 +1690,22 @@ function extractInlineRefs(content) {
1696
1690
  }
1697
1691
  return refs;
1698
1692
  }
1699
- async function parseDocumentationFile(path13) {
1700
- const contentResult = await readFileContent(path13);
1693
+ async function parseDocumentationFile(path20) {
1694
+ const contentResult = await readFileContent(path20);
1701
1695
  if (!contentResult.ok) {
1702
1696
  return Err(
1703
1697
  createEntropyError(
1704
1698
  "PARSE_ERROR",
1705
- `Failed to read documentation file: ${path13}`,
1706
- { file: path13 },
1699
+ `Failed to read documentation file: ${path20}`,
1700
+ { file: path20 },
1707
1701
  ["Check that the file exists"]
1708
1702
  )
1709
1703
  );
1710
1704
  }
1711
1705
  const content = contentResult.value;
1712
- const type = path13.endsWith(".md") ? "markdown" : "text";
1706
+ const type = path20.endsWith(".md") ? "markdown" : "text";
1713
1707
  return Ok({
1714
- path: path13,
1708
+ path: path20,
1715
1709
  type,
1716
1710
  content,
1717
1711
  codeBlocks: extractCodeBlocks(content),
@@ -2522,15 +2516,34 @@ async function detectPatternViolations(snapshot, config) {
2522
2516
  }
2523
2517
  }
2524
2518
  }
2519
+ if (config?.customPatterns) {
2520
+ for (const file of snapshot.files) {
2521
+ for (const custom of config.customPatterns) {
2522
+ const matches = custom.check(file, snapshot);
2523
+ for (const match of matches) {
2524
+ violations.push({
2525
+ pattern: custom.name,
2526
+ file: file.path,
2527
+ line: match.line,
2528
+ message: match.message,
2529
+ suggestion: match.suggestion || "Review and fix this pattern violation",
2530
+ severity: custom.severity
2531
+ });
2532
+ }
2533
+ }
2534
+ }
2535
+ }
2525
2536
  const errorCount = violations.filter((v) => v.severity === "error").length;
2526
2537
  const warningCount = violations.filter((v) => v.severity === "warning").length;
2527
- const totalChecks = snapshot.files.length * patterns.length;
2528
- const passRate = totalChecks > 0 ? (totalChecks - violations.length) / totalChecks : 1;
2538
+ const customCount = config?.customPatterns?.length ?? 0;
2539
+ const allPatternsCount = patterns.length + customCount;
2540
+ const totalChecks = snapshot.files.length * allPatternsCount;
2541
+ const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
2529
2542
  return Ok({
2530
2543
  violations,
2531
2544
  stats: {
2532
2545
  filesChecked: snapshot.files.length,
2533
- patternsApplied: patterns.length,
2546
+ patternsApplied: allPatternsCount,
2534
2547
  violationCount: violations.length,
2535
2548
  errorCount,
2536
2549
  warningCount
@@ -4713,10 +4726,13 @@ var DEFAULT_STATE = {
4713
4726
  progress: {}
4714
4727
  };
4715
4728
 
4716
- // src/state/state-manager.ts
4717
- import * as fs6 from "fs";
4718
- import * as path3 from "path";
4719
- import { execSync as execSync2 } from "child_process";
4729
+ // src/state/state-persistence.ts
4730
+ import * as fs8 from "fs";
4731
+ import * as path5 from "path";
4732
+
4733
+ // src/state/state-shared.ts
4734
+ import * as fs7 from "fs";
4735
+ import * as path4 from "path";
4720
4736
 
4721
4737
  // src/state/stream-resolver.ts
4722
4738
  import * as fs5 from "fs";
@@ -4742,10 +4758,20 @@ var DEFAULT_STREAM_INDEX = {
4742
4758
  streams: {}
4743
4759
  };
4744
4760
 
4745
- // src/state/stream-resolver.ts
4761
+ // src/state/constants.ts
4746
4762
  var HARNESS_DIR = ".harness";
4747
- var STREAMS_DIR = "streams";
4763
+ var STATE_FILE = "state.json";
4764
+ var LEARNINGS_FILE = "learnings.md";
4765
+ var FAILURES_FILE = "failures.md";
4766
+ var HANDOFF_FILE = "handoff.json";
4767
+ var GATE_CONFIG_FILE = "gate.json";
4748
4768
  var INDEX_FILE = "index.json";
4769
+ var SESSIONS_DIR = "sessions";
4770
+ var SESSION_INDEX_FILE = "index.md";
4771
+ var SUMMARY_FILE = "summary.md";
4772
+
4773
+ // src/state/stream-resolver.ts
4774
+ var STREAMS_DIR = "streams";
4749
4775
  var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
4750
4776
  function streamsDir(projectPath) {
4751
4777
  return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
@@ -4972,26 +4998,65 @@ async function migrateToStreams(projectPath) {
4972
4998
  return saveStreamIndex(projectPath, index);
4973
4999
  }
4974
5000
 
4975
- // src/state/state-manager.ts
4976
- var HARNESS_DIR2 = ".harness";
4977
- var STATE_FILE = "state.json";
4978
- var LEARNINGS_FILE = "learnings.md";
4979
- var FAILURES_FILE = "failures.md";
4980
- var HANDOFF_FILE = "handoff.json";
4981
- var GATE_CONFIG_FILE = "gate.json";
4982
- var INDEX_FILE2 = "index.json";
5001
+ // src/state/session-resolver.ts
5002
+ import * as fs6 from "fs";
5003
+ import * as path3 from "path";
5004
+ function resolveSessionDir(projectPath, sessionSlug, options) {
5005
+ if (!sessionSlug || sessionSlug.trim() === "") {
5006
+ return Err(new Error("Session slug must not be empty"));
5007
+ }
5008
+ if (sessionSlug.includes("..") || sessionSlug.includes("/") || sessionSlug.includes("\\")) {
5009
+ return Err(
5010
+ new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
5011
+ );
5012
+ }
5013
+ const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
5014
+ if (options?.create) {
5015
+ fs6.mkdirSync(sessionDir, { recursive: true });
5016
+ }
5017
+ return Ok(sessionDir);
5018
+ }
5019
+ function updateSessionIndex(projectPath, sessionSlug, description) {
5020
+ const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
5021
+ fs6.mkdirSync(sessionsDir, { recursive: true });
5022
+ const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
5023
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5024
+ const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
5025
+ if (!fs6.existsSync(indexPath2)) {
5026
+ fs6.writeFileSync(indexPath2, `## Active Sessions
5027
+
5028
+ ${newLine}
5029
+ `);
5030
+ return;
5031
+ }
5032
+ const content = fs6.readFileSync(indexPath2, "utf-8");
5033
+ const lines = content.split("\n");
5034
+ const slugPattern = `- [${sessionSlug}]`;
5035
+ const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
5036
+ if (existingIdx >= 0) {
5037
+ lines[existingIdx] = newLine;
5038
+ } else {
5039
+ const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
5040
+ lines.splice(lastNonEmpty + 1, 0, newLine);
5041
+ }
5042
+ fs6.writeFileSync(indexPath2, lines.join("\n"));
5043
+ }
5044
+
5045
+ // src/state/state-shared.ts
4983
5046
  var MAX_CACHE_ENTRIES = 8;
4984
- var learningsCacheMap = /* @__PURE__ */ new Map();
4985
- var failuresCacheMap = /* @__PURE__ */ new Map();
4986
5047
  function evictIfNeeded(map) {
4987
5048
  if (map.size > MAX_CACHE_ENTRIES) {
4988
5049
  const oldest = map.keys().next().value;
4989
5050
  if (oldest !== void 0) map.delete(oldest);
4990
5051
  }
4991
5052
  }
4992
- async function getStateDir(projectPath, stream) {
4993
- const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
4994
- const hasStreams = fs6.existsSync(streamsIndexPath);
5053
+ async function getStateDir(projectPath, stream, session) {
5054
+ if (session) {
5055
+ const sessionResult = resolveSessionDir(projectPath, session, { create: true });
5056
+ return sessionResult;
5057
+ }
5058
+ const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
5059
+ const hasStreams = fs7.existsSync(streamsIndexPath);
4995
5060
  if (stream || hasStreams) {
4996
5061
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
4997
5062
  if (result.ok) {
@@ -5001,18 +5066,20 @@ async function getStateDir(projectPath, stream) {
5001
5066
  return result;
5002
5067
  }
5003
5068
  }
5004
- return Ok(path3.join(projectPath, HARNESS_DIR2));
5069
+ return Ok(path4.join(projectPath, HARNESS_DIR));
5005
5070
  }
5006
- async function loadState(projectPath, stream) {
5071
+
5072
+ // src/state/state-persistence.ts
5073
+ async function loadState(projectPath, stream, session) {
5007
5074
  try {
5008
- const dirResult = await getStateDir(projectPath, stream);
5075
+ const dirResult = await getStateDir(projectPath, stream, session);
5009
5076
  if (!dirResult.ok) return dirResult;
5010
5077
  const stateDir = dirResult.value;
5011
- const statePath = path3.join(stateDir, STATE_FILE);
5012
- if (!fs6.existsSync(statePath)) {
5078
+ const statePath = path5.join(stateDir, STATE_FILE);
5079
+ if (!fs8.existsSync(statePath)) {
5013
5080
  return Ok({ ...DEFAULT_STATE });
5014
5081
  }
5015
- const raw = fs6.readFileSync(statePath, "utf-8");
5082
+ const raw = fs8.readFileSync(statePath, "utf-8");
5016
5083
  const parsed = JSON.parse(raw);
5017
5084
  const result = HarnessStateSchema.safeParse(parsed);
5018
5085
  if (!result.success) {
@@ -5025,14 +5092,14 @@ async function loadState(projectPath, stream) {
5025
5092
  );
5026
5093
  }
5027
5094
  }
5028
- async function saveState(projectPath, state, stream) {
5095
+ async function saveState(projectPath, state, stream, session) {
5029
5096
  try {
5030
- const dirResult = await getStateDir(projectPath, stream);
5097
+ const dirResult = await getStateDir(projectPath, stream, session);
5031
5098
  if (!dirResult.ok) return dirResult;
5032
5099
  const stateDir = dirResult.value;
5033
- const statePath = path3.join(stateDir, STATE_FILE);
5034
- fs6.mkdirSync(stateDir, { recursive: true });
5035
- fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
5100
+ const statePath = path5.join(stateDir, STATE_FILE);
5101
+ fs8.mkdirSync(stateDir, { recursive: true });
5102
+ fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
5036
5103
  return Ok(void 0);
5037
5104
  } catch (error) {
5038
5105
  return Err(
@@ -5040,13 +5107,21 @@ async function saveState(projectPath, state, stream) {
5040
5107
  );
5041
5108
  }
5042
5109
  }
5043
- async function appendLearning(projectPath, learning, skillName, outcome, stream) {
5110
+
5111
+ // src/state/learnings.ts
5112
+ import * as fs9 from "fs";
5113
+ import * as path6 from "path";
5114
+ var learningsCacheMap = /* @__PURE__ */ new Map();
5115
+ function clearLearningsCache() {
5116
+ learningsCacheMap.clear();
5117
+ }
5118
+ async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
5044
5119
  try {
5045
- const dirResult = await getStateDir(projectPath, stream);
5120
+ const dirResult = await getStateDir(projectPath, stream, session);
5046
5121
  if (!dirResult.ok) return dirResult;
5047
5122
  const stateDir = dirResult.value;
5048
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
5049
- fs6.mkdirSync(stateDir, { recursive: true });
5123
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
5124
+ fs9.mkdirSync(stateDir, { recursive: true });
5050
5125
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5051
5126
  let entry;
5052
5127
  if (skillName && outcome) {
@@ -5062,11 +5137,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
5062
5137
  - **${timestamp}:** ${learning}
5063
5138
  `;
5064
5139
  }
5065
- if (!fs6.existsSync(learningsPath)) {
5066
- fs6.writeFileSync(learningsPath, `# Learnings
5140
+ if (!fs9.existsSync(learningsPath)) {
5141
+ fs9.writeFileSync(learningsPath, `# Learnings
5067
5142
  ${entry}`);
5068
5143
  } else {
5069
- fs6.appendFileSync(learningsPath, entry);
5144
+ fs9.appendFileSync(learningsPath, entry);
5070
5145
  }
5071
5146
  learningsCacheMap.delete(learningsPath);
5072
5147
  return Ok(void 0);
@@ -5078,23 +5153,92 @@ ${entry}`);
5078
5153
  );
5079
5154
  }
5080
5155
  }
5081
- async function loadRelevantLearnings(projectPath, skillName, stream) {
5156
+ function estimateTokens(text) {
5157
+ return Math.ceil(text.length / 4);
5158
+ }
5159
+ function scoreRelevance(entry, intent) {
5160
+ if (!intent || intent.trim() === "") return 0;
5161
+ const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
5162
+ if (intentWords.length === 0) return 0;
5163
+ const entryLower = entry.toLowerCase();
5164
+ const matches = intentWords.filter((word) => entryLower.includes(word));
5165
+ return matches.length / intentWords.length;
5166
+ }
5167
+ function parseDateFromEntry(entry) {
5168
+ const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
5169
+ return match ? match[1] ?? null : null;
5170
+ }
5171
+ function analyzeLearningPatterns(entries) {
5172
+ const tagGroups = /* @__PURE__ */ new Map();
5173
+ for (const entry of entries) {
5174
+ const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
5175
+ for (const match of tagMatches) {
5176
+ const tag = match[1] ?? match[2];
5177
+ if (tag) {
5178
+ const group = tagGroups.get(tag) ?? [];
5179
+ group.push(entry);
5180
+ tagGroups.set(tag, group);
5181
+ }
5182
+ }
5183
+ }
5184
+ const patterns = [];
5185
+ for (const [tag, groupEntries] of tagGroups) {
5186
+ if (groupEntries.length >= 3) {
5187
+ patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
5188
+ }
5189
+ }
5190
+ return patterns.sort((a, b) => b.count - a.count);
5191
+ }
5192
+ async function loadBudgetedLearnings(projectPath, options) {
5193
+ const { intent, tokenBudget = 1e3, skill, session, stream } = options;
5194
+ const sortByRecencyAndRelevance = (entries) => {
5195
+ return [...entries].sort((a, b) => {
5196
+ const dateA = parseDateFromEntry(a) ?? "0000-00-00";
5197
+ const dateB = parseDateFromEntry(b) ?? "0000-00-00";
5198
+ const dateCompare = dateB.localeCompare(dateA);
5199
+ if (dateCompare !== 0) return dateCompare;
5200
+ return scoreRelevance(b, intent) - scoreRelevance(a, intent);
5201
+ });
5202
+ };
5203
+ const allEntries = [];
5204
+ if (session) {
5205
+ const sessionResult = await loadRelevantLearnings(projectPath, skill, stream, session);
5206
+ if (sessionResult.ok) {
5207
+ allEntries.push(...sortByRecencyAndRelevance(sessionResult.value));
5208
+ }
5209
+ }
5210
+ const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
5211
+ if (globalResult.ok) {
5212
+ allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
5213
+ }
5214
+ const budgeted = [];
5215
+ let totalTokens = 0;
5216
+ for (const entry of allEntries) {
5217
+ const separator = budgeted.length > 0 ? "\n" : "";
5218
+ const entryCost = estimateTokens(entry + separator);
5219
+ if (totalTokens + entryCost > tokenBudget) break;
5220
+ budgeted.push(entry);
5221
+ totalTokens += entryCost;
5222
+ }
5223
+ return Ok(budgeted);
5224
+ }
5225
+ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
5082
5226
  try {
5083
- const dirResult = await getStateDir(projectPath, stream);
5227
+ const dirResult = await getStateDir(projectPath, stream, session);
5084
5228
  if (!dirResult.ok) return dirResult;
5085
5229
  const stateDir = dirResult.value;
5086
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
5087
- if (!fs6.existsSync(learningsPath)) {
5230
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
5231
+ if (!fs9.existsSync(learningsPath)) {
5088
5232
  return Ok([]);
5089
5233
  }
5090
- const stats = fs6.statSync(learningsPath);
5234
+ const stats = fs9.statSync(learningsPath);
5091
5235
  const cacheKey = learningsPath;
5092
5236
  const cached = learningsCacheMap.get(cacheKey);
5093
5237
  let entries;
5094
5238
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5095
5239
  entries = cached.entries;
5096
5240
  } else {
5097
- const content = fs6.readFileSync(learningsPath, "utf-8");
5241
+ const content = fs9.readFileSync(learningsPath, "utf-8");
5098
5242
  const lines = content.split("\n");
5099
5243
  entries = [];
5100
5244
  let currentBlock = [];
@@ -5130,23 +5274,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
5130
5274
  );
5131
5275
  }
5132
5276
  }
5133
- var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
5134
- async function appendFailure(projectPath, description, skillName, type, stream) {
5277
+ async function archiveLearnings(projectPath, entries, stream) {
5278
+ try {
5279
+ const dirResult = await getStateDir(projectPath, stream);
5280
+ if (!dirResult.ok) return dirResult;
5281
+ const stateDir = dirResult.value;
5282
+ const archiveDir = path6.join(stateDir, "learnings-archive");
5283
+ fs9.mkdirSync(archiveDir, { recursive: true });
5284
+ const now = /* @__PURE__ */ new Date();
5285
+ const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
5286
+ const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
5287
+ const archiveContent = entries.join("\n\n") + "\n";
5288
+ if (fs9.existsSync(archivePath)) {
5289
+ fs9.appendFileSync(archivePath, "\n" + archiveContent);
5290
+ } else {
5291
+ fs9.writeFileSync(archivePath, `# Learnings Archive
5292
+
5293
+ ${archiveContent}`);
5294
+ }
5295
+ return Ok(void 0);
5296
+ } catch (error) {
5297
+ return Err(
5298
+ new Error(
5299
+ `Failed to archive learnings: ${error instanceof Error ? error.message : String(error)}`
5300
+ )
5301
+ );
5302
+ }
5303
+ }
5304
+ async function pruneLearnings(projectPath, stream) {
5135
5305
  try {
5136
5306
  const dirResult = await getStateDir(projectPath, stream);
5137
5307
  if (!dirResult.ok) return dirResult;
5138
5308
  const stateDir = dirResult.value;
5139
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5140
- fs6.mkdirSync(stateDir, { recursive: true });
5309
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
5310
+ if (!fs9.existsSync(learningsPath)) {
5311
+ return Ok({ kept: 0, archived: 0, patterns: [] });
5312
+ }
5313
+ const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
5314
+ if (!loadResult.ok) return loadResult;
5315
+ const allEntries = loadResult.value;
5316
+ if (allEntries.length <= 20) {
5317
+ const cutoffDate = /* @__PURE__ */ new Date();
5318
+ cutoffDate.setDate(cutoffDate.getDate() - 14);
5319
+ const cutoffStr = cutoffDate.toISOString().split("T")[0];
5320
+ const hasOld = allEntries.some((entry) => {
5321
+ const date = parseDateFromEntry(entry);
5322
+ return date !== null && date < cutoffStr;
5323
+ });
5324
+ if (!hasOld) {
5325
+ return Ok({ kept: allEntries.length, archived: 0, patterns: [] });
5326
+ }
5327
+ }
5328
+ const sorted = [...allEntries].sort((a, b) => {
5329
+ const dateA = parseDateFromEntry(a) ?? "0000-00-00";
5330
+ const dateB = parseDateFromEntry(b) ?? "0000-00-00";
5331
+ return dateB.localeCompare(dateA);
5332
+ });
5333
+ const toKeep = sorted.slice(0, 20);
5334
+ const toArchive = sorted.slice(20);
5335
+ const patterns = analyzeLearningPatterns(allEntries);
5336
+ if (toArchive.length > 0) {
5337
+ const archiveResult = await archiveLearnings(projectPath, toArchive, stream);
5338
+ if (!archiveResult.ok) return archiveResult;
5339
+ }
5340
+ const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
5341
+ fs9.writeFileSync(learningsPath, newContent);
5342
+ learningsCacheMap.delete(learningsPath);
5343
+ return Ok({
5344
+ kept: toKeep.length,
5345
+ archived: toArchive.length,
5346
+ patterns
5347
+ });
5348
+ } catch (error) {
5349
+ return Err(
5350
+ new Error(
5351
+ `Failed to prune learnings: ${error instanceof Error ? error.message : String(error)}`
5352
+ )
5353
+ );
5354
+ }
5355
+ }
5356
+
5357
+ // src/state/failures.ts
5358
+ import * as fs10 from "fs";
5359
+ import * as path7 from "path";
5360
+ var failuresCacheMap = /* @__PURE__ */ new Map();
5361
+ function clearFailuresCache() {
5362
+ failuresCacheMap.clear();
5363
+ }
5364
+ var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
5365
+ async function appendFailure(projectPath, description, skillName, type, stream, session) {
5366
+ try {
5367
+ const dirResult = await getStateDir(projectPath, stream, session);
5368
+ if (!dirResult.ok) return dirResult;
5369
+ const stateDir = dirResult.value;
5370
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
5371
+ fs10.mkdirSync(stateDir, { recursive: true });
5141
5372
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5142
5373
  const entry = `
5143
5374
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
5144
5375
  `;
5145
- if (!fs6.existsSync(failuresPath)) {
5146
- fs6.writeFileSync(failuresPath, `# Failures
5376
+ if (!fs10.existsSync(failuresPath)) {
5377
+ fs10.writeFileSync(failuresPath, `# Failures
5147
5378
  ${entry}`);
5148
5379
  } else {
5149
- fs6.appendFileSync(failuresPath, entry);
5380
+ fs10.appendFileSync(failuresPath, entry);
5150
5381
  }
5151
5382
  failuresCacheMap.delete(failuresPath);
5152
5383
  return Ok(void 0);
@@ -5158,22 +5389,22 @@ ${entry}`);
5158
5389
  );
5159
5390
  }
5160
5391
  }
5161
- async function loadFailures(projectPath, stream) {
5392
+ async function loadFailures(projectPath, stream, session) {
5162
5393
  try {
5163
- const dirResult = await getStateDir(projectPath, stream);
5394
+ const dirResult = await getStateDir(projectPath, stream, session);
5164
5395
  if (!dirResult.ok) return dirResult;
5165
5396
  const stateDir = dirResult.value;
5166
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5167
- if (!fs6.existsSync(failuresPath)) {
5397
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
5398
+ if (!fs10.existsSync(failuresPath)) {
5168
5399
  return Ok([]);
5169
5400
  }
5170
- const stats = fs6.statSync(failuresPath);
5401
+ const stats = fs10.statSync(failuresPath);
5171
5402
  const cacheKey = failuresPath;
5172
5403
  const cached = failuresCacheMap.get(cacheKey);
5173
5404
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5174
5405
  return Ok(cached.entries);
5175
5406
  }
5176
- const content = fs6.readFileSync(failuresPath, "utf-8");
5407
+ const content = fs10.readFileSync(failuresPath, "utf-8");
5177
5408
  const entries = [];
5178
5409
  for (const line of content.split("\n")) {
5179
5410
  const match = line.match(FAILURE_LINE_REGEX);
@@ -5197,25 +5428,25 @@ async function loadFailures(projectPath, stream) {
5197
5428
  );
5198
5429
  }
5199
5430
  }
5200
- async function archiveFailures(projectPath, stream) {
5431
+ async function archiveFailures(projectPath, stream, session) {
5201
5432
  try {
5202
- const dirResult = await getStateDir(projectPath, stream);
5433
+ const dirResult = await getStateDir(projectPath, stream, session);
5203
5434
  if (!dirResult.ok) return dirResult;
5204
5435
  const stateDir = dirResult.value;
5205
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5206
- if (!fs6.existsSync(failuresPath)) {
5436
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
5437
+ if (!fs10.existsSync(failuresPath)) {
5207
5438
  return Ok(void 0);
5208
5439
  }
5209
- const archiveDir = path3.join(stateDir, "archive");
5210
- fs6.mkdirSync(archiveDir, { recursive: true });
5440
+ const archiveDir = path7.join(stateDir, "archive");
5441
+ fs10.mkdirSync(archiveDir, { recursive: true });
5211
5442
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5212
5443
  let archiveName = `failures-${date}.md`;
5213
5444
  let counter = 2;
5214
- while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
5445
+ while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
5215
5446
  archiveName = `failures-${date}-${counter}.md`;
5216
5447
  counter++;
5217
5448
  }
5218
- fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5449
+ fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
5219
5450
  failuresCacheMap.delete(failuresPath);
5220
5451
  return Ok(void 0);
5221
5452
  } catch (error) {
@@ -5226,14 +5457,18 @@ async function archiveFailures(projectPath, stream) {
5226
5457
  );
5227
5458
  }
5228
5459
  }
5229
- async function saveHandoff(projectPath, handoff, stream) {
5460
+
5461
+ // src/state/handoff.ts
5462
+ import * as fs11 from "fs";
5463
+ import * as path8 from "path";
5464
+ async function saveHandoff(projectPath, handoff, stream, session) {
5230
5465
  try {
5231
- const dirResult = await getStateDir(projectPath, stream);
5466
+ const dirResult = await getStateDir(projectPath, stream, session);
5232
5467
  if (!dirResult.ok) return dirResult;
5233
5468
  const stateDir = dirResult.value;
5234
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5235
- fs6.mkdirSync(stateDir, { recursive: true });
5236
- fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
5469
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
5470
+ fs11.mkdirSync(stateDir, { recursive: true });
5471
+ fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
5237
5472
  return Ok(void 0);
5238
5473
  } catch (error) {
5239
5474
  return Err(
@@ -5241,16 +5476,16 @@ async function saveHandoff(projectPath, handoff, stream) {
5241
5476
  );
5242
5477
  }
5243
5478
  }
5244
- async function loadHandoff(projectPath, stream) {
5479
+ async function loadHandoff(projectPath, stream, session) {
5245
5480
  try {
5246
- const dirResult = await getStateDir(projectPath, stream);
5481
+ const dirResult = await getStateDir(projectPath, stream, session);
5247
5482
  if (!dirResult.ok) return dirResult;
5248
5483
  const stateDir = dirResult.value;
5249
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5250
- if (!fs6.existsSync(handoffPath)) {
5484
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
5485
+ if (!fs11.existsSync(handoffPath)) {
5251
5486
  return Ok(null);
5252
5487
  }
5253
- const raw = fs6.readFileSync(handoffPath, "utf-8");
5488
+ const raw = fs11.readFileSync(handoffPath, "utf-8");
5254
5489
  const parsed = JSON.parse(raw);
5255
5490
  const result = HandoffSchema.safeParse(parsed);
5256
5491
  if (!result.success) {
@@ -5263,73 +5498,82 @@ async function loadHandoff(projectPath, stream) {
5263
5498
  );
5264
5499
  }
5265
5500
  }
5501
+
5502
+ // src/state/mechanical-gate.ts
5503
+ import * as fs12 from "fs";
5504
+ import * as path9 from "path";
5505
+ import { execSync as execSync2 } from "child_process";
5506
+ 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:.-]+$/;
5507
+ function loadChecksFromConfig(gateConfigPath) {
5508
+ if (!fs12.existsSync(gateConfigPath)) return [];
5509
+ const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
5510
+ const config = GateConfigSchema.safeParse(raw);
5511
+ if (config.success && config.data.checks) return config.data.checks;
5512
+ return [];
5513
+ }
5514
+ function discoverChecksFromProject(projectPath) {
5515
+ const checks = [];
5516
+ const packageJsonPath = path9.join(projectPath, "package.json");
5517
+ if (fs12.existsSync(packageJsonPath)) {
5518
+ const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
5519
+ const scripts = pkg.scripts || {};
5520
+ if (scripts.test) checks.push({ name: "test", command: "npm test" });
5521
+ if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
5522
+ if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
5523
+ if (scripts.build) checks.push({ name: "build", command: "npm run build" });
5524
+ }
5525
+ if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
5526
+ checks.push({ name: "test", command: "go test ./..." });
5527
+ checks.push({ name: "build", command: "go build ./..." });
5528
+ }
5529
+ if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
5530
+ checks.push({ name: "test", command: "python -m pytest" });
5531
+ }
5532
+ return checks;
5533
+ }
5534
+ function executeCheck(check, projectPath) {
5535
+ if (!SAFE_GATE_COMMAND.test(check.command)) {
5536
+ return {
5537
+ name: check.name,
5538
+ passed: false,
5539
+ command: check.command,
5540
+ output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
5541
+ duration: 0
5542
+ };
5543
+ }
5544
+ const start = Date.now();
5545
+ try {
5546
+ execSync2(check.command, {
5547
+ cwd: projectPath,
5548
+ stdio: "pipe",
5549
+ timeout: 12e4
5550
+ });
5551
+ return {
5552
+ name: check.name,
5553
+ passed: true,
5554
+ command: check.command,
5555
+ duration: Date.now() - start
5556
+ };
5557
+ } catch (error) {
5558
+ const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
5559
+ return {
5560
+ name: check.name,
5561
+ passed: false,
5562
+ command: check.command,
5563
+ output: output.slice(0, 2e3),
5564
+ duration: Date.now() - start
5565
+ };
5566
+ }
5567
+ }
5266
5568
  async function runMechanicalGate(projectPath) {
5267
- const harnessDir = path3.join(projectPath, HARNESS_DIR2);
5268
- const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
5569
+ const harnessDir = path9.join(projectPath, HARNESS_DIR);
5570
+ const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
5269
5571
  try {
5270
- let checks = [];
5271
- if (fs6.existsSync(gateConfigPath)) {
5272
- const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
5273
- const config = GateConfigSchema.safeParse(raw);
5274
- if (config.success && config.data.checks) {
5275
- checks = config.data.checks;
5276
- }
5277
- }
5572
+ let checks = loadChecksFromConfig(gateConfigPath);
5278
5573
  if (checks.length === 0) {
5279
- const packageJsonPath = path3.join(projectPath, "package.json");
5280
- if (fs6.existsSync(packageJsonPath)) {
5281
- const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
5282
- const scripts = pkg.scripts || {};
5283
- if (scripts.test) checks.push({ name: "test", command: "npm test" });
5284
- if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
5285
- if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
5286
- if (scripts.build) checks.push({ name: "build", command: "npm run build" });
5287
- }
5288
- if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
5289
- checks.push({ name: "test", command: "go test ./..." });
5290
- checks.push({ name: "build", command: "go build ./..." });
5291
- }
5292
- if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
5293
- checks.push({ name: "test", command: "python -m pytest" });
5294
- }
5295
- }
5296
- const results = [];
5297
- const 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:.-]+$/;
5298
- for (const check of checks) {
5299
- if (!SAFE_GATE_COMMAND.test(check.command)) {
5300
- results.push({
5301
- name: check.name,
5302
- passed: false,
5303
- command: check.command,
5304
- output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
5305
- duration: 0
5306
- });
5307
- continue;
5308
- }
5309
- const start = Date.now();
5310
- try {
5311
- execSync2(check.command, {
5312
- cwd: projectPath,
5313
- stdio: "pipe",
5314
- timeout: 12e4
5315
- });
5316
- results.push({
5317
- name: check.name,
5318
- passed: true,
5319
- command: check.command,
5320
- duration: Date.now() - start
5321
- });
5322
- } catch (error) {
5323
- const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
5324
- results.push({
5325
- name: check.name,
5326
- passed: false,
5327
- command: check.command,
5328
- output: output.slice(0, 2e3),
5329
- duration: Date.now() - start
5330
- });
5331
- }
5574
+ checks = discoverChecksFromProject(projectPath);
5332
5575
  }
5576
+ const results = checks.map((check) => executeCheck(check, projectPath));
5333
5577
  return Ok({
5334
5578
  passed: results.length === 0 || results.every((r) => r.passed),
5335
5579
  checks: results
@@ -5343,6 +5587,96 @@ async function runMechanicalGate(projectPath) {
5343
5587
  }
5344
5588
  }
5345
5589
 
5590
+ // src/state/session-summary.ts
5591
+ import * as fs13 from "fs";
5592
+ import * as path10 from "path";
5593
+ function formatSummary(data) {
5594
+ const lines = [
5595
+ "## Session Summary",
5596
+ "",
5597
+ `**Session:** ${data.session}`,
5598
+ `**Last active:** ${data.lastActive}`,
5599
+ `**Skill:** ${data.skill}`
5600
+ ];
5601
+ if (data.phase) {
5602
+ lines.push(`**Phase:** ${data.phase}`);
5603
+ }
5604
+ lines.push(`**Status:** ${data.status}`);
5605
+ if (data.spec) {
5606
+ lines.push(`**Spec:** ${data.spec}`);
5607
+ }
5608
+ if (data.plan) {
5609
+ lines.push(`**Plan:** ${data.plan}`);
5610
+ }
5611
+ lines.push(`**Key context:** ${data.keyContext}`);
5612
+ lines.push(`**Next step:** ${data.nextStep}`);
5613
+ lines.push("");
5614
+ return lines.join("\n");
5615
+ }
5616
+ function deriveIndexDescription(data) {
5617
+ const skillShort = data.skill.replace("harness-", "");
5618
+ const parts = [skillShort];
5619
+ if (data.phase) {
5620
+ parts.push(`phase ${data.phase}`);
5621
+ }
5622
+ parts.push(data.status.toLowerCase());
5623
+ return parts.join(", ");
5624
+ }
5625
+ function writeSessionSummary(projectPath, sessionSlug, data) {
5626
+ try {
5627
+ const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
5628
+ if (!dirResult.ok) return dirResult;
5629
+ const sessionDir = dirResult.value;
5630
+ const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
5631
+ const content = formatSummary(data);
5632
+ fs13.writeFileSync(summaryPath, content);
5633
+ const description = deriveIndexDescription(data);
5634
+ updateSessionIndex(projectPath, sessionSlug, description);
5635
+ return Ok(void 0);
5636
+ } catch (error) {
5637
+ return Err(
5638
+ new Error(
5639
+ `Failed to write session summary: ${error instanceof Error ? error.message : String(error)}`
5640
+ )
5641
+ );
5642
+ }
5643
+ }
5644
+ function loadSessionSummary(projectPath, sessionSlug) {
5645
+ try {
5646
+ const dirResult = resolveSessionDir(projectPath, sessionSlug);
5647
+ if (!dirResult.ok) return dirResult;
5648
+ const sessionDir = dirResult.value;
5649
+ const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
5650
+ if (!fs13.existsSync(summaryPath)) {
5651
+ return Ok(null);
5652
+ }
5653
+ const content = fs13.readFileSync(summaryPath, "utf-8");
5654
+ return Ok(content);
5655
+ } catch (error) {
5656
+ return Err(
5657
+ new Error(
5658
+ `Failed to load session summary: ${error instanceof Error ? error.message : String(error)}`
5659
+ )
5660
+ );
5661
+ }
5662
+ }
5663
+ function listActiveSessions(projectPath) {
5664
+ try {
5665
+ const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
5666
+ if (!fs13.existsSync(indexPath2)) {
5667
+ return Ok(null);
5668
+ }
5669
+ const content = fs13.readFileSync(indexPath2, "utf-8");
5670
+ return Ok(content);
5671
+ } catch (error) {
5672
+ return Err(
5673
+ new Error(
5674
+ `Failed to list active sessions: ${error instanceof Error ? error.message : String(error)}`
5675
+ )
5676
+ );
5677
+ }
5678
+ }
5679
+
5346
5680
  // src/workflow/runner.ts
5347
5681
  async function executeWorkflow(workflow, executor) {
5348
5682
  const stepResults = [];
@@ -5492,7 +5826,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
5492
5826
  }
5493
5827
 
5494
5828
  // src/security/scanner.ts
5495
- import * as fs8 from "fs/promises";
5829
+ import * as fs15 from "fs/promises";
5496
5830
 
5497
5831
  // src/security/rules/registry.ts
5498
5832
  var RuleRegistry = class {
@@ -5579,15 +5913,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
5579
5913
  }
5580
5914
 
5581
5915
  // src/security/stack-detector.ts
5582
- import * as fs7 from "fs";
5583
- import * as path4 from "path";
5916
+ import * as fs14 from "fs";
5917
+ import * as path11 from "path";
5584
5918
  function detectStack(projectRoot) {
5585
5919
  const stacks = [];
5586
- const pkgJsonPath = path4.join(projectRoot, "package.json");
5587
- if (fs7.existsSync(pkgJsonPath)) {
5920
+ const pkgJsonPath = path11.join(projectRoot, "package.json");
5921
+ if (fs14.existsSync(pkgJsonPath)) {
5588
5922
  stacks.push("node");
5589
5923
  try {
5590
- const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5924
+ const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
5591
5925
  const allDeps = {
5592
5926
  ...pkgJson.dependencies,
5593
5927
  ...pkgJson.devDependencies
@@ -5602,13 +5936,13 @@ function detectStack(projectRoot) {
5602
5936
  } catch {
5603
5937
  }
5604
5938
  }
5605
- const goModPath = path4.join(projectRoot, "go.mod");
5606
- if (fs7.existsSync(goModPath)) {
5939
+ const goModPath = path11.join(projectRoot, "go.mod");
5940
+ if (fs14.existsSync(goModPath)) {
5607
5941
  stacks.push("go");
5608
5942
  }
5609
- const requirementsPath = path4.join(projectRoot, "requirements.txt");
5610
- const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
5611
- if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
5943
+ const requirementsPath = path11.join(projectRoot, "requirements.txt");
5944
+ const pyprojectPath = path11.join(projectRoot, "pyproject.toml");
5945
+ if (fs14.existsSync(requirementsPath) || fs14.existsSync(pyprojectPath)) {
5612
5946
  stacks.push("python");
5613
5947
  }
5614
5948
  return stacks;
@@ -6035,7 +6369,7 @@ var SecurityScanner = class {
6035
6369
  }
6036
6370
  async scanFile(filePath) {
6037
6371
  if (!this.config.enabled) return [];
6038
- const content = await fs8.readFile(filePath, "utf-8");
6372
+ const content = await fs15.readFile(filePath, "utf-8");
6039
6373
  return this.scanContent(content, filePath, 1);
6040
6374
  }
6041
6375
  async scanFiles(filePaths) {
@@ -6060,7 +6394,7 @@ var SecurityScanner = class {
6060
6394
  };
6061
6395
 
6062
6396
  // src/ci/check-orchestrator.ts
6063
- import * as path5 from "path";
6397
+ import * as path12 from "path";
6064
6398
  var ALL_CHECKS = [
6065
6399
  "validate",
6066
6400
  "deps",
@@ -6077,7 +6411,7 @@ async function runSingleCheck(name, projectRoot, config) {
6077
6411
  try {
6078
6412
  switch (name) {
6079
6413
  case "validate": {
6080
- const agentsPath = path5.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6414
+ const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6081
6415
  const result = await validateAgentsMap(agentsPath);
6082
6416
  if (!result.ok) {
6083
6417
  issues.push({ severity: "error", message: result.error.message });
@@ -6132,7 +6466,7 @@ async function runSingleCheck(name, projectRoot, config) {
6132
6466
  break;
6133
6467
  }
6134
6468
  case "docs": {
6135
- const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
6469
+ const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
6136
6470
  const entropyConfig = config.entropy || {};
6137
6471
  const result = await checkDocCoverage("project", {
6138
6472
  docsDir,
@@ -6370,7 +6704,7 @@ async function runCIChecks(input) {
6370
6704
  }
6371
6705
 
6372
6706
  // src/review/mechanical-checks.ts
6373
- import * as path6 from "path";
6707
+ import * as path13 from "path";
6374
6708
  async function runMechanicalChecks(options) {
6375
6709
  const { projectRoot, config, skip = [], changedFiles } = options;
6376
6710
  const findings = [];
@@ -6382,7 +6716,7 @@ async function runMechanicalChecks(options) {
6382
6716
  };
6383
6717
  if (!skip.includes("validate")) {
6384
6718
  try {
6385
- const agentsPath = path6.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6719
+ const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6386
6720
  const result = await validateAgentsMap(agentsPath);
6387
6721
  if (!result.ok) {
6388
6722
  statuses.validate = "fail";
@@ -6419,7 +6753,7 @@ async function runMechanicalChecks(options) {
6419
6753
  statuses.validate = "fail";
6420
6754
  findings.push({
6421
6755
  tool: "validate",
6422
- file: path6.join(projectRoot, "AGENTS.md"),
6756
+ file: path13.join(projectRoot, "AGENTS.md"),
6423
6757
  message: err instanceof Error ? err.message : String(err),
6424
6758
  severity: "error"
6425
6759
  });
@@ -6483,7 +6817,7 @@ async function runMechanicalChecks(options) {
6483
6817
  (async () => {
6484
6818
  const localFindings = [];
6485
6819
  try {
6486
- const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
6820
+ const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
6487
6821
  const result = await checkDocCoverage("project", { docsDir });
6488
6822
  if (!result.ok) {
6489
6823
  statuses["check-docs"] = "warn";
@@ -6510,7 +6844,7 @@ async function runMechanicalChecks(options) {
6510
6844
  statuses["check-docs"] = "warn";
6511
6845
  localFindings.push({
6512
6846
  tool: "check-docs",
6513
- file: path6.join(projectRoot, "docs"),
6847
+ file: path13.join(projectRoot, "docs"),
6514
6848
  message: err instanceof Error ? err.message : String(err),
6515
6849
  severity: "warning"
6516
6850
  });
@@ -6658,7 +6992,7 @@ function detectChangeType(commitMessage, diff2) {
6658
6992
  }
6659
6993
 
6660
6994
  // src/review/context-scoper.ts
6661
- import * as path7 from "path";
6995
+ import * as path14 from "path";
6662
6996
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
6663
6997
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
6664
6998
  function computeContextBudget(diffLines) {
@@ -6666,18 +7000,18 @@ function computeContextBudget(diffLines) {
6666
7000
  return diffLines;
6667
7001
  }
6668
7002
  function isWithinProject(absPath, projectRoot) {
6669
- const resolvedRoot = path7.resolve(projectRoot) + path7.sep;
6670
- const resolvedPath = path7.resolve(absPath);
6671
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path7.resolve(projectRoot);
7003
+ const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
7004
+ const resolvedPath = path14.resolve(absPath);
7005
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
6672
7006
  }
6673
7007
  async function readContextFile(projectRoot, filePath, reason) {
6674
- const absPath = path7.isAbsolute(filePath) ? filePath : path7.join(projectRoot, filePath);
7008
+ const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
6675
7009
  if (!isWithinProject(absPath, projectRoot)) return null;
6676
7010
  const result = await readFileContent(absPath);
6677
7011
  if (!result.ok) return null;
6678
7012
  const content = result.value;
6679
7013
  const lines = content.split("\n").length;
6680
- const relPath = path7.isAbsolute(filePath) ? path7.relative(projectRoot, filePath) : filePath;
7014
+ const relPath = path14.isAbsolute(filePath) ? path14.relative(projectRoot, filePath) : filePath;
6681
7015
  return { path: relPath, content, reason, lines };
6682
7016
  }
6683
7017
  function extractImportSources(content) {
@@ -6692,18 +7026,18 @@ function extractImportSources(content) {
6692
7026
  }
6693
7027
  async function resolveImportPath(projectRoot, fromFile, importSource) {
6694
7028
  if (!importSource.startsWith(".")) return null;
6695
- const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
6696
- const basePath = path7.resolve(fromDir, importSource);
7029
+ const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
7030
+ const basePath = path14.resolve(fromDir, importSource);
6697
7031
  if (!isWithinProject(basePath, projectRoot)) return null;
6698
- const relBase = path7.relative(projectRoot, basePath);
7032
+ const relBase = path14.relative(projectRoot, basePath);
6699
7033
  const candidates = [
6700
7034
  relBase + ".ts",
6701
7035
  relBase + ".tsx",
6702
7036
  relBase + ".mts",
6703
- path7.join(relBase, "index.ts")
7037
+ path14.join(relBase, "index.ts")
6704
7038
  ];
6705
7039
  for (const candidate of candidates) {
6706
- const absCandidate = path7.join(projectRoot, candidate);
7040
+ const absCandidate = path14.join(projectRoot, candidate);
6707
7041
  if (await fileExists(absCandidate)) {
6708
7042
  return candidate;
6709
7043
  }
@@ -6711,10 +7045,10 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
6711
7045
  return null;
6712
7046
  }
6713
7047
  async function findTestFiles(projectRoot, sourceFile) {
6714
- const baseName = path7.basename(sourceFile, path7.extname(sourceFile));
7048
+ const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
6715
7049
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
6716
7050
  const results = await findFiles(pattern, projectRoot);
6717
- return results.map((f) => path7.relative(projectRoot, f));
7051
+ return results.map((f) => path14.relative(projectRoot, f));
6718
7052
  }
6719
7053
  async function gatherImportContext(projectRoot, changedFiles, budget) {
6720
7054
  const contextFiles = [];
@@ -7502,7 +7836,7 @@ async function fanOutReview(options) {
7502
7836
  }
7503
7837
 
7504
7838
  // src/review/validate-findings.ts
7505
- import * as path8 from "path";
7839
+ import * as path15 from "path";
7506
7840
  var DOWNGRADE_MAP = {
7507
7841
  critical: "important",
7508
7842
  important: "suggestion",
@@ -7523,7 +7857,7 @@ function normalizePath(filePath, projectRoot) {
7523
7857
  let normalized = filePath;
7524
7858
  normalized = normalized.replace(/\\/g, "/");
7525
7859
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
7526
- if (path8.isAbsolute(normalized)) {
7860
+ if (path15.isAbsolute(normalized)) {
7527
7861
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
7528
7862
  if (normalized.startsWith(root)) {
7529
7863
  normalized = normalized.slice(root.length);
@@ -7548,12 +7882,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
7548
7882
  while ((match = importRegex.exec(content)) !== null) {
7549
7883
  const importPath = match[1];
7550
7884
  if (!importPath.startsWith(".")) continue;
7551
- const dir = path8.dirname(current.file);
7552
- let resolved = path8.join(dir, importPath).replace(/\\/g, "/");
7885
+ const dir = path15.dirname(current.file);
7886
+ let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
7553
7887
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
7554
7888
  resolved += ".ts";
7555
7889
  }
7556
- resolved = path8.normalize(resolved).replace(/\\/g, "/");
7890
+ resolved = path15.normalize(resolved).replace(/\\/g, "/");
7557
7891
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
7558
7892
  queue.push({ file: resolved, depth: current.depth + 1 });
7559
7893
  }
@@ -7570,7 +7904,7 @@ async function validateFindings(options) {
7570
7904
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
7571
7905
  continue;
7572
7906
  }
7573
- const absoluteFile = path8.isAbsolute(finding.file) ? finding.file : path8.join(projectRoot, finding.file).replace(/\\/g, "/");
7907
+ const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
7574
7908
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
7575
7909
  continue;
7576
7910
  }
@@ -8091,6 +8425,8 @@ function parseFrontmatter(raw) {
8091
8425
  const versionStr = map.get("version");
8092
8426
  const lastSynced = map.get("last_synced");
8093
8427
  const lastManualEdit = map.get("last_manual_edit");
8428
+ const created = map.get("created");
8429
+ const updated = map.get("updated");
8094
8430
  if (!project || !versionStr || !lastSynced || !lastManualEdit) {
8095
8431
  return Err2(
8096
8432
  new Error(
@@ -8102,7 +8438,10 @@ function parseFrontmatter(raw) {
8102
8438
  if (isNaN(version)) {
8103
8439
  return Err2(new Error("Frontmatter version must be a number"));
8104
8440
  }
8105
- return Ok2({ project, version, lastSynced, lastManualEdit });
8441
+ const fm = { project, version, lastSynced, lastManualEdit };
8442
+ if (created) fm.created = created;
8443
+ if (updated) fm.updated = updated;
8444
+ return Ok2(fm);
8106
8445
  }
8107
8446
  function parseMilestones(body) {
8108
8447
  const milestones = [];
@@ -8110,12 +8449,12 @@ function parseMilestones(body) {
8110
8449
  const h2Matches = [];
8111
8450
  let match;
8112
8451
  while ((match = h2Pattern.exec(body)) !== null) {
8113
- h2Matches.push({ heading: match[1], startIndex: match.index });
8452
+ h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
8114
8453
  }
8115
8454
  for (let i = 0; i < h2Matches.length; i++) {
8116
8455
  const h2 = h2Matches[i];
8117
8456
  const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
8118
- const sectionBody = body.slice(h2.startIndex + h2.heading.length + 4, nextStart);
8457
+ const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
8119
8458
  const isBacklog = h2.heading === "Backlog";
8120
8459
  const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
8121
8460
  const featuresResult = parseFeatures(sectionBody);
@@ -8130,19 +8469,16 @@ function parseMilestones(body) {
8130
8469
  }
8131
8470
  function parseFeatures(sectionBody) {
8132
8471
  const features = [];
8133
- const h3Pattern = /^### Feature: (.+)$/gm;
8472
+ const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
8134
8473
  const h3Matches = [];
8135
8474
  let match;
8136
8475
  while ((match = h3Pattern.exec(sectionBody)) !== null) {
8137
- h3Matches.push({ name: match[1], startIndex: match.index });
8476
+ h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
8138
8477
  }
8139
8478
  for (let i = 0; i < h3Matches.length; i++) {
8140
8479
  const h3 = h3Matches[i];
8141
8480
  const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
8142
- const featureBody = sectionBody.slice(
8143
- h3.startIndex + `### Feature: ${h3.name}`.length,
8144
- nextStart
8145
- );
8481
+ const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
8146
8482
  const featureResult = parseFeatureFields(h3.name, featureBody);
8147
8483
  if (!featureResult.ok) return featureResult;
8148
8484
  features.push(featureResult.value);
@@ -8167,10 +8503,10 @@ function parseFeatureFields(name, body) {
8167
8503
  const status = statusRaw;
8168
8504
  const specRaw = fieldMap.get("Spec") ?? EM_DASH;
8169
8505
  const spec = specRaw === EM_DASH ? null : specRaw;
8170
- const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
8171
- const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
8172
- const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
8173
- const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
8506
+ const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
8507
+ const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
8508
+ const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
8509
+ const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
8174
8510
  const summary = fieldMap.get("Summary") ?? "";
8175
8511
  return Ok2({ name, status, spec, plans, blockedBy, summary });
8176
8512
  }
@@ -8182,11 +8518,17 @@ function serializeRoadmap(roadmap) {
8182
8518
  lines.push("---");
8183
8519
  lines.push(`project: ${roadmap.frontmatter.project}`);
8184
8520
  lines.push(`version: ${roadmap.frontmatter.version}`);
8521
+ if (roadmap.frontmatter.created) {
8522
+ lines.push(`created: ${roadmap.frontmatter.created}`);
8523
+ }
8524
+ if (roadmap.frontmatter.updated) {
8525
+ lines.push(`updated: ${roadmap.frontmatter.updated}`);
8526
+ }
8185
8527
  lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
8186
8528
  lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
8187
8529
  lines.push("---");
8188
8530
  lines.push("");
8189
- lines.push("# Project Roadmap");
8531
+ lines.push("# Roadmap");
8190
8532
  for (const milestone of roadmap.milestones) {
8191
8533
  lines.push("");
8192
8534
  lines.push(serializeMilestoneHeading(milestone));
@@ -8199,25 +8541,26 @@ function serializeRoadmap(roadmap) {
8199
8541
  return lines.join("\n");
8200
8542
  }
8201
8543
  function serializeMilestoneHeading(milestone) {
8202
- return milestone.isBacklog ? "## Backlog" : `## Milestone: ${milestone.name}`;
8544
+ return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
8203
8545
  }
8204
8546
  function serializeFeature(feature) {
8205
8547
  const spec = feature.spec ?? EM_DASH2;
8206
8548
  const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
8207
8549
  const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
8208
8550
  return [
8209
- `### Feature: ${feature.name}`,
8551
+ `### ${feature.name}`,
8552
+ "",
8210
8553
  `- **Status:** ${feature.status}`,
8211
8554
  `- **Spec:** ${spec}`,
8212
- `- **Plans:** ${plans}`,
8213
- `- **Blocked by:** ${blockedBy}`,
8214
- `- **Summary:** ${feature.summary}`
8555
+ `- **Summary:** ${feature.summary}`,
8556
+ `- **Blockers:** ${blockedBy}`,
8557
+ `- **Plan:** ${plans}`
8215
8558
  ];
8216
8559
  }
8217
8560
 
8218
8561
  // src/roadmap/sync.ts
8219
- import * as fs9 from "fs";
8220
- import * as path9 from "path";
8562
+ import * as fs16 from "fs";
8563
+ import * as path16 from "path";
8221
8564
  import { Ok as Ok3 } from "@harness-engineering/types";
8222
8565
  function inferStatus(feature, projectPath, allFeatures) {
8223
8566
  if (feature.blockedBy.length > 0) {
@@ -8232,10 +8575,10 @@ function inferStatus(feature, projectPath, allFeatures) {
8232
8575
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
8233
8576
  const useRootState = featuresWithPlans.length <= 1;
8234
8577
  if (useRootState) {
8235
- const rootStatePath = path9.join(projectPath, ".harness", "state.json");
8236
- if (fs9.existsSync(rootStatePath)) {
8578
+ const rootStatePath = path16.join(projectPath, ".harness", "state.json");
8579
+ if (fs16.existsSync(rootStatePath)) {
8237
8580
  try {
8238
- const raw = fs9.readFileSync(rootStatePath, "utf-8");
8581
+ const raw = fs16.readFileSync(rootStatePath, "utf-8");
8239
8582
  const state = JSON.parse(raw);
8240
8583
  if (state.progress) {
8241
8584
  for (const status of Object.values(state.progress)) {
@@ -8246,16 +8589,16 @@ function inferStatus(feature, projectPath, allFeatures) {
8246
8589
  }
8247
8590
  }
8248
8591
  }
8249
- const sessionsDir = path9.join(projectPath, ".harness", "sessions");
8250
- if (fs9.existsSync(sessionsDir)) {
8592
+ const sessionsDir = path16.join(projectPath, ".harness", "sessions");
8593
+ if (fs16.existsSync(sessionsDir)) {
8251
8594
  try {
8252
- const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
8595
+ const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
8253
8596
  for (const entry of sessionDirs) {
8254
8597
  if (!entry.isDirectory()) continue;
8255
- const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
8256
- if (!fs9.existsSync(autopilotPath)) continue;
8598
+ const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
8599
+ if (!fs16.existsSync(autopilotPath)) continue;
8257
8600
  try {
8258
- const raw = fs9.readFileSync(autopilotPath, "utf-8");
8601
+ const raw = fs16.readFileSync(autopilotPath, "utf-8");
8259
8602
  const autopilot = JSON.parse(raw);
8260
8603
  if (!autopilot.phases) continue;
8261
8604
  const linkedPhases = autopilot.phases.filter(
@@ -8335,17 +8678,17 @@ var EmitInteractionInputSchema = z6.object({
8335
8678
  });
8336
8679
 
8337
8680
  // src/blueprint/scanner.ts
8338
- import * as fs10 from "fs/promises";
8339
- import * as path10 from "path";
8681
+ import * as fs17 from "fs/promises";
8682
+ import * as path17 from "path";
8340
8683
  var ProjectScanner = class {
8341
8684
  constructor(rootDir) {
8342
8685
  this.rootDir = rootDir;
8343
8686
  }
8344
8687
  async scan() {
8345
- let projectName = path10.basename(this.rootDir);
8688
+ let projectName = path17.basename(this.rootDir);
8346
8689
  try {
8347
- const pkgPath = path10.join(this.rootDir, "package.json");
8348
- const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
8690
+ const pkgPath = path17.join(this.rootDir, "package.json");
8691
+ const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
8349
8692
  const pkg = JSON.parse(pkgRaw);
8350
8693
  if (pkg.name) projectName = pkg.name;
8351
8694
  } catch {
@@ -8386,8 +8729,8 @@ var ProjectScanner = class {
8386
8729
  };
8387
8730
 
8388
8731
  // src/blueprint/generator.ts
8389
- import * as fs11 from "fs/promises";
8390
- import * as path11 from "path";
8732
+ import * as fs18 from "fs/promises";
8733
+ import * as path18 from "path";
8391
8734
  import * as ejs from "ejs";
8392
8735
 
8393
8736
  // src/blueprint/templates.ts
@@ -8471,19 +8814,19 @@ var BlueprintGenerator = class {
8471
8814
  styles: STYLES,
8472
8815
  scripts: SCRIPTS
8473
8816
  });
8474
- await fs11.mkdir(options.outputDir, { recursive: true });
8475
- await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
8817
+ await fs18.mkdir(options.outputDir, { recursive: true });
8818
+ await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
8476
8819
  }
8477
8820
  };
8478
8821
 
8479
8822
  // src/update-checker.ts
8480
- import * as fs12 from "fs";
8481
- import * as path12 from "path";
8823
+ import * as fs19 from "fs";
8824
+ import * as path19 from "path";
8482
8825
  import * as os from "os";
8483
8826
  import { spawn } from "child_process";
8484
8827
  function getStatePath() {
8485
8828
  const home = process.env["HOME"] || os.homedir();
8486
- return path12.join(home, ".harness", "update-check.json");
8829
+ return path19.join(home, ".harness", "update-check.json");
8487
8830
  }
8488
8831
  function isUpdateCheckEnabled(configInterval) {
8489
8832
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -8496,7 +8839,7 @@ function shouldRunCheck(state, intervalMs) {
8496
8839
  }
8497
8840
  function readCheckState() {
8498
8841
  try {
8499
- const raw = fs12.readFileSync(getStatePath(), "utf-8");
8842
+ const raw = fs19.readFileSync(getStatePath(), "utf-8");
8500
8843
  const parsed = JSON.parse(raw);
8501
8844
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
8502
8845
  const state = parsed;
@@ -8513,7 +8856,7 @@ function readCheckState() {
8513
8856
  }
8514
8857
  function spawnBackgroundCheck(currentVersion) {
8515
8858
  const statePath = getStatePath();
8516
- const stateDir = path12.dirname(statePath);
8859
+ const stateDir = path19.dirname(statePath);
8517
8860
  const script = `
8518
8861
  const { execSync } = require('child_process');
8519
8862
  const fs = require('fs');
@@ -8644,6 +8987,7 @@ export {
8644
8987
  ViolationSchema,
8645
8988
  addProvenance,
8646
8989
  analyzeDiff,
8990
+ analyzeLearningPatterns,
8647
8991
  appendFailure,
8648
8992
  appendLearning,
8649
8993
  applyFixes,
@@ -8652,6 +8996,7 @@ export {
8652
8996
  archModule,
8653
8997
  architecture,
8654
8998
  archiveFailures,
8999
+ archiveLearnings,
8655
9000
  archiveStream,
8656
9001
  buildDependencyGraph,
8657
9002
  buildExclusionSet,
@@ -8659,6 +9004,8 @@ export {
8659
9004
  checkDocCoverage,
8660
9005
  checkEligibility,
8661
9006
  classifyFinding,
9007
+ clearFailuresCache,
9008
+ clearLearningsCache,
8662
9009
  configureFeedback,
8663
9010
  constraintRuleId,
8664
9011
  contextBudget,
@@ -8714,16 +9061,20 @@ export {
8714
9061
  injectionRules,
8715
9062
  isSmallSuggestion,
8716
9063
  isUpdateCheckEnabled,
9064
+ listActiveSessions,
8717
9065
  listStreams,
9066
+ loadBudgetedLearnings,
8718
9067
  loadFailures,
8719
9068
  loadHandoff,
8720
9069
  loadRelevantLearnings,
9070
+ loadSessionSummary,
8721
9071
  loadState,
8722
9072
  loadStreamIndex,
8723
9073
  logAgentAction,
8724
9074
  migrateToStreams,
8725
9075
  networkRules,
8726
9076
  nodeRules,
9077
+ parseDateFromEntry,
8727
9078
  parseDiff,
8728
9079
  parseManifest,
8729
9080
  parseRoadmap,
@@ -8731,6 +9082,7 @@ export {
8731
9082
  parseSize,
8732
9083
  pathTraversalRules,
8733
9084
  previewFix,
9085
+ pruneLearnings,
8734
9086
  reactRules,
8735
9087
  readCheckState,
8736
9088
  readLockfile,
@@ -8742,6 +9094,7 @@ export {
8742
9094
  resolveFileToLayer,
8743
9095
  resolveModelTier,
8744
9096
  resolveRuleSeverity,
9097
+ resolveSessionDir,
8745
9098
  resolveStreamPath,
8746
9099
  resolveThresholds,
8747
9100
  runAll,
@@ -8768,6 +9121,7 @@ export {
8768
9121
  syncRoadmap,
8769
9122
  touchStream,
8770
9123
  trackAction,
9124
+ updateSessionIndex,
8771
9125
  validateAgentsMap,
8772
9126
  validateBoundaries,
8773
9127
  validateCommitMessage,
@@ -8780,5 +9134,6 @@ export {
8780
9134
  violationId,
8781
9135
  writeConfig,
8782
9136
  writeLockfile,
9137
+ writeSessionSummary,
8783
9138
  xssRules
8784
9139
  };