@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.js CHANGED
@@ -106,6 +106,7 @@ __export(index_exports, {
106
106
  ViolationSchema: () => ViolationSchema,
107
107
  addProvenance: () => addProvenance,
108
108
  analyzeDiff: () => analyzeDiff,
109
+ analyzeLearningPatterns: () => analyzeLearningPatterns,
109
110
  appendFailure: () => appendFailure,
110
111
  appendLearning: () => appendLearning,
111
112
  applyFixes: () => applyFixes,
@@ -114,6 +115,7 @@ __export(index_exports, {
114
115
  archModule: () => archModule,
115
116
  architecture: () => architecture,
116
117
  archiveFailures: () => archiveFailures,
118
+ archiveLearnings: () => archiveLearnings,
117
119
  archiveStream: () => archiveStream,
118
120
  buildDependencyGraph: () => buildDependencyGraph,
119
121
  buildExclusionSet: () => buildExclusionSet,
@@ -121,6 +123,8 @@ __export(index_exports, {
121
123
  checkDocCoverage: () => checkDocCoverage,
122
124
  checkEligibility: () => checkEligibility,
123
125
  classifyFinding: () => classifyFinding,
126
+ clearFailuresCache: () => clearFailuresCache,
127
+ clearLearningsCache: () => clearLearningsCache,
124
128
  configureFeedback: () => configureFeedback,
125
129
  constraintRuleId: () => constraintRuleId,
126
130
  contextBudget: () => contextBudget,
@@ -176,16 +180,20 @@ __export(index_exports, {
176
180
  injectionRules: () => injectionRules,
177
181
  isSmallSuggestion: () => isSmallSuggestion,
178
182
  isUpdateCheckEnabled: () => isUpdateCheckEnabled,
183
+ listActiveSessions: () => listActiveSessions,
179
184
  listStreams: () => listStreams,
185
+ loadBudgetedLearnings: () => loadBudgetedLearnings,
180
186
  loadFailures: () => loadFailures,
181
187
  loadHandoff: () => loadHandoff,
182
188
  loadRelevantLearnings: () => loadRelevantLearnings,
189
+ loadSessionSummary: () => loadSessionSummary,
183
190
  loadState: () => loadState,
184
191
  loadStreamIndex: () => loadStreamIndex,
185
192
  logAgentAction: () => logAgentAction,
186
193
  migrateToStreams: () => migrateToStreams,
187
194
  networkRules: () => networkRules,
188
195
  nodeRules: () => nodeRules,
196
+ parseDateFromEntry: () => parseDateFromEntry,
189
197
  parseDiff: () => parseDiff,
190
198
  parseManifest: () => parseManifest,
191
199
  parseRoadmap: () => parseRoadmap,
@@ -193,6 +201,7 @@ __export(index_exports, {
193
201
  parseSize: () => parseSize,
194
202
  pathTraversalRules: () => pathTraversalRules,
195
203
  previewFix: () => previewFix,
204
+ pruneLearnings: () => pruneLearnings,
196
205
  reactRules: () => reactRules,
197
206
  readCheckState: () => readCheckState,
198
207
  readLockfile: () => readLockfile,
@@ -204,6 +213,7 @@ __export(index_exports, {
204
213
  resolveFileToLayer: () => resolveFileToLayer,
205
214
  resolveModelTier: () => resolveModelTier,
206
215
  resolveRuleSeverity: () => resolveRuleSeverity,
216
+ resolveSessionDir: () => resolveSessionDir,
207
217
  resolveStreamPath: () => resolveStreamPath,
208
218
  resolveThresholds: () => resolveThresholds,
209
219
  runAll: () => runAll,
@@ -230,6 +240,7 @@ __export(index_exports, {
230
240
  syncRoadmap: () => syncRoadmap,
231
241
  touchStream: () => touchStream,
232
242
  trackAction: () => trackAction,
243
+ updateSessionIndex: () => updateSessionIndex,
233
244
  validateAgentsMap: () => validateAgentsMap,
234
245
  validateBoundaries: () => validateBoundaries,
235
246
  validateCommitMessage: () => validateCommitMessage,
@@ -242,6 +253,7 @@ __export(index_exports, {
242
253
  violationId: () => violationId,
243
254
  writeConfig: () => writeConfig,
244
255
  writeLockfile: () => writeLockfile,
256
+ writeSessionSummary: () => writeSessionSummary,
245
257
  xssRules: () => xssRules
246
258
  });
247
259
  module.exports = __toCommonJS(index_exports);
@@ -264,17 +276,17 @@ var import_util = require("util");
264
276
  var import_glob = require("glob");
265
277
  var accessAsync = (0, import_util.promisify)(import_fs.access);
266
278
  var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
267
- async function fileExists(path13) {
279
+ async function fileExists(path20) {
268
280
  try {
269
- await accessAsync(path13, import_fs.constants.F_OK);
281
+ await accessAsync(path20, import_fs.constants.F_OK);
270
282
  return true;
271
283
  } catch {
272
284
  return false;
273
285
  }
274
286
  }
275
- async function readFileContent(path13) {
287
+ async function readFileContent(path20) {
276
288
  try {
277
- const content = await readFileAsync(path13, "utf-8");
289
+ const content = await readFileAsync(path20, "utf-8");
278
290
  return (0, import_types.Ok)(content);
279
291
  } catch (error) {
280
292
  return (0, import_types.Err)(error);
@@ -322,15 +334,15 @@ function validateConfig(data, schema) {
322
334
  let message = "Configuration validation failed";
323
335
  const suggestions = [];
324
336
  if (firstError) {
325
- const path13 = firstError.path.join(".");
326
- const pathDisplay = path13 ? ` at "${path13}"` : "";
337
+ const path20 = firstError.path.join(".");
338
+ const pathDisplay = path20 ? ` at "${path20}"` : "";
327
339
  if (firstError.code === "invalid_type") {
328
340
  const received = firstError.received;
329
341
  const expected = firstError.expected;
330
342
  if (received === "undefined") {
331
343
  code = "MISSING_FIELD";
332
344
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
333
- suggestions.push(`Field "${path13}" is required and must be of type "${expected}"`);
345
+ suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
334
346
  } else {
335
347
  code = "INVALID_TYPE";
336
348
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -543,30 +555,27 @@ function extractSections(content) {
543
555
  return result;
544
556
  });
545
557
  }
546
- function isExternalLink(path13) {
547
- return path13.startsWith("http://") || path13.startsWith("https://") || path13.startsWith("#") || path13.startsWith("mailto:");
558
+ function isExternalLink(path20) {
559
+ return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
548
560
  }
549
561
  function resolveLinkPath(linkPath, baseDir) {
550
562
  return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
551
563
  }
552
- async function validateAgentsMap(path13 = "./AGENTS.md") {
553
- console.warn(
554
- "[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
555
- );
556
- const contentResult = await readFileContent(path13);
564
+ async function validateAgentsMap(path20 = "./AGENTS.md") {
565
+ const contentResult = await readFileContent(path20);
557
566
  if (!contentResult.ok) {
558
567
  return (0, import_types.Err)(
559
568
  createError(
560
569
  "PARSE_ERROR",
561
570
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
562
- { path: path13 },
571
+ { path: path20 },
563
572
  ["Ensure the file exists", "Check file permissions"]
564
573
  )
565
574
  );
566
575
  }
567
576
  const content = contentResult.value;
568
577
  const sections = extractSections(content);
569
- const baseDir = (0, import_path.dirname)(path13);
578
+ const baseDir = (0, import_path.dirname)(path20);
570
579
  const sectionTitles = sections.map((s) => s.title);
571
580
  const missingSections = REQUIRED_SECTIONS.filter(
572
581
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -707,8 +716,8 @@ async function checkDocCoverage(domain, options = {}) {
707
716
 
708
717
  // src/context/knowledge-map.ts
709
718
  var import_path3 = require("path");
710
- function suggestFix(path13, existingFiles) {
711
- const targetName = (0, import_path3.basename)(path13).toLowerCase();
719
+ function suggestFix(path20, existingFiles) {
720
+ const targetName = (0, import_path3.basename)(path20).toLowerCase();
712
721
  const similar = existingFiles.find((file) => {
713
722
  const fileName = (0, import_path3.basename)(file).toLowerCase();
714
723
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -716,12 +725,9 @@ function suggestFix(path13, existingFiles) {
716
725
  if (similar) {
717
726
  return `Did you mean "${similar}"?`;
718
727
  }
719
- return `Create the file "${path13}" or remove the link`;
728
+ return `Create the file "${path20}" or remove the link`;
720
729
  }
721
730
  async function validateKnowledgeMap(rootDir = process.cwd()) {
722
- console.warn(
723
- "[harness] validateKnowledgeMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
724
- );
725
731
  const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
726
732
  const agentsResult = await validateAgentsMap(agentsPath);
727
733
  if (!agentsResult.ok) {
@@ -1319,8 +1325,8 @@ function createBoundaryValidator(schema, name) {
1319
1325
  return (0, import_types.Ok)(result.data);
1320
1326
  }
1321
1327
  const suggestions = result.error.issues.map((issue) => {
1322
- const path13 = issue.path.join(".");
1323
- return path13 ? `${path13}: ${issue.message}` : issue.message;
1328
+ const path20 = issue.path.join(".");
1329
+ return path20 ? `${path20}: ${issue.message}` : issue.message;
1324
1330
  });
1325
1331
  return (0, import_types.Err)(
1326
1332
  createError(
@@ -1874,11 +1880,11 @@ function walk(node, visitor) {
1874
1880
  var TypeScriptParser = class {
1875
1881
  name = "typescript";
1876
1882
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1877
- async parseFile(path13) {
1878
- const contentResult = await readFileContent(path13);
1883
+ async parseFile(path20) {
1884
+ const contentResult = await readFileContent(path20);
1879
1885
  if (!contentResult.ok) {
1880
1886
  return (0, import_types.Err)(
1881
- createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
1887
+ createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
1882
1888
  "Check that the file exists",
1883
1889
  "Verify the path is correct"
1884
1890
  ])
@@ -1888,7 +1894,7 @@ var TypeScriptParser = class {
1888
1894
  const ast = (0, import_typescript_estree.parse)(contentResult.value, {
1889
1895
  loc: true,
1890
1896
  range: true,
1891
- jsx: path13.endsWith(".tsx"),
1897
+ jsx: path20.endsWith(".tsx"),
1892
1898
  errorOnUnknownASTType: false
1893
1899
  });
1894
1900
  return (0, import_types.Ok)({
@@ -1899,7 +1905,7 @@ var TypeScriptParser = class {
1899
1905
  } catch (e) {
1900
1906
  const error = e;
1901
1907
  return (0, import_types.Err)(
1902
- createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
1908
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
1903
1909
  "Check for syntax errors in the file",
1904
1910
  "Ensure valid TypeScript syntax"
1905
1911
  ])
@@ -2183,22 +2189,22 @@ function extractInlineRefs(content) {
2183
2189
  }
2184
2190
  return refs;
2185
2191
  }
2186
- async function parseDocumentationFile(path13) {
2187
- const contentResult = await readFileContent(path13);
2192
+ async function parseDocumentationFile(path20) {
2193
+ const contentResult = await readFileContent(path20);
2188
2194
  if (!contentResult.ok) {
2189
2195
  return (0, import_types.Err)(
2190
2196
  createEntropyError(
2191
2197
  "PARSE_ERROR",
2192
- `Failed to read documentation file: ${path13}`,
2193
- { file: path13 },
2198
+ `Failed to read documentation file: ${path20}`,
2199
+ { file: path20 },
2194
2200
  ["Check that the file exists"]
2195
2201
  )
2196
2202
  );
2197
2203
  }
2198
2204
  const content = contentResult.value;
2199
- const type = path13.endsWith(".md") ? "markdown" : "text";
2205
+ const type = path20.endsWith(".md") ? "markdown" : "text";
2200
2206
  return (0, import_types.Ok)({
2201
- path: path13,
2207
+ path: path20,
2202
2208
  type,
2203
2209
  content,
2204
2210
  codeBlocks: extractCodeBlocks(content),
@@ -3009,15 +3015,34 @@ async function detectPatternViolations(snapshot, config) {
3009
3015
  }
3010
3016
  }
3011
3017
  }
3018
+ if (config?.customPatterns) {
3019
+ for (const file of snapshot.files) {
3020
+ for (const custom of config.customPatterns) {
3021
+ const matches = custom.check(file, snapshot);
3022
+ for (const match of matches) {
3023
+ violations.push({
3024
+ pattern: custom.name,
3025
+ file: file.path,
3026
+ line: match.line,
3027
+ message: match.message,
3028
+ suggestion: match.suggestion || "Review and fix this pattern violation",
3029
+ severity: custom.severity
3030
+ });
3031
+ }
3032
+ }
3033
+ }
3034
+ }
3012
3035
  const errorCount = violations.filter((v) => v.severity === "error").length;
3013
3036
  const warningCount = violations.filter((v) => v.severity === "warning").length;
3014
- const totalChecks = snapshot.files.length * patterns.length;
3015
- const passRate = totalChecks > 0 ? (totalChecks - violations.length) / totalChecks : 1;
3037
+ const customCount = config?.customPatterns?.length ?? 0;
3038
+ const allPatternsCount = patterns.length + customCount;
3039
+ const totalChecks = snapshot.files.length * allPatternsCount;
3040
+ const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
3016
3041
  return (0, import_types.Ok)({
3017
3042
  violations,
3018
3043
  stats: {
3019
3044
  filesChecked: snapshot.files.length,
3020
- patternsApplied: patterns.length,
3045
+ patternsApplied: allPatternsCount,
3021
3046
  violationCount: violations.length,
3022
3047
  errorCount,
3023
3048
  warningCount
@@ -5516,8 +5541,8 @@ var import_node_path3 = require("path");
5516
5541
  // src/architecture/collectors/hash.ts
5517
5542
  var import_node_crypto = require("crypto");
5518
5543
  function violationId(relativePath, category, normalizedDetail) {
5519
- const path13 = relativePath.replace(/\\/g, "/");
5520
- const input = `${path13}:${category}:${normalizedDetail}`;
5544
+ const path20 = relativePath.replace(/\\/g, "/");
5545
+ const input = `${path20}:${category}:${normalizedDetail}`;
5521
5546
  return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
5522
5547
  }
5523
5548
  function constraintRuleId(category, scope, description) {
@@ -6695,10 +6720,13 @@ var DEFAULT_STATE = {
6695
6720
  progress: {}
6696
6721
  };
6697
6722
 
6698
- // src/state/state-manager.ts
6699
- var fs6 = __toESM(require("fs"));
6700
- var path3 = __toESM(require("path"));
6701
- var import_child_process2 = require("child_process");
6723
+ // src/state/state-persistence.ts
6724
+ var fs8 = __toESM(require("fs"));
6725
+ var path5 = __toESM(require("path"));
6726
+
6727
+ // src/state/state-shared.ts
6728
+ var fs7 = __toESM(require("fs"));
6729
+ var path4 = __toESM(require("path"));
6702
6730
 
6703
6731
  // src/state/stream-resolver.ts
6704
6732
  var fs5 = __toESM(require("fs"));
@@ -6724,10 +6752,20 @@ var DEFAULT_STREAM_INDEX = {
6724
6752
  streams: {}
6725
6753
  };
6726
6754
 
6727
- // src/state/stream-resolver.ts
6755
+ // src/state/constants.ts
6728
6756
  var HARNESS_DIR = ".harness";
6729
- var STREAMS_DIR = "streams";
6757
+ var STATE_FILE = "state.json";
6758
+ var LEARNINGS_FILE = "learnings.md";
6759
+ var FAILURES_FILE = "failures.md";
6760
+ var HANDOFF_FILE = "handoff.json";
6761
+ var GATE_CONFIG_FILE = "gate.json";
6730
6762
  var INDEX_FILE = "index.json";
6763
+ var SESSIONS_DIR = "sessions";
6764
+ var SESSION_INDEX_FILE = "index.md";
6765
+ var SUMMARY_FILE = "summary.md";
6766
+
6767
+ // src/state/stream-resolver.ts
6768
+ var STREAMS_DIR = "streams";
6731
6769
  var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
6732
6770
  function streamsDir(projectPath) {
6733
6771
  return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
@@ -6954,26 +6992,65 @@ async function migrateToStreams(projectPath) {
6954
6992
  return saveStreamIndex(projectPath, index);
6955
6993
  }
6956
6994
 
6957
- // src/state/state-manager.ts
6958
- var HARNESS_DIR2 = ".harness";
6959
- var STATE_FILE = "state.json";
6960
- var LEARNINGS_FILE = "learnings.md";
6961
- var FAILURES_FILE = "failures.md";
6962
- var HANDOFF_FILE = "handoff.json";
6963
- var GATE_CONFIG_FILE = "gate.json";
6964
- var INDEX_FILE2 = "index.json";
6995
+ // src/state/session-resolver.ts
6996
+ var fs6 = __toESM(require("fs"));
6997
+ var path3 = __toESM(require("path"));
6998
+ function resolveSessionDir(projectPath, sessionSlug, options) {
6999
+ if (!sessionSlug || sessionSlug.trim() === "") {
7000
+ return (0, import_types.Err)(new Error("Session slug must not be empty"));
7001
+ }
7002
+ if (sessionSlug.includes("..") || sessionSlug.includes("/") || sessionSlug.includes("\\")) {
7003
+ return (0, import_types.Err)(
7004
+ new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
7005
+ );
7006
+ }
7007
+ const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
7008
+ if (options?.create) {
7009
+ fs6.mkdirSync(sessionDir, { recursive: true });
7010
+ }
7011
+ return (0, import_types.Ok)(sessionDir);
7012
+ }
7013
+ function updateSessionIndex(projectPath, sessionSlug, description) {
7014
+ const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
7015
+ fs6.mkdirSync(sessionsDir, { recursive: true });
7016
+ const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
7017
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7018
+ const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
7019
+ if (!fs6.existsSync(indexPath2)) {
7020
+ fs6.writeFileSync(indexPath2, `## Active Sessions
7021
+
7022
+ ${newLine}
7023
+ `);
7024
+ return;
7025
+ }
7026
+ const content = fs6.readFileSync(indexPath2, "utf-8");
7027
+ const lines = content.split("\n");
7028
+ const slugPattern = `- [${sessionSlug}]`;
7029
+ const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
7030
+ if (existingIdx >= 0) {
7031
+ lines[existingIdx] = newLine;
7032
+ } else {
7033
+ const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
7034
+ lines.splice(lastNonEmpty + 1, 0, newLine);
7035
+ }
7036
+ fs6.writeFileSync(indexPath2, lines.join("\n"));
7037
+ }
7038
+
7039
+ // src/state/state-shared.ts
6965
7040
  var MAX_CACHE_ENTRIES = 8;
6966
- var learningsCacheMap = /* @__PURE__ */ new Map();
6967
- var failuresCacheMap = /* @__PURE__ */ new Map();
6968
7041
  function evictIfNeeded(map) {
6969
7042
  if (map.size > MAX_CACHE_ENTRIES) {
6970
7043
  const oldest = map.keys().next().value;
6971
7044
  if (oldest !== void 0) map.delete(oldest);
6972
7045
  }
6973
7046
  }
6974
- async function getStateDir(projectPath, stream) {
6975
- const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
6976
- const hasStreams = fs6.existsSync(streamsIndexPath);
7047
+ async function getStateDir(projectPath, stream, session) {
7048
+ if (session) {
7049
+ const sessionResult = resolveSessionDir(projectPath, session, { create: true });
7050
+ return sessionResult;
7051
+ }
7052
+ const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
7053
+ const hasStreams = fs7.existsSync(streamsIndexPath);
6977
7054
  if (stream || hasStreams) {
6978
7055
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
6979
7056
  if (result.ok) {
@@ -6983,18 +7060,20 @@ async function getStateDir(projectPath, stream) {
6983
7060
  return result;
6984
7061
  }
6985
7062
  }
6986
- return (0, import_types.Ok)(path3.join(projectPath, HARNESS_DIR2));
7063
+ return (0, import_types.Ok)(path4.join(projectPath, HARNESS_DIR));
6987
7064
  }
6988
- async function loadState(projectPath, stream) {
7065
+
7066
+ // src/state/state-persistence.ts
7067
+ async function loadState(projectPath, stream, session) {
6989
7068
  try {
6990
- const dirResult = await getStateDir(projectPath, stream);
7069
+ const dirResult = await getStateDir(projectPath, stream, session);
6991
7070
  if (!dirResult.ok) return dirResult;
6992
7071
  const stateDir = dirResult.value;
6993
- const statePath = path3.join(stateDir, STATE_FILE);
6994
- if (!fs6.existsSync(statePath)) {
7072
+ const statePath = path5.join(stateDir, STATE_FILE);
7073
+ if (!fs8.existsSync(statePath)) {
6995
7074
  return (0, import_types.Ok)({ ...DEFAULT_STATE });
6996
7075
  }
6997
- const raw = fs6.readFileSync(statePath, "utf-8");
7076
+ const raw = fs8.readFileSync(statePath, "utf-8");
6998
7077
  const parsed = JSON.parse(raw);
6999
7078
  const result = HarnessStateSchema.safeParse(parsed);
7000
7079
  if (!result.success) {
@@ -7007,14 +7086,14 @@ async function loadState(projectPath, stream) {
7007
7086
  );
7008
7087
  }
7009
7088
  }
7010
- async function saveState(projectPath, state, stream) {
7089
+ async function saveState(projectPath, state, stream, session) {
7011
7090
  try {
7012
- const dirResult = await getStateDir(projectPath, stream);
7091
+ const dirResult = await getStateDir(projectPath, stream, session);
7013
7092
  if (!dirResult.ok) return dirResult;
7014
7093
  const stateDir = dirResult.value;
7015
- const statePath = path3.join(stateDir, STATE_FILE);
7016
- fs6.mkdirSync(stateDir, { recursive: true });
7017
- fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
7094
+ const statePath = path5.join(stateDir, STATE_FILE);
7095
+ fs8.mkdirSync(stateDir, { recursive: true });
7096
+ fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
7018
7097
  return (0, import_types.Ok)(void 0);
7019
7098
  } catch (error) {
7020
7099
  return (0, import_types.Err)(
@@ -7022,13 +7101,21 @@ async function saveState(projectPath, state, stream) {
7022
7101
  );
7023
7102
  }
7024
7103
  }
7025
- async function appendLearning(projectPath, learning, skillName, outcome, stream) {
7104
+
7105
+ // src/state/learnings.ts
7106
+ var fs9 = __toESM(require("fs"));
7107
+ var path6 = __toESM(require("path"));
7108
+ var learningsCacheMap = /* @__PURE__ */ new Map();
7109
+ function clearLearningsCache() {
7110
+ learningsCacheMap.clear();
7111
+ }
7112
+ async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
7026
7113
  try {
7027
- const dirResult = await getStateDir(projectPath, stream);
7114
+ const dirResult = await getStateDir(projectPath, stream, session);
7028
7115
  if (!dirResult.ok) return dirResult;
7029
7116
  const stateDir = dirResult.value;
7030
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
7031
- fs6.mkdirSync(stateDir, { recursive: true });
7117
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7118
+ fs9.mkdirSync(stateDir, { recursive: true });
7032
7119
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7033
7120
  let entry;
7034
7121
  if (skillName && outcome) {
@@ -7044,11 +7131,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
7044
7131
  - **${timestamp}:** ${learning}
7045
7132
  `;
7046
7133
  }
7047
- if (!fs6.existsSync(learningsPath)) {
7048
- fs6.writeFileSync(learningsPath, `# Learnings
7134
+ if (!fs9.existsSync(learningsPath)) {
7135
+ fs9.writeFileSync(learningsPath, `# Learnings
7049
7136
  ${entry}`);
7050
7137
  } else {
7051
- fs6.appendFileSync(learningsPath, entry);
7138
+ fs9.appendFileSync(learningsPath, entry);
7052
7139
  }
7053
7140
  learningsCacheMap.delete(learningsPath);
7054
7141
  return (0, import_types.Ok)(void 0);
@@ -7060,23 +7147,92 @@ ${entry}`);
7060
7147
  );
7061
7148
  }
7062
7149
  }
7063
- async function loadRelevantLearnings(projectPath, skillName, stream) {
7150
+ function estimateTokens(text) {
7151
+ return Math.ceil(text.length / 4);
7152
+ }
7153
+ function scoreRelevance(entry, intent) {
7154
+ if (!intent || intent.trim() === "") return 0;
7155
+ const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
7156
+ if (intentWords.length === 0) return 0;
7157
+ const entryLower = entry.toLowerCase();
7158
+ const matches = intentWords.filter((word) => entryLower.includes(word));
7159
+ return matches.length / intentWords.length;
7160
+ }
7161
+ function parseDateFromEntry(entry) {
7162
+ const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
7163
+ return match ? match[1] ?? null : null;
7164
+ }
7165
+ function analyzeLearningPatterns(entries) {
7166
+ const tagGroups = /* @__PURE__ */ new Map();
7167
+ for (const entry of entries) {
7168
+ const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
7169
+ for (const match of tagMatches) {
7170
+ const tag = match[1] ?? match[2];
7171
+ if (tag) {
7172
+ const group = tagGroups.get(tag) ?? [];
7173
+ group.push(entry);
7174
+ tagGroups.set(tag, group);
7175
+ }
7176
+ }
7177
+ }
7178
+ const patterns = [];
7179
+ for (const [tag, groupEntries] of tagGroups) {
7180
+ if (groupEntries.length >= 3) {
7181
+ patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
7182
+ }
7183
+ }
7184
+ return patterns.sort((a, b) => b.count - a.count);
7185
+ }
7186
+ async function loadBudgetedLearnings(projectPath, options) {
7187
+ const { intent, tokenBudget = 1e3, skill, session, stream } = options;
7188
+ const sortByRecencyAndRelevance = (entries) => {
7189
+ return [...entries].sort((a, b) => {
7190
+ const dateA = parseDateFromEntry(a) ?? "0000-00-00";
7191
+ const dateB = parseDateFromEntry(b) ?? "0000-00-00";
7192
+ const dateCompare = dateB.localeCompare(dateA);
7193
+ if (dateCompare !== 0) return dateCompare;
7194
+ return scoreRelevance(b, intent) - scoreRelevance(a, intent);
7195
+ });
7196
+ };
7197
+ const allEntries = [];
7198
+ if (session) {
7199
+ const sessionResult = await loadRelevantLearnings(projectPath, skill, stream, session);
7200
+ if (sessionResult.ok) {
7201
+ allEntries.push(...sortByRecencyAndRelevance(sessionResult.value));
7202
+ }
7203
+ }
7204
+ const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
7205
+ if (globalResult.ok) {
7206
+ allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
7207
+ }
7208
+ const budgeted = [];
7209
+ let totalTokens = 0;
7210
+ for (const entry of allEntries) {
7211
+ const separator = budgeted.length > 0 ? "\n" : "";
7212
+ const entryCost = estimateTokens(entry + separator);
7213
+ if (totalTokens + entryCost > tokenBudget) break;
7214
+ budgeted.push(entry);
7215
+ totalTokens += entryCost;
7216
+ }
7217
+ return (0, import_types.Ok)(budgeted);
7218
+ }
7219
+ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
7064
7220
  try {
7065
- const dirResult = await getStateDir(projectPath, stream);
7221
+ const dirResult = await getStateDir(projectPath, stream, session);
7066
7222
  if (!dirResult.ok) return dirResult;
7067
7223
  const stateDir = dirResult.value;
7068
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
7069
- if (!fs6.existsSync(learningsPath)) {
7224
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7225
+ if (!fs9.existsSync(learningsPath)) {
7070
7226
  return (0, import_types.Ok)([]);
7071
7227
  }
7072
- const stats = fs6.statSync(learningsPath);
7228
+ const stats = fs9.statSync(learningsPath);
7073
7229
  const cacheKey = learningsPath;
7074
7230
  const cached = learningsCacheMap.get(cacheKey);
7075
7231
  let entries;
7076
7232
  if (cached && cached.mtimeMs === stats.mtimeMs) {
7077
7233
  entries = cached.entries;
7078
7234
  } else {
7079
- const content = fs6.readFileSync(learningsPath, "utf-8");
7235
+ const content = fs9.readFileSync(learningsPath, "utf-8");
7080
7236
  const lines = content.split("\n");
7081
7237
  entries = [];
7082
7238
  let currentBlock = [];
@@ -7112,23 +7268,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
7112
7268
  );
7113
7269
  }
7114
7270
  }
7115
- var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
7116
- async function appendFailure(projectPath, description, skillName, type, stream) {
7271
+ async function archiveLearnings(projectPath, entries, stream) {
7117
7272
  try {
7118
7273
  const dirResult = await getStateDir(projectPath, stream);
7119
7274
  if (!dirResult.ok) return dirResult;
7120
7275
  const stateDir = dirResult.value;
7121
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
7122
- fs6.mkdirSync(stateDir, { recursive: true });
7276
+ const archiveDir = path6.join(stateDir, "learnings-archive");
7277
+ fs9.mkdirSync(archiveDir, { recursive: true });
7278
+ const now = /* @__PURE__ */ new Date();
7279
+ const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
7280
+ const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
7281
+ const archiveContent = entries.join("\n\n") + "\n";
7282
+ if (fs9.existsSync(archivePath)) {
7283
+ fs9.appendFileSync(archivePath, "\n" + archiveContent);
7284
+ } else {
7285
+ fs9.writeFileSync(archivePath, `# Learnings Archive
7286
+
7287
+ ${archiveContent}`);
7288
+ }
7289
+ return (0, import_types.Ok)(void 0);
7290
+ } catch (error) {
7291
+ return (0, import_types.Err)(
7292
+ new Error(
7293
+ `Failed to archive learnings: ${error instanceof Error ? error.message : String(error)}`
7294
+ )
7295
+ );
7296
+ }
7297
+ }
7298
+ async function pruneLearnings(projectPath, stream) {
7299
+ try {
7300
+ const dirResult = await getStateDir(projectPath, stream);
7301
+ if (!dirResult.ok) return dirResult;
7302
+ const stateDir = dirResult.value;
7303
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7304
+ if (!fs9.existsSync(learningsPath)) {
7305
+ return (0, import_types.Ok)({ kept: 0, archived: 0, patterns: [] });
7306
+ }
7307
+ const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
7308
+ if (!loadResult.ok) return loadResult;
7309
+ const allEntries = loadResult.value;
7310
+ if (allEntries.length <= 20) {
7311
+ const cutoffDate = /* @__PURE__ */ new Date();
7312
+ cutoffDate.setDate(cutoffDate.getDate() - 14);
7313
+ const cutoffStr = cutoffDate.toISOString().split("T")[0];
7314
+ const hasOld = allEntries.some((entry) => {
7315
+ const date = parseDateFromEntry(entry);
7316
+ return date !== null && date < cutoffStr;
7317
+ });
7318
+ if (!hasOld) {
7319
+ return (0, import_types.Ok)({ kept: allEntries.length, archived: 0, patterns: [] });
7320
+ }
7321
+ }
7322
+ const sorted = [...allEntries].sort((a, b) => {
7323
+ const dateA = parseDateFromEntry(a) ?? "0000-00-00";
7324
+ const dateB = parseDateFromEntry(b) ?? "0000-00-00";
7325
+ return dateB.localeCompare(dateA);
7326
+ });
7327
+ const toKeep = sorted.slice(0, 20);
7328
+ const toArchive = sorted.slice(20);
7329
+ const patterns = analyzeLearningPatterns(allEntries);
7330
+ if (toArchive.length > 0) {
7331
+ const archiveResult = await archiveLearnings(projectPath, toArchive, stream);
7332
+ if (!archiveResult.ok) return archiveResult;
7333
+ }
7334
+ const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
7335
+ fs9.writeFileSync(learningsPath, newContent);
7336
+ learningsCacheMap.delete(learningsPath);
7337
+ return (0, import_types.Ok)({
7338
+ kept: toKeep.length,
7339
+ archived: toArchive.length,
7340
+ patterns
7341
+ });
7342
+ } catch (error) {
7343
+ return (0, import_types.Err)(
7344
+ new Error(
7345
+ `Failed to prune learnings: ${error instanceof Error ? error.message : String(error)}`
7346
+ )
7347
+ );
7348
+ }
7349
+ }
7350
+
7351
+ // src/state/failures.ts
7352
+ var fs10 = __toESM(require("fs"));
7353
+ var path7 = __toESM(require("path"));
7354
+ var failuresCacheMap = /* @__PURE__ */ new Map();
7355
+ function clearFailuresCache() {
7356
+ failuresCacheMap.clear();
7357
+ }
7358
+ var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
7359
+ async function appendFailure(projectPath, description, skillName, type, stream, session) {
7360
+ try {
7361
+ const dirResult = await getStateDir(projectPath, stream, session);
7362
+ if (!dirResult.ok) return dirResult;
7363
+ const stateDir = dirResult.value;
7364
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
7365
+ fs10.mkdirSync(stateDir, { recursive: true });
7123
7366
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7124
7367
  const entry = `
7125
7368
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
7126
7369
  `;
7127
- if (!fs6.existsSync(failuresPath)) {
7128
- fs6.writeFileSync(failuresPath, `# Failures
7370
+ if (!fs10.existsSync(failuresPath)) {
7371
+ fs10.writeFileSync(failuresPath, `# Failures
7129
7372
  ${entry}`);
7130
7373
  } else {
7131
- fs6.appendFileSync(failuresPath, entry);
7374
+ fs10.appendFileSync(failuresPath, entry);
7132
7375
  }
7133
7376
  failuresCacheMap.delete(failuresPath);
7134
7377
  return (0, import_types.Ok)(void 0);
@@ -7140,22 +7383,22 @@ ${entry}`);
7140
7383
  );
7141
7384
  }
7142
7385
  }
7143
- async function loadFailures(projectPath, stream) {
7386
+ async function loadFailures(projectPath, stream, session) {
7144
7387
  try {
7145
- const dirResult = await getStateDir(projectPath, stream);
7388
+ const dirResult = await getStateDir(projectPath, stream, session);
7146
7389
  if (!dirResult.ok) return dirResult;
7147
7390
  const stateDir = dirResult.value;
7148
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
7149
- if (!fs6.existsSync(failuresPath)) {
7391
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
7392
+ if (!fs10.existsSync(failuresPath)) {
7150
7393
  return (0, import_types.Ok)([]);
7151
7394
  }
7152
- const stats = fs6.statSync(failuresPath);
7395
+ const stats = fs10.statSync(failuresPath);
7153
7396
  const cacheKey = failuresPath;
7154
7397
  const cached = failuresCacheMap.get(cacheKey);
7155
7398
  if (cached && cached.mtimeMs === stats.mtimeMs) {
7156
7399
  return (0, import_types.Ok)(cached.entries);
7157
7400
  }
7158
- const content = fs6.readFileSync(failuresPath, "utf-8");
7401
+ const content = fs10.readFileSync(failuresPath, "utf-8");
7159
7402
  const entries = [];
7160
7403
  for (const line of content.split("\n")) {
7161
7404
  const match = line.match(FAILURE_LINE_REGEX);
@@ -7179,25 +7422,25 @@ async function loadFailures(projectPath, stream) {
7179
7422
  );
7180
7423
  }
7181
7424
  }
7182
- async function archiveFailures(projectPath, stream) {
7425
+ async function archiveFailures(projectPath, stream, session) {
7183
7426
  try {
7184
- const dirResult = await getStateDir(projectPath, stream);
7427
+ const dirResult = await getStateDir(projectPath, stream, session);
7185
7428
  if (!dirResult.ok) return dirResult;
7186
7429
  const stateDir = dirResult.value;
7187
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
7188
- if (!fs6.existsSync(failuresPath)) {
7430
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
7431
+ if (!fs10.existsSync(failuresPath)) {
7189
7432
  return (0, import_types.Ok)(void 0);
7190
7433
  }
7191
- const archiveDir = path3.join(stateDir, "archive");
7192
- fs6.mkdirSync(archiveDir, { recursive: true });
7434
+ const archiveDir = path7.join(stateDir, "archive");
7435
+ fs10.mkdirSync(archiveDir, { recursive: true });
7193
7436
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7194
7437
  let archiveName = `failures-${date}.md`;
7195
7438
  let counter = 2;
7196
- while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
7439
+ while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
7197
7440
  archiveName = `failures-${date}-${counter}.md`;
7198
7441
  counter++;
7199
7442
  }
7200
- fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
7443
+ fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
7201
7444
  failuresCacheMap.delete(failuresPath);
7202
7445
  return (0, import_types.Ok)(void 0);
7203
7446
  } catch (error) {
@@ -7208,14 +7451,18 @@ async function archiveFailures(projectPath, stream) {
7208
7451
  );
7209
7452
  }
7210
7453
  }
7211
- async function saveHandoff(projectPath, handoff, stream) {
7454
+
7455
+ // src/state/handoff.ts
7456
+ var fs11 = __toESM(require("fs"));
7457
+ var path8 = __toESM(require("path"));
7458
+ async function saveHandoff(projectPath, handoff, stream, session) {
7212
7459
  try {
7213
- const dirResult = await getStateDir(projectPath, stream);
7460
+ const dirResult = await getStateDir(projectPath, stream, session);
7214
7461
  if (!dirResult.ok) return dirResult;
7215
7462
  const stateDir = dirResult.value;
7216
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
7217
- fs6.mkdirSync(stateDir, { recursive: true });
7218
- fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
7463
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
7464
+ fs11.mkdirSync(stateDir, { recursive: true });
7465
+ fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
7219
7466
  return (0, import_types.Ok)(void 0);
7220
7467
  } catch (error) {
7221
7468
  return (0, import_types.Err)(
@@ -7223,16 +7470,16 @@ async function saveHandoff(projectPath, handoff, stream) {
7223
7470
  );
7224
7471
  }
7225
7472
  }
7226
- async function loadHandoff(projectPath, stream) {
7473
+ async function loadHandoff(projectPath, stream, session) {
7227
7474
  try {
7228
- const dirResult = await getStateDir(projectPath, stream);
7475
+ const dirResult = await getStateDir(projectPath, stream, session);
7229
7476
  if (!dirResult.ok) return dirResult;
7230
7477
  const stateDir = dirResult.value;
7231
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
7232
- if (!fs6.existsSync(handoffPath)) {
7478
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
7479
+ if (!fs11.existsSync(handoffPath)) {
7233
7480
  return (0, import_types.Ok)(null);
7234
7481
  }
7235
- const raw = fs6.readFileSync(handoffPath, "utf-8");
7482
+ const raw = fs11.readFileSync(handoffPath, "utf-8");
7236
7483
  const parsed = JSON.parse(raw);
7237
7484
  const result = HandoffSchema.safeParse(parsed);
7238
7485
  if (!result.success) {
@@ -7245,73 +7492,82 @@ async function loadHandoff(projectPath, stream) {
7245
7492
  );
7246
7493
  }
7247
7494
  }
7495
+
7496
+ // src/state/mechanical-gate.ts
7497
+ var fs12 = __toESM(require("fs"));
7498
+ var path9 = __toESM(require("path"));
7499
+ var import_child_process2 = require("child_process");
7500
+ 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:.-]+$/;
7501
+ function loadChecksFromConfig(gateConfigPath) {
7502
+ if (!fs12.existsSync(gateConfigPath)) return [];
7503
+ const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
7504
+ const config = GateConfigSchema.safeParse(raw);
7505
+ if (config.success && config.data.checks) return config.data.checks;
7506
+ return [];
7507
+ }
7508
+ function discoverChecksFromProject(projectPath) {
7509
+ const checks = [];
7510
+ const packageJsonPath = path9.join(projectPath, "package.json");
7511
+ if (fs12.existsSync(packageJsonPath)) {
7512
+ const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
7513
+ const scripts = pkg.scripts || {};
7514
+ if (scripts.test) checks.push({ name: "test", command: "npm test" });
7515
+ if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
7516
+ if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
7517
+ if (scripts.build) checks.push({ name: "build", command: "npm run build" });
7518
+ }
7519
+ if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
7520
+ checks.push({ name: "test", command: "go test ./..." });
7521
+ checks.push({ name: "build", command: "go build ./..." });
7522
+ }
7523
+ if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
7524
+ checks.push({ name: "test", command: "python -m pytest" });
7525
+ }
7526
+ return checks;
7527
+ }
7528
+ function executeCheck(check, projectPath) {
7529
+ if (!SAFE_GATE_COMMAND.test(check.command)) {
7530
+ return {
7531
+ name: check.name,
7532
+ passed: false,
7533
+ command: check.command,
7534
+ output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
7535
+ duration: 0
7536
+ };
7537
+ }
7538
+ const start = Date.now();
7539
+ try {
7540
+ (0, import_child_process2.execSync)(check.command, {
7541
+ cwd: projectPath,
7542
+ stdio: "pipe",
7543
+ timeout: 12e4
7544
+ });
7545
+ return {
7546
+ name: check.name,
7547
+ passed: true,
7548
+ command: check.command,
7549
+ duration: Date.now() - start
7550
+ };
7551
+ } catch (error) {
7552
+ const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
7553
+ return {
7554
+ name: check.name,
7555
+ passed: false,
7556
+ command: check.command,
7557
+ output: output.slice(0, 2e3),
7558
+ duration: Date.now() - start
7559
+ };
7560
+ }
7561
+ }
7248
7562
  async function runMechanicalGate(projectPath) {
7249
- const harnessDir = path3.join(projectPath, HARNESS_DIR2);
7250
- const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
7563
+ const harnessDir = path9.join(projectPath, HARNESS_DIR);
7564
+ const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
7251
7565
  try {
7252
- let checks = [];
7253
- if (fs6.existsSync(gateConfigPath)) {
7254
- const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
7255
- const config = GateConfigSchema.safeParse(raw);
7256
- if (config.success && config.data.checks) {
7257
- checks = config.data.checks;
7258
- }
7259
- }
7566
+ let checks = loadChecksFromConfig(gateConfigPath);
7260
7567
  if (checks.length === 0) {
7261
- const packageJsonPath = path3.join(projectPath, "package.json");
7262
- if (fs6.existsSync(packageJsonPath)) {
7263
- const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
7264
- const scripts = pkg.scripts || {};
7265
- if (scripts.test) checks.push({ name: "test", command: "npm test" });
7266
- if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
7267
- if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
7268
- if (scripts.build) checks.push({ name: "build", command: "npm run build" });
7269
- }
7270
- if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
7271
- checks.push({ name: "test", command: "go test ./..." });
7272
- checks.push({ name: "build", command: "go build ./..." });
7273
- }
7274
- if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
7275
- checks.push({ name: "test", command: "python -m pytest" });
7276
- }
7277
- }
7278
- const results = [];
7279
- 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:.-]+$/;
7280
- for (const check of checks) {
7281
- if (!SAFE_GATE_COMMAND.test(check.command)) {
7282
- results.push({
7283
- name: check.name,
7284
- passed: false,
7285
- command: check.command,
7286
- output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
7287
- duration: 0
7288
- });
7289
- continue;
7290
- }
7291
- const start = Date.now();
7292
- try {
7293
- (0, import_child_process2.execSync)(check.command, {
7294
- cwd: projectPath,
7295
- stdio: "pipe",
7296
- timeout: 12e4
7297
- });
7298
- results.push({
7299
- name: check.name,
7300
- passed: true,
7301
- command: check.command,
7302
- duration: Date.now() - start
7303
- });
7304
- } catch (error) {
7305
- const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
7306
- results.push({
7307
- name: check.name,
7308
- passed: false,
7309
- command: check.command,
7310
- output: output.slice(0, 2e3),
7311
- duration: Date.now() - start
7312
- });
7313
- }
7568
+ checks = discoverChecksFromProject(projectPath);
7314
7569
  }
7570
+ const results = checks.map((check) => executeCheck(check, projectPath));
7315
7571
  return (0, import_types.Ok)({
7316
7572
  passed: results.length === 0 || results.every((r) => r.passed),
7317
7573
  checks: results
@@ -7325,6 +7581,96 @@ async function runMechanicalGate(projectPath) {
7325
7581
  }
7326
7582
  }
7327
7583
 
7584
+ // src/state/session-summary.ts
7585
+ var fs13 = __toESM(require("fs"));
7586
+ var path10 = __toESM(require("path"));
7587
+ function formatSummary(data) {
7588
+ const lines = [
7589
+ "## Session Summary",
7590
+ "",
7591
+ `**Session:** ${data.session}`,
7592
+ `**Last active:** ${data.lastActive}`,
7593
+ `**Skill:** ${data.skill}`
7594
+ ];
7595
+ if (data.phase) {
7596
+ lines.push(`**Phase:** ${data.phase}`);
7597
+ }
7598
+ lines.push(`**Status:** ${data.status}`);
7599
+ if (data.spec) {
7600
+ lines.push(`**Spec:** ${data.spec}`);
7601
+ }
7602
+ if (data.plan) {
7603
+ lines.push(`**Plan:** ${data.plan}`);
7604
+ }
7605
+ lines.push(`**Key context:** ${data.keyContext}`);
7606
+ lines.push(`**Next step:** ${data.nextStep}`);
7607
+ lines.push("");
7608
+ return lines.join("\n");
7609
+ }
7610
+ function deriveIndexDescription(data) {
7611
+ const skillShort = data.skill.replace("harness-", "");
7612
+ const parts = [skillShort];
7613
+ if (data.phase) {
7614
+ parts.push(`phase ${data.phase}`);
7615
+ }
7616
+ parts.push(data.status.toLowerCase());
7617
+ return parts.join(", ");
7618
+ }
7619
+ function writeSessionSummary(projectPath, sessionSlug, data) {
7620
+ try {
7621
+ const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
7622
+ if (!dirResult.ok) return dirResult;
7623
+ const sessionDir = dirResult.value;
7624
+ const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
7625
+ const content = formatSummary(data);
7626
+ fs13.writeFileSync(summaryPath, content);
7627
+ const description = deriveIndexDescription(data);
7628
+ updateSessionIndex(projectPath, sessionSlug, description);
7629
+ return (0, import_types.Ok)(void 0);
7630
+ } catch (error) {
7631
+ return (0, import_types.Err)(
7632
+ new Error(
7633
+ `Failed to write session summary: ${error instanceof Error ? error.message : String(error)}`
7634
+ )
7635
+ );
7636
+ }
7637
+ }
7638
+ function loadSessionSummary(projectPath, sessionSlug) {
7639
+ try {
7640
+ const dirResult = resolveSessionDir(projectPath, sessionSlug);
7641
+ if (!dirResult.ok) return dirResult;
7642
+ const sessionDir = dirResult.value;
7643
+ const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
7644
+ if (!fs13.existsSync(summaryPath)) {
7645
+ return (0, import_types.Ok)(null);
7646
+ }
7647
+ const content = fs13.readFileSync(summaryPath, "utf-8");
7648
+ return (0, import_types.Ok)(content);
7649
+ } catch (error) {
7650
+ return (0, import_types.Err)(
7651
+ new Error(
7652
+ `Failed to load session summary: ${error instanceof Error ? error.message : String(error)}`
7653
+ )
7654
+ );
7655
+ }
7656
+ }
7657
+ function listActiveSessions(projectPath) {
7658
+ try {
7659
+ const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
7660
+ if (!fs13.existsSync(indexPath2)) {
7661
+ return (0, import_types.Ok)(null);
7662
+ }
7663
+ const content = fs13.readFileSync(indexPath2, "utf-8");
7664
+ return (0, import_types.Ok)(content);
7665
+ } catch (error) {
7666
+ return (0, import_types.Err)(
7667
+ new Error(
7668
+ `Failed to list active sessions: ${error instanceof Error ? error.message : String(error)}`
7669
+ )
7670
+ );
7671
+ }
7672
+ }
7673
+
7328
7674
  // src/workflow/runner.ts
7329
7675
  async function executeWorkflow(workflow, executor) {
7330
7676
  const stepResults = [];
@@ -7474,7 +7820,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
7474
7820
  }
7475
7821
 
7476
7822
  // src/security/scanner.ts
7477
- var fs8 = __toESM(require("fs/promises"));
7823
+ var fs15 = __toESM(require("fs/promises"));
7478
7824
 
7479
7825
  // src/security/rules/registry.ts
7480
7826
  var RuleRegistry = class {
@@ -7561,15 +7907,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
7561
7907
  }
7562
7908
 
7563
7909
  // src/security/stack-detector.ts
7564
- var fs7 = __toESM(require("fs"));
7565
- var path4 = __toESM(require("path"));
7910
+ var fs14 = __toESM(require("fs"));
7911
+ var path11 = __toESM(require("path"));
7566
7912
  function detectStack(projectRoot) {
7567
7913
  const stacks = [];
7568
- const pkgJsonPath = path4.join(projectRoot, "package.json");
7569
- if (fs7.existsSync(pkgJsonPath)) {
7914
+ const pkgJsonPath = path11.join(projectRoot, "package.json");
7915
+ if (fs14.existsSync(pkgJsonPath)) {
7570
7916
  stacks.push("node");
7571
7917
  try {
7572
- const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
7918
+ const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
7573
7919
  const allDeps = {
7574
7920
  ...pkgJson.dependencies,
7575
7921
  ...pkgJson.devDependencies
@@ -7584,13 +7930,13 @@ function detectStack(projectRoot) {
7584
7930
  } catch {
7585
7931
  }
7586
7932
  }
7587
- const goModPath = path4.join(projectRoot, "go.mod");
7588
- if (fs7.existsSync(goModPath)) {
7933
+ const goModPath = path11.join(projectRoot, "go.mod");
7934
+ if (fs14.existsSync(goModPath)) {
7589
7935
  stacks.push("go");
7590
7936
  }
7591
- const requirementsPath = path4.join(projectRoot, "requirements.txt");
7592
- const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
7593
- if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
7937
+ const requirementsPath = path11.join(projectRoot, "requirements.txt");
7938
+ const pyprojectPath = path11.join(projectRoot, "pyproject.toml");
7939
+ if (fs14.existsSync(requirementsPath) || fs14.existsSync(pyprojectPath)) {
7594
7940
  stacks.push("python");
7595
7941
  }
7596
7942
  return stacks;
@@ -8017,7 +8363,7 @@ var SecurityScanner = class {
8017
8363
  }
8018
8364
  async scanFile(filePath) {
8019
8365
  if (!this.config.enabled) return [];
8020
- const content = await fs8.readFile(filePath, "utf-8");
8366
+ const content = await fs15.readFile(filePath, "utf-8");
8021
8367
  return this.scanContent(content, filePath, 1);
8022
8368
  }
8023
8369
  async scanFiles(filePaths) {
@@ -8042,7 +8388,7 @@ var SecurityScanner = class {
8042
8388
  };
8043
8389
 
8044
8390
  // src/ci/check-orchestrator.ts
8045
- var path5 = __toESM(require("path"));
8391
+ var path12 = __toESM(require("path"));
8046
8392
  var ALL_CHECKS = [
8047
8393
  "validate",
8048
8394
  "deps",
@@ -8059,7 +8405,7 @@ async function runSingleCheck(name, projectRoot, config) {
8059
8405
  try {
8060
8406
  switch (name) {
8061
8407
  case "validate": {
8062
- const agentsPath = path5.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8408
+ const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8063
8409
  const result = await validateAgentsMap(agentsPath);
8064
8410
  if (!result.ok) {
8065
8411
  issues.push({ severity: "error", message: result.error.message });
@@ -8114,7 +8460,7 @@ async function runSingleCheck(name, projectRoot, config) {
8114
8460
  break;
8115
8461
  }
8116
8462
  case "docs": {
8117
- const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
8463
+ const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
8118
8464
  const entropyConfig = config.entropy || {};
8119
8465
  const result = await checkDocCoverage("project", {
8120
8466
  docsDir,
@@ -8352,7 +8698,7 @@ async function runCIChecks(input) {
8352
8698
  }
8353
8699
 
8354
8700
  // src/review/mechanical-checks.ts
8355
- var path6 = __toESM(require("path"));
8701
+ var path13 = __toESM(require("path"));
8356
8702
  async function runMechanicalChecks(options) {
8357
8703
  const { projectRoot, config, skip = [], changedFiles } = options;
8358
8704
  const findings = [];
@@ -8364,7 +8710,7 @@ async function runMechanicalChecks(options) {
8364
8710
  };
8365
8711
  if (!skip.includes("validate")) {
8366
8712
  try {
8367
- const agentsPath = path6.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8713
+ const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8368
8714
  const result = await validateAgentsMap(agentsPath);
8369
8715
  if (!result.ok) {
8370
8716
  statuses.validate = "fail";
@@ -8401,7 +8747,7 @@ async function runMechanicalChecks(options) {
8401
8747
  statuses.validate = "fail";
8402
8748
  findings.push({
8403
8749
  tool: "validate",
8404
- file: path6.join(projectRoot, "AGENTS.md"),
8750
+ file: path13.join(projectRoot, "AGENTS.md"),
8405
8751
  message: err instanceof Error ? err.message : String(err),
8406
8752
  severity: "error"
8407
8753
  });
@@ -8465,7 +8811,7 @@ async function runMechanicalChecks(options) {
8465
8811
  (async () => {
8466
8812
  const localFindings = [];
8467
8813
  try {
8468
- const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
8814
+ const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
8469
8815
  const result = await checkDocCoverage("project", { docsDir });
8470
8816
  if (!result.ok) {
8471
8817
  statuses["check-docs"] = "warn";
@@ -8492,7 +8838,7 @@ async function runMechanicalChecks(options) {
8492
8838
  statuses["check-docs"] = "warn";
8493
8839
  localFindings.push({
8494
8840
  tool: "check-docs",
8495
- file: path6.join(projectRoot, "docs"),
8841
+ file: path13.join(projectRoot, "docs"),
8496
8842
  message: err instanceof Error ? err.message : String(err),
8497
8843
  severity: "warning"
8498
8844
  });
@@ -8640,7 +8986,7 @@ function detectChangeType(commitMessage, diff2) {
8640
8986
  }
8641
8987
 
8642
8988
  // src/review/context-scoper.ts
8643
- var path7 = __toESM(require("path"));
8989
+ var path14 = __toESM(require("path"));
8644
8990
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
8645
8991
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
8646
8992
  function computeContextBudget(diffLines) {
@@ -8648,18 +8994,18 @@ function computeContextBudget(diffLines) {
8648
8994
  return diffLines;
8649
8995
  }
8650
8996
  function isWithinProject(absPath, projectRoot) {
8651
- const resolvedRoot = path7.resolve(projectRoot) + path7.sep;
8652
- const resolvedPath = path7.resolve(absPath);
8653
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path7.resolve(projectRoot);
8997
+ const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
8998
+ const resolvedPath = path14.resolve(absPath);
8999
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
8654
9000
  }
8655
9001
  async function readContextFile(projectRoot, filePath, reason) {
8656
- const absPath = path7.isAbsolute(filePath) ? filePath : path7.join(projectRoot, filePath);
9002
+ const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
8657
9003
  if (!isWithinProject(absPath, projectRoot)) return null;
8658
9004
  const result = await readFileContent(absPath);
8659
9005
  if (!result.ok) return null;
8660
9006
  const content = result.value;
8661
9007
  const lines = content.split("\n").length;
8662
- const relPath = path7.isAbsolute(filePath) ? path7.relative(projectRoot, filePath) : filePath;
9008
+ const relPath = path14.isAbsolute(filePath) ? path14.relative(projectRoot, filePath) : filePath;
8663
9009
  return { path: relPath, content, reason, lines };
8664
9010
  }
8665
9011
  function extractImportSources2(content) {
@@ -8674,18 +9020,18 @@ function extractImportSources2(content) {
8674
9020
  }
8675
9021
  async function resolveImportPath2(projectRoot, fromFile, importSource) {
8676
9022
  if (!importSource.startsWith(".")) return null;
8677
- const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
8678
- const basePath = path7.resolve(fromDir, importSource);
9023
+ const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
9024
+ const basePath = path14.resolve(fromDir, importSource);
8679
9025
  if (!isWithinProject(basePath, projectRoot)) return null;
8680
- const relBase = path7.relative(projectRoot, basePath);
9026
+ const relBase = path14.relative(projectRoot, basePath);
8681
9027
  const candidates = [
8682
9028
  relBase + ".ts",
8683
9029
  relBase + ".tsx",
8684
9030
  relBase + ".mts",
8685
- path7.join(relBase, "index.ts")
9031
+ path14.join(relBase, "index.ts")
8686
9032
  ];
8687
9033
  for (const candidate of candidates) {
8688
- const absCandidate = path7.join(projectRoot, candidate);
9034
+ const absCandidate = path14.join(projectRoot, candidate);
8689
9035
  if (await fileExists(absCandidate)) {
8690
9036
  return candidate;
8691
9037
  }
@@ -8693,10 +9039,10 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
8693
9039
  return null;
8694
9040
  }
8695
9041
  async function findTestFiles(projectRoot, sourceFile) {
8696
- const baseName = path7.basename(sourceFile, path7.extname(sourceFile));
9042
+ const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
8697
9043
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
8698
9044
  const results = await findFiles(pattern, projectRoot);
8699
- return results.map((f) => path7.relative(projectRoot, f));
9045
+ return results.map((f) => path14.relative(projectRoot, f));
8700
9046
  }
8701
9047
  async function gatherImportContext(projectRoot, changedFiles, budget) {
8702
9048
  const contextFiles = [];
@@ -9484,7 +9830,7 @@ async function fanOutReview(options) {
9484
9830
  }
9485
9831
 
9486
9832
  // src/review/validate-findings.ts
9487
- var path8 = __toESM(require("path"));
9833
+ var path15 = __toESM(require("path"));
9488
9834
  var DOWNGRADE_MAP = {
9489
9835
  critical: "important",
9490
9836
  important: "suggestion",
@@ -9505,7 +9851,7 @@ function normalizePath(filePath, projectRoot) {
9505
9851
  let normalized = filePath;
9506
9852
  normalized = normalized.replace(/\\/g, "/");
9507
9853
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
9508
- if (path8.isAbsolute(normalized)) {
9854
+ if (path15.isAbsolute(normalized)) {
9509
9855
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
9510
9856
  if (normalized.startsWith(root)) {
9511
9857
  normalized = normalized.slice(root.length);
@@ -9530,12 +9876,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
9530
9876
  while ((match = importRegex.exec(content)) !== null) {
9531
9877
  const importPath = match[1];
9532
9878
  if (!importPath.startsWith(".")) continue;
9533
- const dir = path8.dirname(current.file);
9534
- let resolved = path8.join(dir, importPath).replace(/\\/g, "/");
9879
+ const dir = path15.dirname(current.file);
9880
+ let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
9535
9881
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
9536
9882
  resolved += ".ts";
9537
9883
  }
9538
- resolved = path8.normalize(resolved).replace(/\\/g, "/");
9884
+ resolved = path15.normalize(resolved).replace(/\\/g, "/");
9539
9885
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
9540
9886
  queue.push({ file: resolved, depth: current.depth + 1 });
9541
9887
  }
@@ -9552,7 +9898,7 @@ async function validateFindings(options) {
9552
9898
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
9553
9899
  continue;
9554
9900
  }
9555
- const absoluteFile = path8.isAbsolute(finding.file) ? finding.file : path8.join(projectRoot, finding.file).replace(/\\/g, "/");
9901
+ const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
9556
9902
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
9557
9903
  continue;
9558
9904
  }
@@ -10035,7 +10381,7 @@ async function runReviewPipeline(options) {
10035
10381
  }
10036
10382
 
10037
10383
  // src/roadmap/parse.ts
10038
- var import_types16 = require("@harness-engineering/types");
10384
+ var import_types18 = require("@harness-engineering/types");
10039
10385
  var VALID_STATUSES = /* @__PURE__ */ new Set([
10040
10386
  "backlog",
10041
10387
  "planned",
@@ -10047,14 +10393,14 @@ var EM_DASH = "\u2014";
10047
10393
  function parseRoadmap(markdown) {
10048
10394
  const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
10049
10395
  if (!fmMatch) {
10050
- return (0, import_types16.Err)(new Error("Missing or malformed YAML frontmatter"));
10396
+ return (0, import_types18.Err)(new Error("Missing or malformed YAML frontmatter"));
10051
10397
  }
10052
10398
  const fmResult = parseFrontmatter(fmMatch[1]);
10053
10399
  if (!fmResult.ok) return fmResult;
10054
10400
  const body = markdown.slice(fmMatch[0].length);
10055
10401
  const milestonesResult = parseMilestones(body);
10056
10402
  if (!milestonesResult.ok) return milestonesResult;
10057
- return (0, import_types16.Ok)({
10403
+ return (0, import_types18.Ok)({
10058
10404
  frontmatter: fmResult.value,
10059
10405
  milestones: milestonesResult.value
10060
10406
  });
@@ -10073,8 +10419,10 @@ function parseFrontmatter(raw) {
10073
10419
  const versionStr = map.get("version");
10074
10420
  const lastSynced = map.get("last_synced");
10075
10421
  const lastManualEdit = map.get("last_manual_edit");
10422
+ const created = map.get("created");
10423
+ const updated = map.get("updated");
10076
10424
  if (!project || !versionStr || !lastSynced || !lastManualEdit) {
10077
- return (0, import_types16.Err)(
10425
+ return (0, import_types18.Err)(
10078
10426
  new Error(
10079
10427
  "Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
10080
10428
  )
@@ -10082,9 +10430,12 @@ function parseFrontmatter(raw) {
10082
10430
  }
10083
10431
  const version = parseInt(versionStr, 10);
10084
10432
  if (isNaN(version)) {
10085
- return (0, import_types16.Err)(new Error("Frontmatter version must be a number"));
10433
+ return (0, import_types18.Err)(new Error("Frontmatter version must be a number"));
10086
10434
  }
10087
- return (0, import_types16.Ok)({ project, version, lastSynced, lastManualEdit });
10435
+ const fm = { project, version, lastSynced, lastManualEdit };
10436
+ if (created) fm.created = created;
10437
+ if (updated) fm.updated = updated;
10438
+ return (0, import_types18.Ok)(fm);
10088
10439
  }
10089
10440
  function parseMilestones(body) {
10090
10441
  const milestones = [];
@@ -10092,12 +10443,12 @@ function parseMilestones(body) {
10092
10443
  const h2Matches = [];
10093
10444
  let match;
10094
10445
  while ((match = h2Pattern.exec(body)) !== null) {
10095
- h2Matches.push({ heading: match[1], startIndex: match.index });
10446
+ h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
10096
10447
  }
10097
10448
  for (let i = 0; i < h2Matches.length; i++) {
10098
10449
  const h2 = h2Matches[i];
10099
10450
  const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
10100
- const sectionBody = body.slice(h2.startIndex + h2.heading.length + 4, nextStart);
10451
+ const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
10101
10452
  const isBacklog = h2.heading === "Backlog";
10102
10453
  const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
10103
10454
  const featuresResult = parseFeatures(sectionBody);
@@ -10108,28 +10459,25 @@ function parseMilestones(body) {
10108
10459
  features: featuresResult.value
10109
10460
  });
10110
10461
  }
10111
- return (0, import_types16.Ok)(milestones);
10462
+ return (0, import_types18.Ok)(milestones);
10112
10463
  }
10113
10464
  function parseFeatures(sectionBody) {
10114
10465
  const features = [];
10115
- const h3Pattern = /^### Feature: (.+)$/gm;
10466
+ const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
10116
10467
  const h3Matches = [];
10117
10468
  let match;
10118
10469
  while ((match = h3Pattern.exec(sectionBody)) !== null) {
10119
- h3Matches.push({ name: match[1], startIndex: match.index });
10470
+ h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
10120
10471
  }
10121
10472
  for (let i = 0; i < h3Matches.length; i++) {
10122
10473
  const h3 = h3Matches[i];
10123
10474
  const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
10124
- const featureBody = sectionBody.slice(
10125
- h3.startIndex + `### Feature: ${h3.name}`.length,
10126
- nextStart
10127
- );
10475
+ const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
10128
10476
  const featureResult = parseFeatureFields(h3.name, featureBody);
10129
10477
  if (!featureResult.ok) return featureResult;
10130
10478
  features.push(featureResult.value);
10131
10479
  }
10132
- return (0, import_types16.Ok)(features);
10480
+ return (0, import_types18.Ok)(features);
10133
10481
  }
10134
10482
  function parseFeatureFields(name, body) {
10135
10483
  const fieldMap = /* @__PURE__ */ new Map();
@@ -10140,7 +10488,7 @@ function parseFeatureFields(name, body) {
10140
10488
  }
10141
10489
  const statusRaw = fieldMap.get("Status");
10142
10490
  if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
10143
- return (0, import_types16.Err)(
10491
+ return (0, import_types18.Err)(
10144
10492
  new Error(
10145
10493
  `Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
10146
10494
  )
@@ -10149,12 +10497,12 @@ function parseFeatureFields(name, body) {
10149
10497
  const status = statusRaw;
10150
10498
  const specRaw = fieldMap.get("Spec") ?? EM_DASH;
10151
10499
  const spec = specRaw === EM_DASH ? null : specRaw;
10152
- const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
10153
- const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
10154
- const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
10155
- const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
10500
+ const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
10501
+ const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
10502
+ const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
10503
+ const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
10156
10504
  const summary = fieldMap.get("Summary") ?? "";
10157
- return (0, import_types16.Ok)({ name, status, spec, plans, blockedBy, summary });
10505
+ return (0, import_types18.Ok)({ name, status, spec, plans, blockedBy, summary });
10158
10506
  }
10159
10507
 
10160
10508
  // src/roadmap/serialize.ts
@@ -10164,11 +10512,17 @@ function serializeRoadmap(roadmap) {
10164
10512
  lines.push("---");
10165
10513
  lines.push(`project: ${roadmap.frontmatter.project}`);
10166
10514
  lines.push(`version: ${roadmap.frontmatter.version}`);
10515
+ if (roadmap.frontmatter.created) {
10516
+ lines.push(`created: ${roadmap.frontmatter.created}`);
10517
+ }
10518
+ if (roadmap.frontmatter.updated) {
10519
+ lines.push(`updated: ${roadmap.frontmatter.updated}`);
10520
+ }
10167
10521
  lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
10168
10522
  lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
10169
10523
  lines.push("---");
10170
10524
  lines.push("");
10171
- lines.push("# Project Roadmap");
10525
+ lines.push("# Roadmap");
10172
10526
  for (const milestone of roadmap.milestones) {
10173
10527
  lines.push("");
10174
10528
  lines.push(serializeMilestoneHeading(milestone));
@@ -10181,26 +10535,27 @@ function serializeRoadmap(roadmap) {
10181
10535
  return lines.join("\n");
10182
10536
  }
10183
10537
  function serializeMilestoneHeading(milestone) {
10184
- return milestone.isBacklog ? "## Backlog" : `## Milestone: ${milestone.name}`;
10538
+ return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
10185
10539
  }
10186
10540
  function serializeFeature(feature) {
10187
10541
  const spec = feature.spec ?? EM_DASH2;
10188
10542
  const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
10189
10543
  const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
10190
10544
  return [
10191
- `### Feature: ${feature.name}`,
10545
+ `### ${feature.name}`,
10546
+ "",
10192
10547
  `- **Status:** ${feature.status}`,
10193
10548
  `- **Spec:** ${spec}`,
10194
- `- **Plans:** ${plans}`,
10195
- `- **Blocked by:** ${blockedBy}`,
10196
- `- **Summary:** ${feature.summary}`
10549
+ `- **Summary:** ${feature.summary}`,
10550
+ `- **Blockers:** ${blockedBy}`,
10551
+ `- **Plan:** ${plans}`
10197
10552
  ];
10198
10553
  }
10199
10554
 
10200
10555
  // src/roadmap/sync.ts
10201
- var fs9 = __toESM(require("fs"));
10202
- var path9 = __toESM(require("path"));
10203
- var import_types17 = require("@harness-engineering/types");
10556
+ var fs16 = __toESM(require("fs"));
10557
+ var path16 = __toESM(require("path"));
10558
+ var import_types19 = require("@harness-engineering/types");
10204
10559
  function inferStatus(feature, projectPath, allFeatures) {
10205
10560
  if (feature.blockedBy.length > 0) {
10206
10561
  const blockerNotDone = feature.blockedBy.some((blockerName) => {
@@ -10214,10 +10569,10 @@ function inferStatus(feature, projectPath, allFeatures) {
10214
10569
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
10215
10570
  const useRootState = featuresWithPlans.length <= 1;
10216
10571
  if (useRootState) {
10217
- const rootStatePath = path9.join(projectPath, ".harness", "state.json");
10218
- if (fs9.existsSync(rootStatePath)) {
10572
+ const rootStatePath = path16.join(projectPath, ".harness", "state.json");
10573
+ if (fs16.existsSync(rootStatePath)) {
10219
10574
  try {
10220
- const raw = fs9.readFileSync(rootStatePath, "utf-8");
10575
+ const raw = fs16.readFileSync(rootStatePath, "utf-8");
10221
10576
  const state = JSON.parse(raw);
10222
10577
  if (state.progress) {
10223
10578
  for (const status of Object.values(state.progress)) {
@@ -10228,16 +10583,16 @@ function inferStatus(feature, projectPath, allFeatures) {
10228
10583
  }
10229
10584
  }
10230
10585
  }
10231
- const sessionsDir = path9.join(projectPath, ".harness", "sessions");
10232
- if (fs9.existsSync(sessionsDir)) {
10586
+ const sessionsDir = path16.join(projectPath, ".harness", "sessions");
10587
+ if (fs16.existsSync(sessionsDir)) {
10233
10588
  try {
10234
- const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
10589
+ const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
10235
10590
  for (const entry of sessionDirs) {
10236
10591
  if (!entry.isDirectory()) continue;
10237
- const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
10238
- if (!fs9.existsSync(autopilotPath)) continue;
10592
+ const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
10593
+ if (!fs16.existsSync(autopilotPath)) continue;
10239
10594
  try {
10240
- const raw = fs9.readFileSync(autopilotPath, "utf-8");
10595
+ const raw = fs16.readFileSync(autopilotPath, "utf-8");
10241
10596
  const autopilot = JSON.parse(raw);
10242
10597
  if (!autopilot.phases) continue;
10243
10598
  const linkedPhases = autopilot.phases.filter(
@@ -10284,7 +10639,7 @@ function syncRoadmap(options) {
10284
10639
  to: inferred
10285
10640
  });
10286
10641
  }
10287
- return (0, import_types17.Ok)(changes);
10642
+ return (0, import_types19.Ok)(changes);
10288
10643
  }
10289
10644
 
10290
10645
  // src/interaction/types.ts
@@ -10317,17 +10672,17 @@ var EmitInteractionInputSchema = import_zod7.z.object({
10317
10672
  });
10318
10673
 
10319
10674
  // src/blueprint/scanner.ts
10320
- var fs10 = __toESM(require("fs/promises"));
10321
- var path10 = __toESM(require("path"));
10675
+ var fs17 = __toESM(require("fs/promises"));
10676
+ var path17 = __toESM(require("path"));
10322
10677
  var ProjectScanner = class {
10323
10678
  constructor(rootDir) {
10324
10679
  this.rootDir = rootDir;
10325
10680
  }
10326
10681
  async scan() {
10327
- let projectName = path10.basename(this.rootDir);
10682
+ let projectName = path17.basename(this.rootDir);
10328
10683
  try {
10329
- const pkgPath = path10.join(this.rootDir, "package.json");
10330
- const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
10684
+ const pkgPath = path17.join(this.rootDir, "package.json");
10685
+ const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
10331
10686
  const pkg = JSON.parse(pkgRaw);
10332
10687
  if (pkg.name) projectName = pkg.name;
10333
10688
  } catch {
@@ -10368,8 +10723,8 @@ var ProjectScanner = class {
10368
10723
  };
10369
10724
 
10370
10725
  // src/blueprint/generator.ts
10371
- var fs11 = __toESM(require("fs/promises"));
10372
- var path11 = __toESM(require("path"));
10726
+ var fs18 = __toESM(require("fs/promises"));
10727
+ var path18 = __toESM(require("path"));
10373
10728
  var ejs = __toESM(require("ejs"));
10374
10729
 
10375
10730
  // src/blueprint/templates.ts
@@ -10453,19 +10808,19 @@ var BlueprintGenerator = class {
10453
10808
  styles: STYLES,
10454
10809
  scripts: SCRIPTS
10455
10810
  });
10456
- await fs11.mkdir(options.outputDir, { recursive: true });
10457
- await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
10811
+ await fs18.mkdir(options.outputDir, { recursive: true });
10812
+ await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
10458
10813
  }
10459
10814
  };
10460
10815
 
10461
10816
  // src/update-checker.ts
10462
- var fs12 = __toESM(require("fs"));
10463
- var path12 = __toESM(require("path"));
10817
+ var fs19 = __toESM(require("fs"));
10818
+ var path19 = __toESM(require("path"));
10464
10819
  var os = __toESM(require("os"));
10465
10820
  var import_child_process3 = require("child_process");
10466
10821
  function getStatePath() {
10467
10822
  const home = process.env["HOME"] || os.homedir();
10468
- return path12.join(home, ".harness", "update-check.json");
10823
+ return path19.join(home, ".harness", "update-check.json");
10469
10824
  }
10470
10825
  function isUpdateCheckEnabled(configInterval) {
10471
10826
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -10478,7 +10833,7 @@ function shouldRunCheck(state, intervalMs) {
10478
10833
  }
10479
10834
  function readCheckState() {
10480
10835
  try {
10481
- const raw = fs12.readFileSync(getStatePath(), "utf-8");
10836
+ const raw = fs19.readFileSync(getStatePath(), "utf-8");
10482
10837
  const parsed = JSON.parse(raw);
10483
10838
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
10484
10839
  const state = parsed;
@@ -10495,7 +10850,7 @@ function readCheckState() {
10495
10850
  }
10496
10851
  function spawnBackgroundCheck(currentVersion) {
10497
10852
  const statePath = getStatePath();
10498
- const stateDir = path12.dirname(statePath);
10853
+ const stateDir = path19.dirname(statePath);
10499
10854
  const script = `
10500
10855
  const { execSync } = require('child_process');
10501
10856
  const fs = require('fs');
@@ -10627,6 +10982,7 @@ var VERSION = "0.11.0";
10627
10982
  ViolationSchema,
10628
10983
  addProvenance,
10629
10984
  analyzeDiff,
10985
+ analyzeLearningPatterns,
10630
10986
  appendFailure,
10631
10987
  appendLearning,
10632
10988
  applyFixes,
@@ -10635,6 +10991,7 @@ var VERSION = "0.11.0";
10635
10991
  archModule,
10636
10992
  architecture,
10637
10993
  archiveFailures,
10994
+ archiveLearnings,
10638
10995
  archiveStream,
10639
10996
  buildDependencyGraph,
10640
10997
  buildExclusionSet,
@@ -10642,6 +10999,8 @@ var VERSION = "0.11.0";
10642
10999
  checkDocCoverage,
10643
11000
  checkEligibility,
10644
11001
  classifyFinding,
11002
+ clearFailuresCache,
11003
+ clearLearningsCache,
10645
11004
  configureFeedback,
10646
11005
  constraintRuleId,
10647
11006
  contextBudget,
@@ -10697,16 +11056,20 @@ var VERSION = "0.11.0";
10697
11056
  injectionRules,
10698
11057
  isSmallSuggestion,
10699
11058
  isUpdateCheckEnabled,
11059
+ listActiveSessions,
10700
11060
  listStreams,
11061
+ loadBudgetedLearnings,
10701
11062
  loadFailures,
10702
11063
  loadHandoff,
10703
11064
  loadRelevantLearnings,
11065
+ loadSessionSummary,
10704
11066
  loadState,
10705
11067
  loadStreamIndex,
10706
11068
  logAgentAction,
10707
11069
  migrateToStreams,
10708
11070
  networkRules,
10709
11071
  nodeRules,
11072
+ parseDateFromEntry,
10710
11073
  parseDiff,
10711
11074
  parseManifest,
10712
11075
  parseRoadmap,
@@ -10714,6 +11077,7 @@ var VERSION = "0.11.0";
10714
11077
  parseSize,
10715
11078
  pathTraversalRules,
10716
11079
  previewFix,
11080
+ pruneLearnings,
10717
11081
  reactRules,
10718
11082
  readCheckState,
10719
11083
  readLockfile,
@@ -10725,6 +11089,7 @@ var VERSION = "0.11.0";
10725
11089
  resolveFileToLayer,
10726
11090
  resolveModelTier,
10727
11091
  resolveRuleSeverity,
11092
+ resolveSessionDir,
10728
11093
  resolveStreamPath,
10729
11094
  resolveThresholds,
10730
11095
  runAll,
@@ -10751,6 +11116,7 @@ var VERSION = "0.11.0";
10751
11116
  syncRoadmap,
10752
11117
  touchStream,
10753
11118
  trackAction,
11119
+ updateSessionIndex,
10754
11120
  validateAgentsMap,
10755
11121
  validateBoundaries,
10756
11122
  validateCommitMessage,
@@ -10763,6 +11129,7 @@ var VERSION = "0.11.0";
10763
11129
  violationId,
10764
11130
  writeConfig,
10765
11131
  writeLockfile,
11132
+ writeSessionSummary,
10766
11133
  xssRules,
10767
11134
  ...require("@harness-engineering/types")
10768
11135
  });