@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.mjs CHANGED
@@ -36,11 +36,12 @@ import {
36
36
  fileExists,
37
37
  findFiles,
38
38
  readFileContent,
39
+ relativePosix,
39
40
  resolveFileToLayer,
40
41
  runAll,
41
42
  validateDependencies,
42
43
  violationId
43
- } from "./chunk-ZHGBWFYD.mjs";
44
+ } from "./chunk-D6VFA6AS.mjs";
44
45
 
45
46
  // src/index.ts
46
47
  export * from "@harness-engineering/types";
@@ -83,15 +84,15 @@ function validateConfig(data, schema) {
83
84
  let message = "Configuration validation failed";
84
85
  const suggestions = [];
85
86
  if (firstError) {
86
- const path13 = firstError.path.join(".");
87
- const pathDisplay = path13 ? ` at "${path13}"` : "";
87
+ const path20 = firstError.path.join(".");
88
+ const pathDisplay = path20 ? ` at "${path20}"` : "";
88
89
  if (firstError.code === "invalid_type") {
89
90
  const received = firstError.received;
90
91
  const expected = firstError.expected;
91
92
  if (received === "undefined") {
92
93
  code = "MISSING_FIELD";
93
94
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
94
- suggestions.push(`Field "${path13}" is required and must be of type "${expected}"`);
95
+ suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
95
96
  } else {
96
97
  code = "INVALID_TYPE";
97
98
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -304,30 +305,27 @@ function extractSections(content) {
304
305
  return result;
305
306
  });
306
307
  }
307
- function isExternalLink(path13) {
308
- return path13.startsWith("http://") || path13.startsWith("https://") || path13.startsWith("#") || path13.startsWith("mailto:");
308
+ function isExternalLink(path20) {
309
+ return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
309
310
  }
310
311
  function resolveLinkPath(linkPath, baseDir) {
311
312
  return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
312
313
  }
313
- async function validateAgentsMap(path13 = "./AGENTS.md") {
314
- console.warn(
315
- "[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
316
- );
317
- const contentResult = await readFileContent(path13);
314
+ async function validateAgentsMap(path20 = "./AGENTS.md") {
315
+ const contentResult = await readFileContent(path20);
318
316
  if (!contentResult.ok) {
319
317
  return Err(
320
318
  createError(
321
319
  "PARSE_ERROR",
322
320
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
323
- { path: path13 },
321
+ { path: path20 },
324
322
  ["Ensure the file exists", "Check file permissions"]
325
323
  )
326
324
  );
327
325
  }
328
326
  const content = contentResult.value;
329
327
  const sections = extractSections(content);
330
- const baseDir = dirname(path13);
328
+ const baseDir = dirname(path20);
331
329
  const sectionTitles = sections.map((s) => s.title);
332
330
  const missingSections = REQUIRED_SECTIONS.filter(
333
331
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -368,7 +366,7 @@ async function validateAgentsMap(path13 = "./AGENTS.md") {
368
366
 
369
367
  // src/context/doc-coverage.ts
370
368
  import { minimatch } from "minimatch";
371
- import { basename, relative } from "path";
369
+ import { basename } from "path";
372
370
  function determineImportance(filePath) {
373
371
  const name = basename(filePath).toLowerCase();
374
372
  if (name === "index.ts" || name === "index.js" || name === "main.ts") {
@@ -408,7 +406,7 @@ async function checkDocCoverage(domain, options = {}) {
408
406
  try {
409
407
  const sourceFiles = await findFiles("**/*.{ts,js,tsx,jsx}", sourceDir);
410
408
  const filteredSourceFiles = sourceFiles.filter((file) => {
411
- const relativePath = relative(sourceDir, file);
409
+ const relativePath = relativePosix(sourceDir, file);
412
410
  return !excludePatterns.some((pattern) => {
413
411
  return minimatch(relativePath, pattern, { dot: true }) || minimatch(file, pattern, { dot: true });
414
412
  });
@@ -431,7 +429,7 @@ async function checkDocCoverage(domain, options = {}) {
431
429
  const undocumented = [];
432
430
  const gaps = [];
433
431
  for (const sourceFile of filteredSourceFiles) {
434
- const relativePath = relative(sourceDir, sourceFile);
432
+ const relativePath = relativePosix(sourceDir, sourceFile);
435
433
  const fileName = basename(sourceFile);
436
434
  const isDocumented = documentedPaths.has(relativePath) || documentedPaths.has(fileName) || documentedPaths.has(`src/${relativePath}`);
437
435
  if (isDocumented) {
@@ -467,9 +465,9 @@ async function checkDocCoverage(domain, options = {}) {
467
465
  }
468
466
 
469
467
  // src/context/knowledge-map.ts
470
- import { join as join2, basename as basename2, relative as relative2 } from "path";
471
- function suggestFix(path13, existingFiles) {
472
- const targetName = basename2(path13).toLowerCase();
468
+ import { join as join2, basename as basename2 } from "path";
469
+ function suggestFix(path20, existingFiles) {
470
+ const targetName = basename2(path20).toLowerCase();
473
471
  const similar = existingFiles.find((file) => {
474
472
  const fileName = basename2(file).toLowerCase();
475
473
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -477,12 +475,9 @@ function suggestFix(path13, existingFiles) {
477
475
  if (similar) {
478
476
  return `Did you mean "${similar}"?`;
479
477
  }
480
- return `Create the file "${path13}" or remove the link`;
478
+ return `Create the file "${path20}" or remove the link`;
481
479
  }
482
480
  async function validateKnowledgeMap(rootDir = process.cwd()) {
483
- console.warn(
484
- "[harness] validateKnowledgeMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
485
- );
486
481
  const agentsPath = join2(rootDir, "AGENTS.md");
487
482
  const agentsResult = await validateAgentsMap(agentsPath);
488
483
  if (!agentsResult.ok) {
@@ -494,7 +489,7 @@ async function validateKnowledgeMap(rootDir = process.cwd()) {
494
489
  totalLinks: agentsTotalLinks
495
490
  } = agentsResult.value;
496
491
  const existingFiles = await findFiles("**/*", rootDir);
497
- const relativeExistingFiles = existingFiles.map((f) => relative2(rootDir, f));
492
+ const relativeExistingFiles = existingFiles.map((f) => relativePosix(rootDir, f));
498
493
  const brokenLinks = agentsBrokenLinks.map((link) => {
499
494
  const section = sections.find(
500
495
  (s) => s.links.some((l) => l.path === link.path && l.line === link.line)
@@ -519,7 +514,7 @@ async function validateKnowledgeMap(rootDir = process.cwd()) {
519
514
  }
520
515
 
521
516
  // src/context/generate.ts
522
- import { relative as relative3, basename as basename3, dirname as dirname2 } from "path";
517
+ import { basename as basename3, dirname as dirname2 } from "path";
523
518
  var DEFAULT_SECTIONS = [
524
519
  {
525
520
  name: "Documentation",
@@ -535,7 +530,7 @@ var DEFAULT_SECTIONS = [
535
530
  function groupByDirectory(files, rootDir) {
536
531
  const groups = /* @__PURE__ */ new Map();
537
532
  for (const file of files) {
538
- const relativePath = relative3(rootDir, file);
533
+ const relativePath = relativePosix(rootDir, file);
539
534
  const dir = dirname2(relativePath);
540
535
  if (!groups.has(dir)) {
541
536
  groups.set(dir, []);
@@ -591,7 +586,7 @@ async function generateAgentsMap(config, graphSections) {
591
586
  allFiles.push(...files);
592
587
  }
593
588
  const filteredFiles = allFiles.filter((file) => {
594
- const relativePath = relative3(rootDir, file);
589
+ const relativePath = relativePosix(rootDir, file);
595
590
  return !matchesExcludePattern(relativePath, excludePaths);
596
591
  });
597
592
  lines.push("## Repository Structure");
@@ -619,11 +614,11 @@ async function generateAgentsMap(config, graphSections) {
619
614
  }
620
615
  const sectionFiles = await findFiles(section.pattern, rootDir);
621
616
  const filteredSectionFiles = sectionFiles.filter((file) => {
622
- const relativePath = relative3(rootDir, file);
617
+ const relativePath = relativePosix(rootDir, file);
623
618
  return !matchesExcludePattern(relativePath, excludePaths);
624
619
  });
625
620
  for (const file of filteredSectionFiles.slice(0, 20)) {
626
- lines.push(formatFileLink(relative3(rootDir, file)));
621
+ lines.push(formatFileLink(relativePosix(rootDir, file)));
627
622
  }
628
623
  if (filteredSectionFiles.length > 20) {
629
624
  lines.push(`- _... and ${filteredSectionFiles.length - 20} more files_`);
@@ -832,8 +827,8 @@ function createBoundaryValidator(schema, name) {
832
827
  return Ok(result.data);
833
828
  }
834
829
  const suggestions = result.error.issues.map((issue) => {
835
- const path13 = issue.path.join(".");
836
- return path13 ? `${path13}: ${issue.message}` : issue.message;
830
+ const path20 = issue.path.join(".");
831
+ return path20 ? `${path20}: ${issue.message}` : issue.message;
837
832
  });
838
833
  return Err(
839
834
  createError(
@@ -1387,11 +1382,11 @@ function walk(node, visitor) {
1387
1382
  var TypeScriptParser = class {
1388
1383
  name = "typescript";
1389
1384
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1390
- async parseFile(path13) {
1391
- const contentResult = await readFileContent(path13);
1385
+ async parseFile(path20) {
1386
+ const contentResult = await readFileContent(path20);
1392
1387
  if (!contentResult.ok) {
1393
1388
  return Err(
1394
- createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
1389
+ createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
1395
1390
  "Check that the file exists",
1396
1391
  "Verify the path is correct"
1397
1392
  ])
@@ -1401,7 +1396,7 @@ var TypeScriptParser = class {
1401
1396
  const ast = parse(contentResult.value, {
1402
1397
  loc: true,
1403
1398
  range: true,
1404
- jsx: path13.endsWith(".tsx"),
1399
+ jsx: path20.endsWith(".tsx"),
1405
1400
  errorOnUnknownASTType: false
1406
1401
  });
1407
1402
  return Ok({
@@ -1412,7 +1407,7 @@ var TypeScriptParser = class {
1412
1407
  } catch (e) {
1413
1408
  const error = e;
1414
1409
  return Err(
1415
- createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
1410
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
1416
1411
  "Check for syntax errors in the file",
1417
1412
  "Ensure valid TypeScript syntax"
1418
1413
  ])
@@ -1578,7 +1573,7 @@ var TypeScriptParser = class {
1578
1573
  };
1579
1574
 
1580
1575
  // src/entropy/snapshot.ts
1581
- import { join as join3, resolve, relative as relative4 } from "path";
1576
+ import { join as join3, resolve } from "path";
1582
1577
  import { minimatch as minimatch2 } from "minimatch";
1583
1578
  async function resolveEntryPoints(rootDir, explicitEntries) {
1584
1579
  if (explicitEntries && explicitEntries.length > 0) {
@@ -1696,22 +1691,22 @@ function extractInlineRefs(content) {
1696
1691
  }
1697
1692
  return refs;
1698
1693
  }
1699
- async function parseDocumentationFile(path13) {
1700
- const contentResult = await readFileContent(path13);
1694
+ async function parseDocumentationFile(path20) {
1695
+ const contentResult = await readFileContent(path20);
1701
1696
  if (!contentResult.ok) {
1702
1697
  return Err(
1703
1698
  createEntropyError(
1704
1699
  "PARSE_ERROR",
1705
- `Failed to read documentation file: ${path13}`,
1706
- { file: path13 },
1700
+ `Failed to read documentation file: ${path20}`,
1701
+ { file: path20 },
1707
1702
  ["Check that the file exists"]
1708
1703
  )
1709
1704
  );
1710
1705
  }
1711
1706
  const content = contentResult.value;
1712
- const type = path13.endsWith(".md") ? "markdown" : "text";
1707
+ const type = path20.endsWith(".md") ? "markdown" : "text";
1713
1708
  return Ok({
1714
- path: path13,
1709
+ path: path20,
1715
1710
  type,
1716
1711
  content,
1717
1712
  codeBlocks: extractCodeBlocks(content),
@@ -1842,7 +1837,7 @@ async function buildSnapshot(config) {
1842
1837
  sourceFilePaths.push(...files2);
1843
1838
  }
1844
1839
  sourceFilePaths = sourceFilePaths.filter((f) => {
1845
- const rel = relative4(rootDir, f);
1840
+ const rel = relativePosix(rootDir, f);
1846
1841
  return !excludePatterns.some((p) => minimatch2(rel, p));
1847
1842
  });
1848
1843
  const files = [];
@@ -2374,9 +2369,8 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
2374
2369
 
2375
2370
  // src/entropy/detectors/patterns.ts
2376
2371
  import { minimatch as minimatch3 } from "minimatch";
2377
- import { relative as relative5 } from "path";
2378
2372
  function fileMatchesPattern(filePath, pattern, rootDir) {
2379
- const relativePath = relative5(rootDir, filePath);
2373
+ const relativePath = relativePosix(rootDir, filePath);
2380
2374
  return minimatch3(relativePath, pattern);
2381
2375
  }
2382
2376
  function checkConfigPattern(pattern, file, rootDir) {
@@ -2522,15 +2516,34 @@ async function detectPatternViolations(snapshot, config) {
2522
2516
  }
2523
2517
  }
2524
2518
  }
2519
+ if (config?.customPatterns) {
2520
+ for (const file of snapshot.files) {
2521
+ for (const custom of config.customPatterns) {
2522
+ const matches = custom.check(file, snapshot);
2523
+ for (const match of matches) {
2524
+ violations.push({
2525
+ pattern: custom.name,
2526
+ file: file.path,
2527
+ line: match.line,
2528
+ message: match.message,
2529
+ suggestion: match.suggestion || "Review and fix this pattern violation",
2530
+ severity: custom.severity
2531
+ });
2532
+ }
2533
+ }
2534
+ }
2535
+ }
2525
2536
  const errorCount = violations.filter((v) => v.severity === "error").length;
2526
2537
  const warningCount = violations.filter((v) => v.severity === "warning").length;
2527
- const totalChecks = snapshot.files.length * patterns.length;
2528
- const passRate = totalChecks > 0 ? (totalChecks - violations.length) / totalChecks : 1;
2538
+ const customCount = config?.customPatterns?.length ?? 0;
2539
+ const allPatternsCount = patterns.length + customCount;
2540
+ const totalChecks = snapshot.files.length * allPatternsCount;
2541
+ const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
2529
2542
  return Ok({
2530
2543
  violations,
2531
2544
  stats: {
2532
2545
  filesChecked: snapshot.files.length,
2533
- patternsApplied: patterns.length,
2546
+ patternsApplied: allPatternsCount,
2534
2547
  violationCount: violations.length,
2535
2548
  errorCount,
2536
2549
  warningCount
@@ -4713,10 +4726,13 @@ var DEFAULT_STATE = {
4713
4726
  progress: {}
4714
4727
  };
4715
4728
 
4716
- // src/state/state-manager.ts
4717
- import * as fs6 from "fs";
4718
- import * as path3 from "path";
4719
- import { execSync as execSync2 } from "child_process";
4729
+ // src/state/state-persistence.ts
4730
+ import * as fs8 from "fs";
4731
+ import * as path5 from "path";
4732
+
4733
+ // src/state/state-shared.ts
4734
+ import * as fs7 from "fs";
4735
+ import * as path4 from "path";
4720
4736
 
4721
4737
  // src/state/stream-resolver.ts
4722
4738
  import * as fs5 from "fs";
@@ -4742,10 +4758,20 @@ var DEFAULT_STREAM_INDEX = {
4742
4758
  streams: {}
4743
4759
  };
4744
4760
 
4745
- // src/state/stream-resolver.ts
4761
+ // src/state/constants.ts
4746
4762
  var HARNESS_DIR = ".harness";
4747
- var STREAMS_DIR = "streams";
4763
+ var STATE_FILE = "state.json";
4764
+ var LEARNINGS_FILE = "learnings.md";
4765
+ var FAILURES_FILE = "failures.md";
4766
+ var HANDOFF_FILE = "handoff.json";
4767
+ var GATE_CONFIG_FILE = "gate.json";
4748
4768
  var INDEX_FILE = "index.json";
4769
+ var SESSIONS_DIR = "sessions";
4770
+ var SESSION_INDEX_FILE = "index.md";
4771
+ var SUMMARY_FILE = "summary.md";
4772
+
4773
+ // src/state/stream-resolver.ts
4774
+ var STREAMS_DIR = "streams";
4749
4775
  var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
4750
4776
  function streamsDir(projectPath) {
4751
4777
  return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
@@ -4972,26 +4998,65 @@ async function migrateToStreams(projectPath) {
4972
4998
  return saveStreamIndex(projectPath, index);
4973
4999
  }
4974
5000
 
4975
- // src/state/state-manager.ts
4976
- var HARNESS_DIR2 = ".harness";
4977
- var STATE_FILE = "state.json";
4978
- var LEARNINGS_FILE = "learnings.md";
4979
- var FAILURES_FILE = "failures.md";
4980
- var HANDOFF_FILE = "handoff.json";
4981
- var GATE_CONFIG_FILE = "gate.json";
4982
- var INDEX_FILE2 = "index.json";
5001
+ // src/state/session-resolver.ts
5002
+ import * as fs6 from "fs";
5003
+ import * as path3 from "path";
5004
+ function resolveSessionDir(projectPath, sessionSlug, options) {
5005
+ if (!sessionSlug || sessionSlug.trim() === "") {
5006
+ return Err(new Error("Session slug must not be empty"));
5007
+ }
5008
+ if (sessionSlug.includes("..") || sessionSlug.includes("/") || sessionSlug.includes("\\")) {
5009
+ return Err(
5010
+ new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
5011
+ );
5012
+ }
5013
+ const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
5014
+ if (options?.create) {
5015
+ fs6.mkdirSync(sessionDir, { recursive: true });
5016
+ }
5017
+ return Ok(sessionDir);
5018
+ }
5019
+ function updateSessionIndex(projectPath, sessionSlug, description) {
5020
+ const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
5021
+ fs6.mkdirSync(sessionsDir, { recursive: true });
5022
+ const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
5023
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5024
+ const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
5025
+ if (!fs6.existsSync(indexPath2)) {
5026
+ fs6.writeFileSync(indexPath2, `## Active Sessions
5027
+
5028
+ ${newLine}
5029
+ `);
5030
+ return;
5031
+ }
5032
+ const content = fs6.readFileSync(indexPath2, "utf-8");
5033
+ const lines = content.split("\n");
5034
+ const slugPattern = `- [${sessionSlug}]`;
5035
+ const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
5036
+ if (existingIdx >= 0) {
5037
+ lines[existingIdx] = newLine;
5038
+ } else {
5039
+ const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
5040
+ lines.splice(lastNonEmpty + 1, 0, newLine);
5041
+ }
5042
+ fs6.writeFileSync(indexPath2, lines.join("\n"));
5043
+ }
5044
+
5045
+ // src/state/state-shared.ts
4983
5046
  var MAX_CACHE_ENTRIES = 8;
4984
- var learningsCacheMap = /* @__PURE__ */ new Map();
4985
- var failuresCacheMap = /* @__PURE__ */ new Map();
4986
5047
  function evictIfNeeded(map) {
4987
5048
  if (map.size > MAX_CACHE_ENTRIES) {
4988
5049
  const oldest = map.keys().next().value;
4989
5050
  if (oldest !== void 0) map.delete(oldest);
4990
5051
  }
4991
5052
  }
4992
- async function getStateDir(projectPath, stream) {
4993
- const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
4994
- const hasStreams = fs6.existsSync(streamsIndexPath);
5053
+ async function getStateDir(projectPath, stream, session) {
5054
+ if (session) {
5055
+ const sessionResult = resolveSessionDir(projectPath, session, { create: true });
5056
+ return sessionResult;
5057
+ }
5058
+ const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
5059
+ const hasStreams = fs7.existsSync(streamsIndexPath);
4995
5060
  if (stream || hasStreams) {
4996
5061
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
4997
5062
  if (result.ok) {
@@ -5001,18 +5066,20 @@ async function getStateDir(projectPath, stream) {
5001
5066
  return result;
5002
5067
  }
5003
5068
  }
5004
- return Ok(path3.join(projectPath, HARNESS_DIR2));
5069
+ return Ok(path4.join(projectPath, HARNESS_DIR));
5005
5070
  }
5006
- async function loadState(projectPath, stream) {
5071
+
5072
+ // src/state/state-persistence.ts
5073
+ async function loadState(projectPath, stream, session) {
5007
5074
  try {
5008
- const dirResult = await getStateDir(projectPath, stream);
5075
+ const dirResult = await getStateDir(projectPath, stream, session);
5009
5076
  if (!dirResult.ok) return dirResult;
5010
5077
  const stateDir = dirResult.value;
5011
- const statePath = path3.join(stateDir, STATE_FILE);
5012
- if (!fs6.existsSync(statePath)) {
5078
+ const statePath = path5.join(stateDir, STATE_FILE);
5079
+ if (!fs8.existsSync(statePath)) {
5013
5080
  return Ok({ ...DEFAULT_STATE });
5014
5081
  }
5015
- const raw = fs6.readFileSync(statePath, "utf-8");
5082
+ const raw = fs8.readFileSync(statePath, "utf-8");
5016
5083
  const parsed = JSON.parse(raw);
5017
5084
  const result = HarnessStateSchema.safeParse(parsed);
5018
5085
  if (!result.success) {
@@ -5025,14 +5092,14 @@ async function loadState(projectPath, stream) {
5025
5092
  );
5026
5093
  }
5027
5094
  }
5028
- async function saveState(projectPath, state, stream) {
5095
+ async function saveState(projectPath, state, stream, session) {
5029
5096
  try {
5030
- const dirResult = await getStateDir(projectPath, stream);
5097
+ const dirResult = await getStateDir(projectPath, stream, session);
5031
5098
  if (!dirResult.ok) return dirResult;
5032
5099
  const stateDir = dirResult.value;
5033
- const statePath = path3.join(stateDir, STATE_FILE);
5034
- fs6.mkdirSync(stateDir, { recursive: true });
5035
- fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
5100
+ const statePath = path5.join(stateDir, STATE_FILE);
5101
+ fs8.mkdirSync(stateDir, { recursive: true });
5102
+ fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
5036
5103
  return Ok(void 0);
5037
5104
  } catch (error) {
5038
5105
  return Err(
@@ -5040,13 +5107,21 @@ async function saveState(projectPath, state, stream) {
5040
5107
  );
5041
5108
  }
5042
5109
  }
5043
- async function appendLearning(projectPath, learning, skillName, outcome, stream) {
5110
+
5111
+ // src/state/learnings.ts
5112
+ import * as fs9 from "fs";
5113
+ import * as path6 from "path";
5114
+ var learningsCacheMap = /* @__PURE__ */ new Map();
5115
+ function clearLearningsCache() {
5116
+ learningsCacheMap.clear();
5117
+ }
5118
+ async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
5044
5119
  try {
5045
- const dirResult = await getStateDir(projectPath, stream);
5120
+ const dirResult = await getStateDir(projectPath, stream, session);
5046
5121
  if (!dirResult.ok) return dirResult;
5047
5122
  const stateDir = dirResult.value;
5048
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
5049
- fs6.mkdirSync(stateDir, { recursive: true });
5123
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
5124
+ fs9.mkdirSync(stateDir, { recursive: true });
5050
5125
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5051
5126
  let entry;
5052
5127
  if (skillName && outcome) {
@@ -5062,11 +5137,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
5062
5137
  - **${timestamp}:** ${learning}
5063
5138
  `;
5064
5139
  }
5065
- if (!fs6.existsSync(learningsPath)) {
5066
- fs6.writeFileSync(learningsPath, `# Learnings
5140
+ if (!fs9.existsSync(learningsPath)) {
5141
+ fs9.writeFileSync(learningsPath, `# Learnings
5067
5142
  ${entry}`);
5068
5143
  } else {
5069
- fs6.appendFileSync(learningsPath, entry);
5144
+ fs9.appendFileSync(learningsPath, entry);
5070
5145
  }
5071
5146
  learningsCacheMap.delete(learningsPath);
5072
5147
  return Ok(void 0);
@@ -5078,23 +5153,92 @@ ${entry}`);
5078
5153
  );
5079
5154
  }
5080
5155
  }
5081
- async function loadRelevantLearnings(projectPath, skillName, stream) {
5156
+ function estimateTokens(text) {
5157
+ return Math.ceil(text.length / 4);
5158
+ }
5159
+ function scoreRelevance(entry, intent) {
5160
+ if (!intent || intent.trim() === "") return 0;
5161
+ const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
5162
+ if (intentWords.length === 0) return 0;
5163
+ const entryLower = entry.toLowerCase();
5164
+ const matches = intentWords.filter((word) => entryLower.includes(word));
5165
+ return matches.length / intentWords.length;
5166
+ }
5167
+ function parseDateFromEntry(entry) {
5168
+ const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
5169
+ return match ? match[1] ?? null : null;
5170
+ }
5171
+ function analyzeLearningPatterns(entries) {
5172
+ const tagGroups = /* @__PURE__ */ new Map();
5173
+ for (const entry of entries) {
5174
+ const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
5175
+ for (const match of tagMatches) {
5176
+ const tag = match[1] ?? match[2];
5177
+ if (tag) {
5178
+ const group = tagGroups.get(tag) ?? [];
5179
+ group.push(entry);
5180
+ tagGroups.set(tag, group);
5181
+ }
5182
+ }
5183
+ }
5184
+ const patterns = [];
5185
+ for (const [tag, groupEntries] of tagGroups) {
5186
+ if (groupEntries.length >= 3) {
5187
+ patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
5188
+ }
5189
+ }
5190
+ return patterns.sort((a, b) => b.count - a.count);
5191
+ }
5192
+ async function loadBudgetedLearnings(projectPath, options) {
5193
+ const { intent, tokenBudget = 1e3, skill, session, stream } = options;
5194
+ const sortByRecencyAndRelevance = (entries) => {
5195
+ return [...entries].sort((a, b) => {
5196
+ const dateA = parseDateFromEntry(a) ?? "0000-00-00";
5197
+ const dateB = parseDateFromEntry(b) ?? "0000-00-00";
5198
+ const dateCompare = dateB.localeCompare(dateA);
5199
+ if (dateCompare !== 0) return dateCompare;
5200
+ return scoreRelevance(b, intent) - scoreRelevance(a, intent);
5201
+ });
5202
+ };
5203
+ const allEntries = [];
5204
+ if (session) {
5205
+ const sessionResult = await loadRelevantLearnings(projectPath, skill, stream, session);
5206
+ if (sessionResult.ok) {
5207
+ allEntries.push(...sortByRecencyAndRelevance(sessionResult.value));
5208
+ }
5209
+ }
5210
+ const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
5211
+ if (globalResult.ok) {
5212
+ allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
5213
+ }
5214
+ const budgeted = [];
5215
+ let totalTokens = 0;
5216
+ for (const entry of allEntries) {
5217
+ const separator = budgeted.length > 0 ? "\n" : "";
5218
+ const entryCost = estimateTokens(entry + separator);
5219
+ if (totalTokens + entryCost > tokenBudget) break;
5220
+ budgeted.push(entry);
5221
+ totalTokens += entryCost;
5222
+ }
5223
+ return Ok(budgeted);
5224
+ }
5225
+ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
5082
5226
  try {
5083
- const dirResult = await getStateDir(projectPath, stream);
5227
+ const dirResult = await getStateDir(projectPath, stream, session);
5084
5228
  if (!dirResult.ok) return dirResult;
5085
5229
  const stateDir = dirResult.value;
5086
- const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
5087
- if (!fs6.existsSync(learningsPath)) {
5230
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
5231
+ if (!fs9.existsSync(learningsPath)) {
5088
5232
  return Ok([]);
5089
5233
  }
5090
- const stats = fs6.statSync(learningsPath);
5234
+ const stats = fs9.statSync(learningsPath);
5091
5235
  const cacheKey = learningsPath;
5092
5236
  const cached = learningsCacheMap.get(cacheKey);
5093
5237
  let entries;
5094
5238
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5095
5239
  entries = cached.entries;
5096
5240
  } else {
5097
- const content = fs6.readFileSync(learningsPath, "utf-8");
5241
+ const content = fs9.readFileSync(learningsPath, "utf-8");
5098
5242
  const lines = content.split("\n");
5099
5243
  entries = [];
5100
5244
  let currentBlock = [];
@@ -5130,23 +5274,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
5130
5274
  );
5131
5275
  }
5132
5276
  }
5133
- var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
5134
- async function appendFailure(projectPath, description, skillName, type, stream) {
5277
+ async function archiveLearnings(projectPath, entries, stream) {
5278
+ try {
5279
+ const dirResult = await getStateDir(projectPath, stream);
5280
+ if (!dirResult.ok) return dirResult;
5281
+ const stateDir = dirResult.value;
5282
+ const archiveDir = path6.join(stateDir, "learnings-archive");
5283
+ fs9.mkdirSync(archiveDir, { recursive: true });
5284
+ const now = /* @__PURE__ */ new Date();
5285
+ const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
5286
+ const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
5287
+ const archiveContent = entries.join("\n\n") + "\n";
5288
+ if (fs9.existsSync(archivePath)) {
5289
+ fs9.appendFileSync(archivePath, "\n" + archiveContent);
5290
+ } else {
5291
+ fs9.writeFileSync(archivePath, `# Learnings Archive
5292
+
5293
+ ${archiveContent}`);
5294
+ }
5295
+ return Ok(void 0);
5296
+ } catch (error) {
5297
+ return Err(
5298
+ new Error(
5299
+ `Failed to archive learnings: ${error instanceof Error ? error.message : String(error)}`
5300
+ )
5301
+ );
5302
+ }
5303
+ }
5304
+ async function pruneLearnings(projectPath, stream) {
5135
5305
  try {
5136
5306
  const dirResult = await getStateDir(projectPath, stream);
5137
5307
  if (!dirResult.ok) return dirResult;
5138
5308
  const stateDir = dirResult.value;
5139
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5140
- fs6.mkdirSync(stateDir, { recursive: true });
5309
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
5310
+ if (!fs9.existsSync(learningsPath)) {
5311
+ return Ok({ kept: 0, archived: 0, patterns: [] });
5312
+ }
5313
+ const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
5314
+ if (!loadResult.ok) return loadResult;
5315
+ const allEntries = loadResult.value;
5316
+ if (allEntries.length <= 20) {
5317
+ const cutoffDate = /* @__PURE__ */ new Date();
5318
+ cutoffDate.setDate(cutoffDate.getDate() - 14);
5319
+ const cutoffStr = cutoffDate.toISOString().split("T")[0];
5320
+ const hasOld = allEntries.some((entry) => {
5321
+ const date = parseDateFromEntry(entry);
5322
+ return date !== null && date < cutoffStr;
5323
+ });
5324
+ if (!hasOld) {
5325
+ return Ok({ kept: allEntries.length, archived: 0, patterns: [] });
5326
+ }
5327
+ }
5328
+ const sorted = [...allEntries].sort((a, b) => {
5329
+ const dateA = parseDateFromEntry(a) ?? "0000-00-00";
5330
+ const dateB = parseDateFromEntry(b) ?? "0000-00-00";
5331
+ return dateB.localeCompare(dateA);
5332
+ });
5333
+ const toKeep = sorted.slice(0, 20);
5334
+ const toArchive = sorted.slice(20);
5335
+ const patterns = analyzeLearningPatterns(allEntries);
5336
+ if (toArchive.length > 0) {
5337
+ const archiveResult = await archiveLearnings(projectPath, toArchive, stream);
5338
+ if (!archiveResult.ok) return archiveResult;
5339
+ }
5340
+ const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
5341
+ fs9.writeFileSync(learningsPath, newContent);
5342
+ learningsCacheMap.delete(learningsPath);
5343
+ return Ok({
5344
+ kept: toKeep.length,
5345
+ archived: toArchive.length,
5346
+ patterns
5347
+ });
5348
+ } catch (error) {
5349
+ return Err(
5350
+ new Error(
5351
+ `Failed to prune learnings: ${error instanceof Error ? error.message : String(error)}`
5352
+ )
5353
+ );
5354
+ }
5355
+ }
5356
+
5357
+ // src/state/failures.ts
5358
+ import * as fs10 from "fs";
5359
+ import * as path7 from "path";
5360
+ var failuresCacheMap = /* @__PURE__ */ new Map();
5361
+ function clearFailuresCache() {
5362
+ failuresCacheMap.clear();
5363
+ }
5364
+ var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
5365
+ async function appendFailure(projectPath, description, skillName, type, stream, session) {
5366
+ try {
5367
+ const dirResult = await getStateDir(projectPath, stream, session);
5368
+ if (!dirResult.ok) return dirResult;
5369
+ const stateDir = dirResult.value;
5370
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
5371
+ fs10.mkdirSync(stateDir, { recursive: true });
5141
5372
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5142
5373
  const entry = `
5143
5374
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
5144
5375
  `;
5145
- if (!fs6.existsSync(failuresPath)) {
5146
- fs6.writeFileSync(failuresPath, `# Failures
5376
+ if (!fs10.existsSync(failuresPath)) {
5377
+ fs10.writeFileSync(failuresPath, `# Failures
5147
5378
  ${entry}`);
5148
5379
  } else {
5149
- fs6.appendFileSync(failuresPath, entry);
5380
+ fs10.appendFileSync(failuresPath, entry);
5150
5381
  }
5151
5382
  failuresCacheMap.delete(failuresPath);
5152
5383
  return Ok(void 0);
@@ -5158,22 +5389,22 @@ ${entry}`);
5158
5389
  );
5159
5390
  }
5160
5391
  }
5161
- async function loadFailures(projectPath, stream) {
5392
+ async function loadFailures(projectPath, stream, session) {
5162
5393
  try {
5163
- const dirResult = await getStateDir(projectPath, stream);
5394
+ const dirResult = await getStateDir(projectPath, stream, session);
5164
5395
  if (!dirResult.ok) return dirResult;
5165
5396
  const stateDir = dirResult.value;
5166
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5167
- if (!fs6.existsSync(failuresPath)) {
5397
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
5398
+ if (!fs10.existsSync(failuresPath)) {
5168
5399
  return Ok([]);
5169
5400
  }
5170
- const stats = fs6.statSync(failuresPath);
5401
+ const stats = fs10.statSync(failuresPath);
5171
5402
  const cacheKey = failuresPath;
5172
5403
  const cached = failuresCacheMap.get(cacheKey);
5173
5404
  if (cached && cached.mtimeMs === stats.mtimeMs) {
5174
5405
  return Ok(cached.entries);
5175
5406
  }
5176
- const content = fs6.readFileSync(failuresPath, "utf-8");
5407
+ const content = fs10.readFileSync(failuresPath, "utf-8");
5177
5408
  const entries = [];
5178
5409
  for (const line of content.split("\n")) {
5179
5410
  const match = line.match(FAILURE_LINE_REGEX);
@@ -5197,25 +5428,25 @@ async function loadFailures(projectPath, stream) {
5197
5428
  );
5198
5429
  }
5199
5430
  }
5200
- async function archiveFailures(projectPath, stream) {
5431
+ async function archiveFailures(projectPath, stream, session) {
5201
5432
  try {
5202
- const dirResult = await getStateDir(projectPath, stream);
5433
+ const dirResult = await getStateDir(projectPath, stream, session);
5203
5434
  if (!dirResult.ok) return dirResult;
5204
5435
  const stateDir = dirResult.value;
5205
- const failuresPath = path3.join(stateDir, FAILURES_FILE);
5206
- if (!fs6.existsSync(failuresPath)) {
5436
+ const failuresPath = path7.join(stateDir, FAILURES_FILE);
5437
+ if (!fs10.existsSync(failuresPath)) {
5207
5438
  return Ok(void 0);
5208
5439
  }
5209
- const archiveDir = path3.join(stateDir, "archive");
5210
- fs6.mkdirSync(archiveDir, { recursive: true });
5440
+ const archiveDir = path7.join(stateDir, "archive");
5441
+ fs10.mkdirSync(archiveDir, { recursive: true });
5211
5442
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5212
5443
  let archiveName = `failures-${date}.md`;
5213
5444
  let counter = 2;
5214
- while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
5445
+ while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
5215
5446
  archiveName = `failures-${date}-${counter}.md`;
5216
5447
  counter++;
5217
5448
  }
5218
- fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5449
+ fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
5219
5450
  failuresCacheMap.delete(failuresPath);
5220
5451
  return Ok(void 0);
5221
5452
  } catch (error) {
@@ -5226,14 +5457,18 @@ async function archiveFailures(projectPath, stream) {
5226
5457
  );
5227
5458
  }
5228
5459
  }
5229
- async function saveHandoff(projectPath, handoff, stream) {
5460
+
5461
+ // src/state/handoff.ts
5462
+ import * as fs11 from "fs";
5463
+ import * as path8 from "path";
5464
+ async function saveHandoff(projectPath, handoff, stream, session) {
5230
5465
  try {
5231
- const dirResult = await getStateDir(projectPath, stream);
5466
+ const dirResult = await getStateDir(projectPath, stream, session);
5232
5467
  if (!dirResult.ok) return dirResult;
5233
5468
  const stateDir = dirResult.value;
5234
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5235
- fs6.mkdirSync(stateDir, { recursive: true });
5236
- fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
5469
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
5470
+ fs11.mkdirSync(stateDir, { recursive: true });
5471
+ fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
5237
5472
  return Ok(void 0);
5238
5473
  } catch (error) {
5239
5474
  return Err(
@@ -5241,16 +5476,16 @@ async function saveHandoff(projectPath, handoff, stream) {
5241
5476
  );
5242
5477
  }
5243
5478
  }
5244
- async function loadHandoff(projectPath, stream) {
5479
+ async function loadHandoff(projectPath, stream, session) {
5245
5480
  try {
5246
- const dirResult = await getStateDir(projectPath, stream);
5481
+ const dirResult = await getStateDir(projectPath, stream, session);
5247
5482
  if (!dirResult.ok) return dirResult;
5248
5483
  const stateDir = dirResult.value;
5249
- const handoffPath = path3.join(stateDir, HANDOFF_FILE);
5250
- if (!fs6.existsSync(handoffPath)) {
5484
+ const handoffPath = path8.join(stateDir, HANDOFF_FILE);
5485
+ if (!fs11.existsSync(handoffPath)) {
5251
5486
  return Ok(null);
5252
5487
  }
5253
- const raw = fs6.readFileSync(handoffPath, "utf-8");
5488
+ const raw = fs11.readFileSync(handoffPath, "utf-8");
5254
5489
  const parsed = JSON.parse(raw);
5255
5490
  const result = HandoffSchema.safeParse(parsed);
5256
5491
  if (!result.success) {
@@ -5263,73 +5498,82 @@ async function loadHandoff(projectPath, stream) {
5263
5498
  );
5264
5499
  }
5265
5500
  }
5501
+
5502
+ // src/state/mechanical-gate.ts
5503
+ import * as fs12 from "fs";
5504
+ import * as path9 from "path";
5505
+ import { execSync as execSync2 } from "child_process";
5506
+ var SAFE_GATE_COMMAND = /^(?:npm|pnpm|yarn)\s+(?:test|run\s+[\w.-]+|run-script\s+[\w.-]+)$|^go\s+(?:test|build|vet|fmt)\s+[\w./ -]+$|^(?:python|python3)\s+-m\s+[\w.-]+$|^make\s+[\w.-]+$|^cargo\s+(?:test|build|check|clippy)(?:\s+[\w./ -]+)?$|^(?:gradle|mvn)\s+[\w:.-]+$/;
5507
+ function loadChecksFromConfig(gateConfigPath) {
5508
+ if (!fs12.existsSync(gateConfigPath)) return [];
5509
+ const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
5510
+ const config = GateConfigSchema.safeParse(raw);
5511
+ if (config.success && config.data.checks) return config.data.checks;
5512
+ return [];
5513
+ }
5514
+ function discoverChecksFromProject(projectPath) {
5515
+ const checks = [];
5516
+ const packageJsonPath = path9.join(projectPath, "package.json");
5517
+ if (fs12.existsSync(packageJsonPath)) {
5518
+ const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
5519
+ const scripts = pkg.scripts || {};
5520
+ if (scripts.test) checks.push({ name: "test", command: "npm test" });
5521
+ if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
5522
+ if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
5523
+ if (scripts.build) checks.push({ name: "build", command: "npm run build" });
5524
+ }
5525
+ if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
5526
+ checks.push({ name: "test", command: "go test ./..." });
5527
+ checks.push({ name: "build", command: "go build ./..." });
5528
+ }
5529
+ if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
5530
+ checks.push({ name: "test", command: "python -m pytest" });
5531
+ }
5532
+ return checks;
5533
+ }
5534
+ function executeCheck(check, projectPath) {
5535
+ if (!SAFE_GATE_COMMAND.test(check.command)) {
5536
+ return {
5537
+ name: check.name,
5538
+ passed: false,
5539
+ command: check.command,
5540
+ output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
5541
+ duration: 0
5542
+ };
5543
+ }
5544
+ const start = Date.now();
5545
+ try {
5546
+ execSync2(check.command, {
5547
+ cwd: projectPath,
5548
+ stdio: "pipe",
5549
+ timeout: 12e4
5550
+ });
5551
+ return {
5552
+ name: check.name,
5553
+ passed: true,
5554
+ command: check.command,
5555
+ duration: Date.now() - start
5556
+ };
5557
+ } catch (error) {
5558
+ const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
5559
+ return {
5560
+ name: check.name,
5561
+ passed: false,
5562
+ command: check.command,
5563
+ output: output.slice(0, 2e3),
5564
+ duration: Date.now() - start
5565
+ };
5566
+ }
5567
+ }
5266
5568
  async function runMechanicalGate(projectPath) {
5267
- const harnessDir = path3.join(projectPath, HARNESS_DIR2);
5268
- const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
5569
+ const harnessDir = path9.join(projectPath, HARNESS_DIR);
5570
+ const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
5269
5571
  try {
5270
- let checks = [];
5271
- if (fs6.existsSync(gateConfigPath)) {
5272
- const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
5273
- const config = GateConfigSchema.safeParse(raw);
5274
- if (config.success && config.data.checks) {
5275
- checks = config.data.checks;
5276
- }
5277
- }
5572
+ let checks = loadChecksFromConfig(gateConfigPath);
5278
5573
  if (checks.length === 0) {
5279
- const packageJsonPath = path3.join(projectPath, "package.json");
5280
- if (fs6.existsSync(packageJsonPath)) {
5281
- const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
5282
- const scripts = pkg.scripts || {};
5283
- if (scripts.test) checks.push({ name: "test", command: "npm test" });
5284
- if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
5285
- if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
5286
- if (scripts.build) checks.push({ name: "build", command: "npm run build" });
5287
- }
5288
- if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
5289
- checks.push({ name: "test", command: "go test ./..." });
5290
- checks.push({ name: "build", command: "go build ./..." });
5291
- }
5292
- if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
5293
- checks.push({ name: "test", command: "python -m pytest" });
5294
- }
5295
- }
5296
- const results = [];
5297
- const SAFE_GATE_COMMAND = /^(?:npm|pnpm|yarn)\s+(?:test|run\s+[\w.-]+|run-script\s+[\w.-]+)$|^go\s+(?:test|build|vet|fmt)\s+[\w./ -]+$|^(?:python|python3)\s+-m\s+[\w.-]+$|^make\s+[\w.-]+$|^cargo\s+(?:test|build|check|clippy)(?:\s+[\w./ -]+)?$|^(?:gradle|mvn)\s+[\w:.-]+$/;
5298
- for (const check of checks) {
5299
- if (!SAFE_GATE_COMMAND.test(check.command)) {
5300
- results.push({
5301
- name: check.name,
5302
- passed: false,
5303
- command: check.command,
5304
- output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
5305
- duration: 0
5306
- });
5307
- continue;
5308
- }
5309
- const start = Date.now();
5310
- try {
5311
- execSync2(check.command, {
5312
- cwd: projectPath,
5313
- stdio: "pipe",
5314
- timeout: 12e4
5315
- });
5316
- results.push({
5317
- name: check.name,
5318
- passed: true,
5319
- command: check.command,
5320
- duration: Date.now() - start
5321
- });
5322
- } catch (error) {
5323
- const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
5324
- results.push({
5325
- name: check.name,
5326
- passed: false,
5327
- command: check.command,
5328
- output: output.slice(0, 2e3),
5329
- duration: Date.now() - start
5330
- });
5331
- }
5574
+ checks = discoverChecksFromProject(projectPath);
5332
5575
  }
5576
+ const results = checks.map((check) => executeCheck(check, projectPath));
5333
5577
  return Ok({
5334
5578
  passed: results.length === 0 || results.every((r) => r.passed),
5335
5579
  checks: results
@@ -5343,6 +5587,96 @@ async function runMechanicalGate(projectPath) {
5343
5587
  }
5344
5588
  }
5345
5589
 
5590
+ // src/state/session-summary.ts
5591
+ import * as fs13 from "fs";
5592
+ import * as path10 from "path";
5593
+ function formatSummary(data) {
5594
+ const lines = [
5595
+ "## Session Summary",
5596
+ "",
5597
+ `**Session:** ${data.session}`,
5598
+ `**Last active:** ${data.lastActive}`,
5599
+ `**Skill:** ${data.skill}`
5600
+ ];
5601
+ if (data.phase) {
5602
+ lines.push(`**Phase:** ${data.phase}`);
5603
+ }
5604
+ lines.push(`**Status:** ${data.status}`);
5605
+ if (data.spec) {
5606
+ lines.push(`**Spec:** ${data.spec}`);
5607
+ }
5608
+ if (data.plan) {
5609
+ lines.push(`**Plan:** ${data.plan}`);
5610
+ }
5611
+ lines.push(`**Key context:** ${data.keyContext}`);
5612
+ lines.push(`**Next step:** ${data.nextStep}`);
5613
+ lines.push("");
5614
+ return lines.join("\n");
5615
+ }
5616
+ function deriveIndexDescription(data) {
5617
+ const skillShort = data.skill.replace("harness-", "");
5618
+ const parts = [skillShort];
5619
+ if (data.phase) {
5620
+ parts.push(`phase ${data.phase}`);
5621
+ }
5622
+ parts.push(data.status.toLowerCase());
5623
+ return parts.join(", ");
5624
+ }
5625
+ function writeSessionSummary(projectPath, sessionSlug, data) {
5626
+ try {
5627
+ const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
5628
+ if (!dirResult.ok) return dirResult;
5629
+ const sessionDir = dirResult.value;
5630
+ const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
5631
+ const content = formatSummary(data);
5632
+ fs13.writeFileSync(summaryPath, content);
5633
+ const description = deriveIndexDescription(data);
5634
+ updateSessionIndex(projectPath, sessionSlug, description);
5635
+ return Ok(void 0);
5636
+ } catch (error) {
5637
+ return Err(
5638
+ new Error(
5639
+ `Failed to write session summary: ${error instanceof Error ? error.message : String(error)}`
5640
+ )
5641
+ );
5642
+ }
5643
+ }
5644
+ function loadSessionSummary(projectPath, sessionSlug) {
5645
+ try {
5646
+ const dirResult = resolveSessionDir(projectPath, sessionSlug);
5647
+ if (!dirResult.ok) return dirResult;
5648
+ const sessionDir = dirResult.value;
5649
+ const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
5650
+ if (!fs13.existsSync(summaryPath)) {
5651
+ return Ok(null);
5652
+ }
5653
+ const content = fs13.readFileSync(summaryPath, "utf-8");
5654
+ return Ok(content);
5655
+ } catch (error) {
5656
+ return Err(
5657
+ new Error(
5658
+ `Failed to load session summary: ${error instanceof Error ? error.message : String(error)}`
5659
+ )
5660
+ );
5661
+ }
5662
+ }
5663
+ function listActiveSessions(projectPath) {
5664
+ try {
5665
+ const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
5666
+ if (!fs13.existsSync(indexPath2)) {
5667
+ return Ok(null);
5668
+ }
5669
+ const content = fs13.readFileSync(indexPath2, "utf-8");
5670
+ return Ok(content);
5671
+ } catch (error) {
5672
+ return Err(
5673
+ new Error(
5674
+ `Failed to list active sessions: ${error instanceof Error ? error.message : String(error)}`
5675
+ )
5676
+ );
5677
+ }
5678
+ }
5679
+
5346
5680
  // src/workflow/runner.ts
5347
5681
  async function executeWorkflow(workflow, executor) {
5348
5682
  const stepResults = [];
@@ -5492,7 +5826,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
5492
5826
  }
5493
5827
 
5494
5828
  // src/security/scanner.ts
5495
- import * as fs8 from "fs/promises";
5829
+ import * as fs15 from "fs/promises";
5496
5830
 
5497
5831
  // src/security/rules/registry.ts
5498
5832
  var RuleRegistry = class {
@@ -5579,15 +5913,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
5579
5913
  }
5580
5914
 
5581
5915
  // src/security/stack-detector.ts
5582
- import * as fs7 from "fs";
5583
- import * as path4 from "path";
5916
+ import * as fs14 from "fs";
5917
+ import * as path11 from "path";
5584
5918
  function detectStack(projectRoot) {
5585
5919
  const stacks = [];
5586
- const pkgJsonPath = path4.join(projectRoot, "package.json");
5587
- if (fs7.existsSync(pkgJsonPath)) {
5920
+ const pkgJsonPath = path11.join(projectRoot, "package.json");
5921
+ if (fs14.existsSync(pkgJsonPath)) {
5588
5922
  stacks.push("node");
5589
5923
  try {
5590
- const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
5924
+ const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
5591
5925
  const allDeps = {
5592
5926
  ...pkgJson.dependencies,
5593
5927
  ...pkgJson.devDependencies
@@ -5602,13 +5936,13 @@ function detectStack(projectRoot) {
5602
5936
  } catch {
5603
5937
  }
5604
5938
  }
5605
- const goModPath = path4.join(projectRoot, "go.mod");
5606
- if (fs7.existsSync(goModPath)) {
5939
+ const goModPath = path11.join(projectRoot, "go.mod");
5940
+ if (fs14.existsSync(goModPath)) {
5607
5941
  stacks.push("go");
5608
5942
  }
5609
- const requirementsPath = path4.join(projectRoot, "requirements.txt");
5610
- const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
5611
- if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
5943
+ const requirementsPath = path11.join(projectRoot, "requirements.txt");
5944
+ const pyprojectPath = path11.join(projectRoot, "pyproject.toml");
5945
+ if (fs14.existsSync(requirementsPath) || fs14.existsSync(pyprojectPath)) {
5612
5946
  stacks.push("python");
5613
5947
  }
5614
5948
  return stacks;
@@ -6035,7 +6369,7 @@ var SecurityScanner = class {
6035
6369
  }
6036
6370
  async scanFile(filePath) {
6037
6371
  if (!this.config.enabled) return [];
6038
- const content = await fs8.readFile(filePath, "utf-8");
6372
+ const content = await fs15.readFile(filePath, "utf-8");
6039
6373
  return this.scanContent(content, filePath, 1);
6040
6374
  }
6041
6375
  async scanFiles(filePaths) {
@@ -6060,7 +6394,7 @@ var SecurityScanner = class {
6060
6394
  };
6061
6395
 
6062
6396
  // src/ci/check-orchestrator.ts
6063
- import * as path5 from "path";
6397
+ import * as path12 from "path";
6064
6398
  var ALL_CHECKS = [
6065
6399
  "validate",
6066
6400
  "deps",
@@ -6071,238 +6405,270 @@ var ALL_CHECKS = [
6071
6405
  "phase-gate",
6072
6406
  "arch"
6073
6407
  ];
6074
- async function runSingleCheck(name, projectRoot, config) {
6075
- const start = Date.now();
6408
+ async function runValidateCheck(projectRoot, config) {
6076
6409
  const issues = [];
6077
- try {
6078
- switch (name) {
6079
- case "validate": {
6080
- const agentsPath = path5.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6081
- const result = await validateAgentsMap(agentsPath);
6082
- if (!result.ok) {
6083
- issues.push({ severity: "error", message: result.error.message });
6084
- } else if (!result.value.valid) {
6085
- if (result.value.errors) {
6086
- for (const err of result.value.errors) {
6087
- issues.push({ severity: "error", message: err.message });
6088
- }
6089
- }
6090
- for (const section of result.value.missingSections) {
6091
- issues.push({ severity: "warning", message: `Missing section: ${section}` });
6092
- }
6093
- for (const link of result.value.brokenLinks) {
6094
- issues.push({
6095
- severity: "warning",
6096
- message: `Broken link: ${link.text} \u2192 ${link.path}`,
6097
- file: link.path
6098
- });
6099
- }
6100
- }
6101
- break;
6410
+ const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6411
+ const result = await validateAgentsMap(agentsPath);
6412
+ if (!result.ok) {
6413
+ issues.push({ severity: "error", message: result.error.message });
6414
+ } else if (!result.value.valid) {
6415
+ if (result.value.errors) {
6416
+ for (const err of result.value.errors) {
6417
+ issues.push({ severity: "error", message: err.message });
6418
+ }
6419
+ }
6420
+ for (const section of result.value.missingSections) {
6421
+ issues.push({ severity: "warning", message: `Missing section: ${section}` });
6422
+ }
6423
+ for (const link of result.value.brokenLinks) {
6424
+ issues.push({
6425
+ severity: "warning",
6426
+ message: `Broken link: ${link.text} \u2192 ${link.path}`,
6427
+ file: link.path
6428
+ });
6429
+ }
6430
+ }
6431
+ return issues;
6432
+ }
6433
+ async function runDepsCheck(projectRoot, config) {
6434
+ const issues = [];
6435
+ const rawLayers = config.layers;
6436
+ if (rawLayers && rawLayers.length > 0) {
6437
+ const parser = new TypeScriptParser();
6438
+ const layers = rawLayers.map(
6439
+ (l) => defineLayer(
6440
+ l.name,
6441
+ Array.isArray(l.patterns) ? l.patterns : [l.pattern],
6442
+ l.allowedDependencies
6443
+ )
6444
+ );
6445
+ const result = await validateDependencies({
6446
+ layers,
6447
+ rootDir: projectRoot,
6448
+ parser
6449
+ });
6450
+ if (!result.ok) {
6451
+ issues.push({ severity: "error", message: result.error.message });
6452
+ } else if (result.value.violations.length > 0) {
6453
+ for (const v of result.value.violations) {
6454
+ issues.push({
6455
+ severity: "error",
6456
+ message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
6457
+ file: v.file,
6458
+ line: v.line
6459
+ });
6102
6460
  }
6103
- case "deps": {
6104
- const rawLayers = config.layers;
6105
- if (rawLayers && rawLayers.length > 0) {
6106
- const parser = new TypeScriptParser();
6107
- const layers = rawLayers.map(
6108
- (l) => defineLayer(
6109
- l.name,
6110
- Array.isArray(l.patterns) ? l.patterns : [l.pattern],
6111
- l.allowedDependencies
6112
- )
6113
- );
6114
- const result = await validateDependencies({
6115
- layers,
6116
- rootDir: projectRoot,
6117
- parser
6118
- });
6119
- if (!result.ok) {
6120
- issues.push({ severity: "error", message: result.error.message });
6121
- } else if (result.value.violations.length > 0) {
6122
- for (const v of result.value.violations) {
6123
- issues.push({
6124
- severity: "error",
6125
- message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
6126
- file: v.file,
6127
- line: v.line
6128
- });
6129
- }
6130
- }
6131
- }
6132
- break;
6461
+ }
6462
+ }
6463
+ return issues;
6464
+ }
6465
+ async function runDocsCheck(projectRoot, config) {
6466
+ const issues = [];
6467
+ const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
6468
+ const entropyConfig = config.entropy || {};
6469
+ const result = await checkDocCoverage("project", {
6470
+ docsDir,
6471
+ sourceDir: projectRoot,
6472
+ excludePatterns: entropyConfig.excludePatterns || [
6473
+ "**/node_modules/**",
6474
+ "**/dist/**",
6475
+ "**/*.test.ts",
6476
+ "**/fixtures/**"
6477
+ ]
6478
+ });
6479
+ if (!result.ok) {
6480
+ issues.push({ severity: "warning", message: result.error.message });
6481
+ } else if (result.value.gaps.length > 0) {
6482
+ for (const gap of result.value.gaps) {
6483
+ issues.push({
6484
+ severity: "warning",
6485
+ message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
6486
+ file: gap.file
6487
+ });
6488
+ }
6489
+ }
6490
+ return issues;
6491
+ }
6492
+ async function runEntropyCheck(projectRoot, _config) {
6493
+ const issues = [];
6494
+ const analyzer = new EntropyAnalyzer({
6495
+ rootDir: projectRoot,
6496
+ analyze: { drift: true, deadCode: true, patterns: false }
6497
+ });
6498
+ const result = await analyzer.analyze();
6499
+ if (!result.ok) {
6500
+ issues.push({ severity: "warning", message: result.error.message });
6501
+ } else {
6502
+ const report = result.value;
6503
+ if (report.drift) {
6504
+ for (const drift of report.drift.drifts) {
6505
+ issues.push({
6506
+ severity: "warning",
6507
+ message: `Doc drift (${drift.type}): ${drift.details}`,
6508
+ file: drift.docFile,
6509
+ line: drift.line
6510
+ });
6133
6511
  }
6134
- case "docs": {
6135
- const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
6136
- const entropyConfig = config.entropy || {};
6137
- const result = await checkDocCoverage("project", {
6138
- docsDir,
6139
- sourceDir: projectRoot,
6140
- excludePatterns: entropyConfig.excludePatterns || [
6141
- "**/node_modules/**",
6142
- "**/dist/**",
6143
- "**/*.test.ts",
6144
- "**/fixtures/**"
6145
- ]
6512
+ }
6513
+ if (report.deadCode) {
6514
+ for (const dead of report.deadCode.deadExports) {
6515
+ issues.push({
6516
+ severity: "warning",
6517
+ message: `Dead export: ${dead.name}`,
6518
+ file: dead.file,
6519
+ line: dead.line
6146
6520
  });
6147
- if (!result.ok) {
6148
- issues.push({ severity: "warning", message: result.error.message });
6149
- } else if (result.value.gaps.length > 0) {
6150
- for (const gap of result.value.gaps) {
6151
- issues.push({
6152
- severity: "warning",
6153
- message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
6154
- file: gap.file
6155
- });
6156
- }
6157
- }
6158
- break;
6159
6521
  }
6160
- case "entropy": {
6161
- const analyzer = new EntropyAnalyzer({
6162
- rootDir: projectRoot,
6163
- analyze: { drift: true, deadCode: true, patterns: false }
6522
+ }
6523
+ }
6524
+ return issues;
6525
+ }
6526
+ async function runSecurityCheck(projectRoot, config) {
6527
+ const issues = [];
6528
+ const securityConfig = parseSecurityConfig(config.security);
6529
+ if (!securityConfig.enabled) return issues;
6530
+ const scanner = new SecurityScanner(securityConfig);
6531
+ scanner.configureForProject(projectRoot);
6532
+ const { glob: globFn } = await import("glob");
6533
+ const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
6534
+ cwd: projectRoot,
6535
+ ignore: securityConfig.exclude ?? [
6536
+ "**/node_modules/**",
6537
+ "**/dist/**",
6538
+ "**/*.test.ts",
6539
+ "**/fixtures/**"
6540
+ ],
6541
+ absolute: true
6542
+ });
6543
+ const scanResult = await scanner.scanFiles(sourceFiles);
6544
+ for (const finding of scanResult.findings) {
6545
+ issues.push({
6546
+ severity: finding.severity === "info" ? "warning" : finding.severity,
6547
+ message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
6548
+ file: finding.file,
6549
+ line: finding.line
6550
+ });
6551
+ }
6552
+ return issues;
6553
+ }
6554
+ async function runPerfCheck(projectRoot, config) {
6555
+ const issues = [];
6556
+ const perfConfig = config.performance || {};
6557
+ const perfAnalyzer = new EntropyAnalyzer({
6558
+ rootDir: projectRoot,
6559
+ analyze: {
6560
+ complexity: perfConfig.complexity || true,
6561
+ coupling: perfConfig.coupling || true,
6562
+ sizeBudget: perfConfig.sizeBudget || false
6563
+ }
6564
+ });
6565
+ const perfResult = await perfAnalyzer.analyze();
6566
+ if (!perfResult.ok) {
6567
+ issues.push({ severity: "warning", message: perfResult.error.message });
6568
+ } else {
6569
+ const perfReport = perfResult.value;
6570
+ if (perfReport.complexity) {
6571
+ for (const v of perfReport.complexity.violations) {
6572
+ issues.push({
6573
+ severity: v.severity === "info" ? "warning" : v.severity,
6574
+ message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
6575
+ file: v.file,
6576
+ line: v.line
6164
6577
  });
6165
- const result = await analyzer.analyze();
6166
- if (!result.ok) {
6167
- issues.push({ severity: "warning", message: result.error.message });
6168
- } else {
6169
- const report = result.value;
6170
- if (report.drift) {
6171
- for (const drift of report.drift.drifts) {
6172
- issues.push({
6173
- severity: "warning",
6174
- message: `Doc drift (${drift.type}): ${drift.details}`,
6175
- file: drift.docFile,
6176
- line: drift.line
6177
- });
6178
- }
6179
- }
6180
- if (report.deadCode) {
6181
- for (const dead of report.deadCode.deadExports) {
6182
- issues.push({
6183
- severity: "warning",
6184
- message: `Dead export: ${dead.name}`,
6185
- file: dead.file,
6186
- line: dead.line
6187
- });
6188
- }
6189
- }
6190
- }
6191
- break;
6192
6578
  }
6193
- case "security": {
6194
- const securityConfig = parseSecurityConfig(config.security);
6195
- if (!securityConfig.enabled) break;
6196
- const scanner = new SecurityScanner(securityConfig);
6197
- scanner.configureForProject(projectRoot);
6198
- const { glob: globFn } = await import("glob");
6199
- const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
6200
- cwd: projectRoot,
6201
- ignore: securityConfig.exclude ?? [
6202
- "**/node_modules/**",
6203
- "**/dist/**",
6204
- "**/*.test.ts",
6205
- "**/fixtures/**"
6206
- ],
6207
- absolute: true
6579
+ }
6580
+ if (perfReport.coupling) {
6581
+ for (const v of perfReport.coupling.violations) {
6582
+ issues.push({
6583
+ severity: v.severity === "info" ? "warning" : v.severity,
6584
+ message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
6585
+ file: v.file
6208
6586
  });
6209
- const scanResult = await scanner.scanFiles(sourceFiles);
6210
- for (const finding of scanResult.findings) {
6211
- issues.push({
6212
- severity: finding.severity === "info" ? "warning" : finding.severity,
6213
- message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
6214
- file: finding.file,
6215
- line: finding.line
6216
- });
6217
- }
6218
- break;
6219
6587
  }
6220
- case "perf": {
6221
- const perfConfig = config.performance || {};
6222
- const perfAnalyzer = new EntropyAnalyzer({
6223
- rootDir: projectRoot,
6224
- analyze: {
6225
- complexity: perfConfig.complexity || true,
6226
- coupling: perfConfig.coupling || true,
6227
- sizeBudget: perfConfig.sizeBudget || false
6228
- }
6588
+ }
6589
+ }
6590
+ return issues;
6591
+ }
6592
+ async function runPhaseGateCheck(_projectRoot, config) {
6593
+ const issues = [];
6594
+ const phaseGates = config.phaseGates;
6595
+ if (!phaseGates?.enabled) {
6596
+ return issues;
6597
+ }
6598
+ issues.push({
6599
+ severity: "warning",
6600
+ message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
6601
+ });
6602
+ return issues;
6603
+ }
6604
+ async function runArchCheck(projectRoot, config) {
6605
+ const issues = [];
6606
+ const rawArchConfig = config.architecture;
6607
+ const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
6608
+ if (!archConfig.enabled) return issues;
6609
+ const results = await runAll(archConfig, projectRoot);
6610
+ const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
6611
+ const baseline = baselineManager.load();
6612
+ if (baseline) {
6613
+ const diffResult = diff(results, baseline);
6614
+ if (!diffResult.passed) {
6615
+ for (const v of diffResult.newViolations) {
6616
+ issues.push({
6617
+ severity: v.severity,
6618
+ message: `[${v.category || "arch"}] NEW: ${v.detail}`,
6619
+ file: v.file
6229
6620
  });
6230
- const perfResult = await perfAnalyzer.analyze();
6231
- if (!perfResult.ok) {
6232
- issues.push({ severity: "warning", message: perfResult.error.message });
6233
- } else {
6234
- const perfReport = perfResult.value;
6235
- if (perfReport.complexity) {
6236
- for (const v of perfReport.complexity.violations) {
6237
- issues.push({
6238
- severity: v.severity === "info" ? "warning" : v.severity,
6239
- message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
6240
- file: v.file,
6241
- line: v.line
6242
- });
6243
- }
6244
- }
6245
- if (perfReport.coupling) {
6246
- for (const v of perfReport.coupling.violations) {
6247
- issues.push({
6248
- severity: v.severity === "info" ? "warning" : v.severity,
6249
- message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
6250
- file: v.file
6251
- });
6252
- }
6253
- }
6254
- }
6255
- break;
6256
6621
  }
6257
- case "phase-gate": {
6258
- const phaseGates = config.phaseGates;
6259
- if (!phaseGates?.enabled) {
6260
- break;
6261
- }
6622
+ for (const r of diffResult.regressions) {
6262
6623
  issues.push({
6263
- severity: "warning",
6264
- message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
6624
+ severity: "error",
6625
+ message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
6265
6626
  });
6266
- break;
6267
6627
  }
6268
- case "arch": {
6269
- const rawArchConfig = config.architecture;
6270
- const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
6271
- if (!archConfig.enabled) break;
6272
- const results = await runAll(archConfig, projectRoot);
6273
- const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
6274
- const baseline = baselineManager.load();
6275
- if (baseline) {
6276
- const diffResult = diff(results, baseline);
6277
- if (!diffResult.passed) {
6278
- for (const v of diffResult.newViolations) {
6279
- issues.push({
6280
- severity: v.severity,
6281
- message: `[${v.category || "arch"}] NEW: ${v.detail}`,
6282
- file: v.file
6283
- });
6284
- }
6285
- for (const r of diffResult.regressions) {
6286
- issues.push({
6287
- severity: "error",
6288
- message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
6289
- });
6290
- }
6291
- }
6292
- } else {
6293
- for (const result of results) {
6294
- for (const v of result.violations) {
6295
- issues.push({
6296
- severity: v.severity,
6297
- message: `[${result.category}] ${v.detail}`,
6298
- file: v.file
6299
- });
6300
- }
6301
- }
6302
- }
6303
- break;
6628
+ }
6629
+ } else {
6630
+ for (const result of results) {
6631
+ for (const v of result.violations) {
6632
+ issues.push({
6633
+ severity: v.severity,
6634
+ message: `[${result.category}] ${v.detail}`,
6635
+ file: v.file
6636
+ });
6304
6637
  }
6305
6638
  }
6639
+ }
6640
+ return issues;
6641
+ }
6642
+ async function runSingleCheck(name, projectRoot, config) {
6643
+ const start = Date.now();
6644
+ const issues = [];
6645
+ try {
6646
+ switch (name) {
6647
+ case "validate":
6648
+ issues.push(...await runValidateCheck(projectRoot, config));
6649
+ break;
6650
+ case "deps":
6651
+ issues.push(...await runDepsCheck(projectRoot, config));
6652
+ break;
6653
+ case "docs":
6654
+ issues.push(...await runDocsCheck(projectRoot, config));
6655
+ break;
6656
+ case "entropy":
6657
+ issues.push(...await runEntropyCheck(projectRoot, config));
6658
+ break;
6659
+ case "security":
6660
+ issues.push(...await runSecurityCheck(projectRoot, config));
6661
+ break;
6662
+ case "perf":
6663
+ issues.push(...await runPerfCheck(projectRoot, config));
6664
+ break;
6665
+ case "phase-gate":
6666
+ issues.push(...await runPhaseGateCheck(projectRoot, config));
6667
+ break;
6668
+ case "arch":
6669
+ issues.push(...await runArchCheck(projectRoot, config));
6670
+ break;
6671
+ }
6306
6672
  } catch (error) {
6307
6673
  issues.push({
6308
6674
  severity: "error",
@@ -6370,7 +6736,7 @@ async function runCIChecks(input) {
6370
6736
  }
6371
6737
 
6372
6738
  // src/review/mechanical-checks.ts
6373
- import * as path6 from "path";
6739
+ import * as path13 from "path";
6374
6740
  async function runMechanicalChecks(options) {
6375
6741
  const { projectRoot, config, skip = [], changedFiles } = options;
6376
6742
  const findings = [];
@@ -6382,7 +6748,7 @@ async function runMechanicalChecks(options) {
6382
6748
  };
6383
6749
  if (!skip.includes("validate")) {
6384
6750
  try {
6385
- const agentsPath = path6.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6751
+ const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
6386
6752
  const result = await validateAgentsMap(agentsPath);
6387
6753
  if (!result.ok) {
6388
6754
  statuses.validate = "fail";
@@ -6419,7 +6785,7 @@ async function runMechanicalChecks(options) {
6419
6785
  statuses.validate = "fail";
6420
6786
  findings.push({
6421
6787
  tool: "validate",
6422
- file: path6.join(projectRoot, "AGENTS.md"),
6788
+ file: path13.join(projectRoot, "AGENTS.md"),
6423
6789
  message: err instanceof Error ? err.message : String(err),
6424
6790
  severity: "error"
6425
6791
  });
@@ -6483,7 +6849,7 @@ async function runMechanicalChecks(options) {
6483
6849
  (async () => {
6484
6850
  const localFindings = [];
6485
6851
  try {
6486
- const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
6852
+ const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
6487
6853
  const result = await checkDocCoverage("project", { docsDir });
6488
6854
  if (!result.ok) {
6489
6855
  statuses["check-docs"] = "warn";
@@ -6510,7 +6876,7 @@ async function runMechanicalChecks(options) {
6510
6876
  statuses["check-docs"] = "warn";
6511
6877
  localFindings.push({
6512
6878
  tool: "check-docs",
6513
- file: path6.join(projectRoot, "docs"),
6879
+ file: path13.join(projectRoot, "docs"),
6514
6880
  message: err instanceof Error ? err.message : String(err),
6515
6881
  severity: "warning"
6516
6882
  });
@@ -6658,7 +7024,7 @@ function detectChangeType(commitMessage, diff2) {
6658
7024
  }
6659
7025
 
6660
7026
  // src/review/context-scoper.ts
6661
- import * as path7 from "path";
7027
+ import * as path14 from "path";
6662
7028
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
6663
7029
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
6664
7030
  function computeContextBudget(diffLines) {
@@ -6666,18 +7032,18 @@ function computeContextBudget(diffLines) {
6666
7032
  return diffLines;
6667
7033
  }
6668
7034
  function isWithinProject(absPath, projectRoot) {
6669
- const resolvedRoot = path7.resolve(projectRoot) + path7.sep;
6670
- const resolvedPath = path7.resolve(absPath);
6671
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path7.resolve(projectRoot);
7035
+ const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
7036
+ const resolvedPath = path14.resolve(absPath);
7037
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
6672
7038
  }
6673
7039
  async function readContextFile(projectRoot, filePath, reason) {
6674
- const absPath = path7.isAbsolute(filePath) ? filePath : path7.join(projectRoot, filePath);
7040
+ const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
6675
7041
  if (!isWithinProject(absPath, projectRoot)) return null;
6676
7042
  const result = await readFileContent(absPath);
6677
7043
  if (!result.ok) return null;
6678
7044
  const content = result.value;
6679
7045
  const lines = content.split("\n").length;
6680
- const relPath = path7.isAbsolute(filePath) ? path7.relative(projectRoot, filePath) : filePath;
7046
+ const relPath = path14.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
6681
7047
  return { path: relPath, content, reason, lines };
6682
7048
  }
6683
7049
  function extractImportSources(content) {
@@ -6692,18 +7058,18 @@ function extractImportSources(content) {
6692
7058
  }
6693
7059
  async function resolveImportPath(projectRoot, fromFile, importSource) {
6694
7060
  if (!importSource.startsWith(".")) return null;
6695
- const fromDir = path7.dirname(path7.join(projectRoot, fromFile));
6696
- const basePath = path7.resolve(fromDir, importSource);
7061
+ const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
7062
+ const basePath = path14.resolve(fromDir, importSource);
6697
7063
  if (!isWithinProject(basePath, projectRoot)) return null;
6698
- const relBase = path7.relative(projectRoot, basePath);
7064
+ const relBase = relativePosix(projectRoot, basePath);
6699
7065
  const candidates = [
6700
7066
  relBase + ".ts",
6701
7067
  relBase + ".tsx",
6702
7068
  relBase + ".mts",
6703
- path7.join(relBase, "index.ts")
7069
+ path14.join(relBase, "index.ts")
6704
7070
  ];
6705
7071
  for (const candidate of candidates) {
6706
- const absCandidate = path7.join(projectRoot, candidate);
7072
+ const absCandidate = path14.join(projectRoot, candidate);
6707
7073
  if (await fileExists(absCandidate)) {
6708
7074
  return candidate;
6709
7075
  }
@@ -6711,10 +7077,10 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
6711
7077
  return null;
6712
7078
  }
6713
7079
  async function findTestFiles(projectRoot, sourceFile) {
6714
- const baseName = path7.basename(sourceFile, path7.extname(sourceFile));
7080
+ const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
6715
7081
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
6716
7082
  const results = await findFiles(pattern, projectRoot);
6717
- return results.map((f) => path7.relative(projectRoot, f));
7083
+ return results.map((f) => relativePosix(projectRoot, f));
6718
7084
  }
6719
7085
  async function gatherImportContext(projectRoot, changedFiles, budget) {
6720
7086
  const contextFiles = [];
@@ -7502,7 +7868,7 @@ async function fanOutReview(options) {
7502
7868
  }
7503
7869
 
7504
7870
  // src/review/validate-findings.ts
7505
- import * as path8 from "path";
7871
+ import * as path15 from "path";
7506
7872
  var DOWNGRADE_MAP = {
7507
7873
  critical: "important",
7508
7874
  important: "suggestion",
@@ -7523,7 +7889,7 @@ function normalizePath(filePath, projectRoot) {
7523
7889
  let normalized = filePath;
7524
7890
  normalized = normalized.replace(/\\/g, "/");
7525
7891
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
7526
- if (path8.isAbsolute(normalized)) {
7892
+ if (path15.isAbsolute(normalized)) {
7527
7893
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
7528
7894
  if (normalized.startsWith(root)) {
7529
7895
  normalized = normalized.slice(root.length);
@@ -7548,12 +7914,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
7548
7914
  while ((match = importRegex.exec(content)) !== null) {
7549
7915
  const importPath = match[1];
7550
7916
  if (!importPath.startsWith(".")) continue;
7551
- const dir = path8.dirname(current.file);
7552
- let resolved = path8.join(dir, importPath).replace(/\\/g, "/");
7917
+ const dir = path15.dirname(current.file);
7918
+ let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
7553
7919
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
7554
7920
  resolved += ".ts";
7555
7921
  }
7556
- resolved = path8.normalize(resolved).replace(/\\/g, "/");
7922
+ resolved = path15.normalize(resolved).replace(/\\/g, "/");
7557
7923
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
7558
7924
  queue.push({ file: resolved, depth: current.depth + 1 });
7559
7925
  }
@@ -7570,7 +7936,7 @@ async function validateFindings(options) {
7570
7936
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
7571
7937
  continue;
7572
7938
  }
7573
- const absoluteFile = path8.isAbsolute(finding.file) ? finding.file : path8.join(projectRoot, finding.file).replace(/\\/g, "/");
7939
+ const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
7574
7940
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
7575
7941
  continue;
7576
7942
  }
@@ -8091,6 +8457,8 @@ function parseFrontmatter(raw) {
8091
8457
  const versionStr = map.get("version");
8092
8458
  const lastSynced = map.get("last_synced");
8093
8459
  const lastManualEdit = map.get("last_manual_edit");
8460
+ const created = map.get("created");
8461
+ const updated = map.get("updated");
8094
8462
  if (!project || !versionStr || !lastSynced || !lastManualEdit) {
8095
8463
  return Err2(
8096
8464
  new Error(
@@ -8102,7 +8470,10 @@ function parseFrontmatter(raw) {
8102
8470
  if (isNaN(version)) {
8103
8471
  return Err2(new Error("Frontmatter version must be a number"));
8104
8472
  }
8105
- return Ok2({ project, version, lastSynced, lastManualEdit });
8473
+ const fm = { project, version, lastSynced, lastManualEdit };
8474
+ if (created) fm.created = created;
8475
+ if (updated) fm.updated = updated;
8476
+ return Ok2(fm);
8106
8477
  }
8107
8478
  function parseMilestones(body) {
8108
8479
  const milestones = [];
@@ -8110,12 +8481,12 @@ function parseMilestones(body) {
8110
8481
  const h2Matches = [];
8111
8482
  let match;
8112
8483
  while ((match = h2Pattern.exec(body)) !== null) {
8113
- h2Matches.push({ heading: match[1], startIndex: match.index });
8484
+ h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
8114
8485
  }
8115
8486
  for (let i = 0; i < h2Matches.length; i++) {
8116
8487
  const h2 = h2Matches[i];
8117
8488
  const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
8118
- const sectionBody = body.slice(h2.startIndex + h2.heading.length + 4, nextStart);
8489
+ const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
8119
8490
  const isBacklog = h2.heading === "Backlog";
8120
8491
  const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
8121
8492
  const featuresResult = parseFeatures(sectionBody);
@@ -8130,19 +8501,16 @@ function parseMilestones(body) {
8130
8501
  }
8131
8502
  function parseFeatures(sectionBody) {
8132
8503
  const features = [];
8133
- const h3Pattern = /^### Feature: (.+)$/gm;
8504
+ const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
8134
8505
  const h3Matches = [];
8135
8506
  let match;
8136
8507
  while ((match = h3Pattern.exec(sectionBody)) !== null) {
8137
- h3Matches.push({ name: match[1], startIndex: match.index });
8508
+ h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
8138
8509
  }
8139
8510
  for (let i = 0; i < h3Matches.length; i++) {
8140
8511
  const h3 = h3Matches[i];
8141
8512
  const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
8142
- const featureBody = sectionBody.slice(
8143
- h3.startIndex + `### Feature: ${h3.name}`.length,
8144
- nextStart
8145
- );
8513
+ const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
8146
8514
  const featureResult = parseFeatureFields(h3.name, featureBody);
8147
8515
  if (!featureResult.ok) return featureResult;
8148
8516
  features.push(featureResult.value);
@@ -8167,10 +8535,10 @@ function parseFeatureFields(name, body) {
8167
8535
  const status = statusRaw;
8168
8536
  const specRaw = fieldMap.get("Spec") ?? EM_DASH;
8169
8537
  const spec = specRaw === EM_DASH ? null : specRaw;
8170
- const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
8171
- const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
8172
- const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
8173
- const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
8538
+ const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
8539
+ const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
8540
+ const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
8541
+ const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
8174
8542
  const summary = fieldMap.get("Summary") ?? "";
8175
8543
  return Ok2({ name, status, spec, plans, blockedBy, summary });
8176
8544
  }
@@ -8182,11 +8550,17 @@ function serializeRoadmap(roadmap) {
8182
8550
  lines.push("---");
8183
8551
  lines.push(`project: ${roadmap.frontmatter.project}`);
8184
8552
  lines.push(`version: ${roadmap.frontmatter.version}`);
8553
+ if (roadmap.frontmatter.created) {
8554
+ lines.push(`created: ${roadmap.frontmatter.created}`);
8555
+ }
8556
+ if (roadmap.frontmatter.updated) {
8557
+ lines.push(`updated: ${roadmap.frontmatter.updated}`);
8558
+ }
8185
8559
  lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
8186
8560
  lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
8187
8561
  lines.push("---");
8188
8562
  lines.push("");
8189
- lines.push("# Project Roadmap");
8563
+ lines.push("# Roadmap");
8190
8564
  for (const milestone of roadmap.milestones) {
8191
8565
  lines.push("");
8192
8566
  lines.push(serializeMilestoneHeading(milestone));
@@ -8199,25 +8573,26 @@ function serializeRoadmap(roadmap) {
8199
8573
  return lines.join("\n");
8200
8574
  }
8201
8575
  function serializeMilestoneHeading(milestone) {
8202
- return milestone.isBacklog ? "## Backlog" : `## Milestone: ${milestone.name}`;
8576
+ return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
8203
8577
  }
8204
8578
  function serializeFeature(feature) {
8205
8579
  const spec = feature.spec ?? EM_DASH2;
8206
8580
  const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
8207
8581
  const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
8208
8582
  return [
8209
- `### Feature: ${feature.name}`,
8583
+ `### ${feature.name}`,
8584
+ "",
8210
8585
  `- **Status:** ${feature.status}`,
8211
8586
  `- **Spec:** ${spec}`,
8212
- `- **Plans:** ${plans}`,
8213
- `- **Blocked by:** ${blockedBy}`,
8214
- `- **Summary:** ${feature.summary}`
8587
+ `- **Summary:** ${feature.summary}`,
8588
+ `- **Blockers:** ${blockedBy}`,
8589
+ `- **Plan:** ${plans}`
8215
8590
  ];
8216
8591
  }
8217
8592
 
8218
8593
  // src/roadmap/sync.ts
8219
- import * as fs9 from "fs";
8220
- import * as path9 from "path";
8594
+ import * as fs16 from "fs";
8595
+ import * as path16 from "path";
8221
8596
  import { Ok as Ok3 } from "@harness-engineering/types";
8222
8597
  function inferStatus(feature, projectPath, allFeatures) {
8223
8598
  if (feature.blockedBy.length > 0) {
@@ -8232,10 +8607,10 @@ function inferStatus(feature, projectPath, allFeatures) {
8232
8607
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
8233
8608
  const useRootState = featuresWithPlans.length <= 1;
8234
8609
  if (useRootState) {
8235
- const rootStatePath = path9.join(projectPath, ".harness", "state.json");
8236
- if (fs9.existsSync(rootStatePath)) {
8610
+ const rootStatePath = path16.join(projectPath, ".harness", "state.json");
8611
+ if (fs16.existsSync(rootStatePath)) {
8237
8612
  try {
8238
- const raw = fs9.readFileSync(rootStatePath, "utf-8");
8613
+ const raw = fs16.readFileSync(rootStatePath, "utf-8");
8239
8614
  const state = JSON.parse(raw);
8240
8615
  if (state.progress) {
8241
8616
  for (const status of Object.values(state.progress)) {
@@ -8246,16 +8621,16 @@ function inferStatus(feature, projectPath, allFeatures) {
8246
8621
  }
8247
8622
  }
8248
8623
  }
8249
- const sessionsDir = path9.join(projectPath, ".harness", "sessions");
8250
- if (fs9.existsSync(sessionsDir)) {
8624
+ const sessionsDir = path16.join(projectPath, ".harness", "sessions");
8625
+ if (fs16.existsSync(sessionsDir)) {
8251
8626
  try {
8252
- const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
8627
+ const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
8253
8628
  for (const entry of sessionDirs) {
8254
8629
  if (!entry.isDirectory()) continue;
8255
- const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
8256
- if (!fs9.existsSync(autopilotPath)) continue;
8630
+ const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
8631
+ if (!fs16.existsSync(autopilotPath)) continue;
8257
8632
  try {
8258
- const raw = fs9.readFileSync(autopilotPath, "utf-8");
8633
+ const raw = fs16.readFileSync(autopilotPath, "utf-8");
8259
8634
  const autopilot = JSON.parse(raw);
8260
8635
  if (!autopilot.phases) continue;
8261
8636
  const linkedPhases = autopilot.phases.filter(
@@ -8335,17 +8710,17 @@ var EmitInteractionInputSchema = z6.object({
8335
8710
  });
8336
8711
 
8337
8712
  // src/blueprint/scanner.ts
8338
- import * as fs10 from "fs/promises";
8339
- import * as path10 from "path";
8713
+ import * as fs17 from "fs/promises";
8714
+ import * as path17 from "path";
8340
8715
  var ProjectScanner = class {
8341
8716
  constructor(rootDir) {
8342
8717
  this.rootDir = rootDir;
8343
8718
  }
8344
8719
  async scan() {
8345
- let projectName = path10.basename(this.rootDir);
8720
+ let projectName = path17.basename(this.rootDir);
8346
8721
  try {
8347
- const pkgPath = path10.join(this.rootDir, "package.json");
8348
- const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
8722
+ const pkgPath = path17.join(this.rootDir, "package.json");
8723
+ const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
8349
8724
  const pkg = JSON.parse(pkgRaw);
8350
8725
  if (pkg.name) projectName = pkg.name;
8351
8726
  } catch {
@@ -8386,8 +8761,8 @@ var ProjectScanner = class {
8386
8761
  };
8387
8762
 
8388
8763
  // src/blueprint/generator.ts
8389
- import * as fs11 from "fs/promises";
8390
- import * as path11 from "path";
8764
+ import * as fs18 from "fs/promises";
8765
+ import * as path18 from "path";
8391
8766
  import * as ejs from "ejs";
8392
8767
 
8393
8768
  // src/blueprint/templates.ts
@@ -8471,19 +8846,19 @@ var BlueprintGenerator = class {
8471
8846
  styles: STYLES,
8472
8847
  scripts: SCRIPTS
8473
8848
  });
8474
- await fs11.mkdir(options.outputDir, { recursive: true });
8475
- await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
8849
+ await fs18.mkdir(options.outputDir, { recursive: true });
8850
+ await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
8476
8851
  }
8477
8852
  };
8478
8853
 
8479
8854
  // src/update-checker.ts
8480
- import * as fs12 from "fs";
8481
- import * as path12 from "path";
8855
+ import * as fs19 from "fs";
8856
+ import * as path19 from "path";
8482
8857
  import * as os from "os";
8483
8858
  import { spawn } from "child_process";
8484
8859
  function getStatePath() {
8485
8860
  const home = process.env["HOME"] || os.homedir();
8486
- return path12.join(home, ".harness", "update-check.json");
8861
+ return path19.join(home, ".harness", "update-check.json");
8487
8862
  }
8488
8863
  function isUpdateCheckEnabled(configInterval) {
8489
8864
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -8496,7 +8871,7 @@ function shouldRunCheck(state, intervalMs) {
8496
8871
  }
8497
8872
  function readCheckState() {
8498
8873
  try {
8499
- const raw = fs12.readFileSync(getStatePath(), "utf-8");
8874
+ const raw = fs19.readFileSync(getStatePath(), "utf-8");
8500
8875
  const parsed = JSON.parse(raw);
8501
8876
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
8502
8877
  const state = parsed;
@@ -8513,7 +8888,7 @@ function readCheckState() {
8513
8888
  }
8514
8889
  function spawnBackgroundCheck(currentVersion) {
8515
8890
  const statePath = getStatePath();
8516
- const stateDir = path12.dirname(statePath);
8891
+ const stateDir = path19.dirname(statePath);
8517
8892
  const script = `
8518
8893
  const { execSync } = require('child_process');
8519
8894
  const fs = require('fs');
@@ -8567,7 +8942,7 @@ Run "harness update" to upgrade.`;
8567
8942
  }
8568
8943
 
8569
8944
  // src/index.ts
8570
- var VERSION = "0.11.0";
8945
+ var VERSION = "0.13.0";
8571
8946
  export {
8572
8947
  AGENT_DESCRIPTORS,
8573
8948
  ARCHITECTURE_DESCRIPTOR,
@@ -8644,6 +9019,7 @@ export {
8644
9019
  ViolationSchema,
8645
9020
  addProvenance,
8646
9021
  analyzeDiff,
9022
+ analyzeLearningPatterns,
8647
9023
  appendFailure,
8648
9024
  appendLearning,
8649
9025
  applyFixes,
@@ -8652,6 +9028,7 @@ export {
8652
9028
  archModule,
8653
9029
  architecture,
8654
9030
  archiveFailures,
9031
+ archiveLearnings,
8655
9032
  archiveStream,
8656
9033
  buildDependencyGraph,
8657
9034
  buildExclusionSet,
@@ -8659,6 +9036,8 @@ export {
8659
9036
  checkDocCoverage,
8660
9037
  checkEligibility,
8661
9038
  classifyFinding,
9039
+ clearFailuresCache,
9040
+ clearLearningsCache,
8662
9041
  configureFeedback,
8663
9042
  constraintRuleId,
8664
9043
  contextBudget,
@@ -8714,16 +9093,20 @@ export {
8714
9093
  injectionRules,
8715
9094
  isSmallSuggestion,
8716
9095
  isUpdateCheckEnabled,
9096
+ listActiveSessions,
8717
9097
  listStreams,
9098
+ loadBudgetedLearnings,
8718
9099
  loadFailures,
8719
9100
  loadHandoff,
8720
9101
  loadRelevantLearnings,
9102
+ loadSessionSummary,
8721
9103
  loadState,
8722
9104
  loadStreamIndex,
8723
9105
  logAgentAction,
8724
9106
  migrateToStreams,
8725
9107
  networkRules,
8726
9108
  nodeRules,
9109
+ parseDateFromEntry,
8727
9110
  parseDiff,
8728
9111
  parseManifest,
8729
9112
  parseRoadmap,
@@ -8731,6 +9114,7 @@ export {
8731
9114
  parseSize,
8732
9115
  pathTraversalRules,
8733
9116
  previewFix,
9117
+ pruneLearnings,
8734
9118
  reactRules,
8735
9119
  readCheckState,
8736
9120
  readLockfile,
@@ -8742,6 +9126,7 @@ export {
8742
9126
  resolveFileToLayer,
8743
9127
  resolveModelTier,
8744
9128
  resolveRuleSeverity,
9129
+ resolveSessionDir,
8745
9130
  resolveStreamPath,
8746
9131
  resolveThresholds,
8747
9132
  runAll,
@@ -8768,6 +9153,7 @@ export {
8768
9153
  syncRoadmap,
8769
9154
  touchStream,
8770
9155
  trackAction,
9156
+ updateSessionIndex,
8771
9157
  validateAgentsMap,
8772
9158
  validateBoundaries,
8773
9159
  validateCommitMessage,
@@ -8780,5 +9166,6 @@ export {
8780
9166
  violationId,
8781
9167
  writeConfig,
8782
9168
  writeLockfile,
9169
+ writeSessionSummary,
8783
9170
  xssRules
8784
9171
  };