@harness-engineering/core 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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);
@@ -261,20 +273,21 @@ var import_types = require("@harness-engineering/types");
261
273
  // src/shared/fs-utils.ts
262
274
  var import_fs = require("fs");
263
275
  var import_util = require("util");
276
+ var import_node_path = require("path");
264
277
  var import_glob = require("glob");
265
278
  var accessAsync = (0, import_util.promisify)(import_fs.access);
266
279
  var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
267
- async function fileExists(path13) {
280
+ async function fileExists(path20) {
268
281
  try {
269
- await accessAsync(path13, import_fs.constants.F_OK);
282
+ await accessAsync(path20, import_fs.constants.F_OK);
270
283
  return true;
271
284
  } catch {
272
285
  return false;
273
286
  }
274
287
  }
275
- async function readFileContent(path13) {
288
+ async function readFileContent(path20) {
276
289
  try {
277
- const content = await readFileAsync(path13, "utf-8");
290
+ const content = await readFileAsync(path20, "utf-8");
278
291
  return (0, import_types.Ok)(content);
279
292
  } catch (error) {
280
293
  return (0, import_types.Err)(error);
@@ -283,6 +296,9 @@ async function readFileContent(path13) {
283
296
  async function findFiles(pattern, cwd = process.cwd()) {
284
297
  return (0, import_glob.glob)(pattern, { cwd, absolute: true });
285
298
  }
299
+ function relativePosix(from, to) {
300
+ return (0, import_node_path.relative)(from, to).replaceAll("\\", "/");
301
+ }
286
302
 
287
303
  // src/validation/file-structure.ts
288
304
  async function validateFileStructure(projectPath, conventions) {
@@ -322,15 +338,15 @@ function validateConfig(data, schema) {
322
338
  let message = "Configuration validation failed";
323
339
  const suggestions = [];
324
340
  if (firstError) {
325
- const path13 = firstError.path.join(".");
326
- const pathDisplay = path13 ? ` at "${path13}"` : "";
341
+ const path20 = firstError.path.join(".");
342
+ const pathDisplay = path20 ? ` at "${path20}"` : "";
327
343
  if (firstError.code === "invalid_type") {
328
344
  const received = firstError.received;
329
345
  const expected = firstError.expected;
330
346
  if (received === "undefined") {
331
347
  code = "MISSING_FIELD";
332
348
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
333
- suggestions.push(`Field "${path13}" is required and must be of type "${expected}"`);
349
+ suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
334
350
  } else {
335
351
  code = "INVALID_TYPE";
336
352
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -543,30 +559,27 @@ function extractSections(content) {
543
559
  return result;
544
560
  });
545
561
  }
546
- function isExternalLink(path13) {
547
- return path13.startsWith("http://") || path13.startsWith("https://") || path13.startsWith("#") || path13.startsWith("mailto:");
562
+ function isExternalLink(path20) {
563
+ return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
548
564
  }
549
565
  function resolveLinkPath(linkPath, baseDir) {
550
566
  return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
551
567
  }
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);
568
+ async function validateAgentsMap(path20 = "./AGENTS.md") {
569
+ const contentResult = await readFileContent(path20);
557
570
  if (!contentResult.ok) {
558
571
  return (0, import_types.Err)(
559
572
  createError(
560
573
  "PARSE_ERROR",
561
574
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
562
- { path: path13 },
575
+ { path: path20 },
563
576
  ["Ensure the file exists", "Check file permissions"]
564
577
  )
565
578
  );
566
579
  }
567
580
  const content = contentResult.value;
568
581
  const sections = extractSections(content);
569
- const baseDir = (0, import_path.dirname)(path13);
582
+ const baseDir = (0, import_path.dirname)(path20);
570
583
  const sectionTitles = sections.map((s) => s.title);
571
584
  const missingSections = REQUIRED_SECTIONS.filter(
572
585
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -647,7 +660,7 @@ async function checkDocCoverage(domain, options = {}) {
647
660
  try {
648
661
  const sourceFiles = await findFiles("**/*.{ts,js,tsx,jsx}", sourceDir);
649
662
  const filteredSourceFiles = sourceFiles.filter((file) => {
650
- const relativePath = (0, import_path2.relative)(sourceDir, file);
663
+ const relativePath = relativePosix(sourceDir, file);
651
664
  return !excludePatterns.some((pattern) => {
652
665
  return (0, import_minimatch.minimatch)(relativePath, pattern, { dot: true }) || (0, import_minimatch.minimatch)(file, pattern, { dot: true });
653
666
  });
@@ -670,7 +683,7 @@ async function checkDocCoverage(domain, options = {}) {
670
683
  const undocumented = [];
671
684
  const gaps = [];
672
685
  for (const sourceFile of filteredSourceFiles) {
673
- const relativePath = (0, import_path2.relative)(sourceDir, sourceFile);
686
+ const relativePath = relativePosix(sourceDir, sourceFile);
674
687
  const fileName = (0, import_path2.basename)(sourceFile);
675
688
  const isDocumented = documentedPaths.has(relativePath) || documentedPaths.has(fileName) || documentedPaths.has(`src/${relativePath}`);
676
689
  if (isDocumented) {
@@ -707,8 +720,8 @@ async function checkDocCoverage(domain, options = {}) {
707
720
 
708
721
  // src/context/knowledge-map.ts
709
722
  var import_path3 = require("path");
710
- function suggestFix(path13, existingFiles) {
711
- const targetName = (0, import_path3.basename)(path13).toLowerCase();
723
+ function suggestFix(path20, existingFiles) {
724
+ const targetName = (0, import_path3.basename)(path20).toLowerCase();
712
725
  const similar = existingFiles.find((file) => {
713
726
  const fileName = (0, import_path3.basename)(file).toLowerCase();
714
727
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -716,12 +729,9 @@ function suggestFix(path13, existingFiles) {
716
729
  if (similar) {
717
730
  return `Did you mean "${similar}"?`;
718
731
  }
719
- return `Create the file "${path13}" or remove the link`;
732
+ return `Create the file "${path20}" or remove the link`;
720
733
  }
721
734
  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
735
  const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
726
736
  const agentsResult = await validateAgentsMap(agentsPath);
727
737
  if (!agentsResult.ok) {
@@ -733,7 +743,7 @@ async function validateKnowledgeMap(rootDir = process.cwd()) {
733
743
  totalLinks: agentsTotalLinks
734
744
  } = agentsResult.value;
735
745
  const existingFiles = await findFiles("**/*", rootDir);
736
- const relativeExistingFiles = existingFiles.map((f) => (0, import_path3.relative)(rootDir, f));
746
+ const relativeExistingFiles = existingFiles.map((f) => relativePosix(rootDir, f));
737
747
  const brokenLinks = agentsBrokenLinks.map((link) => {
738
748
  const section = sections.find(
739
749
  (s) => s.links.some((l) => l.path === link.path && l.line === link.line)
@@ -774,7 +784,7 @@ var DEFAULT_SECTIONS = [
774
784
  function groupByDirectory(files, rootDir) {
775
785
  const groups = /* @__PURE__ */ new Map();
776
786
  for (const file of files) {
777
- const relativePath = (0, import_path4.relative)(rootDir, file);
787
+ const relativePath = relativePosix(rootDir, file);
778
788
  const dir = (0, import_path4.dirname)(relativePath);
779
789
  if (!groups.has(dir)) {
780
790
  groups.set(dir, []);
@@ -830,7 +840,7 @@ async function generateAgentsMap(config, graphSections) {
830
840
  allFiles.push(...files);
831
841
  }
832
842
  const filteredFiles = allFiles.filter((file) => {
833
- const relativePath = (0, import_path4.relative)(rootDir, file);
843
+ const relativePath = relativePosix(rootDir, file);
834
844
  return !matchesExcludePattern(relativePath, excludePaths);
835
845
  });
836
846
  lines.push("## Repository Structure");
@@ -858,11 +868,11 @@ async function generateAgentsMap(config, graphSections) {
858
868
  }
859
869
  const sectionFiles = await findFiles(section.pattern, rootDir);
860
870
  const filteredSectionFiles = sectionFiles.filter((file) => {
861
- const relativePath = (0, import_path4.relative)(rootDir, file);
871
+ const relativePath = relativePosix(rootDir, file);
862
872
  return !matchesExcludePattern(relativePath, excludePaths);
863
873
  });
864
874
  for (const file of filteredSectionFiles.slice(0, 20)) {
865
- lines.push(formatFileLink((0, import_path4.relative)(rootDir, file)));
875
+ lines.push(formatFileLink(relativePosix(rootDir, file)));
866
876
  }
867
877
  if (filteredSectionFiles.length > 20) {
868
878
  lines.push(`- _... and ${filteredSectionFiles.length - 20} more files_`);
@@ -1133,8 +1143,8 @@ async function buildDependencyGraph(files, parser, graphDependencyData) {
1133
1143
  function checkLayerViolations(graph, layers, rootDir) {
1134
1144
  const violations = [];
1135
1145
  for (const edge of graph.edges) {
1136
- const fromRelative = (0, import_path5.relative)(rootDir, edge.from);
1137
- const toRelative = (0, import_path5.relative)(rootDir, edge.to);
1146
+ const fromRelative = relativePosix(rootDir, edge.from);
1147
+ const toRelative = relativePosix(rootDir, edge.to);
1138
1148
  const fromLayer = resolveFileToLayer(fromRelative, layers);
1139
1149
  const toLayer = resolveFileToLayer(toRelative, layers);
1140
1150
  if (!fromLayer || !toLayer) continue;
@@ -1319,8 +1329,8 @@ function createBoundaryValidator(schema, name) {
1319
1329
  return (0, import_types.Ok)(result.data);
1320
1330
  }
1321
1331
  const suggestions = result.error.issues.map((issue) => {
1322
- const path13 = issue.path.join(".");
1323
- return path13 ? `${path13}: ${issue.message}` : issue.message;
1332
+ const path20 = issue.path.join(".");
1333
+ return path20 ? `${path20}: ${issue.message}` : issue.message;
1324
1334
  });
1325
1335
  return (0, import_types.Err)(
1326
1336
  createError(
@@ -1874,11 +1884,11 @@ function walk(node, visitor) {
1874
1884
  var TypeScriptParser = class {
1875
1885
  name = "typescript";
1876
1886
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1877
- async parseFile(path13) {
1878
- const contentResult = await readFileContent(path13);
1887
+ async parseFile(path20) {
1888
+ const contentResult = await readFileContent(path20);
1879
1889
  if (!contentResult.ok) {
1880
1890
  return (0, import_types.Err)(
1881
- createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
1891
+ createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
1882
1892
  "Check that the file exists",
1883
1893
  "Verify the path is correct"
1884
1894
  ])
@@ -1888,7 +1898,7 @@ var TypeScriptParser = class {
1888
1898
  const ast = (0, import_typescript_estree.parse)(contentResult.value, {
1889
1899
  loc: true,
1890
1900
  range: true,
1891
- jsx: path13.endsWith(".tsx"),
1901
+ jsx: path20.endsWith(".tsx"),
1892
1902
  errorOnUnknownASTType: false
1893
1903
  });
1894
1904
  return (0, import_types.Ok)({
@@ -1899,7 +1909,7 @@ var TypeScriptParser = class {
1899
1909
  } catch (e) {
1900
1910
  const error = e;
1901
1911
  return (0, import_types.Err)(
1902
- createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
1912
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
1903
1913
  "Check for syntax errors in the file",
1904
1914
  "Ensure valid TypeScript syntax"
1905
1915
  ])
@@ -2183,22 +2193,22 @@ function extractInlineRefs(content) {
2183
2193
  }
2184
2194
  return refs;
2185
2195
  }
2186
- async function parseDocumentationFile(path13) {
2187
- const contentResult = await readFileContent(path13);
2196
+ async function parseDocumentationFile(path20) {
2197
+ const contentResult = await readFileContent(path20);
2188
2198
  if (!contentResult.ok) {
2189
2199
  return (0, import_types.Err)(
2190
2200
  createEntropyError(
2191
2201
  "PARSE_ERROR",
2192
- `Failed to read documentation file: ${path13}`,
2193
- { file: path13 },
2202
+ `Failed to read documentation file: ${path20}`,
2203
+ { file: path20 },
2194
2204
  ["Check that the file exists"]
2195
2205
  )
2196
2206
  );
2197
2207
  }
2198
2208
  const content = contentResult.value;
2199
- const type = path13.endsWith(".md") ? "markdown" : "text";
2209
+ const type = path20.endsWith(".md") ? "markdown" : "text";
2200
2210
  return (0, import_types.Ok)({
2201
- path: path13,
2211
+ path: path20,
2202
2212
  type,
2203
2213
  content,
2204
2214
  codeBlocks: extractCodeBlocks(content),
@@ -2329,7 +2339,7 @@ async function buildSnapshot(config) {
2329
2339
  sourceFilePaths.push(...files2);
2330
2340
  }
2331
2341
  sourceFilePaths = sourceFilePaths.filter((f) => {
2332
- const rel = (0, import_path6.relative)(rootDir, f);
2342
+ const rel = relativePosix(rootDir, f);
2333
2343
  return !excludePatterns.some((p) => (0, import_minimatch3.minimatch)(rel, p));
2334
2344
  });
2335
2345
  const files = [];
@@ -2861,9 +2871,8 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
2861
2871
 
2862
2872
  // src/entropy/detectors/patterns.ts
2863
2873
  var import_minimatch4 = require("minimatch");
2864
- var import_path9 = require("path");
2865
2874
  function fileMatchesPattern(filePath, pattern, rootDir) {
2866
- const relativePath = (0, import_path9.relative)(rootDir, filePath);
2875
+ const relativePath = relativePosix(rootDir, filePath);
2867
2876
  return (0, import_minimatch4.minimatch)(relativePath, pattern);
2868
2877
  }
2869
2878
  function checkConfigPattern(pattern, file, rootDir) {
@@ -3009,15 +3018,34 @@ async function detectPatternViolations(snapshot, config) {
3009
3018
  }
3010
3019
  }
3011
3020
  }
3021
+ if (config?.customPatterns) {
3022
+ for (const file of snapshot.files) {
3023
+ for (const custom of config.customPatterns) {
3024
+ const matches = custom.check(file, snapshot);
3025
+ for (const match of matches) {
3026
+ violations.push({
3027
+ pattern: custom.name,
3028
+ file: file.path,
3029
+ line: match.line,
3030
+ message: match.message,
3031
+ suggestion: match.suggestion || "Review and fix this pattern violation",
3032
+ severity: custom.severity
3033
+ });
3034
+ }
3035
+ }
3036
+ }
3037
+ }
3012
3038
  const errorCount = violations.filter((v) => v.severity === "error").length;
3013
3039
  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;
3040
+ const customCount = config?.customPatterns?.length ?? 0;
3041
+ const allPatternsCount = patterns.length + customCount;
3042
+ const totalChecks = snapshot.files.length * allPatternsCount;
3043
+ const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
3016
3044
  return (0, import_types.Ok)({
3017
3045
  violations,
3018
3046
  stats: {
3019
3047
  filesChecked: snapshot.files.length,
3020
- patternsApplied: patterns.length,
3048
+ patternsApplied: allPatternsCount,
3021
3049
  violationCount: violations.length,
3022
3050
  errorCount,
3023
3051
  warningCount
@@ -3440,7 +3468,7 @@ async function detectCouplingViolations(snapshot, config, graphData) {
3440
3468
 
3441
3469
  // src/entropy/detectors/size-budget.ts
3442
3470
  var import_node_fs = require("fs");
3443
- var import_node_path = require("path");
3471
+ var import_node_path2 = require("path");
3444
3472
  function parseSize(size) {
3445
3473
  const match = size.trim().match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB|B)?$/i);
3446
3474
  if (!match) return 0;
@@ -3467,7 +3495,7 @@ function dirSize(dirPath) {
3467
3495
  }
3468
3496
  for (const entry of entries) {
3469
3497
  if (entry === "node_modules" || entry === ".git") continue;
3470
- const fullPath = (0, import_node_path.join)(dirPath, entry);
3498
+ const fullPath = (0, import_node_path2.join)(dirPath, entry);
3471
3499
  try {
3472
3500
  const stat = (0, import_node_fs.statSync)(fullPath);
3473
3501
  if (stat.isDirectory()) {
@@ -3487,7 +3515,7 @@ async function detectSizeBudgetViolations(rootDir, config) {
3487
3515
  let packagesChecked = 0;
3488
3516
  for (const [pkgPath, budget] of Object.entries(budgets)) {
3489
3517
  packagesChecked++;
3490
- const distPath = (0, import_node_path.join)(rootDir, pkgPath, "dist");
3518
+ const distPath = (0, import_node_path2.join)(rootDir, pkgPath, "dist");
3491
3519
  const currentSize = dirSize(distPath);
3492
3520
  if (budget.warn) {
3493
3521
  const budgetBytes = parseSize(budget.warn);
@@ -3868,7 +3896,7 @@ var EntropyAnalyzer = class {
3868
3896
  // src/entropy/fixers/safe-fixes.ts
3869
3897
  var fs3 = __toESM(require("fs"));
3870
3898
  var import_util2 = require("util");
3871
- var import_path10 = require("path");
3899
+ var import_path9 = require("path");
3872
3900
  var readFile5 = (0, import_util2.promisify)(fs3.readFile);
3873
3901
  var writeFile3 = (0, import_util2.promisify)(fs3.writeFile);
3874
3902
  var unlink2 = (0, import_util2.promisify)(fs3.unlink);
@@ -3884,7 +3912,7 @@ function createDeadFileFixes(deadCodeReport) {
3884
3912
  return deadCodeReport.deadFiles.map((file) => ({
3885
3913
  type: "dead-files",
3886
3914
  file: file.path,
3887
- description: `Delete dead file (${file.reason}): ${(0, import_path10.basename)(file.path)}`,
3915
+ description: `Delete dead file (${file.reason}): ${(0, import_path9.basename)(file.path)}`,
3888
3916
  action: "delete-file",
3889
3917
  safe: true,
3890
3918
  reversible: true
@@ -3967,9 +3995,9 @@ function previewFix(fix) {
3967
3995
  }
3968
3996
  }
3969
3997
  async function createBackup(filePath, backupDir) {
3970
- const backupPath = (0, import_path10.join)(backupDir, `${Date.now()}-${(0, import_path10.basename)(filePath)}`);
3998
+ const backupPath = (0, import_path9.join)(backupDir, `${Date.now()}-${(0, import_path9.basename)(filePath)}`);
3971
3999
  try {
3972
- await mkdir2((0, import_path10.dirname)(backupPath), { recursive: true });
4000
+ await mkdir2((0, import_path9.dirname)(backupPath), { recursive: true });
3973
4001
  await copyFile2(filePath, backupPath);
3974
4002
  return (0, import_types.Ok)(backupPath);
3975
4003
  } catch (e) {
@@ -4305,11 +4333,11 @@ function validatePatternConfig(config) {
4305
4333
 
4306
4334
  // src/performance/baseline-manager.ts
4307
4335
  var import_node_fs2 = require("fs");
4308
- var import_node_path2 = require("path");
4336
+ var import_node_path3 = require("path");
4309
4337
  var BaselineManager = class {
4310
4338
  baselinesPath;
4311
4339
  constructor(projectRoot) {
4312
- this.baselinesPath = (0, import_node_path2.join)(projectRoot, ".harness", "perf", "baselines.json");
4340
+ this.baselinesPath = (0, import_node_path3.join)(projectRoot, ".harness", "perf", "baselines.json");
4313
4341
  }
4314
4342
  /**
4315
4343
  * Load the baselines file from disk.
@@ -4349,7 +4377,7 @@ var BaselineManager = class {
4349
4377
  updatedFrom: commitHash,
4350
4378
  benchmarks
4351
4379
  };
4352
- const dir = (0, import_node_path2.dirname)(this.baselinesPath);
4380
+ const dir = (0, import_node_path3.dirname)(this.baselinesPath);
4353
4381
  if (!(0, import_node_fs2.existsSync)(dir)) {
4354
4382
  (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
4355
4383
  }
@@ -5340,7 +5368,7 @@ async function requestMultiplePeerReviews(requests) {
5340
5368
 
5341
5369
  // src/feedback/logging/file-sink.ts
5342
5370
  var import_fs2 = require("fs");
5343
- var import_path11 = require("path");
5371
+ var import_path10 = require("path");
5344
5372
  var FileSink = class {
5345
5373
  name = "file";
5346
5374
  filePath;
@@ -5363,7 +5391,7 @@ var FileSink = class {
5363
5391
  }
5364
5392
  ensureDirectory() {
5365
5393
  if (!this.initialized) {
5366
- const dir = (0, import_path11.dirname)(this.filePath);
5394
+ const dir = (0, import_path10.dirname)(this.filePath);
5367
5395
  if (!(0, import_fs2.existsSync)(dir)) {
5368
5396
  (0, import_fs2.mkdirSync)(dir, { recursive: true });
5369
5397
  }
@@ -5510,14 +5538,10 @@ var ConstraintRuleSchema = import_zod3.z.object({
5510
5538
  // forward-compat for governs edges
5511
5539
  });
5512
5540
 
5513
- // src/architecture/collectors/circular-deps.ts
5514
- var import_node_path3 = require("path");
5515
-
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 input = `${relativePath}:${category}:${normalizedDetail}`;
5521
5545
  return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
5522
5546
  }
5523
5547
  function constraintRuleId(category, scope, description) {
@@ -5581,8 +5605,8 @@ var CircularDepsCollector = class {
5581
5605
  }
5582
5606
  const { cycles, largestCycle } = result.value;
5583
5607
  const violations = cycles.map((cycle) => {
5584
- const cyclePath = cycle.cycle.map((f) => (0, import_node_path3.relative)(rootDir, f)).join(" -> ");
5585
- const firstFile = (0, import_node_path3.relative)(rootDir, cycle.cycle[0]);
5608
+ const cyclePath = cycle.cycle.map((f) => relativePosix(rootDir, f)).join(" -> ");
5609
+ const firstFile = relativePosix(rootDir, cycle.cycle[0]);
5586
5610
  return {
5587
5611
  id: violationId(firstFile, this.category, cyclePath),
5588
5612
  file: firstFile,
@@ -5603,7 +5627,6 @@ var CircularDepsCollector = class {
5603
5627
  };
5604
5628
 
5605
5629
  // src/architecture/collectors/layer-violations.ts
5606
- var import_node_path4 = require("path");
5607
5630
  var LayerViolationCollector = class {
5608
5631
  category = "layer-violations";
5609
5632
  getRules(_config, _rootDir) {
@@ -5647,8 +5670,8 @@ var LayerViolationCollector = class {
5647
5670
  (v) => v.reason === "WRONG_LAYER"
5648
5671
  );
5649
5672
  const violations = layerViolations.map((v) => {
5650
- const relFile = (0, import_node_path4.relative)(rootDir, v.file);
5651
- const relImport = (0, import_node_path4.relative)(rootDir, v.imports);
5673
+ const relFile = relativePosix(rootDir, v.file);
5674
+ const relImport = relativePosix(rootDir, v.imports);
5652
5675
  const detail = `${v.fromLayer} -> ${v.toLayer}: ${relFile} imports ${relImport}`;
5653
5676
  return {
5654
5677
  id: violationId(relFile, this.category, detail),
@@ -5670,7 +5693,6 @@ var LayerViolationCollector = class {
5670
5693
  };
5671
5694
 
5672
5695
  // src/architecture/collectors/complexity.ts
5673
- var import_node_path5 = require("path");
5674
5696
  var ComplexityCollector = class {
5675
5697
  category = "complexity";
5676
5698
  getRules(_config, _rootDir) {
@@ -5731,7 +5753,7 @@ var ComplexityCollector = class {
5731
5753
  (v) => v.severity === "error" || v.severity === "warning"
5732
5754
  );
5733
5755
  const violations = filtered.map((v) => {
5734
- const relFile = (0, import_node_path5.relative)(rootDir, v.file);
5756
+ const relFile = relativePosix(rootDir, v.file);
5735
5757
  const idDetail = `${v.metric}:${v.function}`;
5736
5758
  return {
5737
5759
  id: violationId(relFile, this.category, idDetail),
@@ -5757,7 +5779,6 @@ var ComplexityCollector = class {
5757
5779
  };
5758
5780
 
5759
5781
  // src/architecture/collectors/coupling.ts
5760
- var import_node_path6 = require("path");
5761
5782
  var CouplingCollector = class {
5762
5783
  category = "coupling";
5763
5784
  getRules(_config, _rootDir) {
@@ -5808,7 +5829,7 @@ var CouplingCollector = class {
5808
5829
  (v) => v.severity === "error" || v.severity === "warning"
5809
5830
  );
5810
5831
  const violations = filtered.map((v) => {
5811
- const relFile = (0, import_node_path6.relative)(rootDir, v.file);
5832
+ const relFile = relativePosix(rootDir, v.file);
5812
5833
  const idDetail = `${v.metric}`;
5813
5834
  return {
5814
5835
  id: violationId(relFile, this.category, idDetail),
@@ -5831,7 +5852,6 @@ var CouplingCollector = class {
5831
5852
  };
5832
5853
 
5833
5854
  // src/architecture/collectors/forbidden-imports.ts
5834
- var import_node_path7 = require("path");
5835
5855
  var ForbiddenImportCollector = class {
5836
5856
  category = "forbidden-imports";
5837
5857
  getRules(_config, _rootDir) {
@@ -5875,8 +5895,8 @@ var ForbiddenImportCollector = class {
5875
5895
  (v) => v.reason === "FORBIDDEN_IMPORT"
5876
5896
  );
5877
5897
  const violations = forbidden.map((v) => {
5878
- const relFile = (0, import_node_path7.relative)(rootDir, v.file);
5879
- const relImport = (0, import_node_path7.relative)(rootDir, v.imports);
5898
+ const relFile = relativePosix(rootDir, v.file);
5899
+ const relImport = relativePosix(rootDir, v.imports);
5880
5900
  const detail = `forbidden import: ${relFile} -> ${relImport}`;
5881
5901
  return {
5882
5902
  id: violationId(relFile, this.category, detail),
@@ -5899,7 +5919,7 @@ var ForbiddenImportCollector = class {
5899
5919
 
5900
5920
  // src/architecture/collectors/module-size.ts
5901
5921
  var import_promises2 = require("fs/promises");
5902
- var import_node_path8 = require("path");
5922
+ var import_node_path4 = require("path");
5903
5923
  async function discoverModules(rootDir) {
5904
5924
  const modules = [];
5905
5925
  async function scanDir(dir) {
@@ -5915,7 +5935,7 @@ async function discoverModules(rootDir) {
5915
5935
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
5916
5936
  continue;
5917
5937
  }
5918
- const fullPath = (0, import_node_path8.join)(dir, entry.name);
5938
+ const fullPath = (0, import_node_path4.join)(dir, entry.name);
5919
5939
  if (entry.isDirectory()) {
5920
5940
  subdirs.push(fullPath);
5921
5941
  } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".test.tsx") && !entry.name.endsWith(".spec.ts")) {
@@ -5932,10 +5952,10 @@ async function discoverModules(rootDir) {
5932
5952
  }
5933
5953
  }
5934
5954
  modules.push({
5935
- modulePath: (0, import_node_path8.relative)(rootDir, dir),
5955
+ modulePath: relativePosix(rootDir, dir),
5936
5956
  fileCount: tsFiles.length,
5937
5957
  totalLoc,
5938
- files: tsFiles.map((f) => (0, import_node_path8.relative)(rootDir, f))
5958
+ files: tsFiles.map((f) => relativePosix(rootDir, f))
5939
5959
  });
5940
5960
  }
5941
5961
  for (const sub of subdirs) {
@@ -6027,16 +6047,16 @@ var ModuleSizeCollector = class {
6027
6047
 
6028
6048
  // src/architecture/collectors/dep-depth.ts
6029
6049
  var import_promises3 = require("fs/promises");
6030
- var import_node_path9 = require("path");
6050
+ var import_node_path5 = require("path");
6031
6051
  function extractImportSources(content, filePath) {
6032
6052
  const importRegex = /(?:import|export)\s+.*?from\s+['"](\.[^'"]+)['"]/g;
6033
6053
  const dynamicRegex = /import\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
6034
6054
  const sources = [];
6035
- const dir = (0, import_node_path9.dirname)(filePath);
6055
+ const dir = (0, import_node_path5.dirname)(filePath);
6036
6056
  for (const regex of [importRegex, dynamicRegex]) {
6037
6057
  let match;
6038
6058
  while ((match = regex.exec(content)) !== null) {
6039
- let resolved = (0, import_node_path9.resolve)(dir, match[1]);
6059
+ let resolved = (0, import_node_path5.resolve)(dir, match[1]);
6040
6060
  if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
6041
6061
  resolved += ".ts";
6042
6062
  }
@@ -6057,7 +6077,7 @@ async function collectTsFiles(dir) {
6057
6077
  for (const entry of entries) {
6058
6078
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
6059
6079
  continue;
6060
- const fullPath = (0, import_node_path9.join)(d, entry.name);
6080
+ const fullPath = (0, import_node_path5.join)(d, entry.name);
6061
6081
  if (entry.isDirectory()) {
6062
6082
  await scan(fullPath);
6063
6083
  } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".test.tsx") && !entry.name.endsWith(".spec.ts")) {
@@ -6111,7 +6131,7 @@ var DepDepthCollector = class {
6111
6131
  }
6112
6132
  const moduleMap = /* @__PURE__ */ new Map();
6113
6133
  for (const file of allFiles) {
6114
- const relDir = (0, import_node_path9.relative)(rootDir, (0, import_node_path9.dirname)(file));
6134
+ const relDir = relativePosix(rootDir, (0, import_node_path5.dirname)(file));
6115
6135
  if (!moduleMap.has(relDir)) moduleMap.set(relDir, []);
6116
6136
  moduleMap.get(relDir).push(file);
6117
6137
  }
@@ -6255,11 +6275,11 @@ function detectStaleConstraints(store, windowDays = 30, category) {
6255
6275
  // src/architecture/baseline-manager.ts
6256
6276
  var import_node_fs3 = require("fs");
6257
6277
  var import_node_crypto2 = require("crypto");
6258
- var import_node_path10 = require("path");
6278
+ var import_node_path6 = require("path");
6259
6279
  var ArchBaselineManager = class {
6260
6280
  baselinesPath;
6261
6281
  constructor(projectRoot, baselinePath) {
6262
- this.baselinesPath = baselinePath ? (0, import_node_path10.join)(projectRoot, baselinePath) : (0, import_node_path10.join)(projectRoot, ".harness", "arch", "baselines.json");
6282
+ this.baselinesPath = baselinePath ? (0, import_node_path6.join)(projectRoot, baselinePath) : (0, import_node_path6.join)(projectRoot, ".harness", "arch", "baselines.json");
6263
6283
  }
6264
6284
  /**
6265
6285
  * Snapshot the current metric results into an ArchBaseline.
@@ -6320,7 +6340,7 @@ var ArchBaselineManager = class {
6320
6340
  * Uses atomic write (write to temp file, then rename) to prevent corruption.
6321
6341
  */
6322
6342
  save(baseline) {
6323
- const dir = (0, import_node_path10.dirname)(this.baselinesPath);
6343
+ const dir = (0, import_node_path6.dirname)(this.baselinesPath);
6324
6344
  if (!(0, import_node_fs3.existsSync)(dir)) {
6325
6345
  (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
6326
6346
  }
@@ -6695,10 +6715,13 @@ var DEFAULT_STATE = {
6695
6715
  progress: {}
6696
6716
  };
6697
6717
 
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");
6718
+ // src/state/state-persistence.ts
6719
+ var fs8 = __toESM(require("fs"));
6720
+ var path5 = __toESM(require("path"));
6721
+
6722
+ // src/state/state-shared.ts
6723
+ var fs7 = __toESM(require("fs"));
6724
+ var path4 = __toESM(require("path"));
6702
6725
 
6703
6726
  // src/state/stream-resolver.ts
6704
6727
  var fs5 = __toESM(require("fs"));
@@ -6724,10 +6747,20 @@ var DEFAULT_STREAM_INDEX = {
6724
6747
  streams: {}
6725
6748
  };
6726
6749
 
6727
- // src/state/stream-resolver.ts
6750
+ // src/state/constants.ts
6728
6751
  var HARNESS_DIR = ".harness";
6729
- var STREAMS_DIR = "streams";
6752
+ var STATE_FILE = "state.json";
6753
+ var LEARNINGS_FILE = "learnings.md";
6754
+ var FAILURES_FILE = "failures.md";
6755
+ var HANDOFF_FILE = "handoff.json";
6756
+ var GATE_CONFIG_FILE = "gate.json";
6730
6757
  var INDEX_FILE = "index.json";
6758
+ var SESSIONS_DIR = "sessions";
6759
+ var SESSION_INDEX_FILE = "index.md";
6760
+ var SUMMARY_FILE = "summary.md";
6761
+
6762
+ // src/state/stream-resolver.ts
6763
+ var STREAMS_DIR = "streams";
6731
6764
  var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
6732
6765
  function streamsDir(projectPath) {
6733
6766
  return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
@@ -6954,26 +6987,65 @@ async function migrateToStreams(projectPath) {
6954
6987
  return saveStreamIndex(projectPath, index);
6955
6988
  }
6956
6989
 
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";
6990
+ // src/state/session-resolver.ts
6991
+ var fs6 = __toESM(require("fs"));
6992
+ var path3 = __toESM(require("path"));
6993
+ function resolveSessionDir(projectPath, sessionSlug, options) {
6994
+ if (!sessionSlug || sessionSlug.trim() === "") {
6995
+ return (0, import_types.Err)(new Error("Session slug must not be empty"));
6996
+ }
6997
+ if (sessionSlug.includes("..") || sessionSlug.includes("/") || sessionSlug.includes("\\")) {
6998
+ return (0, import_types.Err)(
6999
+ new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
7000
+ );
7001
+ }
7002
+ const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
7003
+ if (options?.create) {
7004
+ fs6.mkdirSync(sessionDir, { recursive: true });
7005
+ }
7006
+ return (0, import_types.Ok)(sessionDir);
7007
+ }
7008
+ function updateSessionIndex(projectPath, sessionSlug, description) {
7009
+ const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
7010
+ fs6.mkdirSync(sessionsDir, { recursive: true });
7011
+ const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
7012
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7013
+ const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
7014
+ if (!fs6.existsSync(indexPath2)) {
7015
+ fs6.writeFileSync(indexPath2, `## Active Sessions
7016
+
7017
+ ${newLine}
7018
+ `);
7019
+ return;
7020
+ }
7021
+ const content = fs6.readFileSync(indexPath2, "utf-8");
7022
+ const lines = content.split("\n");
7023
+ const slugPattern = `- [${sessionSlug}]`;
7024
+ const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
7025
+ if (existingIdx >= 0) {
7026
+ lines[existingIdx] = newLine;
7027
+ } else {
7028
+ const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
7029
+ lines.splice(lastNonEmpty + 1, 0, newLine);
7030
+ }
7031
+ fs6.writeFileSync(indexPath2, lines.join("\n"));
7032
+ }
7033
+
7034
+ // src/state/state-shared.ts
6965
7035
  var MAX_CACHE_ENTRIES = 8;
6966
- var learningsCacheMap = /* @__PURE__ */ new Map();
6967
- var failuresCacheMap = /* @__PURE__ */ new Map();
6968
7036
  function evictIfNeeded(map) {
6969
7037
  if (map.size > MAX_CACHE_ENTRIES) {
6970
7038
  const oldest = map.keys().next().value;
6971
7039
  if (oldest !== void 0) map.delete(oldest);
6972
7040
  }
6973
7041
  }
6974
- async function getStateDir(projectPath, stream) {
6975
- const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
6976
- const hasStreams = fs6.existsSync(streamsIndexPath);
7042
+ async function getStateDir(projectPath, stream, session) {
7043
+ if (session) {
7044
+ const sessionResult = resolveSessionDir(projectPath, session, { create: true });
7045
+ return sessionResult;
7046
+ }
7047
+ const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
7048
+ const hasStreams = fs7.existsSync(streamsIndexPath);
6977
7049
  if (stream || hasStreams) {
6978
7050
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
6979
7051
  if (result.ok) {
@@ -6983,18 +7055,20 @@ async function getStateDir(projectPath, stream) {
6983
7055
  return result;
6984
7056
  }
6985
7057
  }
6986
- return (0, import_types.Ok)(path3.join(projectPath, HARNESS_DIR2));
7058
+ return (0, import_types.Ok)(path4.join(projectPath, HARNESS_DIR));
6987
7059
  }
6988
- async function loadState(projectPath, stream) {
7060
+
7061
+ // src/state/state-persistence.ts
7062
+ async function loadState(projectPath, stream, session) {
6989
7063
  try {
6990
- const dirResult = await getStateDir(projectPath, stream);
7064
+ const dirResult = await getStateDir(projectPath, stream, session);
6991
7065
  if (!dirResult.ok) return dirResult;
6992
7066
  const stateDir = dirResult.value;
6993
- const statePath = path3.join(stateDir, STATE_FILE);
6994
- if (!fs6.existsSync(statePath)) {
7067
+ const statePath = path5.join(stateDir, STATE_FILE);
7068
+ if (!fs8.existsSync(statePath)) {
6995
7069
  return (0, import_types.Ok)({ ...DEFAULT_STATE });
6996
7070
  }
6997
- const raw = fs6.readFileSync(statePath, "utf-8");
7071
+ const raw = fs8.readFileSync(statePath, "utf-8");
6998
7072
  const parsed = JSON.parse(raw);
6999
7073
  const result = HarnessStateSchema.safeParse(parsed);
7000
7074
  if (!result.success) {
@@ -7007,14 +7081,14 @@ async function loadState(projectPath, stream) {
7007
7081
  );
7008
7082
  }
7009
7083
  }
7010
- async function saveState(projectPath, state, stream) {
7084
+ async function saveState(projectPath, state, stream, session) {
7011
7085
  try {
7012
- const dirResult = await getStateDir(projectPath, stream);
7086
+ const dirResult = await getStateDir(projectPath, stream, session);
7013
7087
  if (!dirResult.ok) return dirResult;
7014
7088
  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));
7089
+ const statePath = path5.join(stateDir, STATE_FILE);
7090
+ fs8.mkdirSync(stateDir, { recursive: true });
7091
+ fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
7018
7092
  return (0, import_types.Ok)(void 0);
7019
7093
  } catch (error) {
7020
7094
  return (0, import_types.Err)(
@@ -7022,13 +7096,21 @@ async function saveState(projectPath, state, stream) {
7022
7096
  );
7023
7097
  }
7024
7098
  }
7025
- async function appendLearning(projectPath, learning, skillName, outcome, stream) {
7099
+
7100
+ // src/state/learnings.ts
7101
+ var fs9 = __toESM(require("fs"));
7102
+ var path6 = __toESM(require("path"));
7103
+ var learningsCacheMap = /* @__PURE__ */ new Map();
7104
+ function clearLearningsCache() {
7105
+ learningsCacheMap.clear();
7106
+ }
7107
+ async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
7026
7108
  try {
7027
- const dirResult = await getStateDir(projectPath, stream);
7109
+ const dirResult = await getStateDir(projectPath, stream, session);
7028
7110
  if (!dirResult.ok) return dirResult;
7029
7111
  const stateDir = dirResult.value;
7030
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
7031
- fs6.mkdirSync(stateDir, { recursive: true });
7112
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7113
+ fs9.mkdirSync(stateDir, { recursive: true });
7032
7114
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7033
7115
  let entry;
7034
7116
  if (skillName && outcome) {
@@ -7044,11 +7126,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
7044
7126
  - **${timestamp}:** ${learning}
7045
7127
  `;
7046
7128
  }
7047
- if (!fs6.existsSync(learningsPath)) {
7048
- fs6.writeFileSync(learningsPath, `# Learnings
7129
+ if (!fs9.existsSync(learningsPath)) {
7130
+ fs9.writeFileSync(learningsPath, `# Learnings
7049
7131
  ${entry}`);
7050
7132
  } else {
7051
- fs6.appendFileSync(learningsPath, entry);
7133
+ fs9.appendFileSync(learningsPath, entry);
7052
7134
  }
7053
7135
  learningsCacheMap.delete(learningsPath);
7054
7136
  return (0, import_types.Ok)(void 0);
@@ -7060,23 +7142,92 @@ ${entry}`);
7060
7142
  );
7061
7143
  }
7062
7144
  }
7063
- async function loadRelevantLearnings(projectPath, skillName, stream) {
7145
+ function estimateTokens(text) {
7146
+ return Math.ceil(text.length / 4);
7147
+ }
7148
+ function scoreRelevance(entry, intent) {
7149
+ if (!intent || intent.trim() === "") return 0;
7150
+ const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
7151
+ if (intentWords.length === 0) return 0;
7152
+ const entryLower = entry.toLowerCase();
7153
+ const matches = intentWords.filter((word) => entryLower.includes(word));
7154
+ return matches.length / intentWords.length;
7155
+ }
7156
+ function parseDateFromEntry(entry) {
7157
+ const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
7158
+ return match ? match[1] ?? null : null;
7159
+ }
7160
+ function analyzeLearningPatterns(entries) {
7161
+ const tagGroups = /* @__PURE__ */ new Map();
7162
+ for (const entry of entries) {
7163
+ const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
7164
+ for (const match of tagMatches) {
7165
+ const tag = match[1] ?? match[2];
7166
+ if (tag) {
7167
+ const group = tagGroups.get(tag) ?? [];
7168
+ group.push(entry);
7169
+ tagGroups.set(tag, group);
7170
+ }
7171
+ }
7172
+ }
7173
+ const patterns = [];
7174
+ for (const [tag, groupEntries] of tagGroups) {
7175
+ if (groupEntries.length >= 3) {
7176
+ patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
7177
+ }
7178
+ }
7179
+ return patterns.sort((a, b) => b.count - a.count);
7180
+ }
7181
+ async function loadBudgetedLearnings(projectPath, options) {
7182
+ const { intent, tokenBudget = 1e3, skill, session, stream } = options;
7183
+ const sortByRecencyAndRelevance = (entries) => {
7184
+ return [...entries].sort((a, b) => {
7185
+ const dateA = parseDateFromEntry(a) ?? "0000-00-00";
7186
+ const dateB = parseDateFromEntry(b) ?? "0000-00-00";
7187
+ const dateCompare = dateB.localeCompare(dateA);
7188
+ if (dateCompare !== 0) return dateCompare;
7189
+ return scoreRelevance(b, intent) - scoreRelevance(a, intent);
7190
+ });
7191
+ };
7192
+ const allEntries = [];
7193
+ if (session) {
7194
+ const sessionResult = await loadRelevantLearnings(projectPath, skill, stream, session);
7195
+ if (sessionResult.ok) {
7196
+ allEntries.push(...sortByRecencyAndRelevance(sessionResult.value));
7197
+ }
7198
+ }
7199
+ const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
7200
+ if (globalResult.ok) {
7201
+ allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
7202
+ }
7203
+ const budgeted = [];
7204
+ let totalTokens = 0;
7205
+ for (const entry of allEntries) {
7206
+ const separator = budgeted.length > 0 ? "\n" : "";
7207
+ const entryCost = estimateTokens(entry + separator);
7208
+ if (totalTokens + entryCost > tokenBudget) break;
7209
+ budgeted.push(entry);
7210
+ totalTokens += entryCost;
7211
+ }
7212
+ return (0, import_types.Ok)(budgeted);
7213
+ }
7214
+ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
7064
7215
  try {
7065
- const dirResult = await getStateDir(projectPath, stream);
7216
+ const dirResult = await getStateDir(projectPath, stream, session);
7066
7217
  if (!dirResult.ok) return dirResult;
7067
7218
  const stateDir = dirResult.value;
7068
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
7069
- if (!fs6.existsSync(learningsPath)) {
7219
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7220
+ if (!fs9.existsSync(learningsPath)) {
7070
7221
  return (0, import_types.Ok)([]);
7071
7222
  }
7072
- const stats = fs6.statSync(learningsPath);
7223
+ const stats = fs9.statSync(learningsPath);
7073
7224
  const cacheKey = learningsPath;
7074
7225
  const cached = learningsCacheMap.get(cacheKey);
7075
7226
  let entries;
7076
7227
  if (cached && cached.mtimeMs === stats.mtimeMs) {
7077
7228
  entries = cached.entries;
7078
7229
  } else {
7079
- const content = fs6.readFileSync(learningsPath, "utf-8");
7230
+ const content = fs9.readFileSync(learningsPath, "utf-8");
7080
7231
  const lines = content.split("\n");
7081
7232
  entries = [];
7082
7233
  let currentBlock = [];
@@ -7112,23 +7263,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
7112
7263
  );
7113
7264
  }
7114
7265
  }
7115
- var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
7116
- async function appendFailure(projectPath, description, skillName, type, stream) {
7266
+ async function archiveLearnings(projectPath, entries, stream) {
7117
7267
  try {
7118
7268
  const dirResult = await getStateDir(projectPath, stream);
7119
7269
  if (!dirResult.ok) return dirResult;
7120
7270
  const stateDir = dirResult.value;
7121
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
7122
- fs6.mkdirSync(stateDir, { recursive: true });
7271
+ const archiveDir = path6.join(stateDir, "learnings-archive");
7272
+ fs9.mkdirSync(archiveDir, { recursive: true });
7273
+ const now = /* @__PURE__ */ new Date();
7274
+ const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
7275
+ const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
7276
+ const archiveContent = entries.join("\n\n") + "\n";
7277
+ if (fs9.existsSync(archivePath)) {
7278
+ fs9.appendFileSync(archivePath, "\n" + archiveContent);
7279
+ } else {
7280
+ fs9.writeFileSync(archivePath, `# Learnings Archive
7281
+
7282
+ ${archiveContent}`);
7283
+ }
7284
+ return (0, import_types.Ok)(void 0);
7285
+ } catch (error) {
7286
+ return (0, import_types.Err)(
7287
+ new Error(
7288
+ `Failed to archive learnings: ${error instanceof Error ? error.message : String(error)}`
7289
+ )
7290
+ );
7291
+ }
7292
+ }
7293
+ async function pruneLearnings(projectPath, stream) {
7294
+ try {
7295
+ const dirResult = await getStateDir(projectPath, stream);
7296
+ if (!dirResult.ok) return dirResult;
7297
+ const stateDir = dirResult.value;
7298
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7299
+ if (!fs9.existsSync(learningsPath)) {
7300
+ return (0, import_types.Ok)({ kept: 0, archived: 0, patterns: [] });
7301
+ }
7302
+ const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
7303
+ if (!loadResult.ok) return loadResult;
7304
+ const allEntries = loadResult.value;
7305
+ if (allEntries.length <= 20) {
7306
+ const cutoffDate = /* @__PURE__ */ new Date();
7307
+ cutoffDate.setDate(cutoffDate.getDate() - 14);
7308
+ const cutoffStr = cutoffDate.toISOString().split("T")[0];
7309
+ const hasOld = allEntries.some((entry) => {
7310
+ const date = parseDateFromEntry(entry);
7311
+ return date !== null && date < cutoffStr;
7312
+ });
7313
+ if (!hasOld) {
7314
+ return (0, import_types.Ok)({ kept: allEntries.length, archived: 0, patterns: [] });
7315
+ }
7316
+ }
7317
+ const sorted = [...allEntries].sort((a, b) => {
7318
+ const dateA = parseDateFromEntry(a) ?? "0000-00-00";
7319
+ const dateB = parseDateFromEntry(b) ?? "0000-00-00";
7320
+ return dateB.localeCompare(dateA);
7321
+ });
7322
+ const toKeep = sorted.slice(0, 20);
7323
+ const toArchive = sorted.slice(20);
7324
+ const patterns = analyzeLearningPatterns(allEntries);
7325
+ if (toArchive.length > 0) {
7326
+ const archiveResult = await archiveLearnings(projectPath, toArchive, stream);
7327
+ if (!archiveResult.ok) return archiveResult;
7328
+ }
7329
+ const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
7330
+ fs9.writeFileSync(learningsPath, newContent);
7331
+ learningsCacheMap.delete(learningsPath);
7332
+ return (0, import_types.Ok)({
7333
+ kept: toKeep.length,
7334
+ archived: toArchive.length,
7335
+ patterns
7336
+ });
7337
+ } catch (error) {
7338
+ return (0, import_types.Err)(
7339
+ new Error(
7340
+ `Failed to prune learnings: ${error instanceof Error ? error.message : String(error)}`
7341
+ )
7342
+ );
7343
+ }
7344
+ }
7345
+
7346
+ // src/state/failures.ts
7347
+ var fs10 = __toESM(require("fs"));
7348
+ var path7 = __toESM(require("path"));
7349
+ var failuresCacheMap = /* @__PURE__ */ new Map();
7350
+ function clearFailuresCache() {
7351
+ failuresCacheMap.clear();
7352
+ }
7353
+ var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
7354
+ async function appendFailure(projectPath, description, skillName, type, stream, session) {
7355
+ try {
7356
+ const dirResult = await getStateDir(projectPath, stream, session);
7357
+ if (!dirResult.ok) return dirResult;
7358
+ const stateDir = dirResult.value;
7359
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
7360
+ fs10.mkdirSync(stateDir, { recursive: true });
7123
7361
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7124
7362
  const entry = `
7125
7363
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
7126
7364
  `;
7127
- if (!fs6.existsSync(failuresPath)) {
7128
- fs6.writeFileSync(failuresPath, `# Failures
7365
+ if (!fs10.existsSync(failuresPath)) {
7366
+ fs10.writeFileSync(failuresPath, `# Failures
7129
7367
  ${entry}`);
7130
7368
  } else {
7131
- fs6.appendFileSync(failuresPath, entry);
7369
+ fs10.appendFileSync(failuresPath, entry);
7132
7370
  }
7133
7371
  failuresCacheMap.delete(failuresPath);
7134
7372
  return (0, import_types.Ok)(void 0);
@@ -7140,22 +7378,22 @@ ${entry}`);
7140
7378
  );
7141
7379
  }
7142
7380
  }
7143
- async function loadFailures(projectPath, stream) {
7381
+ async function loadFailures(projectPath, stream, session) {
7144
7382
  try {
7145
- const dirResult = await getStateDir(projectPath, stream);
7383
+ const dirResult = await getStateDir(projectPath, stream, session);
7146
7384
  if (!dirResult.ok) return dirResult;
7147
7385
  const stateDir = dirResult.value;
7148
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
7149
- if (!fs6.existsSync(failuresPath)) {
7386
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
7387
+ if (!fs10.existsSync(failuresPath)) {
7150
7388
  return (0, import_types.Ok)([]);
7151
7389
  }
7152
- const stats = fs6.statSync(failuresPath);
7390
+ const stats = fs10.statSync(failuresPath);
7153
7391
  const cacheKey = failuresPath;
7154
7392
  const cached = failuresCacheMap.get(cacheKey);
7155
7393
  if (cached && cached.mtimeMs === stats.mtimeMs) {
7156
7394
  return (0, import_types.Ok)(cached.entries);
7157
7395
  }
7158
- const content = fs6.readFileSync(failuresPath, "utf-8");
7396
+ const content = fs10.readFileSync(failuresPath, "utf-8");
7159
7397
  const entries = [];
7160
7398
  for (const line of content.split("\n")) {
7161
7399
  const match = line.match(FAILURE_LINE_REGEX);
@@ -7179,25 +7417,25 @@ async function loadFailures(projectPath, stream) {
7179
7417
  );
7180
7418
  }
7181
7419
  }
7182
- async function archiveFailures(projectPath, stream) {
7420
+ async function archiveFailures(projectPath, stream, session) {
7183
7421
  try {
7184
- const dirResult = await getStateDir(projectPath, stream);
7422
+ const dirResult = await getStateDir(projectPath, stream, session);
7185
7423
  if (!dirResult.ok) return dirResult;
7186
7424
  const stateDir = dirResult.value;
7187
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
7188
- if (!fs6.existsSync(failuresPath)) {
7425
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
7426
+ if (!fs10.existsSync(failuresPath)) {
7189
7427
  return (0, import_types.Ok)(void 0);
7190
7428
  }
7191
- const archiveDir = path3.join(stateDir, "archive");
7192
- fs6.mkdirSync(archiveDir, { recursive: true });
7429
+ const archiveDir = path7.join(stateDir, "archive");
7430
+ fs10.mkdirSync(archiveDir, { recursive: true });
7193
7431
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7194
7432
  let archiveName = `failures-${date}.md`;
7195
7433
  let counter = 2;
7196
- while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
7434
+ while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
7197
7435
  archiveName = `failures-${date}-${counter}.md`;
7198
7436
  counter++;
7199
7437
  }
7200
- fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
7438
+ fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
7201
7439
  failuresCacheMap.delete(failuresPath);
7202
7440
  return (0, import_types.Ok)(void 0);
7203
7441
  } catch (error) {
@@ -7208,14 +7446,18 @@ async function archiveFailures(projectPath, stream) {
7208
7446
  );
7209
7447
  }
7210
7448
  }
7211
- async function saveHandoff(projectPath, handoff, stream) {
7449
+
7450
+ // src/state/handoff.ts
7451
+ var fs11 = __toESM(require("fs"));
7452
+ var path8 = __toESM(require("path"));
7453
+ async function saveHandoff(projectPath, handoff, stream, session) {
7212
7454
  try {
7213
- const dirResult = await getStateDir(projectPath, stream);
7455
+ const dirResult = await getStateDir(projectPath, stream, session);
7214
7456
  if (!dirResult.ok) return dirResult;
7215
7457
  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));
7458
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
7459
+ fs11.mkdirSync(stateDir, { recursive: true });
7460
+ fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
7219
7461
  return (0, import_types.Ok)(void 0);
7220
7462
  } catch (error) {
7221
7463
  return (0, import_types.Err)(
@@ -7223,16 +7465,16 @@ async function saveHandoff(projectPath, handoff, stream) {
7223
7465
  );
7224
7466
  }
7225
7467
  }
7226
- async function loadHandoff(projectPath, stream) {
7468
+ async function loadHandoff(projectPath, stream, session) {
7227
7469
  try {
7228
- const dirResult = await getStateDir(projectPath, stream);
7470
+ const dirResult = await getStateDir(projectPath, stream, session);
7229
7471
  if (!dirResult.ok) return dirResult;
7230
7472
  const stateDir = dirResult.value;
7231
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
7232
- if (!fs6.existsSync(handoffPath)) {
7473
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
7474
+ if (!fs11.existsSync(handoffPath)) {
7233
7475
  return (0, import_types.Ok)(null);
7234
7476
  }
7235
- const raw = fs6.readFileSync(handoffPath, "utf-8");
7477
+ const raw = fs11.readFileSync(handoffPath, "utf-8");
7236
7478
  const parsed = JSON.parse(raw);
7237
7479
  const result = HandoffSchema.safeParse(parsed);
7238
7480
  if (!result.success) {
@@ -7245,73 +7487,82 @@ async function loadHandoff(projectPath, stream) {
7245
7487
  );
7246
7488
  }
7247
7489
  }
7490
+
7491
+ // src/state/mechanical-gate.ts
7492
+ var fs12 = __toESM(require("fs"));
7493
+ var path9 = __toESM(require("path"));
7494
+ var import_child_process2 = require("child_process");
7495
+ 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:.-]+$/;
7496
+ function loadChecksFromConfig(gateConfigPath) {
7497
+ if (!fs12.existsSync(gateConfigPath)) return [];
7498
+ const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
7499
+ const config = GateConfigSchema.safeParse(raw);
7500
+ if (config.success && config.data.checks) return config.data.checks;
7501
+ return [];
7502
+ }
7503
+ function discoverChecksFromProject(projectPath) {
7504
+ const checks = [];
7505
+ const packageJsonPath = path9.join(projectPath, "package.json");
7506
+ if (fs12.existsSync(packageJsonPath)) {
7507
+ const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
7508
+ const scripts = pkg.scripts || {};
7509
+ if (scripts.test) checks.push({ name: "test", command: "npm test" });
7510
+ if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
7511
+ if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
7512
+ if (scripts.build) checks.push({ name: "build", command: "npm run build" });
7513
+ }
7514
+ if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
7515
+ checks.push({ name: "test", command: "go test ./..." });
7516
+ checks.push({ name: "build", command: "go build ./..." });
7517
+ }
7518
+ if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
7519
+ checks.push({ name: "test", command: "python -m pytest" });
7520
+ }
7521
+ return checks;
7522
+ }
7523
+ function executeCheck(check, projectPath) {
7524
+ if (!SAFE_GATE_COMMAND.test(check.command)) {
7525
+ return {
7526
+ name: check.name,
7527
+ passed: false,
7528
+ command: check.command,
7529
+ output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
7530
+ duration: 0
7531
+ };
7532
+ }
7533
+ const start = Date.now();
7534
+ try {
7535
+ (0, import_child_process2.execSync)(check.command, {
7536
+ cwd: projectPath,
7537
+ stdio: "pipe",
7538
+ timeout: 12e4
7539
+ });
7540
+ return {
7541
+ name: check.name,
7542
+ passed: true,
7543
+ command: check.command,
7544
+ duration: Date.now() - start
7545
+ };
7546
+ } catch (error) {
7547
+ const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
7548
+ return {
7549
+ name: check.name,
7550
+ passed: false,
7551
+ command: check.command,
7552
+ output: output.slice(0, 2e3),
7553
+ duration: Date.now() - start
7554
+ };
7555
+ }
7556
+ }
7248
7557
  async function runMechanicalGate(projectPath) {
7249
- const harnessDir = path3.join(projectPath, HARNESS_DIR2);
7250
- const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
7558
+ const harnessDir = path9.join(projectPath, HARNESS_DIR);
7559
+ const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
7251
7560
  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
- }
7561
+ let checks = loadChecksFromConfig(gateConfigPath);
7260
7562
  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
- }
7563
+ checks = discoverChecksFromProject(projectPath);
7314
7564
  }
7565
+ const results = checks.map((check) => executeCheck(check, projectPath));
7315
7566
  return (0, import_types.Ok)({
7316
7567
  passed: results.length === 0 || results.every((r) => r.passed),
7317
7568
  checks: results
@@ -7325,6 +7576,96 @@ async function runMechanicalGate(projectPath) {
7325
7576
  }
7326
7577
  }
7327
7578
 
7579
+ // src/state/session-summary.ts
7580
+ var fs13 = __toESM(require("fs"));
7581
+ var path10 = __toESM(require("path"));
7582
+ function formatSummary(data) {
7583
+ const lines = [
7584
+ "## Session Summary",
7585
+ "",
7586
+ `**Session:** ${data.session}`,
7587
+ `**Last active:** ${data.lastActive}`,
7588
+ `**Skill:** ${data.skill}`
7589
+ ];
7590
+ if (data.phase) {
7591
+ lines.push(`**Phase:** ${data.phase}`);
7592
+ }
7593
+ lines.push(`**Status:** ${data.status}`);
7594
+ if (data.spec) {
7595
+ lines.push(`**Spec:** ${data.spec}`);
7596
+ }
7597
+ if (data.plan) {
7598
+ lines.push(`**Plan:** ${data.plan}`);
7599
+ }
7600
+ lines.push(`**Key context:** ${data.keyContext}`);
7601
+ lines.push(`**Next step:** ${data.nextStep}`);
7602
+ lines.push("");
7603
+ return lines.join("\n");
7604
+ }
7605
+ function deriveIndexDescription(data) {
7606
+ const skillShort = data.skill.replace("harness-", "");
7607
+ const parts = [skillShort];
7608
+ if (data.phase) {
7609
+ parts.push(`phase ${data.phase}`);
7610
+ }
7611
+ parts.push(data.status.toLowerCase());
7612
+ return parts.join(", ");
7613
+ }
7614
+ function writeSessionSummary(projectPath, sessionSlug, data) {
7615
+ try {
7616
+ const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
7617
+ if (!dirResult.ok) return dirResult;
7618
+ const sessionDir = dirResult.value;
7619
+ const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
7620
+ const content = formatSummary(data);
7621
+ fs13.writeFileSync(summaryPath, content);
7622
+ const description = deriveIndexDescription(data);
7623
+ updateSessionIndex(projectPath, sessionSlug, description);
7624
+ return (0, import_types.Ok)(void 0);
7625
+ } catch (error) {
7626
+ return (0, import_types.Err)(
7627
+ new Error(
7628
+ `Failed to write session summary: ${error instanceof Error ? error.message : String(error)}`
7629
+ )
7630
+ );
7631
+ }
7632
+ }
7633
+ function loadSessionSummary(projectPath, sessionSlug) {
7634
+ try {
7635
+ const dirResult = resolveSessionDir(projectPath, sessionSlug);
7636
+ if (!dirResult.ok) return dirResult;
7637
+ const sessionDir = dirResult.value;
7638
+ const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
7639
+ if (!fs13.existsSync(summaryPath)) {
7640
+ return (0, import_types.Ok)(null);
7641
+ }
7642
+ const content = fs13.readFileSync(summaryPath, "utf-8");
7643
+ return (0, import_types.Ok)(content);
7644
+ } catch (error) {
7645
+ return (0, import_types.Err)(
7646
+ new Error(
7647
+ `Failed to load session summary: ${error instanceof Error ? error.message : String(error)}`
7648
+ )
7649
+ );
7650
+ }
7651
+ }
7652
+ function listActiveSessions(projectPath) {
7653
+ try {
7654
+ const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
7655
+ if (!fs13.existsSync(indexPath2)) {
7656
+ return (0, import_types.Ok)(null);
7657
+ }
7658
+ const content = fs13.readFileSync(indexPath2, "utf-8");
7659
+ return (0, import_types.Ok)(content);
7660
+ } catch (error) {
7661
+ return (0, import_types.Err)(
7662
+ new Error(
7663
+ `Failed to list active sessions: ${error instanceof Error ? error.message : String(error)}`
7664
+ )
7665
+ );
7666
+ }
7667
+ }
7668
+
7328
7669
  // src/workflow/runner.ts
7329
7670
  async function executeWorkflow(workflow, executor) {
7330
7671
  const stepResults = [];
@@ -7474,7 +7815,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
7474
7815
  }
7475
7816
 
7476
7817
  // src/security/scanner.ts
7477
- var fs8 = __toESM(require("fs/promises"));
7818
+ var fs15 = __toESM(require("fs/promises"));
7478
7819
 
7479
7820
  // src/security/rules/registry.ts
7480
7821
  var RuleRegistry = class {
@@ -7561,15 +7902,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
7561
7902
  }
7562
7903
 
7563
7904
  // src/security/stack-detector.ts
7564
- var fs7 = __toESM(require("fs"));
7565
- var path4 = __toESM(require("path"));
7905
+ var fs14 = __toESM(require("fs"));
7906
+ var path11 = __toESM(require("path"));
7566
7907
  function detectStack(projectRoot) {
7567
7908
  const stacks = [];
7568
- const pkgJsonPath = path4.join(projectRoot, "package.json");
7569
- if (fs7.existsSync(pkgJsonPath)) {
7909
+ const pkgJsonPath = path11.join(projectRoot, "package.json");
7910
+ if (fs14.existsSync(pkgJsonPath)) {
7570
7911
  stacks.push("node");
7571
7912
  try {
7572
- const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
7913
+ const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
7573
7914
  const allDeps = {
7574
7915
  ...pkgJson.dependencies,
7575
7916
  ...pkgJson.devDependencies
@@ -7584,13 +7925,13 @@ function detectStack(projectRoot) {
7584
7925
  } catch {
7585
7926
  }
7586
7927
  }
7587
- const goModPath = path4.join(projectRoot, "go.mod");
7588
- if (fs7.existsSync(goModPath)) {
7928
+ const goModPath = path11.join(projectRoot, "go.mod");
7929
+ if (fs14.existsSync(goModPath)) {
7589
7930
  stacks.push("go");
7590
7931
  }
7591
- const requirementsPath = path4.join(projectRoot, "requirements.txt");
7592
- const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
7593
- if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
7932
+ const requirementsPath = path11.join(projectRoot, "requirements.txt");
7933
+ const pyprojectPath = path11.join(projectRoot, "pyproject.toml");
7934
+ if (fs14.existsSync(requirementsPath) || fs14.existsSync(pyprojectPath)) {
7594
7935
  stacks.push("python");
7595
7936
  }
7596
7937
  return stacks;
@@ -8017,7 +8358,7 @@ var SecurityScanner = class {
8017
8358
  }
8018
8359
  async scanFile(filePath) {
8019
8360
  if (!this.config.enabled) return [];
8020
- const content = await fs8.readFile(filePath, "utf-8");
8361
+ const content = await fs15.readFile(filePath, "utf-8");
8021
8362
  return this.scanContent(content, filePath, 1);
8022
8363
  }
8023
8364
  async scanFiles(filePaths) {
@@ -8042,7 +8383,7 @@ var SecurityScanner = class {
8042
8383
  };
8043
8384
 
8044
8385
  // src/ci/check-orchestrator.ts
8045
- var path5 = __toESM(require("path"));
8386
+ var path12 = __toESM(require("path"));
8046
8387
  var ALL_CHECKS = [
8047
8388
  "validate",
8048
8389
  "deps",
@@ -8053,238 +8394,270 @@ var ALL_CHECKS = [
8053
8394
  "phase-gate",
8054
8395
  "arch"
8055
8396
  ];
8056
- async function runSingleCheck(name, projectRoot, config) {
8057
- const start = Date.now();
8397
+ async function runValidateCheck(projectRoot, config) {
8058
8398
  const issues = [];
8059
- try {
8060
- switch (name) {
8061
- case "validate": {
8062
- const agentsPath = path5.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8063
- const result = await validateAgentsMap(agentsPath);
8064
- if (!result.ok) {
8065
- issues.push({ severity: "error", message: result.error.message });
8066
- } else if (!result.value.valid) {
8067
- if (result.value.errors) {
8068
- for (const err of result.value.errors) {
8069
- issues.push({ severity: "error", message: err.message });
8070
- }
8071
- }
8072
- for (const section of result.value.missingSections) {
8073
- issues.push({ severity: "warning", message: `Missing section: ${section}` });
8074
- }
8075
- for (const link of result.value.brokenLinks) {
8076
- issues.push({
8077
- severity: "warning",
8078
- message: `Broken link: ${link.text} \u2192 ${link.path}`,
8079
- file: link.path
8080
- });
8081
- }
8082
- }
8083
- break;
8399
+ const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8400
+ const result = await validateAgentsMap(agentsPath);
8401
+ if (!result.ok) {
8402
+ issues.push({ severity: "error", message: result.error.message });
8403
+ } else if (!result.value.valid) {
8404
+ if (result.value.errors) {
8405
+ for (const err of result.value.errors) {
8406
+ issues.push({ severity: "error", message: err.message });
8084
8407
  }
8085
- case "deps": {
8086
- const rawLayers = config.layers;
8087
- if (rawLayers && rawLayers.length > 0) {
8088
- const parser = new TypeScriptParser();
8089
- const layers = rawLayers.map(
8090
- (l) => defineLayer(
8091
- l.name,
8092
- Array.isArray(l.patterns) ? l.patterns : [l.pattern],
8093
- l.allowedDependencies
8094
- )
8095
- );
8096
- const result = await validateDependencies({
8097
- layers,
8098
- rootDir: projectRoot,
8099
- parser
8100
- });
8101
- if (!result.ok) {
8102
- issues.push({ severity: "error", message: result.error.message });
8103
- } else if (result.value.violations.length > 0) {
8104
- for (const v of result.value.violations) {
8105
- issues.push({
8106
- severity: "error",
8107
- message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
8108
- file: v.file,
8109
- line: v.line
8110
- });
8111
- }
8112
- }
8113
- }
8114
- break;
8408
+ }
8409
+ for (const section of result.value.missingSections) {
8410
+ issues.push({ severity: "warning", message: `Missing section: ${section}` });
8411
+ }
8412
+ for (const link of result.value.brokenLinks) {
8413
+ issues.push({
8414
+ severity: "warning",
8415
+ message: `Broken link: ${link.text} \u2192 ${link.path}`,
8416
+ file: link.path
8417
+ });
8418
+ }
8419
+ }
8420
+ return issues;
8421
+ }
8422
+ async function runDepsCheck(projectRoot, config) {
8423
+ const issues = [];
8424
+ const rawLayers = config.layers;
8425
+ if (rawLayers && rawLayers.length > 0) {
8426
+ const parser = new TypeScriptParser();
8427
+ const layers = rawLayers.map(
8428
+ (l) => defineLayer(
8429
+ l.name,
8430
+ Array.isArray(l.patterns) ? l.patterns : [l.pattern],
8431
+ l.allowedDependencies
8432
+ )
8433
+ );
8434
+ const result = await validateDependencies({
8435
+ layers,
8436
+ rootDir: projectRoot,
8437
+ parser
8438
+ });
8439
+ if (!result.ok) {
8440
+ issues.push({ severity: "error", message: result.error.message });
8441
+ } else if (result.value.violations.length > 0) {
8442
+ for (const v of result.value.violations) {
8443
+ issues.push({
8444
+ severity: "error",
8445
+ message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
8446
+ file: v.file,
8447
+ line: v.line
8448
+ });
8115
8449
  }
8116
- case "docs": {
8117
- const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
8118
- const entropyConfig = config.entropy || {};
8119
- const result = await checkDocCoverage("project", {
8120
- docsDir,
8121
- sourceDir: projectRoot,
8122
- excludePatterns: entropyConfig.excludePatterns || [
8123
- "**/node_modules/**",
8124
- "**/dist/**",
8125
- "**/*.test.ts",
8126
- "**/fixtures/**"
8127
- ]
8450
+ }
8451
+ }
8452
+ return issues;
8453
+ }
8454
+ async function runDocsCheck(projectRoot, config) {
8455
+ const issues = [];
8456
+ const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
8457
+ const entropyConfig = config.entropy || {};
8458
+ const result = await checkDocCoverage("project", {
8459
+ docsDir,
8460
+ sourceDir: projectRoot,
8461
+ excludePatterns: entropyConfig.excludePatterns || [
8462
+ "**/node_modules/**",
8463
+ "**/dist/**",
8464
+ "**/*.test.ts",
8465
+ "**/fixtures/**"
8466
+ ]
8467
+ });
8468
+ if (!result.ok) {
8469
+ issues.push({ severity: "warning", message: result.error.message });
8470
+ } else if (result.value.gaps.length > 0) {
8471
+ for (const gap of result.value.gaps) {
8472
+ issues.push({
8473
+ severity: "warning",
8474
+ message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
8475
+ file: gap.file
8476
+ });
8477
+ }
8478
+ }
8479
+ return issues;
8480
+ }
8481
+ async function runEntropyCheck(projectRoot, _config) {
8482
+ const issues = [];
8483
+ const analyzer = new EntropyAnalyzer({
8484
+ rootDir: projectRoot,
8485
+ analyze: { drift: true, deadCode: true, patterns: false }
8486
+ });
8487
+ const result = await analyzer.analyze();
8488
+ if (!result.ok) {
8489
+ issues.push({ severity: "warning", message: result.error.message });
8490
+ } else {
8491
+ const report = result.value;
8492
+ if (report.drift) {
8493
+ for (const drift of report.drift.drifts) {
8494
+ issues.push({
8495
+ severity: "warning",
8496
+ message: `Doc drift (${drift.type}): ${drift.details}`,
8497
+ file: drift.docFile,
8498
+ line: drift.line
8128
8499
  });
8129
- if (!result.ok) {
8130
- issues.push({ severity: "warning", message: result.error.message });
8131
- } else if (result.value.gaps.length > 0) {
8132
- for (const gap of result.value.gaps) {
8133
- issues.push({
8134
- severity: "warning",
8135
- message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
8136
- file: gap.file
8137
- });
8138
- }
8139
- }
8140
- break;
8141
8500
  }
8142
- case "entropy": {
8143
- const analyzer = new EntropyAnalyzer({
8144
- rootDir: projectRoot,
8145
- analyze: { drift: true, deadCode: true, patterns: false }
8501
+ }
8502
+ if (report.deadCode) {
8503
+ for (const dead of report.deadCode.deadExports) {
8504
+ issues.push({
8505
+ severity: "warning",
8506
+ message: `Dead export: ${dead.name}`,
8507
+ file: dead.file,
8508
+ line: dead.line
8146
8509
  });
8147
- const result = await analyzer.analyze();
8148
- if (!result.ok) {
8149
- issues.push({ severity: "warning", message: result.error.message });
8150
- } else {
8151
- const report = result.value;
8152
- if (report.drift) {
8153
- for (const drift of report.drift.drifts) {
8154
- issues.push({
8155
- severity: "warning",
8156
- message: `Doc drift (${drift.type}): ${drift.details}`,
8157
- file: drift.docFile,
8158
- line: drift.line
8159
- });
8160
- }
8161
- }
8162
- if (report.deadCode) {
8163
- for (const dead of report.deadCode.deadExports) {
8164
- issues.push({
8165
- severity: "warning",
8166
- message: `Dead export: ${dead.name}`,
8167
- file: dead.file,
8168
- line: dead.line
8169
- });
8170
- }
8171
- }
8172
- }
8173
- break;
8174
8510
  }
8175
- case "security": {
8176
- const securityConfig = parseSecurityConfig(config.security);
8177
- if (!securityConfig.enabled) break;
8178
- const scanner = new SecurityScanner(securityConfig);
8179
- scanner.configureForProject(projectRoot);
8180
- const { glob: globFn } = await import("glob");
8181
- const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
8182
- cwd: projectRoot,
8183
- ignore: securityConfig.exclude ?? [
8184
- "**/node_modules/**",
8185
- "**/dist/**",
8186
- "**/*.test.ts",
8187
- "**/fixtures/**"
8188
- ],
8189
- absolute: true
8511
+ }
8512
+ }
8513
+ return issues;
8514
+ }
8515
+ async function runSecurityCheck(projectRoot, config) {
8516
+ const issues = [];
8517
+ const securityConfig = parseSecurityConfig(config.security);
8518
+ if (!securityConfig.enabled) return issues;
8519
+ const scanner = new SecurityScanner(securityConfig);
8520
+ scanner.configureForProject(projectRoot);
8521
+ const { glob: globFn } = await import("glob");
8522
+ const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
8523
+ cwd: projectRoot,
8524
+ ignore: securityConfig.exclude ?? [
8525
+ "**/node_modules/**",
8526
+ "**/dist/**",
8527
+ "**/*.test.ts",
8528
+ "**/fixtures/**"
8529
+ ],
8530
+ absolute: true
8531
+ });
8532
+ const scanResult = await scanner.scanFiles(sourceFiles);
8533
+ for (const finding of scanResult.findings) {
8534
+ issues.push({
8535
+ severity: finding.severity === "info" ? "warning" : finding.severity,
8536
+ message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
8537
+ file: finding.file,
8538
+ line: finding.line
8539
+ });
8540
+ }
8541
+ return issues;
8542
+ }
8543
+ async function runPerfCheck(projectRoot, config) {
8544
+ const issues = [];
8545
+ const perfConfig = config.performance || {};
8546
+ const perfAnalyzer = new EntropyAnalyzer({
8547
+ rootDir: projectRoot,
8548
+ analyze: {
8549
+ complexity: perfConfig.complexity || true,
8550
+ coupling: perfConfig.coupling || true,
8551
+ sizeBudget: perfConfig.sizeBudget || false
8552
+ }
8553
+ });
8554
+ const perfResult = await perfAnalyzer.analyze();
8555
+ if (!perfResult.ok) {
8556
+ issues.push({ severity: "warning", message: perfResult.error.message });
8557
+ } else {
8558
+ const perfReport = perfResult.value;
8559
+ if (perfReport.complexity) {
8560
+ for (const v of perfReport.complexity.violations) {
8561
+ issues.push({
8562
+ severity: v.severity === "info" ? "warning" : v.severity,
8563
+ message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
8564
+ file: v.file,
8565
+ line: v.line
8190
8566
  });
8191
- const scanResult = await scanner.scanFiles(sourceFiles);
8192
- for (const finding of scanResult.findings) {
8193
- issues.push({
8194
- severity: finding.severity === "info" ? "warning" : finding.severity,
8195
- message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
8196
- file: finding.file,
8197
- line: finding.line
8198
- });
8199
- }
8200
- break;
8201
8567
  }
8202
- case "perf": {
8203
- const perfConfig = config.performance || {};
8204
- const perfAnalyzer = new EntropyAnalyzer({
8205
- rootDir: projectRoot,
8206
- analyze: {
8207
- complexity: perfConfig.complexity || true,
8208
- coupling: perfConfig.coupling || true,
8209
- sizeBudget: perfConfig.sizeBudget || false
8210
- }
8568
+ }
8569
+ if (perfReport.coupling) {
8570
+ for (const v of perfReport.coupling.violations) {
8571
+ issues.push({
8572
+ severity: v.severity === "info" ? "warning" : v.severity,
8573
+ message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
8574
+ file: v.file
8211
8575
  });
8212
- const perfResult = await perfAnalyzer.analyze();
8213
- if (!perfResult.ok) {
8214
- issues.push({ severity: "warning", message: perfResult.error.message });
8215
- } else {
8216
- const perfReport = perfResult.value;
8217
- if (perfReport.complexity) {
8218
- for (const v of perfReport.complexity.violations) {
8219
- issues.push({
8220
- severity: v.severity === "info" ? "warning" : v.severity,
8221
- message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
8222
- file: v.file,
8223
- line: v.line
8224
- });
8225
- }
8226
- }
8227
- if (perfReport.coupling) {
8228
- for (const v of perfReport.coupling.violations) {
8229
- issues.push({
8230
- severity: v.severity === "info" ? "warning" : v.severity,
8231
- message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
8232
- file: v.file
8233
- });
8234
- }
8235
- }
8236
- }
8237
- break;
8238
8576
  }
8239
- case "phase-gate": {
8240
- const phaseGates = config.phaseGates;
8241
- if (!phaseGates?.enabled) {
8242
- break;
8243
- }
8577
+ }
8578
+ }
8579
+ return issues;
8580
+ }
8581
+ async function runPhaseGateCheck(_projectRoot, config) {
8582
+ const issues = [];
8583
+ const phaseGates = config.phaseGates;
8584
+ if (!phaseGates?.enabled) {
8585
+ return issues;
8586
+ }
8587
+ issues.push({
8588
+ severity: "warning",
8589
+ message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
8590
+ });
8591
+ return issues;
8592
+ }
8593
+ async function runArchCheck(projectRoot, config) {
8594
+ const issues = [];
8595
+ const rawArchConfig = config.architecture;
8596
+ const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
8597
+ if (!archConfig.enabled) return issues;
8598
+ const results = await runAll(archConfig, projectRoot);
8599
+ const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
8600
+ const baseline = baselineManager.load();
8601
+ if (baseline) {
8602
+ const diffResult = diff(results, baseline);
8603
+ if (!diffResult.passed) {
8604
+ for (const v of diffResult.newViolations) {
8244
8605
  issues.push({
8245
- severity: "warning",
8246
- message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
8606
+ severity: v.severity,
8607
+ message: `[${v.category || "arch"}] NEW: ${v.detail}`,
8608
+ file: v.file
8247
8609
  });
8248
- break;
8249
8610
  }
8250
- case "arch": {
8251
- const rawArchConfig = config.architecture;
8252
- const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
8253
- if (!archConfig.enabled) break;
8254
- const results = await runAll(archConfig, projectRoot);
8255
- const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
8256
- const baseline = baselineManager.load();
8257
- if (baseline) {
8258
- const diffResult = diff(results, baseline);
8259
- if (!diffResult.passed) {
8260
- for (const v of diffResult.newViolations) {
8261
- issues.push({
8262
- severity: v.severity,
8263
- message: `[${v.category || "arch"}] NEW: ${v.detail}`,
8264
- file: v.file
8265
- });
8266
- }
8267
- for (const r of diffResult.regressions) {
8268
- issues.push({
8269
- severity: "error",
8270
- message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
8271
- });
8272
- }
8273
- }
8274
- } else {
8275
- for (const result of results) {
8276
- for (const v of result.violations) {
8277
- issues.push({
8278
- severity: v.severity,
8279
- message: `[${result.category}] ${v.detail}`,
8280
- file: v.file
8281
- });
8282
- }
8283
- }
8284
- }
8285
- break;
8611
+ for (const r of diffResult.regressions) {
8612
+ issues.push({
8613
+ severity: "error",
8614
+ message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
8615
+ });
8616
+ }
8617
+ }
8618
+ } else {
8619
+ for (const result of results) {
8620
+ for (const v of result.violations) {
8621
+ issues.push({
8622
+ severity: v.severity,
8623
+ message: `[${result.category}] ${v.detail}`,
8624
+ file: v.file
8625
+ });
8286
8626
  }
8287
8627
  }
8628
+ }
8629
+ return issues;
8630
+ }
8631
+ async function runSingleCheck(name, projectRoot, config) {
8632
+ const start = Date.now();
8633
+ const issues = [];
8634
+ try {
8635
+ switch (name) {
8636
+ case "validate":
8637
+ issues.push(...await runValidateCheck(projectRoot, config));
8638
+ break;
8639
+ case "deps":
8640
+ issues.push(...await runDepsCheck(projectRoot, config));
8641
+ break;
8642
+ case "docs":
8643
+ issues.push(...await runDocsCheck(projectRoot, config));
8644
+ break;
8645
+ case "entropy":
8646
+ issues.push(...await runEntropyCheck(projectRoot, config));
8647
+ break;
8648
+ case "security":
8649
+ issues.push(...await runSecurityCheck(projectRoot, config));
8650
+ break;
8651
+ case "perf":
8652
+ issues.push(...await runPerfCheck(projectRoot, config));
8653
+ break;
8654
+ case "phase-gate":
8655
+ issues.push(...await runPhaseGateCheck(projectRoot, config));
8656
+ break;
8657
+ case "arch":
8658
+ issues.push(...await runArchCheck(projectRoot, config));
8659
+ break;
8660
+ }
8288
8661
  } catch (error) {
8289
8662
  issues.push({
8290
8663
  severity: "error",
@@ -8352,7 +8725,7 @@ async function runCIChecks(input) {
8352
8725
  }
8353
8726
 
8354
8727
  // src/review/mechanical-checks.ts
8355
- var path6 = __toESM(require("path"));
8728
+ var path13 = __toESM(require("path"));
8356
8729
  async function runMechanicalChecks(options) {
8357
8730
  const { projectRoot, config, skip = [], changedFiles } = options;
8358
8731
  const findings = [];
@@ -8364,7 +8737,7 @@ async function runMechanicalChecks(options) {
8364
8737
  };
8365
8738
  if (!skip.includes("validate")) {
8366
8739
  try {
8367
- const agentsPath = path6.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8740
+ const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8368
8741
  const result = await validateAgentsMap(agentsPath);
8369
8742
  if (!result.ok) {
8370
8743
  statuses.validate = "fail";
@@ -8401,7 +8774,7 @@ async function runMechanicalChecks(options) {
8401
8774
  statuses.validate = "fail";
8402
8775
  findings.push({
8403
8776
  tool: "validate",
8404
- file: path6.join(projectRoot, "AGENTS.md"),
8777
+ file: path13.join(projectRoot, "AGENTS.md"),
8405
8778
  message: err instanceof Error ? err.message : String(err),
8406
8779
  severity: "error"
8407
8780
  });
@@ -8465,7 +8838,7 @@ async function runMechanicalChecks(options) {
8465
8838
  (async () => {
8466
8839
  const localFindings = [];
8467
8840
  try {
8468
- const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
8841
+ const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
8469
8842
  const result = await checkDocCoverage("project", { docsDir });
8470
8843
  if (!result.ok) {
8471
8844
  statuses["check-docs"] = "warn";
@@ -8492,7 +8865,7 @@ async function runMechanicalChecks(options) {
8492
8865
  statuses["check-docs"] = "warn";
8493
8866
  localFindings.push({
8494
8867
  tool: "check-docs",
8495
- file: path6.join(projectRoot, "docs"),
8868
+ file: path13.join(projectRoot, "docs"),
8496
8869
  message: err instanceof Error ? err.message : String(err),
8497
8870
  severity: "warning"
8498
8871
  });
@@ -8640,7 +9013,7 @@ function detectChangeType(commitMessage, diff2) {
8640
9013
  }
8641
9014
 
8642
9015
  // src/review/context-scoper.ts
8643
- var path7 = __toESM(require("path"));
9016
+ var path14 = __toESM(require("path"));
8644
9017
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
8645
9018
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
8646
9019
  function computeContextBudget(diffLines) {
@@ -8648,18 +9021,18 @@ function computeContextBudget(diffLines) {
8648
9021
  return diffLines;
8649
9022
  }
8650
9023
  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);
9024
+ const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
9025
+ const resolvedPath = path14.resolve(absPath);
9026
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
8654
9027
  }
8655
9028
  async function readContextFile(projectRoot, filePath, reason) {
8656
- const absPath = path7.isAbsolute(filePath) ? filePath : path7.join(projectRoot, filePath);
9029
+ const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
8657
9030
  if (!isWithinProject(absPath, projectRoot)) return null;
8658
9031
  const result = await readFileContent(absPath);
8659
9032
  if (!result.ok) return null;
8660
9033
  const content = result.value;
8661
9034
  const lines = content.split("\n").length;
8662
- const relPath = path7.isAbsolute(filePath) ? path7.relative(projectRoot, filePath) : filePath;
9035
+ const relPath = path14.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
8663
9036
  return { path: relPath, content, reason, lines };
8664
9037
  }
8665
9038
  function extractImportSources2(content) {
@@ -8674,18 +9047,18 @@ function extractImportSources2(content) {
8674
9047
  }
8675
9048
  async function resolveImportPath2(projectRoot, fromFile, importSource) {
8676
9049
  if (!importSource.startsWith(".")) return null;
8677
- const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
8678
- const basePath = path7.resolve(fromDir, importSource);
9050
+ const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
9051
+ const basePath = path14.resolve(fromDir, importSource);
8679
9052
  if (!isWithinProject(basePath, projectRoot)) return null;
8680
- const relBase = path7.relative(projectRoot, basePath);
9053
+ const relBase = relativePosix(projectRoot, basePath);
8681
9054
  const candidates = [
8682
9055
  relBase + ".ts",
8683
9056
  relBase + ".tsx",
8684
9057
  relBase + ".mts",
8685
- path7.join(relBase, "index.ts")
9058
+ path14.join(relBase, "index.ts")
8686
9059
  ];
8687
9060
  for (const candidate of candidates) {
8688
- const absCandidate = path7.join(projectRoot, candidate);
9061
+ const absCandidate = path14.join(projectRoot, candidate);
8689
9062
  if (await fileExists(absCandidate)) {
8690
9063
  return candidate;
8691
9064
  }
@@ -8693,10 +9066,10 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
8693
9066
  return null;
8694
9067
  }
8695
9068
  async function findTestFiles(projectRoot, sourceFile) {
8696
- const baseName = path7.basename(sourceFile, path7.extname(sourceFile));
9069
+ const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
8697
9070
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
8698
9071
  const results = await findFiles(pattern, projectRoot);
8699
- return results.map((f) => path7.relative(projectRoot, f));
9072
+ return results.map((f) => relativePosix(projectRoot, f));
8700
9073
  }
8701
9074
  async function gatherImportContext(projectRoot, changedFiles, budget) {
8702
9075
  const contextFiles = [];
@@ -9484,7 +9857,7 @@ async function fanOutReview(options) {
9484
9857
  }
9485
9858
 
9486
9859
  // src/review/validate-findings.ts
9487
- var path8 = __toESM(require("path"));
9860
+ var path15 = __toESM(require("path"));
9488
9861
  var DOWNGRADE_MAP = {
9489
9862
  critical: "important",
9490
9863
  important: "suggestion",
@@ -9505,7 +9878,7 @@ function normalizePath(filePath, projectRoot) {
9505
9878
  let normalized = filePath;
9506
9879
  normalized = normalized.replace(/\\/g, "/");
9507
9880
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
9508
- if (path8.isAbsolute(normalized)) {
9881
+ if (path15.isAbsolute(normalized)) {
9509
9882
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
9510
9883
  if (normalized.startsWith(root)) {
9511
9884
  normalized = normalized.slice(root.length);
@@ -9530,12 +9903,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
9530
9903
  while ((match = importRegex.exec(content)) !== null) {
9531
9904
  const importPath = match[1];
9532
9905
  if (!importPath.startsWith(".")) continue;
9533
- const dir = path8.dirname(current.file);
9534
- let resolved = path8.join(dir, importPath).replace(/\\/g, "/");
9906
+ const dir = path15.dirname(current.file);
9907
+ let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
9535
9908
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
9536
9909
  resolved += ".ts";
9537
9910
  }
9538
- resolved = path8.normalize(resolved).replace(/\\/g, "/");
9911
+ resolved = path15.normalize(resolved).replace(/\\/g, "/");
9539
9912
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
9540
9913
  queue.push({ file: resolved, depth: current.depth + 1 });
9541
9914
  }
@@ -9552,7 +9925,7 @@ async function validateFindings(options) {
9552
9925
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
9553
9926
  continue;
9554
9927
  }
9555
- const absoluteFile = path8.isAbsolute(finding.file) ? finding.file : path8.join(projectRoot, finding.file).replace(/\\/g, "/");
9928
+ const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
9556
9929
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
9557
9930
  continue;
9558
9931
  }
@@ -10035,7 +10408,7 @@ async function runReviewPipeline(options) {
10035
10408
  }
10036
10409
 
10037
10410
  // src/roadmap/parse.ts
10038
- var import_types16 = require("@harness-engineering/types");
10411
+ var import_types18 = require("@harness-engineering/types");
10039
10412
  var VALID_STATUSES = /* @__PURE__ */ new Set([
10040
10413
  "backlog",
10041
10414
  "planned",
@@ -10047,14 +10420,14 @@ var EM_DASH = "\u2014";
10047
10420
  function parseRoadmap(markdown) {
10048
10421
  const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
10049
10422
  if (!fmMatch) {
10050
- return (0, import_types16.Err)(new Error("Missing or malformed YAML frontmatter"));
10423
+ return (0, import_types18.Err)(new Error("Missing or malformed YAML frontmatter"));
10051
10424
  }
10052
10425
  const fmResult = parseFrontmatter(fmMatch[1]);
10053
10426
  if (!fmResult.ok) return fmResult;
10054
10427
  const body = markdown.slice(fmMatch[0].length);
10055
10428
  const milestonesResult = parseMilestones(body);
10056
10429
  if (!milestonesResult.ok) return milestonesResult;
10057
- return (0, import_types16.Ok)({
10430
+ return (0, import_types18.Ok)({
10058
10431
  frontmatter: fmResult.value,
10059
10432
  milestones: milestonesResult.value
10060
10433
  });
@@ -10073,8 +10446,10 @@ function parseFrontmatter(raw) {
10073
10446
  const versionStr = map.get("version");
10074
10447
  const lastSynced = map.get("last_synced");
10075
10448
  const lastManualEdit = map.get("last_manual_edit");
10449
+ const created = map.get("created");
10450
+ const updated = map.get("updated");
10076
10451
  if (!project || !versionStr || !lastSynced || !lastManualEdit) {
10077
- return (0, import_types16.Err)(
10452
+ return (0, import_types18.Err)(
10078
10453
  new Error(
10079
10454
  "Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
10080
10455
  )
@@ -10082,9 +10457,12 @@ function parseFrontmatter(raw) {
10082
10457
  }
10083
10458
  const version = parseInt(versionStr, 10);
10084
10459
  if (isNaN(version)) {
10085
- return (0, import_types16.Err)(new Error("Frontmatter version must be a number"));
10460
+ return (0, import_types18.Err)(new Error("Frontmatter version must be a number"));
10086
10461
  }
10087
- return (0, import_types16.Ok)({ project, version, lastSynced, lastManualEdit });
10462
+ const fm = { project, version, lastSynced, lastManualEdit };
10463
+ if (created) fm.created = created;
10464
+ if (updated) fm.updated = updated;
10465
+ return (0, import_types18.Ok)(fm);
10088
10466
  }
10089
10467
  function parseMilestones(body) {
10090
10468
  const milestones = [];
@@ -10092,12 +10470,12 @@ function parseMilestones(body) {
10092
10470
  const h2Matches = [];
10093
10471
  let match;
10094
10472
  while ((match = h2Pattern.exec(body)) !== null) {
10095
- h2Matches.push({ heading: match[1], startIndex: match.index });
10473
+ h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
10096
10474
  }
10097
10475
  for (let i = 0; i < h2Matches.length; i++) {
10098
10476
  const h2 = h2Matches[i];
10099
10477
  const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
10100
- const sectionBody = body.slice(h2.startIndex + h2.heading.length + 4, nextStart);
10478
+ const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
10101
10479
  const isBacklog = h2.heading === "Backlog";
10102
10480
  const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
10103
10481
  const featuresResult = parseFeatures(sectionBody);
@@ -10108,28 +10486,25 @@ function parseMilestones(body) {
10108
10486
  features: featuresResult.value
10109
10487
  });
10110
10488
  }
10111
- return (0, import_types16.Ok)(milestones);
10489
+ return (0, import_types18.Ok)(milestones);
10112
10490
  }
10113
10491
  function parseFeatures(sectionBody) {
10114
10492
  const features = [];
10115
- const h3Pattern = /^### Feature: (.+)$/gm;
10493
+ const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
10116
10494
  const h3Matches = [];
10117
10495
  let match;
10118
10496
  while ((match = h3Pattern.exec(sectionBody)) !== null) {
10119
- h3Matches.push({ name: match[1], startIndex: match.index });
10497
+ h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
10120
10498
  }
10121
10499
  for (let i = 0; i < h3Matches.length; i++) {
10122
10500
  const h3 = h3Matches[i];
10123
10501
  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
- );
10502
+ const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
10128
10503
  const featureResult = parseFeatureFields(h3.name, featureBody);
10129
10504
  if (!featureResult.ok) return featureResult;
10130
10505
  features.push(featureResult.value);
10131
10506
  }
10132
- return (0, import_types16.Ok)(features);
10507
+ return (0, import_types18.Ok)(features);
10133
10508
  }
10134
10509
  function parseFeatureFields(name, body) {
10135
10510
  const fieldMap = /* @__PURE__ */ new Map();
@@ -10140,7 +10515,7 @@ function parseFeatureFields(name, body) {
10140
10515
  }
10141
10516
  const statusRaw = fieldMap.get("Status");
10142
10517
  if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
10143
- return (0, import_types16.Err)(
10518
+ return (0, import_types18.Err)(
10144
10519
  new Error(
10145
10520
  `Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
10146
10521
  )
@@ -10149,12 +10524,12 @@ function parseFeatureFields(name, body) {
10149
10524
  const status = statusRaw;
10150
10525
  const specRaw = fieldMap.get("Spec") ?? EM_DASH;
10151
10526
  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());
10527
+ const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
10528
+ const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
10529
+ const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
10530
+ const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
10156
10531
  const summary = fieldMap.get("Summary") ?? "";
10157
- return (0, import_types16.Ok)({ name, status, spec, plans, blockedBy, summary });
10532
+ return (0, import_types18.Ok)({ name, status, spec, plans, blockedBy, summary });
10158
10533
  }
10159
10534
 
10160
10535
  // src/roadmap/serialize.ts
@@ -10164,11 +10539,17 @@ function serializeRoadmap(roadmap) {
10164
10539
  lines.push("---");
10165
10540
  lines.push(`project: ${roadmap.frontmatter.project}`);
10166
10541
  lines.push(`version: ${roadmap.frontmatter.version}`);
10542
+ if (roadmap.frontmatter.created) {
10543
+ lines.push(`created: ${roadmap.frontmatter.created}`);
10544
+ }
10545
+ if (roadmap.frontmatter.updated) {
10546
+ lines.push(`updated: ${roadmap.frontmatter.updated}`);
10547
+ }
10167
10548
  lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
10168
10549
  lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
10169
10550
  lines.push("---");
10170
10551
  lines.push("");
10171
- lines.push("# Project Roadmap");
10552
+ lines.push("# Roadmap");
10172
10553
  for (const milestone of roadmap.milestones) {
10173
10554
  lines.push("");
10174
10555
  lines.push(serializeMilestoneHeading(milestone));
@@ -10181,26 +10562,27 @@ function serializeRoadmap(roadmap) {
10181
10562
  return lines.join("\n");
10182
10563
  }
10183
10564
  function serializeMilestoneHeading(milestone) {
10184
- return milestone.isBacklog ? "## Backlog" : `## Milestone: ${milestone.name}`;
10565
+ return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
10185
10566
  }
10186
10567
  function serializeFeature(feature) {
10187
10568
  const spec = feature.spec ?? EM_DASH2;
10188
10569
  const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
10189
10570
  const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
10190
10571
  return [
10191
- `### Feature: ${feature.name}`,
10572
+ `### ${feature.name}`,
10573
+ "",
10192
10574
  `- **Status:** ${feature.status}`,
10193
10575
  `- **Spec:** ${spec}`,
10194
- `- **Plans:** ${plans}`,
10195
- `- **Blocked by:** ${blockedBy}`,
10196
- `- **Summary:** ${feature.summary}`
10576
+ `- **Summary:** ${feature.summary}`,
10577
+ `- **Blockers:** ${blockedBy}`,
10578
+ `- **Plan:** ${plans}`
10197
10579
  ];
10198
10580
  }
10199
10581
 
10200
10582
  // src/roadmap/sync.ts
10201
- var fs9 = __toESM(require("fs"));
10202
- var path9 = __toESM(require("path"));
10203
- var import_types17 = require("@harness-engineering/types");
10583
+ var fs16 = __toESM(require("fs"));
10584
+ var path16 = __toESM(require("path"));
10585
+ var import_types19 = require("@harness-engineering/types");
10204
10586
  function inferStatus(feature, projectPath, allFeatures) {
10205
10587
  if (feature.blockedBy.length > 0) {
10206
10588
  const blockerNotDone = feature.blockedBy.some((blockerName) => {
@@ -10214,10 +10596,10 @@ function inferStatus(feature, projectPath, allFeatures) {
10214
10596
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
10215
10597
  const useRootState = featuresWithPlans.length <= 1;
10216
10598
  if (useRootState) {
10217
- const rootStatePath = path9.join(projectPath, ".harness", "state.json");
10218
- if (fs9.existsSync(rootStatePath)) {
10599
+ const rootStatePath = path16.join(projectPath, ".harness", "state.json");
10600
+ if (fs16.existsSync(rootStatePath)) {
10219
10601
  try {
10220
- const raw = fs9.readFileSync(rootStatePath, "utf-8");
10602
+ const raw = fs16.readFileSync(rootStatePath, "utf-8");
10221
10603
  const state = JSON.parse(raw);
10222
10604
  if (state.progress) {
10223
10605
  for (const status of Object.values(state.progress)) {
@@ -10228,16 +10610,16 @@ function inferStatus(feature, projectPath, allFeatures) {
10228
10610
  }
10229
10611
  }
10230
10612
  }
10231
- const sessionsDir = path9.join(projectPath, ".harness", "sessions");
10232
- if (fs9.existsSync(sessionsDir)) {
10613
+ const sessionsDir = path16.join(projectPath, ".harness", "sessions");
10614
+ if (fs16.existsSync(sessionsDir)) {
10233
10615
  try {
10234
- const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
10616
+ const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
10235
10617
  for (const entry of sessionDirs) {
10236
10618
  if (!entry.isDirectory()) continue;
10237
- const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
10238
- if (!fs9.existsSync(autopilotPath)) continue;
10619
+ const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
10620
+ if (!fs16.existsSync(autopilotPath)) continue;
10239
10621
  try {
10240
- const raw = fs9.readFileSync(autopilotPath, "utf-8");
10622
+ const raw = fs16.readFileSync(autopilotPath, "utf-8");
10241
10623
  const autopilot = JSON.parse(raw);
10242
10624
  if (!autopilot.phases) continue;
10243
10625
  const linkedPhases = autopilot.phases.filter(
@@ -10284,7 +10666,7 @@ function syncRoadmap(options) {
10284
10666
  to: inferred
10285
10667
  });
10286
10668
  }
10287
- return (0, import_types17.Ok)(changes);
10669
+ return (0, import_types19.Ok)(changes);
10288
10670
  }
10289
10671
 
10290
10672
  // src/interaction/types.ts
@@ -10317,17 +10699,17 @@ var EmitInteractionInputSchema = import_zod7.z.object({
10317
10699
  });
10318
10700
 
10319
10701
  // src/blueprint/scanner.ts
10320
- var fs10 = __toESM(require("fs/promises"));
10321
- var path10 = __toESM(require("path"));
10702
+ var fs17 = __toESM(require("fs/promises"));
10703
+ var path17 = __toESM(require("path"));
10322
10704
  var ProjectScanner = class {
10323
10705
  constructor(rootDir) {
10324
10706
  this.rootDir = rootDir;
10325
10707
  }
10326
10708
  async scan() {
10327
- let projectName = path10.basename(this.rootDir);
10709
+ let projectName = path17.basename(this.rootDir);
10328
10710
  try {
10329
- const pkgPath = path10.join(this.rootDir, "package.json");
10330
- const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
10711
+ const pkgPath = path17.join(this.rootDir, "package.json");
10712
+ const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
10331
10713
  const pkg = JSON.parse(pkgRaw);
10332
10714
  if (pkg.name) projectName = pkg.name;
10333
10715
  } catch {
@@ -10368,8 +10750,8 @@ var ProjectScanner = class {
10368
10750
  };
10369
10751
 
10370
10752
  // src/blueprint/generator.ts
10371
- var fs11 = __toESM(require("fs/promises"));
10372
- var path11 = __toESM(require("path"));
10753
+ var fs18 = __toESM(require("fs/promises"));
10754
+ var path18 = __toESM(require("path"));
10373
10755
  var ejs = __toESM(require("ejs"));
10374
10756
 
10375
10757
  // src/blueprint/templates.ts
@@ -10453,19 +10835,19 @@ var BlueprintGenerator = class {
10453
10835
  styles: STYLES,
10454
10836
  scripts: SCRIPTS
10455
10837
  });
10456
- await fs11.mkdir(options.outputDir, { recursive: true });
10457
- await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
10838
+ await fs18.mkdir(options.outputDir, { recursive: true });
10839
+ await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
10458
10840
  }
10459
10841
  };
10460
10842
 
10461
10843
  // src/update-checker.ts
10462
- var fs12 = __toESM(require("fs"));
10463
- var path12 = __toESM(require("path"));
10844
+ var fs19 = __toESM(require("fs"));
10845
+ var path19 = __toESM(require("path"));
10464
10846
  var os = __toESM(require("os"));
10465
10847
  var import_child_process3 = require("child_process");
10466
10848
  function getStatePath() {
10467
10849
  const home = process.env["HOME"] || os.homedir();
10468
- return path12.join(home, ".harness", "update-check.json");
10850
+ return path19.join(home, ".harness", "update-check.json");
10469
10851
  }
10470
10852
  function isUpdateCheckEnabled(configInterval) {
10471
10853
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -10478,7 +10860,7 @@ function shouldRunCheck(state, intervalMs) {
10478
10860
  }
10479
10861
  function readCheckState() {
10480
10862
  try {
10481
- const raw = fs12.readFileSync(getStatePath(), "utf-8");
10863
+ const raw = fs19.readFileSync(getStatePath(), "utf-8");
10482
10864
  const parsed = JSON.parse(raw);
10483
10865
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
10484
10866
  const state = parsed;
@@ -10495,7 +10877,7 @@ function readCheckState() {
10495
10877
  }
10496
10878
  function spawnBackgroundCheck(currentVersion) {
10497
10879
  const statePath = getStatePath();
10498
- const stateDir = path12.dirname(statePath);
10880
+ const stateDir = path19.dirname(statePath);
10499
10881
  const script = `
10500
10882
  const { execSync } = require('child_process');
10501
10883
  const fs = require('fs');
@@ -10549,7 +10931,7 @@ Run "harness update" to upgrade.`;
10549
10931
  }
10550
10932
 
10551
10933
  // src/index.ts
10552
- var VERSION = "0.11.0";
10934
+ var VERSION = "0.13.0";
10553
10935
  // Annotate the CommonJS export names for ESM import in node:
10554
10936
  0 && (module.exports = {
10555
10937
  AGENT_DESCRIPTORS,
@@ -10627,6 +11009,7 @@ var VERSION = "0.11.0";
10627
11009
  ViolationSchema,
10628
11010
  addProvenance,
10629
11011
  analyzeDiff,
11012
+ analyzeLearningPatterns,
10630
11013
  appendFailure,
10631
11014
  appendLearning,
10632
11015
  applyFixes,
@@ -10635,6 +11018,7 @@ var VERSION = "0.11.0";
10635
11018
  archModule,
10636
11019
  architecture,
10637
11020
  archiveFailures,
11021
+ archiveLearnings,
10638
11022
  archiveStream,
10639
11023
  buildDependencyGraph,
10640
11024
  buildExclusionSet,
@@ -10642,6 +11026,8 @@ var VERSION = "0.11.0";
10642
11026
  checkDocCoverage,
10643
11027
  checkEligibility,
10644
11028
  classifyFinding,
11029
+ clearFailuresCache,
11030
+ clearLearningsCache,
10645
11031
  configureFeedback,
10646
11032
  constraintRuleId,
10647
11033
  contextBudget,
@@ -10697,16 +11083,20 @@ var VERSION = "0.11.0";
10697
11083
  injectionRules,
10698
11084
  isSmallSuggestion,
10699
11085
  isUpdateCheckEnabled,
11086
+ listActiveSessions,
10700
11087
  listStreams,
11088
+ loadBudgetedLearnings,
10701
11089
  loadFailures,
10702
11090
  loadHandoff,
10703
11091
  loadRelevantLearnings,
11092
+ loadSessionSummary,
10704
11093
  loadState,
10705
11094
  loadStreamIndex,
10706
11095
  logAgentAction,
10707
11096
  migrateToStreams,
10708
11097
  networkRules,
10709
11098
  nodeRules,
11099
+ parseDateFromEntry,
10710
11100
  parseDiff,
10711
11101
  parseManifest,
10712
11102
  parseRoadmap,
@@ -10714,6 +11104,7 @@ var VERSION = "0.11.0";
10714
11104
  parseSize,
10715
11105
  pathTraversalRules,
10716
11106
  previewFix,
11107
+ pruneLearnings,
10717
11108
  reactRules,
10718
11109
  readCheckState,
10719
11110
  readLockfile,
@@ -10725,6 +11116,7 @@ var VERSION = "0.11.0";
10725
11116
  resolveFileToLayer,
10726
11117
  resolveModelTier,
10727
11118
  resolveRuleSeverity,
11119
+ resolveSessionDir,
10728
11120
  resolveStreamPath,
10729
11121
  resolveThresholds,
10730
11122
  runAll,
@@ -10751,6 +11143,7 @@ var VERSION = "0.11.0";
10751
11143
  syncRoadmap,
10752
11144
  touchStream,
10753
11145
  trackAction,
11146
+ updateSessionIndex,
10754
11147
  validateAgentsMap,
10755
11148
  validateBoundaries,
10756
11149
  validateCommitMessage,
@@ -10763,6 +11156,7 @@ var VERSION = "0.11.0";
10763
11156
  violationId,
10764
11157
  writeConfig,
10765
11158
  writeLockfile,
11159
+ writeSessionSummary,
10766
11160
  xssRules,
10767
11161
  ...require("@harness-engineering/types")
10768
11162
  });