@harness-engineering/core 0.11.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(
@@ -1140,8 +1134,6 @@ function deepMergeConstraints(localConfig, bundleConstraints, _existingContribut
1140
1134
  }
1141
1135
  if (bundleConstraints.architecture) {
1142
1136
  const localArch = localConfig.architecture ?? {
1143
- enabled: true,
1144
- baselinePath: ".harness/arch/baselines.json",
1145
1137
  thresholds: {},
1146
1138
  modules: {}
1147
1139
  };
@@ -1264,7 +1256,7 @@ async function readLockfile(lockfilePath) {
1264
1256
  return { ok: true, value: result.data };
1265
1257
  }
1266
1258
  async function writeLockfile(lockfilePath, lockfile) {
1267
- await writeConfig(lockfilePath, lockfile);
1259
+ return writeConfig(lockfilePath, lockfile);
1268
1260
  }
1269
1261
  function addProvenance(lockfile, packageName, entry) {
1270
1262
  return {
@@ -1293,6 +1285,77 @@ function isNodeError(err) {
1293
1285
  return err instanceof Error && "code" in err;
1294
1286
  }
1295
1287
 
1288
+ // src/constraints/sharing/remove.ts
1289
+ function removeContributions(config, contributions) {
1290
+ const result = { ...config };
1291
+ const layerNames = contributions.layers;
1292
+ if (layerNames && layerNames.length > 0 && Array.isArray(result.layers)) {
1293
+ const nameSet = new Set(layerNames);
1294
+ result.layers = result.layers.filter((l) => !nameSet.has(l.name));
1295
+ }
1296
+ const fromKeys = contributions.forbiddenImports;
1297
+ if (fromKeys && fromKeys.length > 0 && Array.isArray(result.forbiddenImports)) {
1298
+ const fromSet = new Set(fromKeys);
1299
+ result.forbiddenImports = result.forbiddenImports.filter(
1300
+ (r) => !fromSet.has(r.from)
1301
+ );
1302
+ }
1303
+ const boundarySchemas = contributions.boundaries;
1304
+ if (boundarySchemas && boundarySchemas.length > 0 && result.boundaries) {
1305
+ const boundaries = result.boundaries;
1306
+ if (boundaries.requireSchema) {
1307
+ const schemaSet = new Set(boundarySchemas);
1308
+ result.boundaries = {
1309
+ ...boundaries,
1310
+ requireSchema: boundaries.requireSchema.filter((s) => !schemaSet.has(s))
1311
+ };
1312
+ }
1313
+ }
1314
+ const thresholdKeys = contributions["architecture.thresholds"];
1315
+ if (thresholdKeys && thresholdKeys.length > 0 && result.architecture) {
1316
+ const arch = { ...result.architecture };
1317
+ const thresholds = { ...arch.thresholds };
1318
+ for (const key of thresholdKeys) {
1319
+ delete thresholds[key];
1320
+ }
1321
+ arch.thresholds = thresholds;
1322
+ result.architecture = arch;
1323
+ }
1324
+ const moduleKeys = contributions["architecture.modules"];
1325
+ if (moduleKeys && moduleKeys.length > 0 && result.architecture) {
1326
+ const arch = { ...result.architecture };
1327
+ const modules = { ...arch.modules };
1328
+ for (const key of moduleKeys) {
1329
+ const colonIdx = key.indexOf(":");
1330
+ if (colonIdx === -1) continue;
1331
+ const modulePath = key.substring(0, colonIdx);
1332
+ const category = key.substring(colonIdx + 1);
1333
+ if (modules[modulePath]) {
1334
+ const moduleCategories = { ...modules[modulePath] };
1335
+ delete moduleCategories[category];
1336
+ if (Object.keys(moduleCategories).length === 0) {
1337
+ delete modules[modulePath];
1338
+ } else {
1339
+ modules[modulePath] = moduleCategories;
1340
+ }
1341
+ }
1342
+ }
1343
+ arch.modules = modules;
1344
+ result.architecture = arch;
1345
+ }
1346
+ const ruleIds = contributions["security.rules"];
1347
+ if (ruleIds && ruleIds.length > 0 && result.security) {
1348
+ const security = { ...result.security };
1349
+ const rules = { ...security.rules };
1350
+ for (const id of ruleIds) {
1351
+ delete rules[id];
1352
+ }
1353
+ security.rules = rules;
1354
+ result.security = security;
1355
+ }
1356
+ return result;
1357
+ }
1358
+
1296
1359
  // src/shared/parsers/typescript.ts
1297
1360
  import { parse } from "@typescript-eslint/typescript-estree";
1298
1361
 
@@ -1318,11 +1381,11 @@ function walk(node, visitor) {
1318
1381
  var TypeScriptParser = class {
1319
1382
  name = "typescript";
1320
1383
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1321
- async parseFile(path13) {
1322
- const contentResult = await readFileContent(path13);
1384
+ async parseFile(path20) {
1385
+ const contentResult = await readFileContent(path20);
1323
1386
  if (!contentResult.ok) {
1324
1387
  return Err(
1325
- createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
1388
+ createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
1326
1389
  "Check that the file exists",
1327
1390
  "Verify the path is correct"
1328
1391
  ])
@@ -1332,7 +1395,7 @@ var TypeScriptParser = class {
1332
1395
  const ast = parse(contentResult.value, {
1333
1396
  loc: true,
1334
1397
  range: true,
1335
- jsx: path13.endsWith(".tsx"),
1398
+ jsx: path20.endsWith(".tsx"),
1336
1399
  errorOnUnknownASTType: false
1337
1400
  });
1338
1401
  return Ok({
@@ -1343,7 +1406,7 @@ var TypeScriptParser = class {
1343
1406
  } catch (e) {
1344
1407
  const error = e;
1345
1408
  return Err(
1346
- createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
1409
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
1347
1410
  "Check for syntax errors in the file",
1348
1411
  "Ensure valid TypeScript syntax"
1349
1412
  ])
@@ -1627,22 +1690,22 @@ function extractInlineRefs(content) {
1627
1690
  }
1628
1691
  return refs;
1629
1692
  }
1630
- async function parseDocumentationFile(path13) {
1631
- const contentResult = await readFileContent(path13);
1693
+ async function parseDocumentationFile(path20) {
1694
+ const contentResult = await readFileContent(path20);
1632
1695
  if (!contentResult.ok) {
1633
1696
  return Err(
1634
1697
  createEntropyError(
1635
1698
  "PARSE_ERROR",
1636
- `Failed to read documentation file: ${path13}`,
1637
- { file: path13 },
1699
+ `Failed to read documentation file: ${path20}`,
1700
+ { file: path20 },
1638
1701
  ["Check that the file exists"]
1639
1702
  )
1640
1703
  );
1641
1704
  }
1642
1705
  const content = contentResult.value;
1643
- const type = path13.endsWith(".md") ? "markdown" : "text";
1706
+ const type = path20.endsWith(".md") ? "markdown" : "text";
1644
1707
  return Ok({
1645
- path: path13,
1708
+ path: path20,
1646
1709
  type,
1647
1710
  content,
1648
1711
  codeBlocks: extractCodeBlocks(content),
@@ -2453,15 +2516,34 @@ async function detectPatternViolations(snapshot, config) {
2453
2516
  }
2454
2517
  }
2455
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
+ }
2456
2536
  const errorCount = violations.filter((v) => v.severity === "error").length;
2457
2537
  const warningCount = violations.filter((v) => v.severity === "warning").length;
2458
- const totalChecks = snapshot.files.length * patterns.length;
2459
- 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;
2460
2542
  return Ok({
2461
2543
  violations,
2462
2544
  stats: {
2463
2545
  filesChecked: snapshot.files.length,
2464
- patternsApplied: patterns.length,
2546
+ patternsApplied: allPatternsCount,
2465
2547
  violationCount: violations.length,
2466
2548
  errorCount,
2467
2549
  warningCount
@@ -4644,10 +4726,13 @@ var DEFAULT_STATE = {
4644
4726
  progress: {}
4645
4727
  };
4646
4728
 
4647
- // src/state/state-manager.ts
4648
- import * as fs6 from "fs";
4649
- import * as path3 from "path";
4650
- 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";
4651
4736
 
4652
4737
  // src/state/stream-resolver.ts
4653
4738
  import * as fs5 from "fs";
@@ -4673,10 +4758,20 @@ var DEFAULT_STREAM_INDEX = {
4673
4758
  streams: {}
4674
4759
  };
4675
4760
 
4676
- // src/state/stream-resolver.ts
4761
+ // src/state/constants.ts
4677
4762
  var HARNESS_DIR = ".harness";
4678
- 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";
4679
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";
4680
4775
  var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
4681
4776
  function streamsDir(projectPath) {
4682
4777
  return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
@@ -4903,26 +4998,65 @@ async function migrateToStreams(projectPath) {
4903
4998
  return saveStreamIndex(projectPath, index);
4904
4999
  }
4905
5000
 
4906
- // src/state/state-manager.ts
4907
- var HARNESS_DIR2 = ".harness";
4908
- var STATE_FILE = "state.json";
4909
- var LEARNINGS_FILE = "learnings.md";
4910
- var FAILURES_FILE = "failures.md";
4911
- var HANDOFF_FILE = "handoff.json";
4912
- var GATE_CONFIG_FILE = "gate.json";
4913
- 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
4914
5046
  var MAX_CACHE_ENTRIES = 8;
4915
- var learningsCacheMap = /* @__PURE__ */ new Map();
4916
- var failuresCacheMap = /* @__PURE__ */ new Map();
4917
5047
  function evictIfNeeded(map) {
4918
5048
  if (map.size > MAX_CACHE_ENTRIES) {
4919
5049
  const oldest = map.keys().next().value;
4920
5050
  if (oldest !== void 0) map.delete(oldest);
4921
5051
  }
4922
5052
  }
4923
- async function getStateDir(projectPath, stream) {
4924
- const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
4925
- 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);
4926
5060
  if (stream || hasStreams) {
4927
5061
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
4928
5062
  if (result.ok) {
@@ -4932,18 +5066,20 @@ async function getStateDir(projectPath, stream) {
4932
5066
  return result;
4933
5067
  }
4934
5068
  }
4935
- return Ok(path3.join(projectPath, HARNESS_DIR2));
5069
+ return Ok(path4.join(projectPath, HARNESS_DIR));
4936
5070
  }
4937
- async function loadState(projectPath, stream) {
5071
+
5072
+ // src/state/state-persistence.ts
5073
+ async function loadState(projectPath, stream, session) {
4938
5074
  try {
4939
- const dirResult = await getStateDir(projectPath, stream);
5075
+ const dirResult = await getStateDir(projectPath, stream, session);
4940
5076
  if (!dirResult.ok) return dirResult;
4941
5077
  const stateDir = dirResult.value;
4942
- const statePath = path3.join(stateDir, STATE_FILE);
4943
- if (!fs6.existsSync(statePath)) {
5078
+ const statePath = path5.join(stateDir, STATE_FILE);
5079
+ if (!fs8.existsSync(statePath)) {
4944
5080
  return Ok({ ...DEFAULT_STATE });
4945
5081
  }
4946
- const raw = fs6.readFileSync(statePath, "utf-8");
5082
+ const raw = fs8.readFileSync(statePath, "utf-8");
4947
5083
  const parsed = JSON.parse(raw);
4948
5084
  const result = HarnessStateSchema.safeParse(parsed);
4949
5085
  if (!result.success) {
@@ -4956,14 +5092,14 @@ async function loadState(projectPath, stream) {
4956
5092
  );
4957
5093
  }
4958
5094
  }
4959
- async function saveState(projectPath, state, stream) {
5095
+ async function saveState(projectPath, state, stream, session) {
4960
5096
  try {
4961
- const dirResult = await getStateDir(projectPath, stream);
5097
+ const dirResult = await getStateDir(projectPath, stream, session);
4962
5098
  if (!dirResult.ok) return dirResult;
4963
5099
  const stateDir = dirResult.value;
4964
- const statePath = path3.join(stateDir, STATE_FILE);
4965
- fs6.mkdirSync(stateDir, { recursive: true });
4966
- 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));
4967
5103
  return Ok(void 0);
4968
5104
  } catch (error) {
4969
5105
  return Err(
@@ -4971,13 +5107,21 @@ async function saveState(projectPath, state, stream) {
4971
5107
  );
4972
5108
  }
4973
5109
  }
4974
- 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) {
4975
5119
  try {
4976
- const dirResult = await getStateDir(projectPath, stream);
5120
+ const dirResult = await getStateDir(projectPath, stream, session);
4977
5121
  if (!dirResult.ok) return dirResult;
4978
5122
  const stateDir = dirResult.value;
4979
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
4980
- fs6.mkdirSync(stateDir, { recursive: true });
5123
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
5124
+ fs9.mkdirSync(stateDir, { recursive: true });
4981
5125
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4982
5126
  let entry;
4983
5127
  if (skillName && outcome) {
@@ -4993,11 +5137,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
4993
5137
  - **${timestamp}:** ${learning}
4994
5138
  `;
4995
5139
  }
4996
- if (!fs6.existsSync(learningsPath)) {
4997
- fs6.writeFileSync(learningsPath, `# Learnings
5140
+ if (!fs9.existsSync(learningsPath)) {
5141
+ fs9.writeFileSync(learningsPath, `# Learnings
4998
5142
  ${entry}`);
4999
5143
  } else {
5000
- fs6.appendFileSync(learningsPath, entry);
5144
+ fs9.appendFileSync(learningsPath, entry);
5001
5145
  }
5002
5146
  learningsCacheMap.delete(learningsPath);
5003
5147
  return Ok(void 0);
@@ -5009,23 +5153,92 @@ ${entry}`);
5009
5153
  );
5010
5154
  }
5011
5155
  }
5012
- 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) {
5013
5226
  try {
5014
- const dirResult = await getStateDir(projectPath, stream);
5227
+ const dirResult = await getStateDir(projectPath, stream, session);
5015
5228
  if (!dirResult.ok) return dirResult;
5016
5229
  const stateDir = dirResult.value;
5017
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
5018
- if (!fs6.existsSync(learningsPath)) {
5230
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
5231
+ if (!fs9.existsSync(learningsPath)) {
5019
5232
  return Ok([]);
5020
5233
  }
5021
- const stats = fs6.statSync(learningsPath);
5234
+ const stats = fs9.statSync(learningsPath);
5022
5235
  const cacheKey = learningsPath;
5023
5236
  const cached = learningsCacheMap.get(cacheKey);
5024
5237
  let entries;
5025
5238
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5026
5239
  entries = cached.entries;
5027
5240
  } else {
5028
- const content = fs6.readFileSync(learningsPath, "utf-8");
5241
+ const content = fs9.readFileSync(learningsPath, "utf-8");
5029
5242
  const lines = content.split("\n");
5030
5243
  entries = [];
5031
5244
  let currentBlock = [];
@@ -5061,23 +5274,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
5061
5274
  );
5062
5275
  }
5063
5276
  }
5064
- var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
5065
- async function appendFailure(projectPath, description, skillName, type, stream) {
5277
+ async function archiveLearnings(projectPath, entries, stream) {
5066
5278
  try {
5067
5279
  const dirResult = await getStateDir(projectPath, stream);
5068
5280
  if (!dirResult.ok) return dirResult;
5069
5281
  const stateDir = dirResult.value;
5070
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5071
- fs6.mkdirSync(stateDir, { recursive: true });
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) {
5305
+ try {
5306
+ const dirResult = await getStateDir(projectPath, stream);
5307
+ if (!dirResult.ok) return dirResult;
5308
+ const stateDir = dirResult.value;
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 });
5072
5372
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5073
5373
  const entry = `
5074
5374
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
5075
5375
  `;
5076
- if (!fs6.existsSync(failuresPath)) {
5077
- fs6.writeFileSync(failuresPath, `# Failures
5376
+ if (!fs10.existsSync(failuresPath)) {
5377
+ fs10.writeFileSync(failuresPath, `# Failures
5078
5378
  ${entry}`);
5079
5379
  } else {
5080
- fs6.appendFileSync(failuresPath, entry);
5380
+ fs10.appendFileSync(failuresPath, entry);
5081
5381
  }
5082
5382
  failuresCacheMap.delete(failuresPath);
5083
5383
  return Ok(void 0);
@@ -5089,22 +5389,22 @@ ${entry}`);
5089
5389
  );
5090
5390
  }
5091
5391
  }
5092
- async function loadFailures(projectPath, stream) {
5392
+ async function loadFailures(projectPath, stream, session) {
5093
5393
  try {
5094
- const dirResult = await getStateDir(projectPath, stream);
5394
+ const dirResult = await getStateDir(projectPath, stream, session);
5095
5395
  if (!dirResult.ok) return dirResult;
5096
5396
  const stateDir = dirResult.value;
5097
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5098
- if (!fs6.existsSync(failuresPath)) {
5397
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
5398
+ if (!fs10.existsSync(failuresPath)) {
5099
5399
  return Ok([]);
5100
5400
  }
5101
- const stats = fs6.statSync(failuresPath);
5401
+ const stats = fs10.statSync(failuresPath);
5102
5402
  const cacheKey = failuresPath;
5103
5403
  const cached = failuresCacheMap.get(cacheKey);
5104
5404
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5105
5405
  return Ok(cached.entries);
5106
5406
  }
5107
- const content = fs6.readFileSync(failuresPath, "utf-8");
5407
+ const content = fs10.readFileSync(failuresPath, "utf-8");
5108
5408
  const entries = [];
5109
5409
  for (const line of content.split("\n")) {
5110
5410
  const match = line.match(FAILURE_LINE_REGEX);
@@ -5128,25 +5428,25 @@ async function loadFailures(projectPath, stream) {
5128
5428
  );
5129
5429
  }
5130
5430
  }
5131
- async function archiveFailures(projectPath, stream) {
5431
+ async function archiveFailures(projectPath, stream, session) {
5132
5432
  try {
5133
- const dirResult = await getStateDir(projectPath, stream);
5433
+ const dirResult = await getStateDir(projectPath, stream, session);
5134
5434
  if (!dirResult.ok) return dirResult;
5135
5435
  const stateDir = dirResult.value;
5136
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5137
- if (!fs6.existsSync(failuresPath)) {
5436
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
5437
+ if (!fs10.existsSync(failuresPath)) {
5138
5438
  return Ok(void 0);
5139
5439
  }
5140
- const archiveDir = path3.join(stateDir, "archive");
5141
- fs6.mkdirSync(archiveDir, { recursive: true });
5440
+ const archiveDir = path7.join(stateDir, "archive");
5441
+ fs10.mkdirSync(archiveDir, { recursive: true });
5142
5442
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5143
5443
  let archiveName = `failures-${date}.md`;
5144
5444
  let counter = 2;
5145
- while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
5445
+ while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
5146
5446
  archiveName = `failures-${date}-${counter}.md`;
5147
5447
  counter++;
5148
5448
  }
5149
- fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5449
+ fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
5150
5450
  failuresCacheMap.delete(failuresPath);
5151
5451
  return Ok(void 0);
5152
5452
  } catch (error) {
@@ -5157,14 +5457,18 @@ async function archiveFailures(projectPath, stream) {
5157
5457
  );
5158
5458
  }
5159
5459
  }
5160
- 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) {
5161
5465
  try {
5162
- const dirResult = await getStateDir(projectPath, stream);
5466
+ const dirResult = await getStateDir(projectPath, stream, session);
5163
5467
  if (!dirResult.ok) return dirResult;
5164
5468
  const stateDir = dirResult.value;
5165
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5166
- fs6.mkdirSync(stateDir, { recursive: true });
5167
- 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));
5168
5472
  return Ok(void 0);
5169
5473
  } catch (error) {
5170
5474
  return Err(
@@ -5172,16 +5476,16 @@ async function saveHandoff(projectPath, handoff, stream) {
5172
5476
  );
5173
5477
  }
5174
5478
  }
5175
- async function loadHandoff(projectPath, stream) {
5479
+ async function loadHandoff(projectPath, stream, session) {
5176
5480
  try {
5177
- const dirResult = await getStateDir(projectPath, stream);
5481
+ const dirResult = await getStateDir(projectPath, stream, session);
5178
5482
  if (!dirResult.ok) return dirResult;
5179
5483
  const stateDir = dirResult.value;
5180
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5181
- if (!fs6.existsSync(handoffPath)) {
5484
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
5485
+ if (!fs11.existsSync(handoffPath)) {
5182
5486
  return Ok(null);
5183
5487
  }
5184
- const raw = fs6.readFileSync(handoffPath, "utf-8");
5488
+ const raw = fs11.readFileSync(handoffPath, "utf-8");
5185
5489
  const parsed = JSON.parse(raw);
5186
5490
  const result = HandoffSchema.safeParse(parsed);
5187
5491
  if (!result.success) {
@@ -5194,73 +5498,82 @@ async function loadHandoff(projectPath, stream) {
5194
5498
  );
5195
5499
  }
5196
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
+ }
5197
5568
  async function runMechanicalGate(projectPath) {
5198
- const harnessDir = path3.join(projectPath, HARNESS_DIR2);
5199
- 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);
5200
5571
  try {
5201
- let checks = [];
5202
- if (fs6.existsSync(gateConfigPath)) {
5203
- const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
5204
- const config = GateConfigSchema.safeParse(raw);
5205
- if (config.success && config.data.checks) {
5206
- checks = config.data.checks;
5207
- }
5208
- }
5572
+ let checks = loadChecksFromConfig(gateConfigPath);
5209
5573
  if (checks.length === 0) {
5210
- const packageJsonPath = path3.join(projectPath, "package.json");
5211
- if (fs6.existsSync(packageJsonPath)) {
5212
- const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
5213
- const scripts = pkg.scripts || {};
5214
- if (scripts.test) checks.push({ name: "test", command: "npm test" });
5215
- if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
5216
- if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
5217
- if (scripts.build) checks.push({ name: "build", command: "npm run build" });
5218
- }
5219
- if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
5220
- checks.push({ name: "test", command: "go test ./..." });
5221
- checks.push({ name: "build", command: "go build ./..." });
5222
- }
5223
- if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
5224
- checks.push({ name: "test", command: "python -m pytest" });
5225
- }
5226
- }
5227
- const results = [];
5228
- 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:.-]+$/;
5229
- for (const check of checks) {
5230
- if (!SAFE_GATE_COMMAND.test(check.command)) {
5231
- results.push({
5232
- name: check.name,
5233
- passed: false,
5234
- command: check.command,
5235
- output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
5236
- duration: 0
5237
- });
5238
- continue;
5239
- }
5240
- const start = Date.now();
5241
- try {
5242
- execSync2(check.command, {
5243
- cwd: projectPath,
5244
- stdio: "pipe",
5245
- timeout: 12e4
5246
- });
5247
- results.push({
5248
- name: check.name,
5249
- passed: true,
5250
- command: check.command,
5251
- duration: Date.now() - start
5252
- });
5253
- } catch (error) {
5254
- const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
5255
- results.push({
5256
- name: check.name,
5257
- passed: false,
5258
- command: check.command,
5259
- output: output.slice(0, 2e3),
5260
- duration: Date.now() - start
5261
- });
5262
- }
5574
+ checks = discoverChecksFromProject(projectPath);
5263
5575
  }
5576
+ const results = checks.map((check) => executeCheck(check, projectPath));
5264
5577
  return Ok({
5265
5578
  passed: results.length === 0 || results.every((r) => r.passed),
5266
5579
  checks: results
@@ -5274,6 +5587,96 @@ async function runMechanicalGate(projectPath) {
5274
5587
  }
5275
5588
  }
5276
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
+
5277
5680
  // src/workflow/runner.ts
5278
5681
  async function executeWorkflow(workflow, executor) {
5279
5682
  const stepResults = [];
@@ -5423,7 +5826,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
5423
5826
  }
5424
5827
 
5425
5828
  // src/security/scanner.ts
5426
- import * as fs8 from "fs/promises";
5829
+ import * as fs15 from "fs/promises";
5427
5830
 
5428
5831
  // src/security/rules/registry.ts
5429
5832
  var RuleRegistry = class {
@@ -5510,15 +5913,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
5510
5913
  }
5511
5914
 
5512
5915
  // src/security/stack-detector.ts
5513
- import * as fs7 from "fs";
5514
- import * as path4 from "path";
5916
+ import * as fs14 from "fs";
5917
+ import * as path11 from "path";
5515
5918
  function detectStack(projectRoot) {
5516
5919
  const stacks = [];
5517
- const pkgJsonPath = path4.join(projectRoot, "package.json");
5518
- if (fs7.existsSync(pkgJsonPath)) {
5920
+ const pkgJsonPath = path11.join(projectRoot, "package.json");
5921
+ if (fs14.existsSync(pkgJsonPath)) {
5519
5922
  stacks.push("node");
5520
5923
  try {
5521
- const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5924
+ const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
5522
5925
  const allDeps = {
5523
5926
  ...pkgJson.dependencies,
5524
5927
  ...pkgJson.devDependencies
@@ -5533,13 +5936,13 @@ function detectStack(projectRoot) {
5533
5936
  } catch {
5534
5937
  }
5535
5938
  }
5536
- const goModPath = path4.join(projectRoot, "go.mod");
5537
- if (fs7.existsSync(goModPath)) {
5939
+ const goModPath = path11.join(projectRoot, "go.mod");
5940
+ if (fs14.existsSync(goModPath)) {
5538
5941
  stacks.push("go");
5539
5942
  }
5540
- const requirementsPath = path4.join(projectRoot, "requirements.txt");
5541
- const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
5542
- 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)) {
5543
5946
  stacks.push("python");
5544
5947
  }
5545
5948
  return stacks;
@@ -5966,7 +6369,7 @@ var SecurityScanner = class {
5966
6369
  }
5967
6370
  async scanFile(filePath) {
5968
6371
  if (!this.config.enabled) return [];
5969
- const content = await fs8.readFile(filePath, "utf-8");
6372
+ const content = await fs15.readFile(filePath, "utf-8");
5970
6373
  return this.scanContent(content, filePath, 1);
5971
6374
  }
5972
6375
  async scanFiles(filePaths) {
@@ -5991,7 +6394,7 @@ var SecurityScanner = class {
5991
6394
  };
5992
6395
 
5993
6396
  // src/ci/check-orchestrator.ts
5994
- import * as path5 from "path";
6397
+ import * as path12 from "path";
5995
6398
  var ALL_CHECKS = [
5996
6399
  "validate",
5997
6400
  "deps",
@@ -6008,7 +6411,7 @@ async function runSingleCheck(name, projectRoot, config) {
6008
6411
  try {
6009
6412
  switch (name) {
6010
6413
  case "validate": {
6011
- const agentsPath = path5.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6414
+ const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6012
6415
  const result = await validateAgentsMap(agentsPath);
6013
6416
  if (!result.ok) {
6014
6417
  issues.push({ severity: "error", message: result.error.message });
@@ -6063,7 +6466,7 @@ async function runSingleCheck(name, projectRoot, config) {
6063
6466
  break;
6064
6467
  }
6065
6468
  case "docs": {
6066
- const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
6469
+ const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
6067
6470
  const entropyConfig = config.entropy || {};
6068
6471
  const result = await checkDocCoverage("project", {
6069
6472
  docsDir,
@@ -6301,7 +6704,7 @@ async function runCIChecks(input) {
6301
6704
  }
6302
6705
 
6303
6706
  // src/review/mechanical-checks.ts
6304
- import * as path6 from "path";
6707
+ import * as path13 from "path";
6305
6708
  async function runMechanicalChecks(options) {
6306
6709
  const { projectRoot, config, skip = [], changedFiles } = options;
6307
6710
  const findings = [];
@@ -6313,7 +6716,7 @@ async function runMechanicalChecks(options) {
6313
6716
  };
6314
6717
  if (!skip.includes("validate")) {
6315
6718
  try {
6316
- const agentsPath = path6.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6719
+ const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6317
6720
  const result = await validateAgentsMap(agentsPath);
6318
6721
  if (!result.ok) {
6319
6722
  statuses.validate = "fail";
@@ -6350,7 +6753,7 @@ async function runMechanicalChecks(options) {
6350
6753
  statuses.validate = "fail";
6351
6754
  findings.push({
6352
6755
  tool: "validate",
6353
- file: path6.join(projectRoot, "AGENTS.md"),
6756
+ file: path13.join(projectRoot, "AGENTS.md"),
6354
6757
  message: err instanceof Error ? err.message : String(err),
6355
6758
  severity: "error"
6356
6759
  });
@@ -6414,7 +6817,7 @@ async function runMechanicalChecks(options) {
6414
6817
  (async () => {
6415
6818
  const localFindings = [];
6416
6819
  try {
6417
- const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
6820
+ const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
6418
6821
  const result = await checkDocCoverage("project", { docsDir });
6419
6822
  if (!result.ok) {
6420
6823
  statuses["check-docs"] = "warn";
@@ -6441,7 +6844,7 @@ async function runMechanicalChecks(options) {
6441
6844
  statuses["check-docs"] = "warn";
6442
6845
  localFindings.push({
6443
6846
  tool: "check-docs",
6444
- file: path6.join(projectRoot, "docs"),
6847
+ file: path13.join(projectRoot, "docs"),
6445
6848
  message: err instanceof Error ? err.message : String(err),
6446
6849
  severity: "warning"
6447
6850
  });
@@ -6589,7 +6992,7 @@ function detectChangeType(commitMessage, diff2) {
6589
6992
  }
6590
6993
 
6591
6994
  // src/review/context-scoper.ts
6592
- import * as path7 from "path";
6995
+ import * as path14 from "path";
6593
6996
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
6594
6997
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
6595
6998
  function computeContextBudget(diffLines) {
@@ -6597,18 +7000,18 @@ function computeContextBudget(diffLines) {
6597
7000
  return diffLines;
6598
7001
  }
6599
7002
  function isWithinProject(absPath, projectRoot) {
6600
- const resolvedRoot = path7.resolve(projectRoot) + path7.sep;
6601
- const resolvedPath = path7.resolve(absPath);
6602
- 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);
6603
7006
  }
6604
7007
  async function readContextFile(projectRoot, filePath, reason) {
6605
- const absPath = path7.isAbsolute(filePath) ? filePath : path7.join(projectRoot, filePath);
7008
+ const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
6606
7009
  if (!isWithinProject(absPath, projectRoot)) return null;
6607
7010
  const result = await readFileContent(absPath);
6608
7011
  if (!result.ok) return null;
6609
7012
  const content = result.value;
6610
7013
  const lines = content.split("\n").length;
6611
- const relPath = path7.isAbsolute(filePath) ? path7.relative(projectRoot, filePath) : filePath;
7014
+ const relPath = path14.isAbsolute(filePath) ? path14.relative(projectRoot, filePath) : filePath;
6612
7015
  return { path: relPath, content, reason, lines };
6613
7016
  }
6614
7017
  function extractImportSources(content) {
@@ -6623,18 +7026,18 @@ function extractImportSources(content) {
6623
7026
  }
6624
7027
  async function resolveImportPath(projectRoot, fromFile, importSource) {
6625
7028
  if (!importSource.startsWith(".")) return null;
6626
- const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
6627
- const basePath = path7.resolve(fromDir, importSource);
7029
+ const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
7030
+ const basePath = path14.resolve(fromDir, importSource);
6628
7031
  if (!isWithinProject(basePath, projectRoot)) return null;
6629
- const relBase = path7.relative(projectRoot, basePath);
7032
+ const relBase = path14.relative(projectRoot, basePath);
6630
7033
  const candidates = [
6631
7034
  relBase + ".ts",
6632
7035
  relBase + ".tsx",
6633
7036
  relBase + ".mts",
6634
- path7.join(relBase, "index.ts")
7037
+ path14.join(relBase, "index.ts")
6635
7038
  ];
6636
7039
  for (const candidate of candidates) {
6637
- const absCandidate = path7.join(projectRoot, candidate);
7040
+ const absCandidate = path14.join(projectRoot, candidate);
6638
7041
  if (await fileExists(absCandidate)) {
6639
7042
  return candidate;
6640
7043
  }
@@ -6642,10 +7045,10 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
6642
7045
  return null;
6643
7046
  }
6644
7047
  async function findTestFiles(projectRoot, sourceFile) {
6645
- const baseName = path7.basename(sourceFile, path7.extname(sourceFile));
7048
+ const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
6646
7049
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
6647
7050
  const results = await findFiles(pattern, projectRoot);
6648
- return results.map((f) => path7.relative(projectRoot, f));
7051
+ return results.map((f) => path14.relative(projectRoot, f));
6649
7052
  }
6650
7053
  async function gatherImportContext(projectRoot, changedFiles, budget) {
6651
7054
  const contextFiles = [];
@@ -7433,7 +7836,7 @@ async function fanOutReview(options) {
7433
7836
  }
7434
7837
 
7435
7838
  // src/review/validate-findings.ts
7436
- import * as path8 from "path";
7839
+ import * as path15 from "path";
7437
7840
  var DOWNGRADE_MAP = {
7438
7841
  critical: "important",
7439
7842
  important: "suggestion",
@@ -7454,7 +7857,7 @@ function normalizePath(filePath, projectRoot) {
7454
7857
  let normalized = filePath;
7455
7858
  normalized = normalized.replace(/\\/g, "/");
7456
7859
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
7457
- if (path8.isAbsolute(normalized)) {
7860
+ if (path15.isAbsolute(normalized)) {
7458
7861
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
7459
7862
  if (normalized.startsWith(root)) {
7460
7863
  normalized = normalized.slice(root.length);
@@ -7479,12 +7882,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
7479
7882
  while ((match = importRegex.exec(content)) !== null) {
7480
7883
  const importPath = match[1];
7481
7884
  if (!importPath.startsWith(".")) continue;
7482
- const dir = path8.dirname(current.file);
7483
- let resolved = path8.join(dir, importPath).replace(/\\/g, "/");
7885
+ const dir = path15.dirname(current.file);
7886
+ let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
7484
7887
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
7485
7888
  resolved += ".ts";
7486
7889
  }
7487
- resolved = path8.normalize(resolved).replace(/\\/g, "/");
7890
+ resolved = path15.normalize(resolved).replace(/\\/g, "/");
7488
7891
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
7489
7892
  queue.push({ file: resolved, depth: current.depth + 1 });
7490
7893
  }
@@ -7501,7 +7904,7 @@ async function validateFindings(options) {
7501
7904
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
7502
7905
  continue;
7503
7906
  }
7504
- 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, "/");
7505
7908
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
7506
7909
  continue;
7507
7910
  }
@@ -8022,6 +8425,8 @@ function parseFrontmatter(raw) {
8022
8425
  const versionStr = map.get("version");
8023
8426
  const lastSynced = map.get("last_synced");
8024
8427
  const lastManualEdit = map.get("last_manual_edit");
8428
+ const created = map.get("created");
8429
+ const updated = map.get("updated");
8025
8430
  if (!project || !versionStr || !lastSynced || !lastManualEdit) {
8026
8431
  return Err2(
8027
8432
  new Error(
@@ -8033,7 +8438,10 @@ function parseFrontmatter(raw) {
8033
8438
  if (isNaN(version)) {
8034
8439
  return Err2(new Error("Frontmatter version must be a number"));
8035
8440
  }
8036
- 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);
8037
8445
  }
8038
8446
  function parseMilestones(body) {
8039
8447
  const milestones = [];
@@ -8041,12 +8449,12 @@ function parseMilestones(body) {
8041
8449
  const h2Matches = [];
8042
8450
  let match;
8043
8451
  while ((match = h2Pattern.exec(body)) !== null) {
8044
- h2Matches.push({ heading: match[1], startIndex: match.index });
8452
+ h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
8045
8453
  }
8046
8454
  for (let i = 0; i < h2Matches.length; i++) {
8047
8455
  const h2 = h2Matches[i];
8048
8456
  const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
8049
- const sectionBody = body.slice(h2.startIndex + h2.heading.length + 4, nextStart);
8457
+ const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
8050
8458
  const isBacklog = h2.heading === "Backlog";
8051
8459
  const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
8052
8460
  const featuresResult = parseFeatures(sectionBody);
@@ -8061,19 +8469,16 @@ function parseMilestones(body) {
8061
8469
  }
8062
8470
  function parseFeatures(sectionBody) {
8063
8471
  const features = [];
8064
- const h3Pattern = /^### Feature: (.+)$/gm;
8472
+ const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
8065
8473
  const h3Matches = [];
8066
8474
  let match;
8067
8475
  while ((match = h3Pattern.exec(sectionBody)) !== null) {
8068
- h3Matches.push({ name: match[1], startIndex: match.index });
8476
+ h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
8069
8477
  }
8070
8478
  for (let i = 0; i < h3Matches.length; i++) {
8071
8479
  const h3 = h3Matches[i];
8072
8480
  const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
8073
- const featureBody = sectionBody.slice(
8074
- h3.startIndex + `### Feature: ${h3.name}`.length,
8075
- nextStart
8076
- );
8481
+ const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
8077
8482
  const featureResult = parseFeatureFields(h3.name, featureBody);
8078
8483
  if (!featureResult.ok) return featureResult;
8079
8484
  features.push(featureResult.value);
@@ -8098,10 +8503,10 @@ function parseFeatureFields(name, body) {
8098
8503
  const status = statusRaw;
8099
8504
  const specRaw = fieldMap.get("Spec") ?? EM_DASH;
8100
8505
  const spec = specRaw === EM_DASH ? null : specRaw;
8101
- const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
8102
- const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
8103
- const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
8104
- 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());
8105
8510
  const summary = fieldMap.get("Summary") ?? "";
8106
8511
  return Ok2({ name, status, spec, plans, blockedBy, summary });
8107
8512
  }
@@ -8113,11 +8518,17 @@ function serializeRoadmap(roadmap) {
8113
8518
  lines.push("---");
8114
8519
  lines.push(`project: ${roadmap.frontmatter.project}`);
8115
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
+ }
8116
8527
  lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
8117
8528
  lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
8118
8529
  lines.push("---");
8119
8530
  lines.push("");
8120
- lines.push("# Project Roadmap");
8531
+ lines.push("# Roadmap");
8121
8532
  for (const milestone of roadmap.milestones) {
8122
8533
  lines.push("");
8123
8534
  lines.push(serializeMilestoneHeading(milestone));
@@ -8130,25 +8541,26 @@ function serializeRoadmap(roadmap) {
8130
8541
  return lines.join("\n");
8131
8542
  }
8132
8543
  function serializeMilestoneHeading(milestone) {
8133
- return milestone.isBacklog ? "## Backlog" : `## Milestone: ${milestone.name}`;
8544
+ return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
8134
8545
  }
8135
8546
  function serializeFeature(feature) {
8136
8547
  const spec = feature.spec ?? EM_DASH2;
8137
8548
  const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
8138
8549
  const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
8139
8550
  return [
8140
- `### Feature: ${feature.name}`,
8551
+ `### ${feature.name}`,
8552
+ "",
8141
8553
  `- **Status:** ${feature.status}`,
8142
8554
  `- **Spec:** ${spec}`,
8143
- `- **Plans:** ${plans}`,
8144
- `- **Blocked by:** ${blockedBy}`,
8145
- `- **Summary:** ${feature.summary}`
8555
+ `- **Summary:** ${feature.summary}`,
8556
+ `- **Blockers:** ${blockedBy}`,
8557
+ `- **Plan:** ${plans}`
8146
8558
  ];
8147
8559
  }
8148
8560
 
8149
8561
  // src/roadmap/sync.ts
8150
- import * as fs9 from "fs";
8151
- import * as path9 from "path";
8562
+ import * as fs16 from "fs";
8563
+ import * as path16 from "path";
8152
8564
  import { Ok as Ok3 } from "@harness-engineering/types";
8153
8565
  function inferStatus(feature, projectPath, allFeatures) {
8154
8566
  if (feature.blockedBy.length > 0) {
@@ -8163,10 +8575,10 @@ function inferStatus(feature, projectPath, allFeatures) {
8163
8575
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
8164
8576
  const useRootState = featuresWithPlans.length <= 1;
8165
8577
  if (useRootState) {
8166
- const rootStatePath = path9.join(projectPath, ".harness", "state.json");
8167
- if (fs9.existsSync(rootStatePath)) {
8578
+ const rootStatePath = path16.join(projectPath, ".harness", "state.json");
8579
+ if (fs16.existsSync(rootStatePath)) {
8168
8580
  try {
8169
- const raw = fs9.readFileSync(rootStatePath, "utf-8");
8581
+ const raw = fs16.readFileSync(rootStatePath, "utf-8");
8170
8582
  const state = JSON.parse(raw);
8171
8583
  if (state.progress) {
8172
8584
  for (const status of Object.values(state.progress)) {
@@ -8177,16 +8589,16 @@ function inferStatus(feature, projectPath, allFeatures) {
8177
8589
  }
8178
8590
  }
8179
8591
  }
8180
- const sessionsDir = path9.join(projectPath, ".harness", "sessions");
8181
- if (fs9.existsSync(sessionsDir)) {
8592
+ const sessionsDir = path16.join(projectPath, ".harness", "sessions");
8593
+ if (fs16.existsSync(sessionsDir)) {
8182
8594
  try {
8183
- const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
8595
+ const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
8184
8596
  for (const entry of sessionDirs) {
8185
8597
  if (!entry.isDirectory()) continue;
8186
- const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
8187
- if (!fs9.existsSync(autopilotPath)) continue;
8598
+ const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
8599
+ if (!fs16.existsSync(autopilotPath)) continue;
8188
8600
  try {
8189
- const raw = fs9.readFileSync(autopilotPath, "utf-8");
8601
+ const raw = fs16.readFileSync(autopilotPath, "utf-8");
8190
8602
  const autopilot = JSON.parse(raw);
8191
8603
  if (!autopilot.phases) continue;
8192
8604
  const linkedPhases = autopilot.phases.filter(
@@ -8266,17 +8678,17 @@ var EmitInteractionInputSchema = z6.object({
8266
8678
  });
8267
8679
 
8268
8680
  // src/blueprint/scanner.ts
8269
- import * as fs10 from "fs/promises";
8270
- import * as path10 from "path";
8681
+ import * as fs17 from "fs/promises";
8682
+ import * as path17 from "path";
8271
8683
  var ProjectScanner = class {
8272
8684
  constructor(rootDir) {
8273
8685
  this.rootDir = rootDir;
8274
8686
  }
8275
8687
  async scan() {
8276
- let projectName = path10.basename(this.rootDir);
8688
+ let projectName = path17.basename(this.rootDir);
8277
8689
  try {
8278
- const pkgPath = path10.join(this.rootDir, "package.json");
8279
- 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");
8280
8692
  const pkg = JSON.parse(pkgRaw);
8281
8693
  if (pkg.name) projectName = pkg.name;
8282
8694
  } catch {
@@ -8317,8 +8729,8 @@ var ProjectScanner = class {
8317
8729
  };
8318
8730
 
8319
8731
  // src/blueprint/generator.ts
8320
- import * as fs11 from "fs/promises";
8321
- import * as path11 from "path";
8732
+ import * as fs18 from "fs/promises";
8733
+ import * as path18 from "path";
8322
8734
  import * as ejs from "ejs";
8323
8735
 
8324
8736
  // src/blueprint/templates.ts
@@ -8346,16 +8758,6 @@ var SHELL_TEMPLATE = `
8346
8758
  <div class="content">
8347
8759
  <h3>Code Translation</h3>
8348
8760
  <div class="translation"><%- module.content.codeTranslation %></div>
8349
- <h3>Knowledge Check</h3>
8350
- <div class="quiz">
8351
- <% module.content.quiz.questions.forEach((q, i) => { %>
8352
- <div class="question">
8353
- <p><%= q.question %></p>
8354
- <button onclick="reveal(this)">Reveal Answer</button>
8355
- <p class="answer" style="display:none;"><%= q.answer %></p>
8356
- </div>
8357
- <% }) %>
8358
- </div>
8359
8761
  </div>
8360
8762
  </article>
8361
8763
 
@@ -8374,10 +8776,6 @@ header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10p
8374
8776
  .module h2 { margin-top: 0; color: #0066cc; }
8375
8777
  `;
8376
8778
  var SCRIPTS = `
8377
- function reveal(btn) {
8378
- btn.nextElementSibling.style.display = 'block';
8379
- btn.style.display = 'none';
8380
- }
8381
8779
  console.log('Blueprint player initialized.');
8382
8780
  `;
8383
8781
 
@@ -8396,20 +8794,8 @@ var ContentPipeline = class {
8396
8794
  const translation = await llmService.generate(
8397
8795
  `You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
8398
8796
  );
8399
- const quizJson = await llmService.generate(
8400
- `Create 3 technical quiz questions for this code. Return ONLY valid JSON in this format: { "questions": [{ "question": "...", "answer": "..." }] }. Code: ${codeContext}`
8401
- );
8402
- let quiz;
8403
- try {
8404
- const cleanJson = quizJson.replace(/```json/g, "").replace(/```/g, "").trim();
8405
- quiz = JSON.parse(cleanJson);
8406
- } catch (e) {
8407
- console.error("Failed to parse quiz JSON", e);
8408
- quiz = { questions: [{ question: "Failed to generate quiz", answer: "N/A" }] };
8409
- }
8410
8797
  return {
8411
- codeTranslation: translation,
8412
- quiz
8798
+ codeTranslation: translation
8413
8799
  };
8414
8800
  }
8415
8801
  };
@@ -8428,19 +8814,19 @@ var BlueprintGenerator = class {
8428
8814
  styles: STYLES,
8429
8815
  scripts: SCRIPTS
8430
8816
  });
8431
- await fs11.mkdir(options.outputDir, { recursive: true });
8432
- 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);
8433
8819
  }
8434
8820
  };
8435
8821
 
8436
8822
  // src/update-checker.ts
8437
- import * as fs12 from "fs";
8438
- import * as path12 from "path";
8823
+ import * as fs19 from "fs";
8824
+ import * as path19 from "path";
8439
8825
  import * as os from "os";
8440
8826
  import { spawn } from "child_process";
8441
8827
  function getStatePath() {
8442
8828
  const home = process.env["HOME"] || os.homedir();
8443
- return path12.join(home, ".harness", "update-check.json");
8829
+ return path19.join(home, ".harness", "update-check.json");
8444
8830
  }
8445
8831
  function isUpdateCheckEnabled(configInterval) {
8446
8832
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -8453,7 +8839,7 @@ function shouldRunCheck(state, intervalMs) {
8453
8839
  }
8454
8840
  function readCheckState() {
8455
8841
  try {
8456
- const raw = fs12.readFileSync(getStatePath(), "utf-8");
8842
+ const raw = fs19.readFileSync(getStatePath(), "utf-8");
8457
8843
  const parsed = JSON.parse(raw);
8458
8844
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
8459
8845
  const state = parsed;
@@ -8470,7 +8856,7 @@ function readCheckState() {
8470
8856
  }
8471
8857
  function spawnBackgroundCheck(currentVersion) {
8472
8858
  const statePath = getStatePath();
8473
- const stateDir = path12.dirname(statePath);
8859
+ const stateDir = path19.dirname(statePath);
8474
8860
  const script = `
8475
8861
  const { execSync } = require('child_process');
8476
8862
  const fs = require('fs');
@@ -8601,6 +8987,7 @@ export {
8601
8987
  ViolationSchema,
8602
8988
  addProvenance,
8603
8989
  analyzeDiff,
8990
+ analyzeLearningPatterns,
8604
8991
  appendFailure,
8605
8992
  appendLearning,
8606
8993
  applyFixes,
@@ -8609,6 +8996,7 @@ export {
8609
8996
  archModule,
8610
8997
  architecture,
8611
8998
  archiveFailures,
8999
+ archiveLearnings,
8612
9000
  archiveStream,
8613
9001
  buildDependencyGraph,
8614
9002
  buildExclusionSet,
@@ -8616,6 +9004,8 @@ export {
8616
9004
  checkDocCoverage,
8617
9005
  checkEligibility,
8618
9006
  classifyFinding,
9007
+ clearFailuresCache,
9008
+ clearLearningsCache,
8619
9009
  configureFeedback,
8620
9010
  constraintRuleId,
8621
9011
  contextBudget,
@@ -8671,16 +9061,20 @@ export {
8671
9061
  injectionRules,
8672
9062
  isSmallSuggestion,
8673
9063
  isUpdateCheckEnabled,
9064
+ listActiveSessions,
8674
9065
  listStreams,
9066
+ loadBudgetedLearnings,
8675
9067
  loadFailures,
8676
9068
  loadHandoff,
8677
9069
  loadRelevantLearnings,
9070
+ loadSessionSummary,
8678
9071
  loadState,
8679
9072
  loadStreamIndex,
8680
9073
  logAgentAction,
8681
9074
  migrateToStreams,
8682
9075
  networkRules,
8683
9076
  nodeRules,
9077
+ parseDateFromEntry,
8684
9078
  parseDiff,
8685
9079
  parseManifest,
8686
9080
  parseRoadmap,
@@ -8688,9 +9082,11 @@ export {
8688
9082
  parseSize,
8689
9083
  pathTraversalRules,
8690
9084
  previewFix,
9085
+ pruneLearnings,
8691
9086
  reactRules,
8692
9087
  readCheckState,
8693
9088
  readLockfile,
9089
+ removeContributions,
8694
9090
  removeProvenance,
8695
9091
  requestMultiplePeerReviews,
8696
9092
  requestPeerReview,
@@ -8698,6 +9094,7 @@ export {
8698
9094
  resolveFileToLayer,
8699
9095
  resolveModelTier,
8700
9096
  resolveRuleSeverity,
9097
+ resolveSessionDir,
8701
9098
  resolveStreamPath,
8702
9099
  resolveThresholds,
8703
9100
  runAll,
@@ -8724,6 +9121,7 @@ export {
8724
9121
  syncRoadmap,
8725
9122
  touchStream,
8726
9123
  trackAction,
9124
+ updateSessionIndex,
8727
9125
  validateAgentsMap,
8728
9126
  validateBoundaries,
8729
9127
  validateCommitMessage,
@@ -8736,5 +9134,6 @@ export {
8736
9134
  violationId,
8737
9135
  writeConfig,
8738
9136
  writeLockfile,
9137
+ writeSessionSummary,
8739
9138
  xssRules
8740
9139
  };