@harness-engineering/cli 1.21.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/agents/skills/claude-code/cleanup-dead-code/skill.yaml +3 -0
  2. package/dist/agents/skills/claude-code/detect-doc-drift/skill.yaml +5 -0
  3. package/dist/agents/skills/claude-code/enforce-architecture/skill.yaml +13 -0
  4. package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +5 -0
  5. package/dist/agents/skills/claude-code/harness-codebase-cleanup/skill.yaml +5 -0
  6. package/dist/agents/skills/claude-code/harness-debugging/skill.yaml +5 -0
  7. package/dist/agents/skills/claude-code/harness-dependency-health/skill.yaml +9 -0
  8. package/dist/agents/skills/claude-code/harness-hotspot-detector/skill.yaml +9 -0
  9. package/dist/agents/skills/claude-code/harness-integrity/skill.yaml +5 -0
  10. package/dist/agents/skills/claude-code/harness-perf/skill.yaml +3 -0
  11. package/dist/agents/skills/claude-code/harness-refactoring/skill.yaml +9 -0
  12. package/dist/agents/skills/claude-code/harness-security-scan/skill.yaml +3 -0
  13. package/dist/agents/skills/claude-code/harness-soundness-review/skill.yaml +5 -0
  14. package/dist/agents/skills/claude-code/harness-supply-chain-audit/skill.yaml +3 -0
  15. package/dist/agents/skills/claude-code/harness-tdd/skill.yaml +3 -0
  16. package/dist/agents/skills/codex/cleanup-dead-code/skill.yaml +3 -0
  17. package/dist/agents/skills/codex/detect-doc-drift/skill.yaml +5 -0
  18. package/dist/agents/skills/codex/enforce-architecture/skill.yaml +13 -0
  19. package/dist/agents/skills/codex/harness-code-review/skill.yaml +5 -0
  20. package/dist/agents/skills/codex/harness-codebase-cleanup/skill.yaml +5 -0
  21. package/dist/agents/skills/codex/harness-debugging/skill.yaml +5 -0
  22. package/dist/agents/skills/codex/harness-dependency-health/skill.yaml +9 -0
  23. package/dist/agents/skills/codex/harness-hotspot-detector/skill.yaml +9 -0
  24. package/dist/agents/skills/codex/harness-integrity/skill.yaml +5 -0
  25. package/dist/agents/skills/codex/harness-perf/skill.yaml +3 -0
  26. package/dist/agents/skills/codex/harness-refactoring/skill.yaml +9 -0
  27. package/dist/agents/skills/codex/harness-security-scan/skill.yaml +3 -0
  28. package/dist/agents/skills/codex/harness-soundness-review/skill.yaml +5 -0
  29. package/dist/agents/skills/codex/harness-supply-chain-audit/skill.yaml +3 -0
  30. package/dist/agents/skills/codex/harness-tdd/skill.yaml +3 -0
  31. package/dist/agents/skills/cursor/cleanup-dead-code/skill.yaml +3 -0
  32. package/dist/agents/skills/cursor/detect-doc-drift/skill.yaml +5 -0
  33. package/dist/agents/skills/cursor/enforce-architecture/skill.yaml +13 -0
  34. package/dist/agents/skills/cursor/harness-code-review/skill.yaml +5 -0
  35. package/dist/agents/skills/cursor/harness-codebase-cleanup/skill.yaml +5 -0
  36. package/dist/agents/skills/cursor/harness-debugging/skill.yaml +5 -0
  37. package/dist/agents/skills/cursor/harness-dependency-health/skill.yaml +9 -0
  38. package/dist/agents/skills/cursor/harness-hotspot-detector/skill.yaml +9 -0
  39. package/dist/agents/skills/cursor/harness-integrity/skill.yaml +5 -0
  40. package/dist/agents/skills/cursor/harness-perf/skill.yaml +3 -0
  41. package/dist/agents/skills/cursor/harness-refactoring/skill.yaml +9 -0
  42. package/dist/agents/skills/cursor/harness-security-scan/skill.yaml +3 -0
  43. package/dist/agents/skills/cursor/harness-soundness-review/skill.yaml +5 -0
  44. package/dist/agents/skills/cursor/harness-supply-chain-audit/skill.yaml +3 -0
  45. package/dist/agents/skills/cursor/harness-tdd/skill.yaml +3 -0
  46. package/dist/agents/skills/gemini-cli/cleanup-dead-code/skill.yaml +3 -0
  47. package/dist/agents/skills/gemini-cli/detect-doc-drift/skill.yaml +5 -0
  48. package/dist/agents/skills/gemini-cli/enforce-architecture/skill.yaml +13 -0
  49. package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +5 -0
  50. package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/skill.yaml +5 -0
  51. package/dist/agents/skills/gemini-cli/harness-debugging/skill.yaml +5 -0
  52. package/dist/agents/skills/gemini-cli/harness-dependency-health/skill.yaml +9 -0
  53. package/dist/agents/skills/gemini-cli/harness-hotspot-detector/skill.yaml +9 -0
  54. package/dist/agents/skills/gemini-cli/harness-integrity/skill.yaml +5 -0
  55. package/dist/agents/skills/gemini-cli/harness-perf/skill.yaml +3 -0
  56. package/dist/agents/skills/gemini-cli/harness-refactoring/skill.yaml +9 -0
  57. package/dist/agents/skills/gemini-cli/harness-security-scan/skill.yaml +3 -0
  58. package/dist/agents/skills/gemini-cli/harness-soundness-review/skill.yaml +5 -0
  59. package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/skill.yaml +3 -0
  60. package/dist/agents/skills/gemini-cli/harness-tdd/skill.yaml +3 -0
  61. package/dist/{agents-md-TDTLYAQU.js → agents-md-PM7LO74M.js} +2 -1
  62. package/dist/{architecture-NANP4XPE.js → architecture-OVOCDTI6.js} +3 -2
  63. package/dist/assess-project-R2OZIDDS.js +9 -0
  64. package/dist/bin/harness-mcp.js +15 -13
  65. package/dist/bin/harness.js +21 -19
  66. package/dist/{check-phase-gate-I4NQOCSU.js → check-phase-gate-7JQ6EW5R.js} +4 -3
  67. package/dist/{chunk-M6TIO6NF.js → chunk-2PAPHA77.js} +1 -1
  68. package/dist/chunk-5FBWWMY2.js +293 -0
  69. package/dist/{chunk-YF5ROTWR.js → chunk-5QTWFO24.js} +8 -8
  70. package/dist/{chunk-CZZXE6BL.js → chunk-ASS5TD2Y.js} +1 -1
  71. package/dist/{chunk-L6LTNZQZ.js → chunk-B4WHXHF7.js} +1 -1
  72. package/dist/{chunk-TMSGI27F.js → chunk-DJEBBENF.js} +967 -385
  73. package/dist/{chunk-H6LXAH66.js → chunk-DXYOAQQC.js} +1 -1
  74. package/dist/{chunk-UVJFBKCX.js → chunk-FSLFBLYW.js} +7 -7
  75. package/dist/{chunk-SZ5TGZMI.js → chunk-GRJ7A4WT.js} +17 -2
  76. package/dist/{chunk-WXI5ONCU.js → chunk-IK5GSLW6.js} +4 -4
  77. package/dist/{chunk-SPUK5W4W.js → chunk-J7W4LTRK.js} +2 -2
  78. package/dist/{chunk-7G2ZUTZA.js → chunk-PUOMFNRO.js} +26 -3
  79. package/dist/{chunk-HKUX2X7O.js → chunk-SE4YPMLH.js} +9 -1
  80. package/dist/{chunk-UEKQ5G3V.js → chunk-SOTTK27D.js} +459 -37
  81. package/dist/{chunk-LRG3B43J.js → chunk-T5QWCVGK.js} +1 -1
  82. package/dist/{dist-U7EAO6T2.js → chunk-TEZI27SA.js} +401 -60
  83. package/dist/{chunk-YZYBQZVL.js → chunk-U44JNY3Y.js} +1539 -587
  84. package/dist/{chunk-HUDEBSR2.js → chunk-W6MPLFXU.js} +3 -3
  85. package/dist/{chunk-6GEYPBDU.js → chunk-ZEIEUCZL.js} +9 -9
  86. package/dist/{ci-workflow-Z4IUJBZL.js → ci-workflow-OTTEERPF.js} +2 -1
  87. package/dist/{create-skill-NDXQSTIK.js → create-skill-U3XCFRZN.js} +2 -2
  88. package/dist/dist-IA6XYKNO.js +92 -0
  89. package/dist/{dist-KV2ICL5X.js → dist-LCR2IO7U.js} +56 -3
  90. package/dist/{docs-2PCZVSGB.js → docs-CHAYSGOP.js} +4 -3
  91. package/dist/{engine-EOXMI5MD.js → engine-4MY2U5RZ.js} +2 -1
  92. package/dist/{entropy-VGXXBIGX.js → entropy-AKSZG7G5.js} +3 -2
  93. package/dist/{feedback-VTSPL3O7.js → feedback-QGCSW7SB.js} +1 -1
  94. package/dist/{generate-agent-definitions-QICSCGXB.js → generate-agent-definitions-KU6X2UQN.js} +2 -1
  95. package/dist/{graph-loader-KMHDQYDT.js → graph-loader-FJN4H7Y4.js} +1 -1
  96. package/dist/index.d.ts +58 -2
  97. package/dist/index.js +27 -23
  98. package/dist/{loader-7S4FYAPP.js → loader-AV5XEMER.js} +2 -1
  99. package/dist/{mcp-DF25USTE.js → mcp-LWHVQRG7.js} +15 -13
  100. package/dist/{performance-RV4DUMFI.js → performance-ETZVXXGQ.js} +4 -3
  101. package/dist/{review-pipeline-7KQJB4SI.js → review-pipeline-3ZS3GJSP.js} +1 -1
  102. package/dist/{runtime-XKOHGGRC.js → runtime-KQTJRK3H.js} +2 -1
  103. package/dist/{security-NLWTMK3G.js → security-LJCLZES6.js} +1 -1
  104. package/dist/{skill-executor-XEVDGXUM.js → skill-executor-2BZQLHYN.js} +2 -2
  105. package/dist/{validate-VHFE6J6O.js → validate-4IA5RPEX.js} +3 -2
  106. package/dist/{validate-cross-check-PFRKABCS.js → validate-cross-check-VX2BAHQI.js} +2 -1
  107. package/package.json +4 -4
@@ -3,6 +3,10 @@ import {
3
3
  Ok,
4
4
  SESSION_SECTION_NAMES
5
5
  } from "./chunk-ERS5EVUZ.js";
6
+ import {
7
+ GraphStore,
8
+ queryTraceability
9
+ } from "./chunk-TEZI27SA.js";
6
10
 
7
11
  // ../core/dist/chunk-BQUWXBGR.mjs
8
12
  import { z } from "zod";
@@ -135,17 +139,17 @@ function resolveFileToLayer(file, layers) {
135
139
  }
136
140
  var accessAsync = promisify(access);
137
141
  var readFileAsync = promisify(readFile);
138
- async function fileExists(path26) {
142
+ async function fileExists(path28) {
139
143
  try {
140
- await accessAsync(path26, constants.F_OK);
144
+ await accessAsync(path28, constants.F_OK);
141
145
  return true;
142
146
  } catch {
143
147
  return false;
144
148
  }
145
149
  }
146
- async function readFileContent(path26) {
150
+ async function readFileContent(path28) {
147
151
  try {
148
- const content = await readFileAsync(path26, "utf-8");
152
+ const content = await readFileAsync(path28, "utf-8");
149
153
  return Ok(content);
150
154
  } catch (error) {
151
155
  return Err(error);
@@ -1820,66 +1824,75 @@ import * as path from "path";
1820
1824
  import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync22, mkdirSync as mkdirSync22 } from "fs";
1821
1825
  import { dirname as dirname7 } from "path";
1822
1826
  import { z as z3 } from "zod";
1823
- import * as fs8 from "fs";
1824
- import * as path5 from "path";
1825
- import * as fs7 from "fs";
1826
- import * as path4 from "path";
1827
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync3, renameSync as renameSync2, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
1828
+ import { randomBytes as randomBytes2 } from "crypto";
1829
+ import { join as join8, dirname as dirname8 } from "path";
1830
+ import { z as z4 } from "zod";
1827
1831
  import * as fs5 from "fs";
1828
1832
  import * as path2 from "path";
1829
- import { execSync } from "child_process";
1830
- import { z as z4 } from "zod";
1831
1833
  import * as fs6 from "fs";
1832
1834
  import * as path3 from "path";
1833
- import * as fs9 from "fs";
1834
- import * as path6 from "path";
1835
- import * as crypto from "crypto";
1835
+ import { z as z5 } from "zod";
1836
1836
  import * as fs10 from "fs";
1837
1837
  import * as path7 from "path";
1838
+ import * as fs9 from "fs";
1839
+ import * as path6 from "path";
1840
+ import * as fs7 from "fs";
1841
+ import * as path4 from "path";
1842
+ import { execSync } from "child_process";
1843
+ import { z as z6 } from "zod";
1844
+ import * as fs8 from "fs";
1845
+ import * as path5 from "path";
1838
1846
  import * as fs11 from "fs";
1839
1847
  import * as path8 from "path";
1848
+ import * as crypto from "crypto";
1840
1849
  import * as fs12 from "fs";
1841
1850
  import * as path9 from "path";
1842
- import { execSync as execSync2 } from "child_process";
1843
1851
  import * as fs13 from "fs";
1844
1852
  import * as path10 from "path";
1845
1853
  import * as fs14 from "fs";
1846
1854
  import * as path11 from "path";
1855
+ import { execSync as execSync2 } from "child_process";
1847
1856
  import * as fs15 from "fs";
1848
1857
  import * as path12 from "path";
1849
1858
  import * as fs16 from "fs";
1850
1859
  import * as path13 from "path";
1851
- import { z as z5 } from "zod";
1852
- import * as fs18 from "fs/promises";
1853
- import { minimatch as minimatch4 } from "minimatch";
1854
- import { z as z6 } from "zod";
1855
1860
  import * as fs17 from "fs";
1856
1861
  import * as path14 from "path";
1857
- import { readFileSync as readFileSync142, writeFileSync as writeFileSync11, unlinkSync, mkdirSync as mkdirSync11, readdirSync as readdirSync3 } from "fs";
1858
- import { join as join21, dirname as dirname8 } from "path";
1862
+ import * as fs18 from "fs";
1859
1863
  import * as path15 from "path";
1864
+ import { z as z7 } from "zod";
1865
+ import * as fs20 from "fs/promises";
1866
+ import { minimatch as minimatch4 } from "minimatch";
1867
+ import { z as z8 } from "zod";
1868
+ import * as fs19 from "fs";
1860
1869
  import * as path16 from "path";
1870
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync12, unlinkSync, mkdirSync as mkdirSync12, readdirSync as readdirSync3 } from "fs";
1871
+ import { join as join242, dirname as dirname9 } from "path";
1861
1872
  import * as path17 from "path";
1862
1873
  import * as path18 from "path";
1863
- import * as fs19 from "fs";
1864
1874
  import * as path19 from "path";
1865
- import * as fs20 from "fs";
1866
- import { z as z7 } from "zod";
1867
- import * as fs21 from "fs/promises";
1868
1875
  import * as path20 from "path";
1869
- import * as fs22 from "fs/promises";
1876
+ import * as fs21 from "fs";
1870
1877
  import * as path21 from "path";
1871
- import * as ejs from "ejs";
1872
- import * as fs23 from "fs";
1878
+ import * as fs22 from "fs";
1879
+ import { z as z9 } from "zod";
1880
+ import * as fs23 from "fs/promises";
1873
1881
  import * as path22 from "path";
1874
- import * as os from "os";
1875
- import { spawn } from "child_process";
1876
- import Parser from "web-tree-sitter";
1877
1882
  import * as fs24 from "fs/promises";
1878
1883
  import * as path23 from "path";
1884
+ import * as ejs from "ejs";
1879
1885
  import * as fs25 from "fs";
1880
1886
  import * as path24 from "path";
1881
- import * as fs26 from "fs";
1887
+ import * as os from "os";
1888
+ import { spawn } from "child_process";
1889
+ import Parser from "web-tree-sitter";
1890
+ import * as fs26 from "fs/promises";
1882
1891
  import * as path25 from "path";
1892
+ import * as fs27 from "fs";
1893
+ import * as path26 from "path";
1894
+ import * as fs28 from "fs";
1895
+ import * as path27 from "path";
1883
1896
  import * as os2 from "os";
1884
1897
  async function validateFileStructure(projectPath, conventions) {
1885
1898
  const missing = [];
@@ -1916,15 +1929,15 @@ function validateConfig(data, schema) {
1916
1929
  let message = "Configuration validation failed";
1917
1930
  const suggestions = [];
1918
1931
  if (firstError) {
1919
- const path26 = firstError.path.join(".");
1920
- const pathDisplay = path26 ? ` at "${path26}"` : "";
1932
+ const path28 = firstError.path.join(".");
1933
+ const pathDisplay = path28 ? ` at "${path28}"` : "";
1921
1934
  if (firstError.code === "invalid_type") {
1922
1935
  const received = firstError.received;
1923
1936
  const expected = firstError.expected;
1924
1937
  if (received === "undefined") {
1925
1938
  code = "MISSING_FIELD";
1926
1939
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
1927
- suggestions.push(`Field "${path26}" is required and must be of type "${expected}"`);
1940
+ suggestions.push(`Field "${path28}" is required and must be of type "${expected}"`);
1928
1941
  } else {
1929
1942
  code = "INVALID_TYPE";
1930
1943
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -2133,27 +2146,27 @@ function extractSections(content) {
2133
2146
  }
2134
2147
  return sections.map((section) => buildAgentMapSection(section, lines));
2135
2148
  }
2136
- function isExternalLink(path26) {
2137
- return path26.startsWith("http://") || path26.startsWith("https://") || path26.startsWith("#") || path26.startsWith("mailto:");
2149
+ function isExternalLink(path28) {
2150
+ return path28.startsWith("http://") || path28.startsWith("https://") || path28.startsWith("#") || path28.startsWith("mailto:");
2138
2151
  }
2139
2152
  function resolveLinkPath(linkPath, baseDir) {
2140
2153
  return linkPath.startsWith(".") ? join4(baseDir, linkPath) : linkPath;
2141
2154
  }
2142
- async function validateAgentsMap(path26 = "./AGENTS.md") {
2143
- const contentResult = await readFileContent(path26);
2155
+ async function validateAgentsMap(path28 = "./AGENTS.md") {
2156
+ const contentResult = await readFileContent(path28);
2144
2157
  if (!contentResult.ok) {
2145
2158
  return Err(
2146
2159
  createError(
2147
2160
  "PARSE_ERROR",
2148
2161
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
2149
- { path: path26 },
2162
+ { path: path28 },
2150
2163
  ["Ensure the file exists", "Check file permissions"]
2151
2164
  )
2152
2165
  );
2153
2166
  }
2154
2167
  const content = contentResult.value;
2155
2168
  const sections = extractSections(content);
2156
- const baseDir = dirname4(path26);
2169
+ const baseDir = dirname4(path28);
2157
2170
  const sectionTitles = sections.map((s) => s.title);
2158
2171
  const missingSections = REQUIRED_SECTIONS.filter(
2159
2172
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -2287,8 +2300,8 @@ async function checkDocCoverage(domain, options = {}) {
2287
2300
  );
2288
2301
  }
2289
2302
  }
2290
- function suggestFix(path26, existingFiles) {
2291
- const targetName = basename2(path26).toLowerCase();
2303
+ function suggestFix(path28, existingFiles) {
2304
+ const targetName = basename2(path28).toLowerCase();
2292
2305
  const similar = existingFiles.find((file) => {
2293
2306
  const fileName = basename2(file).toLowerCase();
2294
2307
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -2296,7 +2309,7 @@ function suggestFix(path26, existingFiles) {
2296
2309
  if (similar) {
2297
2310
  return `Did you mean "${similar}"?`;
2298
2311
  }
2299
- return `Create the file "${path26}" or remove the link`;
2312
+ return `Create the file "${path28}" or remove the link`;
2300
2313
  }
2301
2314
  async function validateKnowledgeMap(rootDir = process.cwd()) {
2302
2315
  const agentsPath = join22(rootDir, "AGENTS.md");
@@ -2639,8 +2652,8 @@ function createBoundaryValidator(schema, name) {
2639
2652
  return Ok(result.data);
2640
2653
  }
2641
2654
  const suggestions = result.error.issues.map((issue) => {
2642
- const path26 = issue.path.join(".");
2643
- return path26 ? `${path26}: ${issue.message}` : issue.message;
2655
+ const path28 = issue.path.join(".");
2656
+ return path28 ? `${path28}: ${issue.message}` : issue.message;
2644
2657
  });
2645
2658
  return Err(
2646
2659
  createError(
@@ -3248,11 +3261,11 @@ function processExportListSpecifiers(exportDecl, exports) {
3248
3261
  var TypeScriptParser = class {
3249
3262
  name = "typescript";
3250
3263
  extensions = [".ts", ".tsx", ".mts", ".cts"];
3251
- async parseFile(path26) {
3252
- const contentResult = await readFileContent(path26);
3264
+ async parseFile(path28) {
3265
+ const contentResult = await readFileContent(path28);
3253
3266
  if (!contentResult.ok) {
3254
3267
  return Err(
3255
- createParseError("NOT_FOUND", `File not found: ${path26}`, { path: path26 }, [
3268
+ createParseError("NOT_FOUND", `File not found: ${path28}`, { path: path28 }, [
3256
3269
  "Check that the file exists",
3257
3270
  "Verify the path is correct"
3258
3271
  ])
@@ -3262,7 +3275,7 @@ var TypeScriptParser = class {
3262
3275
  const ast = parse(contentResult.value, {
3263
3276
  loc: true,
3264
3277
  range: true,
3265
- jsx: path26.endsWith(".tsx"),
3278
+ jsx: path28.endsWith(".tsx"),
3266
3279
  errorOnUnknownASTType: false
3267
3280
  });
3268
3281
  return Ok({
@@ -3273,7 +3286,7 @@ var TypeScriptParser = class {
3273
3286
  } catch (e) {
3274
3287
  const error = e;
3275
3288
  return Err(
3276
- createParseError("SYNTAX_ERROR", `Failed to parse ${path26}: ${error.message}`, { path: path26 }, [
3289
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path28}: ${error.message}`, { path: path28 }, [
3277
3290
  "Check for syntax errors in the file",
3278
3291
  "Ensure valid TypeScript syntax"
3279
3292
  ])
@@ -3454,22 +3467,22 @@ function extractInlineRefs(content) {
3454
3467
  }
3455
3468
  return refs;
3456
3469
  }
3457
- async function parseDocumentationFile(path26) {
3458
- const contentResult = await readFileContent(path26);
3470
+ async function parseDocumentationFile(path28) {
3471
+ const contentResult = await readFileContent(path28);
3459
3472
  if (!contentResult.ok) {
3460
3473
  return Err(
3461
3474
  createEntropyError(
3462
3475
  "PARSE_ERROR",
3463
- `Failed to read documentation file: ${path26}`,
3464
- { file: path26 },
3476
+ `Failed to read documentation file: ${path28}`,
3477
+ { file: path28 },
3465
3478
  ["Check that the file exists"]
3466
3479
  )
3467
3480
  );
3468
3481
  }
3469
3482
  const content = contentResult.value;
3470
- const type = path26.endsWith(".md") ? "markdown" : "text";
3483
+ const type = path28.endsWith(".md") ? "markdown" : "text";
3471
3484
  return Ok({
3472
- path: path26,
3485
+ path: path28,
3473
3486
  type,
3474
3487
  content,
3475
3488
  codeBlocks: extractCodeBlocks(content),
@@ -6379,96 +6392,1166 @@ function resolveThresholds2(scope, config) {
6379
6392
  for (const [key, val] of Object.entries(config.thresholds)) {
6380
6393
  projectThresholds[key] = typeof val === "object" && val !== null && !Array.isArray(val) ? { ...val } : val;
6381
6394
  }
6382
- if (scope === "project") {
6383
- return projectThresholds;
6395
+ if (scope === "project") {
6396
+ return projectThresholds;
6397
+ }
6398
+ const moduleOverrides = config.modules[scope];
6399
+ if (!moduleOverrides) {
6400
+ return projectThresholds;
6401
+ }
6402
+ const merged = { ...projectThresholds };
6403
+ for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
6404
+ const projectValue2 = projectThresholds[category];
6405
+ if (projectValue2 !== void 0 && typeof projectValue2 === "object" && !Array.isArray(projectValue2) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
6406
+ merged[category] = {
6407
+ ...projectValue2,
6408
+ ...moduleValue
6409
+ };
6410
+ } else {
6411
+ merged[category] = moduleValue;
6412
+ }
6413
+ }
6414
+ return merged;
6415
+ }
6416
+ var CategorySnapshotSchema = z3.object({
6417
+ /** Aggregate metric value (e.g., violation count, avg complexity) */
6418
+ value: z3.number(),
6419
+ /** Count of violations in this category */
6420
+ violationCount: z3.number()
6421
+ });
6422
+ var TimelineSnapshotSchema = z3.object({
6423
+ /** ISO 8601 timestamp of capture */
6424
+ capturedAt: z3.string().datetime(),
6425
+ /** Git commit hash at capture time */
6426
+ commitHash: z3.string(),
6427
+ /** Composite stability score (0-100, higher is healthier) */
6428
+ stabilityScore: z3.number().min(0).max(100),
6429
+ /** Per-category metric aggregates */
6430
+ metrics: z3.record(ArchMetricCategorySchema, CategorySnapshotSchema)
6431
+ });
6432
+ var TimelineFileSchema = z3.object({
6433
+ version: z3.literal(1),
6434
+ snapshots: z3.array(TimelineSnapshotSchema)
6435
+ });
6436
+ var TrendLineSchema = z3.object({
6437
+ /** Current value */
6438
+ current: z3.number(),
6439
+ /** Previous value (from comparison snapshot) */
6440
+ previous: z3.number(),
6441
+ /** Absolute delta (current - previous) */
6442
+ delta: z3.number(),
6443
+ /** Direction indicator */
6444
+ direction: z3.enum(["improving", "stable", "declining"])
6445
+ });
6446
+ var TrendResultSchema = z3.object({
6447
+ /** Overall stability trend */
6448
+ stability: TrendLineSchema,
6449
+ /** Per-category trends */
6450
+ categories: z3.record(ArchMetricCategorySchema, TrendLineSchema),
6451
+ /** Number of snapshots analyzed */
6452
+ snapshotCount: z3.number(),
6453
+ /** Time range covered */
6454
+ from: z3.string(),
6455
+ to: z3.string()
6456
+ });
6457
+ var DEFAULT_STABILITY_THRESHOLDS = {
6458
+ "circular-deps": 5,
6459
+ "layer-violations": 10,
6460
+ complexity: 100,
6461
+ coupling: 2,
6462
+ "forbidden-imports": 5,
6463
+ "module-size": 10,
6464
+ "dependency-depth": 10
6465
+ };
6466
+ var ALL_CATEGORIES = ArchMetricCategorySchema.options;
6467
+ var TimelineManager = class {
6468
+ timelinePath;
6469
+ constructor(rootDir) {
6470
+ this.timelinePath = join8(rootDir, ".harness", "arch", "timeline.json");
6471
+ }
6472
+ /**
6473
+ * Load timeline from disk.
6474
+ * Returns empty TimelineFile if file does not exist or is invalid.
6475
+ */
6476
+ load() {
6477
+ if (!existsSync3(this.timelinePath)) {
6478
+ return { version: 1, snapshots: [] };
6479
+ }
6480
+ try {
6481
+ const raw = readFileSync32(this.timelinePath, "utf-8");
6482
+ const data = JSON.parse(raw);
6483
+ const parsed = TimelineFileSchema.safeParse(data);
6484
+ if (!parsed.success) {
6485
+ console.error(
6486
+ `Timeline validation failed for ${this.timelinePath}:`,
6487
+ parsed.error.format()
6488
+ );
6489
+ return { version: 1, snapshots: [] };
6490
+ }
6491
+ return parsed.data;
6492
+ } catch (error) {
6493
+ console.error(`Error loading timeline from ${this.timelinePath}:`, error);
6494
+ return { version: 1, snapshots: [] };
6495
+ }
6496
+ }
6497
+ /**
6498
+ * Save timeline to disk using atomic write (temp file + rename).
6499
+ * Creates parent directories if they do not exist.
6500
+ */
6501
+ save(timeline) {
6502
+ const dir = dirname8(this.timelinePath);
6503
+ if (!existsSync3(dir)) {
6504
+ mkdirSync3(dir, { recursive: true });
6505
+ }
6506
+ const tmp = this.timelinePath + "." + randomBytes2(4).toString("hex") + ".tmp";
6507
+ writeFileSync3(tmp, JSON.stringify(timeline, null, 2));
6508
+ renameSync2(tmp, this.timelinePath);
6509
+ }
6510
+ /**
6511
+ * Capture a new snapshot from current metric results.
6512
+ * Aggregates MetricResult[] by category, computes stability score,
6513
+ * appends to timeline (or replaces if same commitHash), and saves.
6514
+ */
6515
+ capture(results, commitHash) {
6516
+ const metrics = this.aggregateByCategory(results);
6517
+ const stabilityScore = this.computeStabilityScore(metrics);
6518
+ const snapshot = {
6519
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
6520
+ commitHash,
6521
+ stabilityScore,
6522
+ metrics
6523
+ };
6524
+ const timeline = this.load();
6525
+ const lastIndex = timeline.snapshots.length - 1;
6526
+ if (lastIndex >= 0 && timeline.snapshots[lastIndex].commitHash === commitHash) {
6527
+ timeline.snapshots[lastIndex] = snapshot;
6528
+ } else {
6529
+ timeline.snapshots.push(snapshot);
6530
+ }
6531
+ this.save(timeline);
6532
+ return snapshot;
6533
+ }
6534
+ /**
6535
+ * Compute trends between snapshots over a window.
6536
+ * @param options.last - Number of recent snapshots to analyze (default: 10)
6537
+ * @param options.since - ISO date string to filter snapshots from
6538
+ */
6539
+ trends(options) {
6540
+ const timeline = this.load();
6541
+ let snapshots = timeline.snapshots;
6542
+ if (options?.since) {
6543
+ const sinceDate = new Date(options.since);
6544
+ snapshots = snapshots.filter((s) => new Date(s.capturedAt) >= sinceDate);
6545
+ }
6546
+ if (options?.last && snapshots.length > options.last) {
6547
+ snapshots = snapshots.slice(-options.last);
6548
+ }
6549
+ if (snapshots.length === 0) {
6550
+ return this.emptyTrendResult();
6551
+ }
6552
+ if (snapshots.length === 1) {
6553
+ const only = snapshots[0];
6554
+ const m = only.metrics;
6555
+ return {
6556
+ stability: this.buildTrendLine(only.stabilityScore, only.stabilityScore, true),
6557
+ categories: this.buildCategoryTrends(m, m),
6558
+ snapshotCount: 1,
6559
+ from: only.capturedAt,
6560
+ to: only.capturedAt
6561
+ };
6562
+ }
6563
+ const first = snapshots[0];
6564
+ const last = snapshots[snapshots.length - 1];
6565
+ return {
6566
+ stability: this.buildTrendLine(last.stabilityScore, first.stabilityScore, true),
6567
+ categories: this.buildCategoryTrends(
6568
+ last.metrics,
6569
+ first.metrics
6570
+ ),
6571
+ snapshotCount: snapshots.length,
6572
+ from: first.capturedAt,
6573
+ to: last.capturedAt
6574
+ };
6575
+ }
6576
+ /**
6577
+ * Compute composite stability score from category metrics.
6578
+ * Equal weight across all categories. Score is 0-100 (higher = healthier).
6579
+ * health = max(0, 1 - (value / threshold)) per category.
6580
+ */
6581
+ computeStabilityScore(metrics, thresholds = DEFAULT_STABILITY_THRESHOLDS) {
6582
+ const healthScores = [];
6583
+ for (const category of ALL_CATEGORIES) {
6584
+ const snapshot = metrics[category];
6585
+ if (!snapshot) {
6586
+ healthScores.push(1);
6587
+ continue;
6588
+ }
6589
+ const threshold = thresholds[category] ?? 10;
6590
+ const health = Math.max(0, 1 - snapshot.value / threshold);
6591
+ healthScores.push(health);
6592
+ }
6593
+ const mean = healthScores.reduce((sum, h) => sum + h, 0) / healthScores.length;
6594
+ return Math.round(mean * 100);
6595
+ }
6596
+ // --- Private helpers ---
6597
+ aggregateByCategory(results) {
6598
+ const metrics = {};
6599
+ for (const result of results) {
6600
+ const existing = metrics[result.category];
6601
+ if (existing) {
6602
+ existing.value += result.value;
6603
+ existing.violationCount += result.violations.length;
6604
+ } else {
6605
+ metrics[result.category] = {
6606
+ value: result.value,
6607
+ violationCount: result.violations.length
6608
+ };
6609
+ }
6610
+ }
6611
+ for (const category of ALL_CATEGORIES) {
6612
+ if (!metrics[category]) {
6613
+ metrics[category] = { value: 0, violationCount: 0 };
6614
+ }
6615
+ }
6616
+ return metrics;
6617
+ }
6618
+ buildTrendLine(current, previous, isStabilityScore) {
6619
+ const delta = current - previous;
6620
+ let direction;
6621
+ if (Math.abs(delta) < 2) {
6622
+ direction = "stable";
6623
+ } else if (isStabilityScore) {
6624
+ direction = delta > 0 ? "improving" : "declining";
6625
+ } else {
6626
+ direction = delta < 0 ? "improving" : "declining";
6627
+ }
6628
+ return { current, previous, delta, direction };
6629
+ }
6630
+ buildCategoryTrends(currentMetrics, previousMetrics) {
6631
+ const trends = {};
6632
+ for (const category of ALL_CATEGORIES) {
6633
+ const current = currentMetrics[category]?.value ?? 0;
6634
+ const previous = previousMetrics[category]?.value ?? 0;
6635
+ trends[category] = this.buildTrendLine(current, previous, false);
6636
+ }
6637
+ return trends;
6638
+ }
6639
+ emptyTrendResult() {
6640
+ const zeroLine = { current: 0, previous: 0, delta: 0, direction: "stable" };
6641
+ const categories = {};
6642
+ for (const category of ALL_CATEGORIES) {
6643
+ categories[category] = { ...zeroLine };
6644
+ }
6645
+ return {
6646
+ stability: { ...zeroLine },
6647
+ categories,
6648
+ snapshotCount: 0,
6649
+ from: "",
6650
+ to: ""
6651
+ };
6652
+ }
6653
+ };
6654
+ var ConfidenceTierSchema = z4.enum(["high", "medium", "low"]);
6655
+ var RegressionResultSchema = z4.object({
6656
+ slope: z4.number(),
6657
+ intercept: z4.number(),
6658
+ rSquared: z4.number().min(0).max(1),
6659
+ dataPoints: z4.number().int().min(0)
6660
+ });
6661
+ var DirectionSchema = z4.enum(["improving", "stable", "declining"]);
6662
+ var CategoryForecastSchema = z4.object({
6663
+ category: ArchMetricCategorySchema,
6664
+ current: z4.number(),
6665
+ threshold: z4.number(),
6666
+ projectedValue4w: z4.number(),
6667
+ projectedValue8w: z4.number(),
6668
+ projectedValue12w: z4.number(),
6669
+ thresholdCrossingWeeks: z4.number().nullable(),
6670
+ confidence: ConfidenceTierSchema,
6671
+ regression: RegressionResultSchema,
6672
+ direction: DirectionSchema
6673
+ });
6674
+ var SpecImpactSignalsSchema = z4.object({
6675
+ newFileCount: z4.number().int().min(0),
6676
+ affectedLayers: z4.array(z4.string()),
6677
+ newDependencies: z4.number().int().min(0),
6678
+ phaseCount: z4.number().int().min(0)
6679
+ });
6680
+ var SpecImpactEstimateSchema = z4.object({
6681
+ specPath: z4.string(),
6682
+ featureName: z4.string(),
6683
+ signals: SpecImpactSignalsSchema,
6684
+ deltas: z4.record(ArchMetricCategorySchema, z4.number()).optional()
6685
+ });
6686
+ var ContributingFeatureSchema = z4.object({
6687
+ name: z4.string(),
6688
+ specPath: z4.string(),
6689
+ delta: z4.number()
6690
+ });
6691
+ var AdjustedForecastSchema = z4.object({
6692
+ baseline: CategoryForecastSchema,
6693
+ adjusted: CategoryForecastSchema,
6694
+ contributingFeatures: z4.array(ContributingFeatureSchema)
6695
+ });
6696
+ var PredictionWarningSchema = z4.object({
6697
+ severity: z4.enum(["critical", "warning", "info"]),
6698
+ category: ArchMetricCategorySchema,
6699
+ message: z4.string(),
6700
+ weeksUntil: z4.number(),
6701
+ confidence: ConfidenceTierSchema,
6702
+ contributingFeatures: z4.array(z4.string())
6703
+ });
6704
+ var StabilityForecastSchema = z4.object({
6705
+ current: z4.number(),
6706
+ projected4w: z4.number(),
6707
+ projected8w: z4.number(),
6708
+ projected12w: z4.number(),
6709
+ confidence: ConfidenceTierSchema,
6710
+ direction: DirectionSchema
6711
+ });
6712
+ var PredictionResultSchema = z4.object({
6713
+ generatedAt: z4.string(),
6714
+ snapshotsUsed: z4.number().int().min(0),
6715
+ timelineRange: z4.object({
6716
+ from: z4.string(),
6717
+ to: z4.string()
6718
+ }),
6719
+ stabilityForecast: StabilityForecastSchema,
6720
+ categories: z4.record(ArchMetricCategorySchema, AdjustedForecastSchema),
6721
+ warnings: z4.array(PredictionWarningSchema)
6722
+ });
6723
+ var PredictionOptionsSchema = z4.object({
6724
+ horizon: z4.number().int().min(1).default(12),
6725
+ includeRoadmap: z4.boolean().default(true),
6726
+ categories: z4.array(ArchMetricCategorySchema).optional(),
6727
+ thresholds: z4.record(ArchMetricCategorySchema, z4.number()).optional()
6728
+ });
6729
+ function computeWeightedSums(points) {
6730
+ let sumW = 0, sumWt = 0, sumWv = 0, sumWtt = 0, sumWtv = 0;
6731
+ for (const p of points) {
6732
+ const w = p.weight;
6733
+ sumW += w;
6734
+ sumWt += w * p.t;
6735
+ sumWv += w * p.value;
6736
+ sumWtt += w * p.t * p.t;
6737
+ sumWtv += w * p.t * p.value;
6738
+ }
6739
+ return { sumW, sumWt, sumWv, sumWtt, sumWtv };
6740
+ }
6741
+ function computeRSquared(points, slope, intercept, meanV) {
6742
+ let ssRes = 0, ssTot = 0;
6743
+ for (const p of points) {
6744
+ const predicted = slope * p.t + intercept;
6745
+ ssRes += p.weight * (p.value - predicted) ** 2;
6746
+ ssTot += p.weight * (p.value - meanV) ** 2;
6747
+ }
6748
+ return ssTot < 1e-12 ? 1 : Math.max(0, 1 - ssRes / ssTot);
6749
+ }
6750
+ function weightedLinearRegression(points) {
6751
+ if (points.length < 2) {
6752
+ throw new Error(`Regression requires at least 2 data points, got ${points.length}`);
6753
+ }
6754
+ const n = points.length;
6755
+ const { sumW, sumWt, sumWv, sumWtt, sumWtv } = computeWeightedSums(points);
6756
+ const meanT = sumWt / sumW;
6757
+ const meanV = sumWv / sumW;
6758
+ const denominator = sumWtt - sumWt * sumWt / sumW;
6759
+ if (Math.abs(denominator) < 1e-12) {
6760
+ return { slope: 0, intercept: meanV, rSquared: 0, dataPoints: n };
6761
+ }
6762
+ const slope = (sumWtv - sumWt * sumWv / sumW) / denominator;
6763
+ const intercept = meanV - slope * meanT;
6764
+ const rSquared = computeRSquared(points, slope, intercept, meanV);
6765
+ return { slope, intercept, rSquared, dataPoints: n };
6766
+ }
6767
+ function applyRecencyWeights(values, decay = 0.85) {
6768
+ const n = values.length;
6769
+ return values.map((v, i) => ({
6770
+ t: v.t,
6771
+ value: v.value,
6772
+ weight: Math.pow(decay, n - 1 - i)
6773
+ }));
6774
+ }
6775
+ function projectValue(fit, t) {
6776
+ return fit.slope * t + fit.intercept;
6777
+ }
6778
+ function weeksUntilThreshold(fit, currentT, threshold) {
6779
+ if (fit.slope <= 0) {
6780
+ return null;
6781
+ }
6782
+ const currentProjected = projectValue(fit, currentT);
6783
+ if (currentProjected >= threshold) {
6784
+ return null;
6785
+ }
6786
+ const weeks = (threshold - currentProjected) / fit.slope;
6787
+ return Math.ceil(weeks);
6788
+ }
6789
+ function classifyConfidence(rSquared, dataPoints) {
6790
+ if (rSquared >= 0.7 && dataPoints >= 5) return "high";
6791
+ if (rSquared >= 0.4 && dataPoints >= 3) return "medium";
6792
+ return "low";
6793
+ }
6794
+ var VALID_STATUSES = /* @__PURE__ */ new Set([
6795
+ "backlog",
6796
+ "planned",
6797
+ "in-progress",
6798
+ "done",
6799
+ "blocked"
6800
+ ]);
6801
+ var EM_DASH = "\u2014";
6802
+ var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
6803
+ function parseRoadmap(markdown) {
6804
+ const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
6805
+ if (!fmMatch) {
6806
+ return Err(new Error("Missing or malformed YAML frontmatter"));
6807
+ }
6808
+ const fmResult = parseFrontmatter(fmMatch[1]);
6809
+ if (!fmResult.ok) return fmResult;
6810
+ const body = markdown.slice(fmMatch[0].length);
6811
+ const milestonesResult = parseMilestones(body);
6812
+ if (!milestonesResult.ok) return milestonesResult;
6813
+ const historyResult = parseAssignmentHistory(body);
6814
+ if (!historyResult.ok) return historyResult;
6815
+ return Ok({
6816
+ frontmatter: fmResult.value,
6817
+ milestones: milestonesResult.value,
6818
+ assignmentHistory: historyResult.value
6819
+ });
6820
+ }
6821
+ function parseFrontmatter(raw) {
6822
+ const lines = raw.split("\n");
6823
+ const map = /* @__PURE__ */ new Map();
6824
+ for (const line of lines) {
6825
+ const idx = line.indexOf(":");
6826
+ if (idx === -1) continue;
6827
+ const key = line.slice(0, idx).trim();
6828
+ const val = line.slice(idx + 1).trim();
6829
+ map.set(key, val);
6830
+ }
6831
+ const project = map.get("project");
6832
+ const versionStr = map.get("version");
6833
+ const lastSynced = map.get("last_synced");
6834
+ const lastManualEdit = map.get("last_manual_edit");
6835
+ const created = map.get("created");
6836
+ const updated = map.get("updated");
6837
+ if (!project || !versionStr || !lastSynced || !lastManualEdit) {
6838
+ return Err(
6839
+ new Error(
6840
+ "Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
6841
+ )
6842
+ );
6843
+ }
6844
+ const version = parseInt(versionStr, 10);
6845
+ if (isNaN(version)) {
6846
+ return Err(new Error("Frontmatter version must be a number"));
6847
+ }
6848
+ const fm = { project, version, lastSynced, lastManualEdit };
6849
+ if (created) fm.created = created;
6850
+ if (updated) fm.updated = updated;
6851
+ return Ok(fm);
6852
+ }
6853
+ function parseMilestones(body) {
6854
+ const milestones = [];
6855
+ const h2Pattern = /^## (.+)$/gm;
6856
+ const h2Matches = [];
6857
+ let match;
6858
+ let bodyEnd = body.length;
6859
+ while ((match = h2Pattern.exec(body)) !== null) {
6860
+ if (match[1] === "Assignment History") {
6861
+ bodyEnd = match.index;
6862
+ break;
6863
+ }
6864
+ h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
6865
+ }
6866
+ for (let i = 0; i < h2Matches.length; i++) {
6867
+ const h2 = h2Matches[i];
6868
+ const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : bodyEnd;
6869
+ const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
6870
+ const isBacklog = h2.heading === "Backlog";
6871
+ const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
6872
+ const featuresResult = parseFeatures(sectionBody);
6873
+ if (!featuresResult.ok) return featuresResult;
6874
+ milestones.push({
6875
+ name: milestoneName,
6876
+ isBacklog,
6877
+ features: featuresResult.value
6878
+ });
6879
+ }
6880
+ return Ok(milestones);
6881
+ }
6882
+ function parseFeatures(sectionBody) {
6883
+ const features = [];
6884
+ const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
6885
+ const h3Matches = [];
6886
+ let match;
6887
+ while ((match = h3Pattern.exec(sectionBody)) !== null) {
6888
+ h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
6889
+ }
6890
+ for (let i = 0; i < h3Matches.length; i++) {
6891
+ const h3 = h3Matches[i];
6892
+ const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
6893
+ const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
6894
+ const featureResult = parseFeatureFields(h3.name, featureBody);
6895
+ if (!featureResult.ok) return featureResult;
6896
+ features.push(featureResult.value);
6897
+ }
6898
+ return Ok(features);
6899
+ }
6900
+ function extractFieldMap(body) {
6901
+ const fieldMap = /* @__PURE__ */ new Map();
6902
+ const fieldPattern = /^- \*\*(.+?):\*\* (.+)$/gm;
6903
+ let match;
6904
+ while ((match = fieldPattern.exec(body)) !== null) {
6905
+ fieldMap.set(match[1], match[2]);
6906
+ }
6907
+ return fieldMap;
6908
+ }
6909
+ function parseListField(fieldMap, ...keys) {
6910
+ let raw = EM_DASH;
6911
+ for (const key of keys) {
6912
+ const val = fieldMap.get(key);
6913
+ if (val !== void 0) {
6914
+ raw = val;
6915
+ break;
6916
+ }
6917
+ }
6918
+ if (raw === EM_DASH || raw === "none") return [];
6919
+ return raw.split(",").map((s) => s.trim());
6920
+ }
6921
+ function parseFeatureFields(name, body) {
6922
+ const fieldMap = extractFieldMap(body);
6923
+ const statusRaw = fieldMap.get("Status");
6924
+ if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
6925
+ return Err(
6926
+ new Error(
6927
+ `Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
6928
+ )
6929
+ );
6930
+ }
6931
+ const specRaw = fieldMap.get("Spec") ?? EM_DASH;
6932
+ const plans = parseListField(fieldMap, "Plans", "Plan");
6933
+ const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
6934
+ const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
6935
+ const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
6936
+ const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
6937
+ if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
6938
+ return Err(
6939
+ new Error(
6940
+ `Feature "${name}" has invalid priority: "${priorityRaw}". Valid priorities: ${[...VALID_PRIORITIES].join(", ")}`
6941
+ )
6942
+ );
6943
+ }
6944
+ return Ok({
6945
+ name,
6946
+ status: statusRaw,
6947
+ spec: specRaw === EM_DASH ? null : specRaw,
6948
+ plans,
6949
+ blockedBy,
6950
+ summary: fieldMap.get("Summary") ?? "",
6951
+ assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
6952
+ priority: priorityRaw === EM_DASH ? null : priorityRaw,
6953
+ externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
6954
+ });
6955
+ }
6956
+ function parseAssignmentHistory(body) {
6957
+ const historyMatch = body.match(/^## Assignment History\s*\n/m);
6958
+ if (!historyMatch || historyMatch.index === void 0) return Ok([]);
6959
+ const historyStart = historyMatch.index + historyMatch[0].length;
6960
+ const rawHistoryBody = body.slice(historyStart);
6961
+ const nextH2 = rawHistoryBody.search(/^## /m);
6962
+ const historyBody = nextH2 === -1 ? rawHistoryBody : rawHistoryBody.slice(0, nextH2);
6963
+ const records = [];
6964
+ const lines = historyBody.split("\n");
6965
+ let pastHeader = false;
6966
+ for (const line of lines) {
6967
+ const trimmed = line.trim();
6968
+ if (!trimmed.startsWith("|")) continue;
6969
+ if (!pastHeader) {
6970
+ if (trimmed.match(/^\|[-\s|]+\|$/)) {
6971
+ pastHeader = true;
6972
+ }
6973
+ continue;
6974
+ }
6975
+ const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
6976
+ if (cells.length < 4) continue;
6977
+ const action = cells[2];
6978
+ if (!["assigned", "completed", "unassigned"].includes(action)) continue;
6979
+ records.push({
6980
+ feature: cells[0],
6981
+ assignee: cells[1],
6982
+ action,
6983
+ date: cells[3]
6984
+ });
6985
+ }
6986
+ return Ok(records);
6987
+ }
6988
+ var ALL_CATEGORIES2 = ArchMetricCategorySchema.options;
6989
+ var DIRECTION_THRESHOLD = 1e-3;
6990
+ var PredictionEngine = class {
6991
+ constructor(rootDir, timelineManager, estimator) {
6992
+ this.rootDir = rootDir;
6993
+ this.timelineManager = timelineManager;
6994
+ this.estimator = estimator;
6995
+ }
6996
+ rootDir;
6997
+ timelineManager;
6998
+ estimator;
6999
+ /**
7000
+ * Produce a PredictionResult with per-category forecasts and warnings.
7001
+ * Throws if fewer than 3 snapshots are available.
7002
+ */
7003
+ predict(options) {
7004
+ const opts = this.resolveOptions(options);
7005
+ const timeline = this.timelineManager.load();
7006
+ const snapshots = timeline.snapshots;
7007
+ if (snapshots.length < 3) {
7008
+ throw new Error(
7009
+ `PredictionEngine requires at least 3 snapshots, got ${snapshots.length}. Run "harness snapshot" to capture more data points.`
7010
+ );
7011
+ }
7012
+ const thresholds = this.resolveThresholds(opts);
7013
+ const categoriesToProcess = opts.categories ?? [...ALL_CATEGORIES2];
7014
+ const firstDate = new Date(snapshots[0].capturedAt).getTime();
7015
+ const lastSnapshot = snapshots[snapshots.length - 1];
7016
+ const currentT = (new Date(lastSnapshot.capturedAt).getTime() - firstDate) / (7 * 24 * 60 * 60 * 1e3);
7017
+ const baselines = {};
7018
+ for (const category of ALL_CATEGORIES2) {
7019
+ const threshold = thresholds[category];
7020
+ const shouldProcess = categoriesToProcess.includes(category);
7021
+ if (!shouldProcess) {
7022
+ baselines[category] = this.zeroForecast(category, threshold);
7023
+ continue;
7024
+ }
7025
+ const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
7026
+ baselines[category] = this.forecastCategory(
7027
+ category,
7028
+ timeSeries,
7029
+ currentT,
7030
+ threshold,
7031
+ opts.horizon
7032
+ );
7033
+ }
7034
+ const specImpacts = this.computeSpecImpacts(opts);
7035
+ const categories = {};
7036
+ for (const category of ALL_CATEGORIES2) {
7037
+ const baseline = baselines[category];
7038
+ const threshold = thresholds[category];
7039
+ if (!specImpacts || specImpacts.length === 0) {
7040
+ categories[category] = {
7041
+ baseline,
7042
+ adjusted: baseline,
7043
+ contributingFeatures: []
7044
+ };
7045
+ continue;
7046
+ }
7047
+ let totalDelta = 0;
7048
+ const contributing = [];
7049
+ for (const impact of specImpacts) {
7050
+ const delta = impact.deltas?.[category] ?? 0;
7051
+ if (delta !== 0) {
7052
+ totalDelta += delta;
7053
+ contributing.push({
7054
+ name: impact.featureName,
7055
+ specPath: impact.specPath,
7056
+ delta
7057
+ });
7058
+ }
7059
+ }
7060
+ if (totalDelta === 0) {
7061
+ categories[category] = {
7062
+ baseline,
7063
+ adjusted: baseline,
7064
+ contributingFeatures: []
7065
+ };
7066
+ continue;
7067
+ }
7068
+ const adjusted = {
7069
+ ...baseline,
7070
+ projectedValue4w: baseline.projectedValue4w + totalDelta,
7071
+ projectedValue8w: baseline.projectedValue8w + totalDelta,
7072
+ projectedValue12w: baseline.projectedValue12w + totalDelta
7073
+ };
7074
+ const adjustedFit = {
7075
+ slope: baseline.regression.slope,
7076
+ intercept: baseline.regression.intercept + totalDelta,
7077
+ rSquared: baseline.regression.rSquared,
7078
+ dataPoints: baseline.regression.dataPoints
7079
+ };
7080
+ adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
7081
+ adjusted.regression = {
7082
+ slope: adjustedFit.slope,
7083
+ intercept: adjustedFit.intercept,
7084
+ rSquared: adjustedFit.rSquared,
7085
+ dataPoints: adjustedFit.dataPoints
7086
+ };
7087
+ categories[category] = {
7088
+ baseline,
7089
+ adjusted,
7090
+ contributingFeatures: contributing
7091
+ };
7092
+ }
7093
+ const warnings = this.generateWarnings(
7094
+ categories,
7095
+ opts.horizon
7096
+ );
7097
+ const stabilityForecast = this.computeStabilityForecast(
7098
+ categories,
7099
+ thresholds,
7100
+ snapshots
7101
+ );
7102
+ return {
7103
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7104
+ snapshotsUsed: snapshots.length,
7105
+ timelineRange: {
7106
+ from: snapshots[0].capturedAt,
7107
+ to: lastSnapshot.capturedAt
7108
+ },
7109
+ stabilityForecast,
7110
+ categories,
7111
+ warnings
7112
+ };
7113
+ }
7114
+ // --- Private helpers ---
7115
+ resolveOptions(options) {
7116
+ return {
7117
+ horizon: options?.horizon ?? 12,
7118
+ includeRoadmap: options?.includeRoadmap ?? true,
7119
+ categories: options?.categories,
7120
+ thresholds: options?.thresholds
7121
+ };
7122
+ }
7123
+ resolveThresholds(opts) {
7124
+ const base = { ...DEFAULT_STABILITY_THRESHOLDS };
7125
+ if (opts.thresholds) {
7126
+ for (const [key, value] of Object.entries(opts.thresholds)) {
7127
+ if (value !== void 0) {
7128
+ base[key] = value;
7129
+ }
7130
+ }
7131
+ }
7132
+ return base;
7133
+ }
7134
+ /**
7135
+ * Extract time series for a single category from snapshots.
7136
+ * Returns array of { t (weeks from first), value } sorted oldest first.
7137
+ */
7138
+ extractTimeSeries(snapshots, category, firstDateMs) {
7139
+ return snapshots.map((s) => {
7140
+ const t = (new Date(s.capturedAt).getTime() - firstDateMs) / (7 * 24 * 60 * 60 * 1e3);
7141
+ const metrics = s.metrics;
7142
+ const value = metrics[category]?.value ?? 0;
7143
+ return { t, value };
7144
+ });
7145
+ }
7146
+ /**
7147
+ * Produce a CategoryForecast for a single category using regression.
7148
+ */
7149
+ forecastCategory(category, timeSeries, currentT, threshold, horizon = 12) {
7150
+ const weighted = applyRecencyWeights(timeSeries, 0.85);
7151
+ const fit = weightedLinearRegression(weighted);
7152
+ const current = timeSeries[timeSeries.length - 1].value;
7153
+ const h3 = Math.round(horizon / 3);
7154
+ const h2 = Math.round(horizon * 2 / 3);
7155
+ const projected4w = projectValue(fit, currentT + h3);
7156
+ const projected8w = projectValue(fit, currentT + h2);
7157
+ const projected12w = projectValue(fit, currentT + horizon);
7158
+ const crossing = weeksUntilThreshold(fit, currentT, threshold);
7159
+ const confidence = classifyConfidence(fit.rSquared, fit.dataPoints);
7160
+ const direction = this.classifyDirection(fit.slope);
7161
+ return {
7162
+ category,
7163
+ current,
7164
+ threshold,
7165
+ projectedValue4w: projected4w,
7166
+ projectedValue8w: projected8w,
7167
+ projectedValue12w: projected12w,
7168
+ thresholdCrossingWeeks: crossing,
7169
+ confidence,
7170
+ regression: {
7171
+ slope: fit.slope,
7172
+ intercept: fit.intercept,
7173
+ rSquared: fit.rSquared,
7174
+ dataPoints: fit.dataPoints
7175
+ },
7176
+ direction
7177
+ };
7178
+ }
7179
+ classifyDirection(slope) {
7180
+ if (Math.abs(slope) < DIRECTION_THRESHOLD) return "stable";
7181
+ return slope > 0 ? "declining" : "improving";
7182
+ }
7183
+ zeroForecast(category, threshold) {
7184
+ return {
7185
+ category,
7186
+ current: 0,
7187
+ threshold,
7188
+ projectedValue4w: 0,
7189
+ projectedValue8w: 0,
7190
+ projectedValue12w: 0,
7191
+ thresholdCrossingWeeks: null,
7192
+ confidence: "low",
7193
+ regression: { slope: 0, intercept: 0, rSquared: 0, dataPoints: 0 },
7194
+ direction: "stable"
7195
+ };
7196
+ }
7197
+ /**
7198
+ * Generate warnings based on severity rules from spec:
7199
+ * - critical: threshold crossing <= 4w, confidence high or medium
7200
+ * - warning: threshold crossing <= 8w, confidence high or medium
7201
+ * - info: threshold crossing <= 12w, any confidence
7202
+ */
7203
+ generateWarnings(categories, horizon = 12) {
7204
+ const warnings = [];
7205
+ const criticalWindow = Math.round(horizon / 3);
7206
+ const warningWindow = Math.round(horizon * 2 / 3);
7207
+ for (const category of ALL_CATEGORIES2) {
7208
+ const af = categories[category];
7209
+ if (!af) continue;
7210
+ const forecast = af.adjusted;
7211
+ const crossing = forecast.thresholdCrossingWeeks;
7212
+ if (crossing === null || crossing <= 0) continue;
7213
+ let severity = null;
7214
+ if (crossing <= criticalWindow && (forecast.confidence === "high" || forecast.confidence === "medium")) {
7215
+ severity = "critical";
7216
+ } else if (crossing <= warningWindow && (forecast.confidence === "high" || forecast.confidence === "medium")) {
7217
+ severity = "warning";
7218
+ } else if (crossing <= horizon) {
7219
+ severity = "info";
7220
+ }
7221
+ if (severity) {
7222
+ const contributingNames = af.contributingFeatures.map((f) => f.name);
7223
+ warnings.push({
7224
+ severity,
7225
+ category,
7226
+ message: `${category} projected to exceed threshold (~${crossing}w, ${forecast.confidence} confidence)`,
7227
+ weeksUntil: crossing,
7228
+ confidence: forecast.confidence,
7229
+ contributingFeatures: contributingNames
7230
+ });
7231
+ }
7232
+ }
7233
+ return warnings;
7234
+ }
7235
+ /**
7236
+ * Compute composite stability forecast by projecting per-category values
7237
+ * forward and computing stability scores at each horizon.
7238
+ */
7239
+ computeStabilityForecast(categories, thresholds, _snapshots) {
7240
+ const currentMetrics = this.buildMetricsFromForecasts(categories, "current");
7241
+ const current = this.timelineManager.computeStabilityScore(currentMetrics, thresholds);
7242
+ const metrics4w = this.buildMetricsFromForecasts(categories, "4w");
7243
+ const projected4w = this.timelineManager.computeStabilityScore(metrics4w, thresholds);
7244
+ const metrics8w = this.buildMetricsFromForecasts(categories, "8w");
7245
+ const projected8w = this.timelineManager.computeStabilityScore(metrics8w, thresholds);
7246
+ const metrics12w = this.buildMetricsFromForecasts(categories, "12w");
7247
+ const projected12w = this.timelineManager.computeStabilityScore(metrics12w, thresholds);
7248
+ const delta = projected12w - current;
7249
+ let direction;
7250
+ if (Math.abs(delta) < 2) {
7251
+ direction = "stable";
7252
+ } else {
7253
+ direction = delta > 0 ? "improving" : "declining";
7254
+ }
7255
+ const confidences = ALL_CATEGORIES2.map((c) => categories[c]?.adjusted.confidence ?? "low");
7256
+ const confidence = this.medianConfidence(confidences);
7257
+ return { current, projected4w, projected8w, projected12w, confidence, direction };
7258
+ }
7259
+ buildMetricsFromForecasts(categories, horizon) {
7260
+ const metrics = {};
7261
+ for (const cat of ALL_CATEGORIES2) {
7262
+ const forecast = categories[cat]?.adjusted;
7263
+ let value = 0;
7264
+ if (forecast) {
7265
+ switch (horizon) {
7266
+ case "current":
7267
+ value = forecast.current;
7268
+ break;
7269
+ case "4w":
7270
+ value = forecast.projectedValue4w;
7271
+ break;
7272
+ case "8w":
7273
+ value = forecast.projectedValue8w;
7274
+ break;
7275
+ case "12w":
7276
+ value = forecast.projectedValue12w;
7277
+ break;
7278
+ }
7279
+ }
7280
+ metrics[cat] = { value: Math.max(0, value), violationCount: 0 };
7281
+ }
7282
+ return metrics;
7283
+ }
7284
+ medianConfidence(confidences) {
7285
+ const order = { low: 0, medium: 1, high: 2 };
7286
+ const sorted = [...confidences].sort((a, b) => order[a] - order[b]);
7287
+ const mid = Math.floor(sorted.length / 2);
7288
+ return sorted[mid] ?? "low";
7289
+ }
7290
+ /**
7291
+ * Load roadmap features, estimate spec impacts via the estimator.
7292
+ * Returns null if estimator is null or includeRoadmap is false.
7293
+ */
7294
+ computeSpecImpacts(opts) {
7295
+ if (!this.estimator || !opts.includeRoadmap) {
7296
+ return null;
7297
+ }
7298
+ try {
7299
+ const roadmapPath = path2.join(this.rootDir, "roadmap.md");
7300
+ const raw = fs5.readFileSync(roadmapPath, "utf-8");
7301
+ const parseResult = parseRoadmap(raw);
7302
+ if (!parseResult.ok) return null;
7303
+ const features = [];
7304
+ for (const milestone of parseResult.value.milestones) {
7305
+ for (const feature of milestone.features) {
7306
+ if (feature.status === "planned" || feature.status === "in-progress") {
7307
+ features.push({ name: feature.name, spec: feature.spec });
7308
+ }
7309
+ }
7310
+ }
7311
+ if (features.length === 0) return null;
7312
+ return this.estimator.estimateAll(features);
7313
+ } catch {
7314
+ return null;
7315
+ }
7316
+ }
7317
+ };
7318
+ var DEFAULT_COEFFICIENTS = {
7319
+ newFileModuleSize: 0.3,
7320
+ newFileComplexity: 1.5,
7321
+ layerViolation: 0.5,
7322
+ depCoupling: 0.2,
7323
+ depDepth: 0.3,
7324
+ phaseComplexity: 2
7325
+ };
7326
+ var SpecImpactEstimator = class {
7327
+ constructor(rootDir, coefficients) {
7328
+ this.rootDir = rootDir;
7329
+ this.coefficients = { ...DEFAULT_COEFFICIENTS, ...coefficients };
7330
+ this.layerNames = this.loadLayerNames();
7331
+ }
7332
+ rootDir;
7333
+ coefficients;
7334
+ layerNames;
7335
+ /**
7336
+ * Estimate impact of a single spec file.
7337
+ * @param specPath - Relative path from rootDir to the spec file.
7338
+ */
7339
+ estimate(specPath) {
7340
+ const absolutePath = path3.join(this.rootDir, specPath);
7341
+ const content = fs6.readFileSync(absolutePath, "utf-8");
7342
+ const newFileCount = this.extractNewFileCount(content);
7343
+ const affectedLayers = this.extractAffectedLayers(content);
7344
+ const newDependencies = this.extractNewDependencies(content);
7345
+ const phaseCount = this.extractPhaseCount(content);
7346
+ const deltas = this.computeDeltas(
7347
+ newFileCount,
7348
+ affectedLayers.length,
7349
+ newDependencies,
7350
+ phaseCount
7351
+ );
7352
+ const h1Match = content.match(/^#\s+(.+)$/m);
7353
+ const featureName = h1Match ? h1Match[1].trim() : path3.basename(specPath, ".md");
7354
+ return {
7355
+ specPath,
7356
+ featureName,
7357
+ signals: {
7358
+ newFileCount,
7359
+ affectedLayers,
7360
+ newDependencies,
7361
+ phaseCount
7362
+ },
7363
+ deltas
7364
+ };
7365
+ }
7366
+ /**
7367
+ * Estimate impact for all planned features that have specs.
7368
+ * Skips features with null specs or specs that don't exist on disk.
7369
+ */
7370
+ estimateAll(features) {
7371
+ const results = [];
7372
+ for (const feature of features) {
7373
+ if (!feature.spec) continue;
7374
+ const absolutePath = path3.join(this.rootDir, feature.spec);
7375
+ if (!fs6.existsSync(absolutePath)) continue;
7376
+ const estimate = this.estimate(feature.spec);
7377
+ results.push({ ...estimate, featureName: feature.name });
7378
+ }
7379
+ return results;
7380
+ }
7381
+ // --- Private: Signal Extraction ---
7382
+ /**
7383
+ * Count file paths in Technical Design sections that don't exist on disk.
7384
+ * Looks for paths in code blocks (```) under ## Technical Design.
7385
+ */
7386
+ extractNewFileCount(content) {
7387
+ const techDesignMatch = content.match(/## Technical Design\b[\s\S]*?(?=\n## |\n# |$)/i);
7388
+ if (!techDesignMatch) return 0;
7389
+ const section = techDesignMatch[0];
7390
+ const codeBlocks = section.match(/```[\s\S]*?```/g) ?? [];
7391
+ const filePaths = [];
7392
+ for (const block of codeBlocks) {
7393
+ const inner = block.replace(/^```\w*\n?/, "").replace(/\n?```$/, "");
7394
+ for (const line of inner.split("\n")) {
7395
+ const trimmed = line.trim();
7396
+ if (trimmed.match(/^[\w@.-]+\/[\w./-]+\.\w+$/)) {
7397
+ filePaths.push(trimmed);
7398
+ }
7399
+ }
7400
+ }
7401
+ let count = 0;
7402
+ for (const fp of filePaths) {
7403
+ const absolute = path3.join(this.rootDir, fp);
7404
+ if (!fs6.existsSync(absolute)) {
7405
+ count++;
7406
+ }
7407
+ }
7408
+ return count;
7409
+ }
7410
+ /**
7411
+ * Match layer names from harness.config.json mentioned in the spec.
7412
+ * Returns deduplicated array of matched layer names.
7413
+ */
7414
+ extractAffectedLayers(content) {
7415
+ if (this.layerNames.length === 0) return [];
7416
+ const matched = /* @__PURE__ */ new Set();
7417
+ for (const layer of this.layerNames) {
7418
+ const pattern = new RegExp(`\\b${this.escapeRegex(layer)}\\b`, "i");
7419
+ if (pattern.test(content)) {
7420
+ matched.add(layer);
7421
+ }
7422
+ }
7423
+ return [...matched].sort();
7424
+ }
7425
+ /**
7426
+ * Count dependency-related keywords: "import", "depend" (covers depends/dependency),
7427
+ * "package" in dependency context.
7428
+ */
7429
+ extractNewDependencies(content) {
7430
+ const patterns = [/\bimport\b/gi, /\bdepend\w*\b/gi, /\bpackage\b/gi];
7431
+ let count = 0;
7432
+ for (const pattern of patterns) {
7433
+ const matches = content.match(pattern);
7434
+ if (matches) count += matches.length;
7435
+ }
7436
+ return count;
6384
7437
  }
6385
- const moduleOverrides = config.modules[scope];
6386
- if (!moduleOverrides) {
6387
- return projectThresholds;
7438
+ /**
7439
+ * Count H3/H4 headings under "Implementation" or "Implementation Order" sections.
7440
+ */
7441
+ extractPhaseCount(content) {
7442
+ const implMatch = content.match(/## Implementation\b[\s\S]*?(?=\n## |\n# |$)/i);
7443
+ if (!implMatch) return 0;
7444
+ const section = implMatch[0];
7445
+ const headings = section.match(/^#{3,4}\s+.+$/gm);
7446
+ return headings ? headings.length : 0;
7447
+ }
7448
+ // --- Private: Delta Computation ---
7449
+ computeDeltas(newFileCount, crossLayerCount, newDependencies, phaseCount) {
7450
+ const deltas = {};
7451
+ const c = this.coefficients;
7452
+ const addDelta = (category, value) => {
7453
+ deltas[category] = (deltas[category] ?? 0) + value;
7454
+ };
7455
+ if (newFileCount > 0) {
7456
+ addDelta("module-size", newFileCount * c.newFileModuleSize);
7457
+ addDelta("complexity", newFileCount * c.newFileComplexity);
7458
+ }
7459
+ if (crossLayerCount > 0) {
7460
+ addDelta("layer-violations", crossLayerCount * c.layerViolation);
7461
+ }
7462
+ if (newDependencies > 0) {
7463
+ addDelta("coupling", newDependencies * c.depCoupling);
7464
+ addDelta("dependency-depth", newDependencies * c.depDepth);
7465
+ }
7466
+ if (phaseCount > 1) {
7467
+ addDelta("complexity", (phaseCount - 1) * c.phaseComplexity);
7468
+ }
7469
+ return deltas;
6388
7470
  }
6389
- const merged = { ...projectThresholds };
6390
- for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
6391
- const projectValue = projectThresholds[category];
6392
- if (projectValue !== void 0 && typeof projectValue === "object" && !Array.isArray(projectValue) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
6393
- merged[category] = {
6394
- ...projectValue,
6395
- ...moduleValue
6396
- };
6397
- } else {
6398
- merged[category] = moduleValue;
7471
+ // --- Private: Config Loading ---
7472
+ loadLayerNames() {
7473
+ try {
7474
+ const configPath = path3.join(this.rootDir, "harness.config.json");
7475
+ const raw = fs6.readFileSync(configPath, "utf-8");
7476
+ const config = JSON.parse(raw);
7477
+ return (config.layers ?? []).map((l) => l.name);
7478
+ } catch {
7479
+ return [];
6399
7480
  }
6400
7481
  }
6401
- return merged;
6402
- }
6403
- var FailureEntrySchema = z3.object({
6404
- date: z3.string(),
6405
- skill: z3.string(),
6406
- type: z3.string(),
6407
- description: z3.string()
7482
+ escapeRegex(str) {
7483
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7484
+ }
7485
+ };
7486
+ var FailureEntrySchema = z5.object({
7487
+ date: z5.string(),
7488
+ skill: z5.string(),
7489
+ type: z5.string(),
7490
+ description: z5.string()
6408
7491
  });
6409
- var HandoffSchema = z3.object({
6410
- timestamp: z3.string(),
6411
- fromSkill: z3.string(),
6412
- phase: z3.string(),
6413
- summary: z3.string(),
6414
- completed: z3.array(z3.string()).default([]),
6415
- pending: z3.array(z3.string()).default([]),
6416
- concerns: z3.array(z3.string()).default([]),
6417
- decisions: z3.array(
6418
- z3.object({
6419
- what: z3.string(),
6420
- why: z3.string()
7492
+ var HandoffSchema = z5.object({
7493
+ timestamp: z5.string(),
7494
+ fromSkill: z5.string(),
7495
+ phase: z5.string(),
7496
+ summary: z5.string(),
7497
+ completed: z5.array(z5.string()).default([]),
7498
+ pending: z5.array(z5.string()).default([]),
7499
+ concerns: z5.array(z5.string()).default([]),
7500
+ decisions: z5.array(
7501
+ z5.object({
7502
+ what: z5.string(),
7503
+ why: z5.string()
6421
7504
  })
6422
7505
  ).default([]),
6423
- blockers: z3.array(z3.string()).default([]),
6424
- contextKeywords: z3.array(z3.string()).default([])
7506
+ blockers: z5.array(z5.string()).default([]),
7507
+ contextKeywords: z5.array(z5.string()).default([])
6425
7508
  });
6426
- var GateCheckSchema = z3.object({
6427
- name: z3.string(),
6428
- passed: z3.boolean(),
6429
- command: z3.string(),
6430
- output: z3.string().optional(),
6431
- duration: z3.number().optional()
7509
+ var GateCheckSchema = z5.object({
7510
+ name: z5.string(),
7511
+ passed: z5.boolean(),
7512
+ command: z5.string(),
7513
+ output: z5.string().optional(),
7514
+ duration: z5.number().optional()
6432
7515
  });
6433
- var GateResultSchema = z3.object({
6434
- passed: z3.boolean(),
6435
- checks: z3.array(GateCheckSchema)
7516
+ var GateResultSchema = z5.object({
7517
+ passed: z5.boolean(),
7518
+ checks: z5.array(GateCheckSchema)
6436
7519
  });
6437
- var GateConfigSchema = z3.object({
6438
- checks: z3.array(
6439
- z3.object({
6440
- name: z3.string(),
6441
- command: z3.string()
7520
+ var GateConfigSchema = z5.object({
7521
+ checks: z5.array(
7522
+ z5.object({
7523
+ name: z5.string(),
7524
+ command: z5.string()
6442
7525
  })
6443
7526
  ).optional(),
6444
- trace: z3.boolean().optional()
7527
+ trace: z5.boolean().optional()
6445
7528
  });
6446
- var HarnessStateSchema = z3.object({
6447
- schemaVersion: z3.literal(1),
6448
- position: z3.object({
6449
- phase: z3.string().optional(),
6450
- task: z3.string().optional()
7529
+ var HarnessStateSchema = z5.object({
7530
+ schemaVersion: z5.literal(1),
7531
+ position: z5.object({
7532
+ phase: z5.string().optional(),
7533
+ task: z5.string().optional()
6451
7534
  }).default({}),
6452
- decisions: z3.array(
6453
- z3.object({
6454
- date: z3.string(),
6455
- decision: z3.string(),
6456
- context: z3.string()
7535
+ decisions: z5.array(
7536
+ z5.object({
7537
+ date: z5.string(),
7538
+ decision: z5.string(),
7539
+ context: z5.string()
6457
7540
  })
6458
7541
  ).default([]),
6459
- blockers: z3.array(
6460
- z3.object({
6461
- id: z3.string(),
6462
- description: z3.string(),
6463
- status: z3.enum(["open", "resolved"])
7542
+ blockers: z5.array(
7543
+ z5.object({
7544
+ id: z5.string(),
7545
+ description: z5.string(),
7546
+ status: z5.enum(["open", "resolved"])
6464
7547
  })
6465
7548
  ).default([]),
6466
- progress: z3.record(z3.enum(["pending", "in_progress", "complete"])).default({}),
6467
- lastSession: z3.object({
6468
- date: z3.string(),
6469
- summary: z3.string(),
6470
- lastSkill: z3.string().optional(),
6471
- pendingTasks: z3.array(z3.string()).optional()
7549
+ progress: z5.record(z5.enum(["pending", "in_progress", "complete"])).default({}),
7550
+ lastSession: z5.object({
7551
+ date: z5.string(),
7552
+ summary: z5.string(),
7553
+ lastSkill: z5.string().optional(),
7554
+ pendingTasks: z5.array(z5.string()).optional()
6472
7555
  }).optional()
6473
7556
  });
6474
7557
  var DEFAULT_STATE = {
@@ -6478,16 +7561,16 @@ var DEFAULT_STATE = {
6478
7561
  blockers: [],
6479
7562
  progress: {}
6480
7563
  };
6481
- var StreamInfoSchema = z4.object({
6482
- name: z4.string(),
6483
- branch: z4.string().optional(),
6484
- createdAt: z4.string(),
6485
- lastActiveAt: z4.string()
7564
+ var StreamInfoSchema = z6.object({
7565
+ name: z6.string(),
7566
+ branch: z6.string().optional(),
7567
+ createdAt: z6.string(),
7568
+ lastActiveAt: z6.string()
6486
7569
  });
6487
- var StreamIndexSchema = z4.object({
6488
- schemaVersion: z4.literal(1),
6489
- activeStream: z4.string().nullable(),
6490
- streams: z4.record(StreamInfoSchema)
7570
+ var StreamIndexSchema = z6.object({
7571
+ schemaVersion: z6.literal(1),
7572
+ activeStream: z6.string().nullable(),
7573
+ streams: z6.record(StreamInfoSchema)
6491
7574
  });
6492
7575
  var DEFAULT_STREAM_INDEX = {
6493
7576
  schemaVersion: 1,
@@ -6511,10 +7594,10 @@ var EVENTS_FILE = "events.jsonl";
6511
7594
  var STREAMS_DIR = "streams";
6512
7595
  var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
6513
7596
  function streamsDir(projectPath) {
6514
- return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
7597
+ return path4.join(projectPath, HARNESS_DIR, STREAMS_DIR);
6515
7598
  }
6516
7599
  function indexPath(projectPath) {
6517
- return path2.join(streamsDir(projectPath), INDEX_FILE);
7600
+ return path4.join(streamsDir(projectPath), INDEX_FILE);
6518
7601
  }
6519
7602
  function validateStreamName(name) {
6520
7603
  if (!STREAM_NAME_REGEX.test(name)) {
@@ -6528,11 +7611,11 @@ function validateStreamName(name) {
6528
7611
  }
6529
7612
  async function loadStreamIndex(projectPath) {
6530
7613
  const idxPath = indexPath(projectPath);
6531
- if (!fs5.existsSync(idxPath)) {
7614
+ if (!fs7.existsSync(idxPath)) {
6532
7615
  return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
6533
7616
  }
6534
7617
  try {
6535
- const raw = fs5.readFileSync(idxPath, "utf-8");
7618
+ const raw = fs7.readFileSync(idxPath, "utf-8");
6536
7619
  const parsed = JSON.parse(raw);
6537
7620
  const result = StreamIndexSchema.safeParse(parsed);
6538
7621
  if (!result.success) {
@@ -6550,8 +7633,8 @@ async function loadStreamIndex(projectPath) {
6550
7633
  async function saveStreamIndex(projectPath, index) {
6551
7634
  const dir = streamsDir(projectPath);
6552
7635
  try {
6553
- fs5.mkdirSync(dir, { recursive: true });
6554
- fs5.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
7636
+ fs7.mkdirSync(dir, { recursive: true });
7637
+ fs7.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
6555
7638
  return Ok(void 0);
6556
7639
  } catch (error) {
6557
7640
  return Err(
@@ -6592,18 +7675,18 @@ async function resolveStreamPath(projectPath, options) {
6592
7675
  )
6593
7676
  );
6594
7677
  }
6595
- return Ok(path2.join(streamsDir(projectPath), options.stream));
7678
+ return Ok(path4.join(streamsDir(projectPath), options.stream));
6596
7679
  }
6597
7680
  const branch = getCurrentBranch(projectPath);
6598
7681
  if (branch && branch !== "main" && branch !== "master") {
6599
7682
  for (const [name, info] of Object.entries(index.streams)) {
6600
7683
  if (info.branch === branch) {
6601
- return Ok(path2.join(streamsDir(projectPath), name));
7684
+ return Ok(path4.join(streamsDir(projectPath), name));
6602
7685
  }
6603
7686
  }
6604
7687
  }
6605
7688
  if (index.activeStream && index.streams[index.activeStream]) {
6606
- return Ok(path2.join(streamsDir(projectPath), index.activeStream));
7689
+ return Ok(path4.join(streamsDir(projectPath), index.activeStream));
6607
7690
  }
6608
7691
  return Err(
6609
7692
  new Error(
@@ -6631,9 +7714,9 @@ async function createStream(projectPath, name, branch) {
6631
7714
  if (index.streams[name]) {
6632
7715
  return Err(new Error(`Stream '${name}' already exists`));
6633
7716
  }
6634
- const streamPath = path2.join(streamsDir(projectPath), name);
7717
+ const streamPath = path4.join(streamsDir(projectPath), name);
6635
7718
  try {
6636
- fs5.mkdirSync(streamPath, { recursive: true });
7719
+ fs7.mkdirSync(streamPath, { recursive: true });
6637
7720
  } catch (error) {
6638
7721
  return Err(
6639
7722
  new Error(
@@ -6674,12 +7757,12 @@ async function archiveStream(projectPath, name) {
6674
7757
  if (!index.streams[name]) {
6675
7758
  return Err(new Error(`Stream '${name}' not found`));
6676
7759
  }
6677
- const streamPath = path2.join(streamsDir(projectPath), name);
6678
- const archiveDir = path2.join(projectPath, HARNESS_DIR, "archive", "streams");
7760
+ const streamPath = path4.join(streamsDir(projectPath), name);
7761
+ const archiveDir = path4.join(projectPath, HARNESS_DIR, "archive", "streams");
6679
7762
  try {
6680
- fs5.mkdirSync(archiveDir, { recursive: true });
7763
+ fs7.mkdirSync(archiveDir, { recursive: true });
6681
7764
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6682
- fs5.renameSync(streamPath, path2.join(archiveDir, `${name}-${date}`));
7765
+ fs7.renameSync(streamPath, path4.join(archiveDir, `${name}-${date}`));
6683
7766
  } catch (error) {
6684
7767
  return Err(
6685
7768
  new Error(
@@ -6701,19 +7784,19 @@ function getStreamForBranch(index, branch) {
6701
7784
  }
6702
7785
  var STATE_FILES = ["state.json", "handoff.json", "learnings.md", "failures.md"];
6703
7786
  async function migrateToStreams(projectPath) {
6704
- const harnessDir = path2.join(projectPath, HARNESS_DIR);
6705
- if (fs5.existsSync(indexPath(projectPath))) {
7787
+ const harnessDir = path4.join(projectPath, HARNESS_DIR);
7788
+ if (fs7.existsSync(indexPath(projectPath))) {
6706
7789
  return Ok(void 0);
6707
7790
  }
6708
- const filesToMove = STATE_FILES.filter((f) => fs5.existsSync(path2.join(harnessDir, f)));
7791
+ const filesToMove = STATE_FILES.filter((f) => fs7.existsSync(path4.join(harnessDir, f)));
6709
7792
  if (filesToMove.length === 0) {
6710
7793
  return Ok(void 0);
6711
7794
  }
6712
- const defaultDir = path2.join(streamsDir(projectPath), "default");
7795
+ const defaultDir = path4.join(streamsDir(projectPath), "default");
6713
7796
  try {
6714
- fs5.mkdirSync(defaultDir, { recursive: true });
7797
+ fs7.mkdirSync(defaultDir, { recursive: true });
6715
7798
  for (const file of filesToMove) {
6716
- fs5.renameSync(path2.join(harnessDir, file), path2.join(defaultDir, file));
7799
+ fs7.renameSync(path4.join(harnessDir, file), path4.join(defaultDir, file));
6717
7800
  }
6718
7801
  } catch (error) {
6719
7802
  return Err(
@@ -6743,26 +7826,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
6743
7826
  new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
6744
7827
  );
6745
7828
  }
6746
- const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
7829
+ const sessionDir = path5.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
6747
7830
  if (options?.create) {
6748
- fs6.mkdirSync(sessionDir, { recursive: true });
7831
+ fs8.mkdirSync(sessionDir, { recursive: true });
6749
7832
  }
6750
7833
  return Ok(sessionDir);
6751
7834
  }
6752
7835
  function updateSessionIndex(projectPath, sessionSlug, description) {
6753
- const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
6754
- fs6.mkdirSync(sessionsDir, { recursive: true });
6755
- const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
7836
+ const sessionsDir = path5.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
7837
+ fs8.mkdirSync(sessionsDir, { recursive: true });
7838
+ const indexPath2 = path5.join(sessionsDir, SESSION_INDEX_FILE);
6756
7839
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6757
7840
  const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
6758
- if (!fs6.existsSync(indexPath2)) {
6759
- fs6.writeFileSync(indexPath2, `## Active Sessions
7841
+ if (!fs8.existsSync(indexPath2)) {
7842
+ fs8.writeFileSync(indexPath2, `## Active Sessions
6760
7843
 
6761
7844
  ${newLine}
6762
7845
  `);
6763
7846
  return;
6764
7847
  }
6765
- const content = fs6.readFileSync(indexPath2, "utf-8");
7848
+ const content = fs8.readFileSync(indexPath2, "utf-8");
6766
7849
  const lines = content.split("\n");
6767
7850
  const slugPattern = `- [${sessionSlug}]`;
6768
7851
  const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
@@ -6772,7 +7855,7 @@ ${newLine}
6772
7855
  const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
6773
7856
  lines.splice(lastNonEmpty + 1, 0, newLine);
6774
7857
  }
6775
- fs6.writeFileSync(indexPath2, lines.join("\n"));
7858
+ fs8.writeFileSync(indexPath2, lines.join("\n"));
6776
7859
  }
6777
7860
  var MAX_CACHE_ENTRIES = 8;
6778
7861
  function evictIfNeeded(map) {
@@ -6786,8 +7869,8 @@ async function getStateDir(projectPath, stream, session) {
6786
7869
  const sessionResult = resolveSessionDir(projectPath, session, { create: true });
6787
7870
  return sessionResult;
6788
7871
  }
6789
- const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
6790
- const hasStreams = fs7.existsSync(streamsIndexPath);
7872
+ const streamsIndexPath = path6.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
7873
+ const hasStreams = fs9.existsSync(streamsIndexPath);
6791
7874
  if (stream || hasStreams) {
6792
7875
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
6793
7876
  if (result.ok) {
@@ -6797,18 +7880,18 @@ async function getStateDir(projectPath, stream, session) {
6797
7880
  return result;
6798
7881
  }
6799
7882
  }
6800
- return Ok(path4.join(projectPath, HARNESS_DIR));
7883
+ return Ok(path6.join(projectPath, HARNESS_DIR));
6801
7884
  }
6802
7885
  async function loadState(projectPath, stream, session) {
6803
7886
  try {
6804
7887
  const dirResult = await getStateDir(projectPath, stream, session);
6805
7888
  if (!dirResult.ok) return dirResult;
6806
7889
  const stateDir = dirResult.value;
6807
- const statePath = path5.join(stateDir, STATE_FILE);
6808
- if (!fs8.existsSync(statePath)) {
7890
+ const statePath = path7.join(stateDir, STATE_FILE);
7891
+ if (!fs10.existsSync(statePath)) {
6809
7892
  return Ok({ ...DEFAULT_STATE });
6810
7893
  }
6811
- const raw = fs8.readFileSync(statePath, "utf-8");
7894
+ const raw = fs10.readFileSync(statePath, "utf-8");
6812
7895
  const parsed = JSON.parse(raw);
6813
7896
  const result = HarnessStateSchema.safeParse(parsed);
6814
7897
  if (!result.success) {
@@ -6826,9 +7909,9 @@ async function saveState(projectPath, state, stream, session) {
6826
7909
  const dirResult = await getStateDir(projectPath, stream, session);
6827
7910
  if (!dirResult.ok) return dirResult;
6828
7911
  const stateDir = dirResult.value;
6829
- const statePath = path5.join(stateDir, STATE_FILE);
6830
- fs8.mkdirSync(stateDir, { recursive: true });
6831
- fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
7912
+ const statePath = path7.join(stateDir, STATE_FILE);
7913
+ fs10.mkdirSync(stateDir, { recursive: true });
7914
+ fs10.writeFileSync(statePath, JSON.stringify(state, null, 2));
6832
7915
  return Ok(void 0);
6833
7916
  } catch (error) {
6834
7917
  return Err(
@@ -6836,7 +7919,7 @@ async function saveState(projectPath, state, stream, session) {
6836
7919
  );
6837
7920
  }
6838
7921
  }
6839
- function parseFrontmatter(line) {
7922
+ function parseFrontmatter2(line) {
6840
7923
  const match = line.match(/^<!--\s+hash:([a-f0-9]+)(?:\s+tags:([^\s]+))?\s+-->/);
6841
7924
  if (!match) return null;
6842
7925
  const hash = match[1];
@@ -6862,10 +7945,10 @@ function computeContentHash(text) {
6862
7945
  return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
6863
7946
  }
6864
7947
  function loadContentHashes(stateDir) {
6865
- const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
6866
- if (!fs9.existsSync(hashesPath)) return {};
7948
+ const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
7949
+ if (!fs11.existsSync(hashesPath)) return {};
6867
7950
  try {
6868
- const raw = fs9.readFileSync(hashesPath, "utf-8");
7951
+ const raw = fs11.readFileSync(hashesPath, "utf-8");
6869
7952
  const parsed = JSON.parse(raw);
6870
7953
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
6871
7954
  return parsed;
@@ -6874,13 +7957,13 @@ function loadContentHashes(stateDir) {
6874
7957
  }
6875
7958
  }
6876
7959
  function saveContentHashes(stateDir, index) {
6877
- const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
6878
- fs9.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
7960
+ const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
7961
+ fs11.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
6879
7962
  }
6880
7963
  function rebuildContentHashes(stateDir) {
6881
- const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
6882
- if (!fs9.existsSync(learningsPath)) return {};
6883
- const content = fs9.readFileSync(learningsPath, "utf-8");
7964
+ const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
7965
+ if (!fs11.existsSync(learningsPath)) return {};
7966
+ const content = fs11.readFileSync(learningsPath, "utf-8");
6884
7967
  const lines = content.split("\n");
6885
7968
  const index = {};
6886
7969
  for (let i = 0; i < lines.length; i++) {
@@ -6923,18 +8006,18 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
6923
8006
  const dirResult = await getStateDir(projectPath, stream, session);
6924
8007
  if (!dirResult.ok) return dirResult;
6925
8008
  const stateDir = dirResult.value;
6926
- const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
6927
- fs9.mkdirSync(stateDir, { recursive: true });
8009
+ const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8010
+ fs11.mkdirSync(stateDir, { recursive: true });
6928
8011
  const normalizedContent = normalizeLearningContent(learning);
6929
8012
  const contentHash = computeContentHash(normalizedContent);
6930
- const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
8013
+ const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
6931
8014
  let contentHashes;
6932
- if (fs9.existsSync(hashesPath)) {
8015
+ if (fs11.existsSync(hashesPath)) {
6933
8016
  contentHashes = loadContentHashes(stateDir);
6934
- if (Object.keys(contentHashes).length === 0 && fs9.existsSync(learningsPath)) {
8017
+ if (Object.keys(contentHashes).length === 0 && fs11.existsSync(learningsPath)) {
6935
8018
  contentHashes = rebuildContentHashes(stateDir);
6936
8019
  }
6937
- } else if (fs9.existsSync(learningsPath)) {
8020
+ } else if (fs11.existsSync(learningsPath)) {
6938
8021
  contentHashes = rebuildContentHashes(stateDir);
6939
8022
  } else {
6940
8023
  contentHashes = {};
@@ -6962,14 +8045,14 @@ ${frontmatter}
6962
8045
  ${bulletLine}
6963
8046
  `;
6964
8047
  let existingLineCount;
6965
- if (!fs9.existsSync(learningsPath)) {
6966
- fs9.writeFileSync(learningsPath, `# Learnings
8048
+ if (!fs11.existsSync(learningsPath)) {
8049
+ fs11.writeFileSync(learningsPath, `# Learnings
6967
8050
  ${entry}`);
6968
8051
  existingLineCount = 1;
6969
8052
  } else {
6970
- const existingContent = fs9.readFileSync(learningsPath, "utf-8");
8053
+ const existingContent = fs11.readFileSync(learningsPath, "utf-8");
6971
8054
  existingLineCount = existingContent.split("\n").length;
6972
- fs9.appendFileSync(learningsPath, entry);
8055
+ fs11.appendFileSync(learningsPath, entry);
6973
8056
  }
6974
8057
  const bulletLine_lineNum = existingLineCount + 2;
6975
8058
  contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
@@ -7083,18 +8166,18 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
7083
8166
  const dirResult = await getStateDir(projectPath, stream, session);
7084
8167
  if (!dirResult.ok) return dirResult;
7085
8168
  const stateDir = dirResult.value;
7086
- const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7087
- if (!fs9.existsSync(learningsPath)) {
8169
+ const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8170
+ if (!fs11.existsSync(learningsPath)) {
7088
8171
  return Ok([]);
7089
8172
  }
7090
- const content = fs9.readFileSync(learningsPath, "utf-8");
8173
+ const content = fs11.readFileSync(learningsPath, "utf-8");
7091
8174
  const lines = content.split("\n");
7092
8175
  const indexEntries = [];
7093
8176
  let pendingFrontmatter = null;
7094
8177
  let currentBlock = [];
7095
8178
  for (const line of lines) {
7096
8179
  if (line.startsWith("# ")) continue;
7097
- const fm = parseFrontmatter(line);
8180
+ const fm = parseFrontmatter2(line);
7098
8181
  if (fm) {
7099
8182
  pendingFrontmatter = fm;
7100
8183
  continue;
@@ -7145,18 +8228,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
7145
8228
  const dirResult = await getStateDir(projectPath, stream, session);
7146
8229
  if (!dirResult.ok) return dirResult;
7147
8230
  const stateDir = dirResult.value;
7148
- const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7149
- if (!fs9.existsSync(learningsPath)) {
8231
+ const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8232
+ if (!fs11.existsSync(learningsPath)) {
7150
8233
  return Ok([]);
7151
8234
  }
7152
- const stats = fs9.statSync(learningsPath);
8235
+ const stats = fs11.statSync(learningsPath);
7153
8236
  const cacheKey = learningsPath;
7154
8237
  const cached = learningsCacheMap.get(cacheKey);
7155
8238
  let entries;
7156
8239
  if (cached && cached.mtimeMs === stats.mtimeMs) {
7157
8240
  entries = cached.entries;
7158
8241
  } else {
7159
- const content = fs9.readFileSync(learningsPath, "utf-8");
8242
+ const content = fs11.readFileSync(learningsPath, "utf-8");
7160
8243
  const lines = content.split("\n");
7161
8244
  entries = [];
7162
8245
  let currentBlock = [];
@@ -7198,16 +8281,16 @@ async function archiveLearnings(projectPath, entries, stream) {
7198
8281
  const dirResult = await getStateDir(projectPath, stream);
7199
8282
  if (!dirResult.ok) return dirResult;
7200
8283
  const stateDir = dirResult.value;
7201
- const archiveDir = path6.join(stateDir, "learnings-archive");
7202
- fs9.mkdirSync(archiveDir, { recursive: true });
8284
+ const archiveDir = path8.join(stateDir, "learnings-archive");
8285
+ fs11.mkdirSync(archiveDir, { recursive: true });
7203
8286
  const now = /* @__PURE__ */ new Date();
7204
8287
  const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
7205
- const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
8288
+ const archivePath = path8.join(archiveDir, `${yearMonth}.md`);
7206
8289
  const archiveContent = entries.join("\n\n") + "\n";
7207
- if (fs9.existsSync(archivePath)) {
7208
- fs9.appendFileSync(archivePath, "\n" + archiveContent);
8290
+ if (fs11.existsSync(archivePath)) {
8291
+ fs11.appendFileSync(archivePath, "\n" + archiveContent);
7209
8292
  } else {
7210
- fs9.writeFileSync(archivePath, `# Learnings Archive
8293
+ fs11.writeFileSync(archivePath, `# Learnings Archive
7211
8294
 
7212
8295
  ${archiveContent}`);
7213
8296
  }
@@ -7225,8 +8308,8 @@ async function pruneLearnings(projectPath, stream) {
7225
8308
  const dirResult = await getStateDir(projectPath, stream);
7226
8309
  if (!dirResult.ok) return dirResult;
7227
8310
  const stateDir = dirResult.value;
7228
- const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7229
- if (!fs9.existsSync(learningsPath)) {
8311
+ const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
8312
+ if (!fs11.existsSync(learningsPath)) {
7230
8313
  return Ok({ kept: 0, archived: 0, patterns: [] });
7231
8314
  }
7232
8315
  const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
@@ -7257,7 +8340,7 @@ async function pruneLearnings(projectPath, stream) {
7257
8340
  if (!archiveResult.ok) return archiveResult;
7258
8341
  }
7259
8342
  const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
7260
- fs9.writeFileSync(learningsPath, newContent);
8343
+ fs11.writeFileSync(learningsPath, newContent);
7261
8344
  learningsCacheMap.delete(learningsPath);
7262
8345
  return Ok({
7263
8346
  kept: toKeep.length,
@@ -7302,19 +8385,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
7302
8385
  const dirResult = await getStateDir(projectPath, stream);
7303
8386
  if (!dirResult.ok) return dirResult;
7304
8387
  const stateDir = dirResult.value;
7305
- const globalPath = path6.join(stateDir, LEARNINGS_FILE);
7306
- const existingGlobal = fs9.existsSync(globalPath) ? fs9.readFileSync(globalPath, "utf-8") : "";
8388
+ const globalPath = path8.join(stateDir, LEARNINGS_FILE);
8389
+ const existingGlobal = fs11.existsSync(globalPath) ? fs11.readFileSync(globalPath, "utf-8") : "";
7307
8390
  const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
7308
8391
  if (newEntries.length === 0) {
7309
8392
  return Ok({ promoted: 0, skipped: skipped + toPromote.length });
7310
8393
  }
7311
8394
  const promotedContent = newEntries.join("\n\n") + "\n";
7312
8395
  if (!existingGlobal) {
7313
- fs9.writeFileSync(globalPath, `# Learnings
8396
+ fs11.writeFileSync(globalPath, `# Learnings
7314
8397
 
7315
8398
  ${promotedContent}`);
7316
8399
  } else {
7317
- fs9.appendFileSync(globalPath, "\n\n" + promotedContent);
8400
+ fs11.appendFileSync(globalPath, "\n\n" + promotedContent);
7318
8401
  }
7319
8402
  learningsCacheMap.delete(globalPath);
7320
8403
  return Ok({
@@ -7344,17 +8427,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
7344
8427
  const dirResult = await getStateDir(projectPath, stream, session);
7345
8428
  if (!dirResult.ok) return dirResult;
7346
8429
  const stateDir = dirResult.value;
7347
- const failuresPath = path7.join(stateDir, FAILURES_FILE);
7348
- fs10.mkdirSync(stateDir, { recursive: true });
8430
+ const failuresPath = path9.join(stateDir, FAILURES_FILE);
8431
+ fs12.mkdirSync(stateDir, { recursive: true });
7349
8432
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7350
8433
  const entry = `
7351
8434
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
7352
8435
  `;
7353
- if (!fs10.existsSync(failuresPath)) {
7354
- fs10.writeFileSync(failuresPath, `# Failures
8436
+ if (!fs12.existsSync(failuresPath)) {
8437
+ fs12.writeFileSync(failuresPath, `# Failures
7355
8438
  ${entry}`);
7356
8439
  } else {
7357
- fs10.appendFileSync(failuresPath, entry);
8440
+ fs12.appendFileSync(failuresPath, entry);
7358
8441
  }
7359
8442
  failuresCacheMap.delete(failuresPath);
7360
8443
  return Ok(void 0);
@@ -7371,17 +8454,17 @@ async function loadFailures(projectPath, stream, session) {
7371
8454
  const dirResult = await getStateDir(projectPath, stream, session);
7372
8455
  if (!dirResult.ok) return dirResult;
7373
8456
  const stateDir = dirResult.value;
7374
- const failuresPath = path7.join(stateDir, FAILURES_FILE);
7375
- if (!fs10.existsSync(failuresPath)) {
8457
+ const failuresPath = path9.join(stateDir, FAILURES_FILE);
8458
+ if (!fs12.existsSync(failuresPath)) {
7376
8459
  return Ok([]);
7377
8460
  }
7378
- const stats = fs10.statSync(failuresPath);
8461
+ const stats = fs12.statSync(failuresPath);
7379
8462
  const cacheKey = failuresPath;
7380
8463
  const cached = failuresCacheMap.get(cacheKey);
7381
8464
  if (cached && cached.mtimeMs === stats.mtimeMs) {
7382
8465
  return Ok(cached.entries);
7383
8466
  }
7384
- const content = fs10.readFileSync(failuresPath, "utf-8");
8467
+ const content = fs12.readFileSync(failuresPath, "utf-8");
7385
8468
  const entries = [];
7386
8469
  for (const line of content.split("\n")) {
7387
8470
  const match = line.match(FAILURE_LINE_REGEX);
@@ -7410,20 +8493,20 @@ async function archiveFailures(projectPath, stream, session) {
7410
8493
  const dirResult = await getStateDir(projectPath, stream, session);
7411
8494
  if (!dirResult.ok) return dirResult;
7412
8495
  const stateDir = dirResult.value;
7413
- const failuresPath = path7.join(stateDir, FAILURES_FILE);
7414
- if (!fs10.existsSync(failuresPath)) {
8496
+ const failuresPath = path9.join(stateDir, FAILURES_FILE);
8497
+ if (!fs12.existsSync(failuresPath)) {
7415
8498
  return Ok(void 0);
7416
8499
  }
7417
- const archiveDir = path7.join(stateDir, "archive");
7418
- fs10.mkdirSync(archiveDir, { recursive: true });
8500
+ const archiveDir = path9.join(stateDir, "archive");
8501
+ fs12.mkdirSync(archiveDir, { recursive: true });
7419
8502
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7420
8503
  let archiveName = `failures-${date}.md`;
7421
8504
  let counter = 2;
7422
- while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
8505
+ while (fs12.existsSync(path9.join(archiveDir, archiveName))) {
7423
8506
  archiveName = `failures-${date}-${counter}.md`;
7424
8507
  counter++;
7425
8508
  }
7426
- fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
8509
+ fs12.renameSync(failuresPath, path9.join(archiveDir, archiveName));
7427
8510
  failuresCacheMap.delete(failuresPath);
7428
8511
  return Ok(void 0);
7429
8512
  } catch (error) {
@@ -7439,9 +8522,9 @@ async function saveHandoff(projectPath, handoff, stream, session) {
7439
8522
  const dirResult = await getStateDir(projectPath, stream, session);
7440
8523
  if (!dirResult.ok) return dirResult;
7441
8524
  const stateDir = dirResult.value;
7442
- const handoffPath = path8.join(stateDir, HANDOFF_FILE);
7443
- fs11.mkdirSync(stateDir, { recursive: true });
7444
- fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
8525
+ const handoffPath = path10.join(stateDir, HANDOFF_FILE);
8526
+ fs13.mkdirSync(stateDir, { recursive: true });
8527
+ fs13.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
7445
8528
  return Ok(void 0);
7446
8529
  } catch (error) {
7447
8530
  return Err(
@@ -7454,11 +8537,11 @@ async function loadHandoff(projectPath, stream, session) {
7454
8537
  const dirResult = await getStateDir(projectPath, stream, session);
7455
8538
  if (!dirResult.ok) return dirResult;
7456
8539
  const stateDir = dirResult.value;
7457
- const handoffPath = path8.join(stateDir, HANDOFF_FILE);
7458
- if (!fs11.existsSync(handoffPath)) {
8540
+ const handoffPath = path10.join(stateDir, HANDOFF_FILE);
8541
+ if (!fs13.existsSync(handoffPath)) {
7459
8542
  return Ok(null);
7460
8543
  }
7461
- const raw = fs11.readFileSync(handoffPath, "utf-8");
8544
+ const raw = fs13.readFileSync(handoffPath, "utf-8");
7462
8545
  const parsed = JSON.parse(raw);
7463
8546
  const result = HandoffSchema.safeParse(parsed);
7464
8547
  if (!result.success) {
@@ -7473,28 +8556,28 @@ async function loadHandoff(projectPath, stream, session) {
7473
8556
  }
7474
8557
  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:.-]+$/;
7475
8558
  function loadChecksFromConfig(gateConfigPath) {
7476
- if (!fs12.existsSync(gateConfigPath)) return [];
7477
- const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
8559
+ if (!fs14.existsSync(gateConfigPath)) return [];
8560
+ const raw = JSON.parse(fs14.readFileSync(gateConfigPath, "utf-8"));
7478
8561
  const config = GateConfigSchema.safeParse(raw);
7479
8562
  if (config.success && config.data.checks) return config.data.checks;
7480
8563
  return [];
7481
8564
  }
7482
8565
  function discoverChecksFromProject(projectPath) {
7483
8566
  const checks = [];
7484
- const packageJsonPath = path9.join(projectPath, "package.json");
7485
- if (fs12.existsSync(packageJsonPath)) {
7486
- const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
8567
+ const packageJsonPath = path11.join(projectPath, "package.json");
8568
+ if (fs14.existsSync(packageJsonPath)) {
8569
+ const pkg = JSON.parse(fs14.readFileSync(packageJsonPath, "utf-8"));
7487
8570
  const scripts = pkg.scripts || {};
7488
8571
  if (scripts.test) checks.push({ name: "test", command: "npm test" });
7489
8572
  if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
7490
8573
  if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
7491
8574
  if (scripts.build) checks.push({ name: "build", command: "npm run build" });
7492
8575
  }
7493
- if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
8576
+ if (fs14.existsSync(path11.join(projectPath, "go.mod"))) {
7494
8577
  checks.push({ name: "test", command: "go test ./..." });
7495
8578
  checks.push({ name: "build", command: "go build ./..." });
7496
8579
  }
7497
- if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
8580
+ if (fs14.existsSync(path11.join(projectPath, "pyproject.toml")) || fs14.existsSync(path11.join(projectPath, "setup.py"))) {
7498
8581
  checks.push({ name: "test", command: "python -m pytest" });
7499
8582
  }
7500
8583
  return checks;
@@ -7534,8 +8617,8 @@ function executeCheck(check, projectPath) {
7534
8617
  }
7535
8618
  }
7536
8619
  async function runMechanicalGate(projectPath) {
7537
- const harnessDir = path9.join(projectPath, HARNESS_DIR);
7538
- const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
8620
+ const harnessDir = path11.join(projectPath, HARNESS_DIR);
8621
+ const gateConfigPath = path11.join(harnessDir, GATE_CONFIG_FILE);
7539
8622
  try {
7540
8623
  let checks = loadChecksFromConfig(gateConfigPath);
7541
8624
  if (checks.length === 0) {
@@ -7591,9 +8674,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
7591
8674
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
7592
8675
  if (!dirResult.ok) return dirResult;
7593
8676
  const sessionDir = dirResult.value;
7594
- const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
8677
+ const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
7595
8678
  const content = formatSummary(data);
7596
- fs13.writeFileSync(summaryPath, content);
8679
+ fs15.writeFileSync(summaryPath, content);
7597
8680
  const description = deriveIndexDescription(data);
7598
8681
  updateSessionIndex(projectPath, sessionSlug, description);
7599
8682
  return Ok(void 0);
@@ -7610,11 +8693,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
7610
8693
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7611
8694
  if (!dirResult.ok) return dirResult;
7612
8695
  const sessionDir = dirResult.value;
7613
- const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
7614
- if (!fs13.existsSync(summaryPath)) {
8696
+ const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
8697
+ if (!fs15.existsSync(summaryPath)) {
7615
8698
  return Ok(null);
7616
8699
  }
7617
- const content = fs13.readFileSync(summaryPath, "utf-8");
8700
+ const content = fs15.readFileSync(summaryPath, "utf-8");
7618
8701
  return Ok(content);
7619
8702
  } catch (error) {
7620
8703
  return Err(
@@ -7626,11 +8709,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
7626
8709
  }
7627
8710
  function listActiveSessions(projectPath) {
7628
8711
  try {
7629
- const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
7630
- if (!fs13.existsSync(indexPath2)) {
8712
+ const indexPath2 = path12.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
8713
+ if (!fs15.existsSync(indexPath2)) {
7631
8714
  return Ok(null);
7632
8715
  }
7633
- const content = fs13.readFileSync(indexPath2, "utf-8");
8716
+ const content = fs15.readFileSync(indexPath2, "utf-8");
7634
8717
  return Ok(content);
7635
8718
  } catch (error) {
7636
8719
  return Err(
@@ -7651,12 +8734,12 @@ async function loadSessionState(projectPath, sessionSlug) {
7651
8734
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7652
8735
  if (!dirResult.ok) return dirResult;
7653
8736
  const sessionDir = dirResult.value;
7654
- const filePath = path11.join(sessionDir, SESSION_STATE_FILE);
7655
- if (!fs14.existsSync(filePath)) {
8737
+ const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
8738
+ if (!fs16.existsSync(filePath)) {
7656
8739
  return Ok(emptySections());
7657
8740
  }
7658
8741
  try {
7659
- const raw = fs14.readFileSync(filePath, "utf-8");
8742
+ const raw = fs16.readFileSync(filePath, "utf-8");
7660
8743
  const parsed = JSON.parse(raw);
7661
8744
  const sections = emptySections();
7662
8745
  for (const name of SESSION_SECTION_NAMES) {
@@ -7677,9 +8760,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
7677
8760
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
7678
8761
  if (!dirResult.ok) return dirResult;
7679
8762
  const sessionDir = dirResult.value;
7680
- const filePath = path11.join(sessionDir, SESSION_STATE_FILE);
8763
+ const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
7681
8764
  try {
7682
- fs14.writeFileSync(filePath, JSON.stringify(sections, null, 2));
8765
+ fs16.writeFileSync(filePath, JSON.stringify(sections, null, 2));
7683
8766
  return Ok(void 0);
7684
8767
  } catch (error) {
7685
8768
  return Err(
@@ -7735,26 +8818,26 @@ async function archiveSession(projectPath, sessionSlug) {
7735
8818
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7736
8819
  if (!dirResult.ok) return dirResult;
7737
8820
  const sessionDir = dirResult.value;
7738
- if (!fs15.existsSync(sessionDir)) {
8821
+ if (!fs17.existsSync(sessionDir)) {
7739
8822
  return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
7740
8823
  }
7741
- const archiveBase = path12.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
8824
+ const archiveBase = path14.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
7742
8825
  try {
7743
- fs15.mkdirSync(archiveBase, { recursive: true });
8826
+ fs17.mkdirSync(archiveBase, { recursive: true });
7744
8827
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7745
8828
  let archiveName = `${sessionSlug}-${date}`;
7746
8829
  let counter = 1;
7747
- while (fs15.existsSync(path12.join(archiveBase, archiveName))) {
8830
+ while (fs17.existsSync(path14.join(archiveBase, archiveName))) {
7748
8831
  archiveName = `${sessionSlug}-${date}-${counter}`;
7749
8832
  counter++;
7750
8833
  }
7751
- const dest = path12.join(archiveBase, archiveName);
8834
+ const dest = path14.join(archiveBase, archiveName);
7752
8835
  try {
7753
- fs15.renameSync(sessionDir, dest);
8836
+ fs17.renameSync(sessionDir, dest);
7754
8837
  } catch (renameErr) {
7755
8838
  if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
7756
- fs15.cpSync(sessionDir, dest, { recursive: true });
7757
- fs15.rmSync(sessionDir, { recursive: true });
8839
+ fs17.cpSync(sessionDir, dest, { recursive: true });
8840
+ fs17.rmSync(sessionDir, { recursive: true });
7758
8841
  } else {
7759
8842
  throw renameErr;
7760
8843
  }
@@ -7768,15 +8851,15 @@ async function archiveSession(projectPath, sessionSlug) {
7768
8851
  );
7769
8852
  }
7770
8853
  }
7771
- var SkillEventSchema = z5.object({
7772
- timestamp: z5.string(),
7773
- skill: z5.string(),
7774
- session: z5.string().optional(),
7775
- type: z5.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
7776
- summary: z5.string(),
7777
- data: z5.record(z5.unknown()).optional(),
7778
- refs: z5.array(z5.string()).optional(),
7779
- contentHash: z5.string().optional()
8854
+ var SkillEventSchema = z7.object({
8855
+ timestamp: z7.string(),
8856
+ skill: z7.string(),
8857
+ session: z7.string().optional(),
8858
+ type: z7.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
8859
+ summary: z7.string(),
8860
+ data: z7.record(z7.unknown()).optional(),
8861
+ refs: z7.array(z7.string()).optional(),
8862
+ contentHash: z7.string().optional()
7780
8863
  });
7781
8864
  function computeEventHash(event, session) {
7782
8865
  const identity = `${event.skill}|${event.type}|${event.summary}|${session ?? ""}`;
@@ -7787,8 +8870,8 @@ function loadKnownHashes(eventsPath) {
7787
8870
  const cached = knownHashesCache.get(eventsPath);
7788
8871
  if (cached) return cached;
7789
8872
  const hashes = /* @__PURE__ */ new Set();
7790
- if (fs16.existsSync(eventsPath)) {
7791
- const content = fs16.readFileSync(eventsPath, "utf-8");
8873
+ if (fs18.existsSync(eventsPath)) {
8874
+ const content = fs18.readFileSync(eventsPath, "utf-8");
7792
8875
  const lines = content.split("\n").filter((line) => line.trim() !== "");
7793
8876
  for (const line of lines) {
7794
8877
  try {
@@ -7811,8 +8894,8 @@ async function emitEvent(projectPath, event, options) {
7811
8894
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
7812
8895
  if (!dirResult.ok) return dirResult;
7813
8896
  const stateDir = dirResult.value;
7814
- const eventsPath = path13.join(stateDir, EVENTS_FILE);
7815
- fs16.mkdirSync(stateDir, { recursive: true });
8897
+ const eventsPath = path15.join(stateDir, EVENTS_FILE);
8898
+ fs18.mkdirSync(stateDir, { recursive: true });
7816
8899
  const contentHash = computeEventHash(event, options?.session);
7817
8900
  const knownHashes = loadKnownHashes(eventsPath);
7818
8901
  if (knownHashes.has(contentHash)) {
@@ -7826,7 +8909,7 @@ async function emitEvent(projectPath, event, options) {
7826
8909
  if (options?.session) {
7827
8910
  fullEvent.session = options.session;
7828
8911
  }
7829
- fs16.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
8912
+ fs18.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
7830
8913
  knownHashes.add(contentHash);
7831
8914
  return Ok({ written: true });
7832
8915
  } catch (error) {
@@ -7840,11 +8923,11 @@ async function loadEvents(projectPath, options) {
7840
8923
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
7841
8924
  if (!dirResult.ok) return dirResult;
7842
8925
  const stateDir = dirResult.value;
7843
- const eventsPath = path13.join(stateDir, EVENTS_FILE);
7844
- if (!fs16.existsSync(eventsPath)) {
8926
+ const eventsPath = path15.join(stateDir, EVENTS_FILE);
8927
+ if (!fs18.existsSync(eventsPath)) {
7845
8928
  return Ok([]);
7846
8929
  }
7847
- const content = fs16.readFileSync(eventsPath, "utf-8");
8930
+ const content = fs18.readFileSync(eventsPath, "utf-8");
7848
8931
  const lines = content.split("\n").filter((line) => line.trim() !== "");
7849
8932
  const events = [];
7850
8933
  for (const line of lines) {
@@ -8084,19 +9167,19 @@ var DEFAULT_SECURITY_CONFIG = {
8084
9167
  rules: {},
8085
9168
  exclude: ["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]
8086
9169
  };
8087
- var RuleOverrideSchema = z6.enum(["off", "error", "warning", "info"]);
8088
- var SecurityConfigSchema = z6.object({
8089
- enabled: z6.boolean().default(true),
8090
- strict: z6.boolean().default(false),
8091
- rules: z6.record(z6.string(), RuleOverrideSchema).optional().default({}),
8092
- exclude: z6.array(z6.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
8093
- external: z6.object({
8094
- semgrep: z6.object({
8095
- enabled: z6.union([z6.literal("auto"), z6.boolean()]).default("auto"),
8096
- rulesets: z6.array(z6.string()).optional()
9170
+ var RuleOverrideSchema = z8.enum(["off", "error", "warning", "info"]);
9171
+ var SecurityConfigSchema = z8.object({
9172
+ enabled: z8.boolean().default(true),
9173
+ strict: z8.boolean().default(false),
9174
+ rules: z8.record(z8.string(), RuleOverrideSchema).optional().default({}),
9175
+ exclude: z8.array(z8.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
9176
+ external: z8.object({
9177
+ semgrep: z8.object({
9178
+ enabled: z8.union([z8.literal("auto"), z8.boolean()]).default("auto"),
9179
+ rulesets: z8.array(z8.string()).optional()
8097
9180
  }).optional(),
8098
- gitleaks: z6.object({
8099
- enabled: z6.union([z6.literal("auto"), z6.boolean()]).default("auto")
9181
+ gitleaks: z8.object({
9182
+ enabled: z8.union([z8.literal("auto"), z8.boolean()]).default("auto")
8100
9183
  }).optional()
8101
9184
  }).optional()
8102
9185
  });
@@ -8129,11 +9212,11 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
8129
9212
  }
8130
9213
  function detectStack(projectRoot) {
8131
9214
  const stacks = [];
8132
- const pkgJsonPath = path14.join(projectRoot, "package.json");
8133
- if (fs17.existsSync(pkgJsonPath)) {
9215
+ const pkgJsonPath = path16.join(projectRoot, "package.json");
9216
+ if (fs19.existsSync(pkgJsonPath)) {
8134
9217
  stacks.push("node");
8135
9218
  try {
8136
- const pkgJson = JSON.parse(fs17.readFileSync(pkgJsonPath, "utf-8"));
9219
+ const pkgJson = JSON.parse(fs19.readFileSync(pkgJsonPath, "utf-8"));
8137
9220
  const allDeps = {
8138
9221
  ...pkgJson.dependencies,
8139
9222
  ...pkgJson.devDependencies
@@ -8148,13 +9231,13 @@ function detectStack(projectRoot) {
8148
9231
  } catch {
8149
9232
  }
8150
9233
  }
8151
- const goModPath = path14.join(projectRoot, "go.mod");
8152
- if (fs17.existsSync(goModPath)) {
9234
+ const goModPath = path16.join(projectRoot, "go.mod");
9235
+ if (fs19.existsSync(goModPath)) {
8153
9236
  stacks.push("go");
8154
9237
  }
8155
- const requirementsPath = path14.join(projectRoot, "requirements.txt");
8156
- const pyprojectPath = path14.join(projectRoot, "pyproject.toml");
8157
- if (fs17.existsSync(requirementsPath) || fs17.existsSync(pyprojectPath)) {
9238
+ const requirementsPath = path16.join(projectRoot, "requirements.txt");
9239
+ const pyprojectPath = path16.join(projectRoot, "pyproject.toml");
9240
+ if (fs19.existsSync(requirementsPath) || fs19.existsSync(pyprojectPath)) {
8158
9241
  stacks.push("python");
8159
9242
  }
8160
9243
  return stacks;
@@ -8953,7 +10036,7 @@ var SecurityScanner = class {
8953
10036
  }
8954
10037
  async scanFile(filePath) {
8955
10038
  if (!this.config.enabled) return [];
8956
- const content = await fs18.readFile(filePath, "utf-8");
10039
+ const content = await fs20.readFile(filePath, "utf-8");
8957
10040
  return this.scanContentForFile(content, filePath, 1);
8958
10041
  }
8959
10042
  scanContentForFile(content, filePath, startLine = 1) {
@@ -9280,13 +10363,13 @@ var TAINT_DURATION_MS = 30 * 60 * 1e3;
9280
10363
  var DEFAULT_SESSION_ID = "default";
9281
10364
  function getTaintFilePath(projectRoot, sessionId) {
9282
10365
  const id = sessionId || DEFAULT_SESSION_ID;
9283
- return join21(projectRoot, ".harness", `session-taint-${id}.json`);
10366
+ return join242(projectRoot, ".harness", `session-taint-${id}.json`);
9284
10367
  }
9285
10368
  function readTaint(projectRoot, sessionId) {
9286
10369
  const filePath = getTaintFilePath(projectRoot, sessionId);
9287
10370
  let content;
9288
10371
  try {
9289
- content = readFileSync142(filePath, "utf8");
10372
+ content = readFileSync17(filePath, "utf8");
9290
10373
  } catch {
9291
10374
  return null;
9292
10375
  }
@@ -9330,8 +10413,8 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
9330
10413
  const id = sessionId || DEFAULT_SESSION_ID;
9331
10414
  const filePath = getTaintFilePath(projectRoot, id);
9332
10415
  const now = (/* @__PURE__ */ new Date()).toISOString();
9333
- const dir = dirname8(filePath);
9334
- mkdirSync11(dir, { recursive: true });
10416
+ const dir = dirname9(filePath);
10417
+ mkdirSync12(dir, { recursive: true });
9335
10418
  const existing = readTaint(projectRoot, id);
9336
10419
  const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
9337
10420
  const taintFindings = findings.map((f) => ({
@@ -9349,7 +10432,7 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
9349
10432
  severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
9350
10433
  findings: [...existing?.findings || [], ...taintFindings]
9351
10434
  };
9352
- writeFileSync11(filePath, JSON.stringify(state, null, 2) + "\n");
10435
+ writeFileSync12(filePath, JSON.stringify(state, null, 2) + "\n");
9353
10436
  return state;
9354
10437
  }
9355
10438
  function clearTaint(projectRoot, sessionId) {
@@ -9362,14 +10445,14 @@ function clearTaint(projectRoot, sessionId) {
9362
10445
  return 0;
9363
10446
  }
9364
10447
  }
9365
- const harnessDir = join21(projectRoot, ".harness");
10448
+ const harnessDir = join242(projectRoot, ".harness");
9366
10449
  let count = 0;
9367
10450
  try {
9368
10451
  const files = readdirSync3(harnessDir);
9369
10452
  for (const file of files) {
9370
10453
  if (file.startsWith("session-taint-") && file.endsWith(".json")) {
9371
10454
  try {
9372
- unlinkSync(join21(harnessDir, file));
10455
+ unlinkSync(join242(harnessDir, file));
9373
10456
  count++;
9374
10457
  } catch {
9375
10458
  }
@@ -9380,7 +10463,7 @@ function clearTaint(projectRoot, sessionId) {
9380
10463
  return count;
9381
10464
  }
9382
10465
  function listTaintedSessions(projectRoot) {
9383
- const harnessDir = join21(projectRoot, ".harness");
10466
+ const harnessDir = join242(projectRoot, ".harness");
9384
10467
  const sessions = [];
9385
10468
  try {
9386
10469
  const files = readdirSync3(harnessDir);
@@ -9454,11 +10537,12 @@ var ALL_CHECKS = [
9454
10537
  "security",
9455
10538
  "perf",
9456
10539
  "phase-gate",
9457
- "arch"
10540
+ "arch",
10541
+ "traceability"
9458
10542
  ];
9459
10543
  async function runValidateCheck(projectRoot, config) {
9460
10544
  const issues = [];
9461
- const agentsPath = path15.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10545
+ const agentsPath = path17.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9462
10546
  const result = await validateAgentsMap(agentsPath);
9463
10547
  if (!result.ok) {
9464
10548
  issues.push({ severity: "error", message: result.error.message });
@@ -9515,7 +10599,7 @@ async function runDepsCheck(projectRoot, config) {
9515
10599
  }
9516
10600
  async function runDocsCheck(projectRoot, config) {
9517
10601
  const issues = [];
9518
- const docsDir = path15.join(projectRoot, config.docsDir ?? "docs");
10602
+ const docsDir = path17.join(projectRoot, config.docsDir ?? "docs");
9519
10603
  const entropyConfig = config.entropy || {};
9520
10604
  const result = await checkDocCoverage("project", {
9521
10605
  docsDir,
@@ -9696,6 +10780,39 @@ async function runArchCheck(projectRoot, config) {
9696
10780
  }
9697
10781
  return issues;
9698
10782
  }
10783
+ async function runTraceabilityCheck(projectRoot, config) {
10784
+ const issues = [];
10785
+ const traceConfig = config.traceability || {};
10786
+ if (traceConfig.enabled === false) return issues;
10787
+ const graphDir = path17.join(projectRoot, ".harness", "graph");
10788
+ const store = new GraphStore();
10789
+ const loaded = await store.load(graphDir);
10790
+ if (!loaded) {
10791
+ return issues;
10792
+ }
10793
+ const results = queryTraceability(store);
10794
+ if (results.length === 0) return issues;
10795
+ const minCoverage = traceConfig.minCoverage ?? 0;
10796
+ const severity = traceConfig.severity ?? "warning";
10797
+ for (const result of results) {
10798
+ const pct = result.summary.coveragePercent;
10799
+ if (pct < minCoverage) {
10800
+ issues.push({
10801
+ severity,
10802
+ message: `Traceability coverage for "${result.featureName}" is ${pct}% (minimum: ${minCoverage}%)`
10803
+ });
10804
+ }
10805
+ for (const req of result.requirements) {
10806
+ if (req.status === "none") {
10807
+ issues.push({
10808
+ severity: "warning",
10809
+ message: `Requirement "${req.requirementName}" has no traced code or tests`
10810
+ });
10811
+ }
10812
+ }
10813
+ }
10814
+ return issues;
10815
+ }
9699
10816
  async function runSingleCheck(name, projectRoot, config) {
9700
10817
  const start = Date.now();
9701
10818
  const issues = [];
@@ -9725,6 +10842,9 @@ async function runSingleCheck(name, projectRoot, config) {
9725
10842
  case "arch":
9726
10843
  issues.push(...await runArchCheck(projectRoot, config));
9727
10844
  break;
10845
+ case "traceability":
10846
+ issues.push(...await runTraceabilityCheck(projectRoot, config));
10847
+ break;
9728
10848
  }
9729
10849
  } catch (error) {
9730
10850
  issues.push({
@@ -9802,7 +10922,7 @@ async function runMechanicalChecks(options) {
9802
10922
  };
9803
10923
  if (!skip.includes("validate")) {
9804
10924
  try {
9805
- const agentsPath = path16.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10925
+ const agentsPath = path18.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9806
10926
  const result = await validateAgentsMap(agentsPath);
9807
10927
  if (!result.ok) {
9808
10928
  statuses.validate = "fail";
@@ -9839,7 +10959,7 @@ async function runMechanicalChecks(options) {
9839
10959
  statuses.validate = "fail";
9840
10960
  findings.push({
9841
10961
  tool: "validate",
9842
- file: path16.join(projectRoot, "AGENTS.md"),
10962
+ file: path18.join(projectRoot, "AGENTS.md"),
9843
10963
  message: err instanceof Error ? err.message : String(err),
9844
10964
  severity: "error"
9845
10965
  });
@@ -9903,7 +11023,7 @@ async function runMechanicalChecks(options) {
9903
11023
  (async () => {
9904
11024
  const localFindings = [];
9905
11025
  try {
9906
- const docsDir = path16.join(projectRoot, config.docsDir ?? "docs");
11026
+ const docsDir = path18.join(projectRoot, config.docsDir ?? "docs");
9907
11027
  const result = await checkDocCoverage("project", { docsDir });
9908
11028
  if (!result.ok) {
9909
11029
  statuses["check-docs"] = "warn";
@@ -9930,7 +11050,7 @@ async function runMechanicalChecks(options) {
9930
11050
  statuses["check-docs"] = "warn";
9931
11051
  localFindings.push({
9932
11052
  tool: "check-docs",
9933
- file: path16.join(projectRoot, "docs"),
11053
+ file: path18.join(projectRoot, "docs"),
9934
11054
  message: err instanceof Error ? err.message : String(err),
9935
11055
  severity: "warning"
9936
11056
  });
@@ -10079,18 +11199,18 @@ function computeContextBudget(diffLines) {
10079
11199
  return diffLines;
10080
11200
  }
10081
11201
  function isWithinProject(absPath, projectRoot) {
10082
- const resolvedRoot = path17.resolve(projectRoot) + path17.sep;
10083
- const resolvedPath = path17.resolve(absPath);
10084
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path17.resolve(projectRoot);
11202
+ const resolvedRoot = path19.resolve(projectRoot) + path19.sep;
11203
+ const resolvedPath = path19.resolve(absPath);
11204
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path19.resolve(projectRoot);
10085
11205
  }
10086
11206
  async function readContextFile(projectRoot, filePath, reason) {
10087
- const absPath = path17.isAbsolute(filePath) ? filePath : path17.join(projectRoot, filePath);
11207
+ const absPath = path19.isAbsolute(filePath) ? filePath : path19.join(projectRoot, filePath);
10088
11208
  if (!isWithinProject(absPath, projectRoot)) return null;
10089
11209
  const result = await readFileContent(absPath);
10090
11210
  if (!result.ok) return null;
10091
11211
  const content = result.value;
10092
11212
  const lines = content.split("\n").length;
10093
- const relPath = path17.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
11213
+ const relPath = path19.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
10094
11214
  return { path: relPath, content, reason, lines };
10095
11215
  }
10096
11216
  function extractImportSources2(content) {
@@ -10105,18 +11225,18 @@ function extractImportSources2(content) {
10105
11225
  }
10106
11226
  async function resolveImportPath2(projectRoot, fromFile, importSource) {
10107
11227
  if (!importSource.startsWith(".")) return null;
10108
- const fromDir = path17.dirname(path17.join(projectRoot, fromFile));
10109
- const basePath = path17.resolve(fromDir, importSource);
11228
+ const fromDir = path19.dirname(path19.join(projectRoot, fromFile));
11229
+ const basePath = path19.resolve(fromDir, importSource);
10110
11230
  if (!isWithinProject(basePath, projectRoot)) return null;
10111
11231
  const relBase = relativePosix(projectRoot, basePath);
10112
11232
  const candidates = [
10113
11233
  relBase + ".ts",
10114
11234
  relBase + ".tsx",
10115
11235
  relBase + ".mts",
10116
- path17.join(relBase, "index.ts")
11236
+ path19.join(relBase, "index.ts")
10117
11237
  ];
10118
11238
  for (const candidate of candidates) {
10119
- const absCandidate = path17.join(projectRoot, candidate);
11239
+ const absCandidate = path19.join(projectRoot, candidate);
10120
11240
  if (await fileExists(absCandidate)) {
10121
11241
  return candidate;
10122
11242
  }
@@ -10124,7 +11244,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
10124
11244
  return null;
10125
11245
  }
10126
11246
  async function findTestFiles(projectRoot, sourceFile) {
10127
- const baseName = path17.basename(sourceFile, path17.extname(sourceFile));
11247
+ const baseName = path19.basename(sourceFile, path19.extname(sourceFile));
10128
11248
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
10129
11249
  const results = await findFiles(pattern, projectRoot);
10130
11250
  return results.map((f) => relativePosix(projectRoot, f));
@@ -10937,7 +12057,7 @@ function normalizePath(filePath, projectRoot) {
10937
12057
  let normalized = filePath;
10938
12058
  normalized = normalized.replace(/\\/g, "/");
10939
12059
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
10940
- if (path18.isAbsolute(normalized)) {
12060
+ if (path20.isAbsolute(normalized)) {
10941
12061
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
10942
12062
  if (normalized.startsWith(root)) {
10943
12063
  normalized = normalized.slice(root.length);
@@ -10962,12 +12082,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
10962
12082
  while ((match = importRegex.exec(content)) !== null) {
10963
12083
  const importPath = match[1];
10964
12084
  if (!importPath.startsWith(".")) continue;
10965
- const dir = path18.dirname(current.file);
10966
- let resolved = path18.join(dir, importPath).replace(/\\/g, "/");
12085
+ const dir = path20.dirname(current.file);
12086
+ let resolved = path20.join(dir, importPath).replace(/\\/g, "/");
10967
12087
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
10968
12088
  resolved += ".ts";
10969
12089
  }
10970
- resolved = path18.normalize(resolved).replace(/\\/g, "/");
12090
+ resolved = path20.normalize(resolved).replace(/\\/g, "/");
10971
12091
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
10972
12092
  queue.push({ file: resolved, depth: current.depth + 1 });
10973
12093
  }
@@ -10984,7 +12104,7 @@ async function validateFindings(options) {
10984
12104
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
10985
12105
  continue;
10986
12106
  }
10987
- const absoluteFile = path18.isAbsolute(finding.file) ? finding.file : path18.join(projectRoot, finding.file).replace(/\\/g, "/");
12107
+ const absoluteFile = path20.isAbsolute(finding.file) ? finding.file : path20.join(projectRoot, finding.file).replace(/\\/g, "/");
10988
12108
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
10989
12109
  continue;
10990
12110
  }
@@ -11580,200 +12700,6 @@ async function runReviewPipeline(options) {
11580
12700
  ...evidenceCoverage != null ? { evidenceCoverage } : {}
11581
12701
  };
11582
12702
  }
11583
- var VALID_STATUSES = /* @__PURE__ */ new Set([
11584
- "backlog",
11585
- "planned",
11586
- "in-progress",
11587
- "done",
11588
- "blocked"
11589
- ]);
11590
- var EM_DASH = "\u2014";
11591
- var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
11592
- function parseRoadmap(markdown) {
11593
- const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
11594
- if (!fmMatch) {
11595
- return Err(new Error("Missing or malformed YAML frontmatter"));
11596
- }
11597
- const fmResult = parseFrontmatter2(fmMatch[1]);
11598
- if (!fmResult.ok) return fmResult;
11599
- const body = markdown.slice(fmMatch[0].length);
11600
- const milestonesResult = parseMilestones(body);
11601
- if (!milestonesResult.ok) return milestonesResult;
11602
- const historyResult = parseAssignmentHistory(body);
11603
- if (!historyResult.ok) return historyResult;
11604
- return Ok({
11605
- frontmatter: fmResult.value,
11606
- milestones: milestonesResult.value,
11607
- assignmentHistory: historyResult.value
11608
- });
11609
- }
11610
- function parseFrontmatter2(raw) {
11611
- const lines = raw.split("\n");
11612
- const map = /* @__PURE__ */ new Map();
11613
- for (const line of lines) {
11614
- const idx = line.indexOf(":");
11615
- if (idx === -1) continue;
11616
- const key = line.slice(0, idx).trim();
11617
- const val = line.slice(idx + 1).trim();
11618
- map.set(key, val);
11619
- }
11620
- const project = map.get("project");
11621
- const versionStr = map.get("version");
11622
- const lastSynced = map.get("last_synced");
11623
- const lastManualEdit = map.get("last_manual_edit");
11624
- const created = map.get("created");
11625
- const updated = map.get("updated");
11626
- if (!project || !versionStr || !lastSynced || !lastManualEdit) {
11627
- return Err(
11628
- new Error(
11629
- "Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
11630
- )
11631
- );
11632
- }
11633
- const version = parseInt(versionStr, 10);
11634
- if (isNaN(version)) {
11635
- return Err(new Error("Frontmatter version must be a number"));
11636
- }
11637
- const fm = { project, version, lastSynced, lastManualEdit };
11638
- if (created) fm.created = created;
11639
- if (updated) fm.updated = updated;
11640
- return Ok(fm);
11641
- }
11642
- function parseMilestones(body) {
11643
- const milestones = [];
11644
- const h2Pattern = /^## (.+)$/gm;
11645
- const h2Matches = [];
11646
- let match;
11647
- let bodyEnd = body.length;
11648
- while ((match = h2Pattern.exec(body)) !== null) {
11649
- if (match[1] === "Assignment History") {
11650
- bodyEnd = match.index;
11651
- break;
11652
- }
11653
- h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
11654
- }
11655
- for (let i = 0; i < h2Matches.length; i++) {
11656
- const h2 = h2Matches[i];
11657
- const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : bodyEnd;
11658
- const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
11659
- const isBacklog = h2.heading === "Backlog";
11660
- const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
11661
- const featuresResult = parseFeatures(sectionBody);
11662
- if (!featuresResult.ok) return featuresResult;
11663
- milestones.push({
11664
- name: milestoneName,
11665
- isBacklog,
11666
- features: featuresResult.value
11667
- });
11668
- }
11669
- return Ok(milestones);
11670
- }
11671
- function parseFeatures(sectionBody) {
11672
- const features = [];
11673
- const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
11674
- const h3Matches = [];
11675
- let match;
11676
- while ((match = h3Pattern.exec(sectionBody)) !== null) {
11677
- h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
11678
- }
11679
- for (let i = 0; i < h3Matches.length; i++) {
11680
- const h3 = h3Matches[i];
11681
- const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
11682
- const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
11683
- const featureResult = parseFeatureFields(h3.name, featureBody);
11684
- if (!featureResult.ok) return featureResult;
11685
- features.push(featureResult.value);
11686
- }
11687
- return Ok(features);
11688
- }
11689
- function extractFieldMap(body) {
11690
- const fieldMap = /* @__PURE__ */ new Map();
11691
- const fieldPattern = /^- \*\*(.+?):\*\* (.+)$/gm;
11692
- let match;
11693
- while ((match = fieldPattern.exec(body)) !== null) {
11694
- fieldMap.set(match[1], match[2]);
11695
- }
11696
- return fieldMap;
11697
- }
11698
- function parseListField(fieldMap, ...keys) {
11699
- let raw = EM_DASH;
11700
- for (const key of keys) {
11701
- const val = fieldMap.get(key);
11702
- if (val !== void 0) {
11703
- raw = val;
11704
- break;
11705
- }
11706
- }
11707
- if (raw === EM_DASH || raw === "none") return [];
11708
- return raw.split(",").map((s) => s.trim());
11709
- }
11710
- function parseFeatureFields(name, body) {
11711
- const fieldMap = extractFieldMap(body);
11712
- const statusRaw = fieldMap.get("Status");
11713
- if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
11714
- return Err(
11715
- new Error(
11716
- `Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
11717
- )
11718
- );
11719
- }
11720
- const specRaw = fieldMap.get("Spec") ?? EM_DASH;
11721
- const plans = parseListField(fieldMap, "Plans", "Plan");
11722
- const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
11723
- const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
11724
- const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
11725
- const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
11726
- if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
11727
- return Err(
11728
- new Error(
11729
- `Feature "${name}" has invalid priority: "${priorityRaw}". Valid priorities: ${[...VALID_PRIORITIES].join(", ")}`
11730
- )
11731
- );
11732
- }
11733
- return Ok({
11734
- name,
11735
- status: statusRaw,
11736
- spec: specRaw === EM_DASH ? null : specRaw,
11737
- plans,
11738
- blockedBy,
11739
- summary: fieldMap.get("Summary") ?? "",
11740
- assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
11741
- priority: priorityRaw === EM_DASH ? null : priorityRaw,
11742
- externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
11743
- });
11744
- }
11745
- function parseAssignmentHistory(body) {
11746
- const historyMatch = body.match(/^## Assignment History\s*\n/m);
11747
- if (!historyMatch || historyMatch.index === void 0) return Ok([]);
11748
- const historyStart = historyMatch.index + historyMatch[0].length;
11749
- const rawHistoryBody = body.slice(historyStart);
11750
- const nextH2 = rawHistoryBody.search(/^## /m);
11751
- const historyBody = nextH2 === -1 ? rawHistoryBody : rawHistoryBody.slice(0, nextH2);
11752
- const records = [];
11753
- const lines = historyBody.split("\n");
11754
- let pastHeader = false;
11755
- for (const line of lines) {
11756
- const trimmed = line.trim();
11757
- if (!trimmed.startsWith("|")) continue;
11758
- if (!pastHeader) {
11759
- if (trimmed.match(/^\|[-\s|]+\|$/)) {
11760
- pastHeader = true;
11761
- }
11762
- continue;
11763
- }
11764
- const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
11765
- if (cells.length < 4) continue;
11766
- const action = cells[2];
11767
- if (!["assigned", "completed", "unassigned"].includes(action)) continue;
11768
- records.push({
11769
- feature: cells[0],
11770
- assignee: cells[1],
11771
- action,
11772
- date: cells[3]
11773
- });
11774
- }
11775
- return Ok(records);
11776
- }
11777
12703
  var EM_DASH2 = "\u2014";
11778
12704
  function serializeRoadmap(roadmap) {
11779
12705
  const lines = [];
@@ -11865,10 +12791,10 @@ function inferStatus(feature, projectPath, allFeatures) {
11865
12791
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
11866
12792
  const useRootState = featuresWithPlans.length <= 1;
11867
12793
  if (useRootState) {
11868
- const rootStatePath = path19.join(projectPath, ".harness", "state.json");
11869
- if (fs19.existsSync(rootStatePath)) {
12794
+ const rootStatePath = path21.join(projectPath, ".harness", "state.json");
12795
+ if (fs21.existsSync(rootStatePath)) {
11870
12796
  try {
11871
- const raw = fs19.readFileSync(rootStatePath, "utf-8");
12797
+ const raw = fs21.readFileSync(rootStatePath, "utf-8");
11872
12798
  const state = JSON.parse(raw);
11873
12799
  if (state.progress) {
11874
12800
  for (const status of Object.values(state.progress)) {
@@ -11879,16 +12805,16 @@ function inferStatus(feature, projectPath, allFeatures) {
11879
12805
  }
11880
12806
  }
11881
12807
  }
11882
- const sessionsDir = path19.join(projectPath, ".harness", "sessions");
11883
- if (fs19.existsSync(sessionsDir)) {
12808
+ const sessionsDir = path21.join(projectPath, ".harness", "sessions");
12809
+ if (fs21.existsSync(sessionsDir)) {
11884
12810
  try {
11885
- const sessionDirs = fs19.readdirSync(sessionsDir, { withFileTypes: true });
12811
+ const sessionDirs = fs21.readdirSync(sessionsDir, { withFileTypes: true });
11886
12812
  for (const entry of sessionDirs) {
11887
12813
  if (!entry.isDirectory()) continue;
11888
- const autopilotPath = path19.join(sessionsDir, entry.name, "autopilot-state.json");
11889
- if (!fs19.existsSync(autopilotPath)) continue;
12814
+ const autopilotPath = path21.join(sessionsDir, entry.name, "autopilot-state.json");
12815
+ if (!fs21.existsSync(autopilotPath)) continue;
11890
12816
  try {
11891
- const raw = fs19.readFileSync(autopilotPath, "utf-8");
12817
+ const raw = fs21.readFileSync(autopilotPath, "utf-8");
11892
12818
  const autopilot = JSON.parse(raw);
11893
12819
  if (!autopilot.phases) continue;
11894
12820
  const linkedPhases = autopilot.phases.filter(
@@ -12368,7 +13294,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
12368
13294
  });
12369
13295
  await previousSync;
12370
13296
  try {
12371
- const raw = fs20.readFileSync(roadmapPath, "utf-8");
13297
+ const raw = fs22.readFileSync(roadmapPath, "utf-8");
12372
13298
  const parseResult = parseRoadmap(raw);
12373
13299
  if (!parseResult.ok) {
12374
13300
  return {
@@ -12379,7 +13305,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
12379
13305
  const roadmap = parseResult.value;
12380
13306
  const pushResult = await syncToExternal(roadmap, adapter, config);
12381
13307
  const pullResult = await syncFromExternal(roadmap, adapter, config, options);
12382
- fs20.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13308
+ fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
12383
13309
  return {
12384
13310
  created: pushResult.created,
12385
13311
  updated: pushResult.updated,
@@ -12505,28 +13431,28 @@ function assignFeature(roadmap, feature, assignee, date) {
12505
13431
  date
12506
13432
  });
12507
13433
  }
12508
- var InteractionTypeSchema = z7.enum(["question", "confirmation", "transition"]);
12509
- var QuestionSchema = z7.object({
12510
- text: z7.string(),
12511
- options: z7.array(z7.string()).optional(),
12512
- default: z7.string().optional()
13434
+ var InteractionTypeSchema = z9.enum(["question", "confirmation", "transition"]);
13435
+ var QuestionSchema = z9.object({
13436
+ text: z9.string(),
13437
+ options: z9.array(z9.string()).optional(),
13438
+ default: z9.string().optional()
12513
13439
  });
12514
- var ConfirmationSchema = z7.object({
12515
- text: z7.string(),
12516
- context: z7.string()
13440
+ var ConfirmationSchema = z9.object({
13441
+ text: z9.string(),
13442
+ context: z9.string()
12517
13443
  });
12518
- var TransitionSchema = z7.object({
12519
- completedPhase: z7.string(),
12520
- suggestedNext: z7.string(),
12521
- reason: z7.string(),
12522
- artifacts: z7.array(z7.string()),
12523
- requiresConfirmation: z7.boolean(),
12524
- summary: z7.string()
13444
+ var TransitionSchema = z9.object({
13445
+ completedPhase: z9.string(),
13446
+ suggestedNext: z9.string(),
13447
+ reason: z9.string(),
13448
+ artifacts: z9.array(z9.string()),
13449
+ requiresConfirmation: z9.boolean(),
13450
+ summary: z9.string()
12525
13451
  });
12526
- var EmitInteractionInputSchema = z7.object({
12527
- path: z7.string(),
13452
+ var EmitInteractionInputSchema = z9.object({
13453
+ path: z9.string(),
12528
13454
  type: InteractionTypeSchema,
12529
- stream: z7.string().optional(),
13455
+ stream: z9.string().optional(),
12530
13456
  question: QuestionSchema.optional(),
12531
13457
  confirmation: ConfirmationSchema.optional(),
12532
13458
  transition: TransitionSchema.optional()
@@ -12537,10 +13463,10 @@ var ProjectScanner = class {
12537
13463
  }
12538
13464
  rootDir;
12539
13465
  async scan() {
12540
- let projectName = path20.basename(this.rootDir);
13466
+ let projectName = path22.basename(this.rootDir);
12541
13467
  try {
12542
- const pkgPath = path20.join(this.rootDir, "package.json");
12543
- const pkgRaw = await fs21.readFile(pkgPath, "utf-8");
13468
+ const pkgPath = path22.join(this.rootDir, "package.json");
13469
+ const pkgRaw = await fs23.readFile(pkgPath, "utf-8");
12544
13470
  const pkg = JSON.parse(pkgRaw);
12545
13471
  if (pkg.name) projectName = pkg.name;
12546
13472
  } catch {
@@ -12653,13 +13579,13 @@ var BlueprintGenerator = class {
12653
13579
  styles: STYLES,
12654
13580
  scripts: SCRIPTS
12655
13581
  });
12656
- await fs22.mkdir(options.outputDir, { recursive: true });
12657
- await fs22.writeFile(path21.join(options.outputDir, "index.html"), html);
13582
+ await fs24.mkdir(options.outputDir, { recursive: true });
13583
+ await fs24.writeFile(path23.join(options.outputDir, "index.html"), html);
12658
13584
  }
12659
13585
  };
12660
13586
  function getStatePath() {
12661
13587
  const home = process.env["HOME"] || os.homedir();
12662
- return path22.join(home, ".harness", "update-check.json");
13588
+ return path24.join(home, ".harness", "update-check.json");
12663
13589
  }
12664
13590
  function isUpdateCheckEnabled(configInterval) {
12665
13591
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -12672,7 +13598,7 @@ function shouldRunCheck(state, intervalMs) {
12672
13598
  }
12673
13599
  function readCheckState() {
12674
13600
  try {
12675
- const raw = fs23.readFileSync(getStatePath(), "utf-8");
13601
+ const raw = fs25.readFileSync(getStatePath(), "utf-8");
12676
13602
  const parsed = JSON.parse(raw);
12677
13603
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
12678
13604
  const state = parsed;
@@ -12689,7 +13615,7 @@ function readCheckState() {
12689
13615
  }
12690
13616
  function spawnBackgroundCheck(currentVersion) {
12691
13617
  const statePath = getStatePath();
12692
- const stateDir = path22.dirname(statePath);
13618
+ const stateDir = path24.dirname(statePath);
12693
13619
  const script = `
12694
13620
  const { execSync } = require('child_process');
12695
13621
  const fs = require('fs');
@@ -12773,9 +13699,9 @@ async function resolveWasmPath(grammarName) {
12773
13699
  const { createRequire } = await import("module");
12774
13700
  const require2 = createRequire(import.meta.url ?? __filename);
12775
13701
  const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
12776
- const path26 = await import("path");
12777
- const pkgDir = path26.dirname(pkgPath);
12778
- return path26.join(pkgDir, "out", `${grammarName}.wasm`);
13702
+ const path28 = await import("path");
13703
+ const pkgDir = path28.dirname(pkgPath);
13704
+ return path28.join(pkgDir, "out", `${grammarName}.wasm`);
12779
13705
  }
12780
13706
  async function loadLanguage(lang) {
12781
13707
  const grammarName = GRAMMAR_MAP[lang];
@@ -13217,14 +14143,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
13217
14143
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
13218
14144
  var STALENESS_WARNING_DAYS = 7;
13219
14145
  function getCachePath(projectRoot) {
13220
- return path23.join(projectRoot, ".harness", "cache", "pricing.json");
14146
+ return path25.join(projectRoot, ".harness", "cache", "pricing.json");
13221
14147
  }
13222
14148
  function getStalenessMarkerPath(projectRoot) {
13223
- return path23.join(projectRoot, ".harness", "cache", "staleness-marker.json");
14149
+ return path25.join(projectRoot, ".harness", "cache", "staleness-marker.json");
13224
14150
  }
13225
14151
  async function readDiskCache(projectRoot) {
13226
14152
  try {
13227
- const raw = await fs24.readFile(getCachePath(projectRoot), "utf-8");
14153
+ const raw = await fs26.readFile(getCachePath(projectRoot), "utf-8");
13228
14154
  return JSON.parse(raw);
13229
14155
  } catch {
13230
14156
  return null;
@@ -13232,8 +14158,8 @@ async function readDiskCache(projectRoot) {
13232
14158
  }
13233
14159
  async function writeDiskCache(projectRoot, data) {
13234
14160
  const cachePath = getCachePath(projectRoot);
13235
- await fs24.mkdir(path23.dirname(cachePath), { recursive: true });
13236
- await fs24.writeFile(cachePath, JSON.stringify(data, null, 2));
14161
+ await fs26.mkdir(path25.dirname(cachePath), { recursive: true });
14162
+ await fs26.writeFile(cachePath, JSON.stringify(data, null, 2));
13237
14163
  }
13238
14164
  async function fetchFromNetwork() {
13239
14165
  try {
@@ -13260,7 +14186,7 @@ function loadFallbackDataset() {
13260
14186
  async function checkAndWarnStaleness(projectRoot) {
13261
14187
  const markerPath = getStalenessMarkerPath(projectRoot);
13262
14188
  try {
13263
- const raw = await fs24.readFile(markerPath, "utf-8");
14189
+ const raw = await fs26.readFile(markerPath, "utf-8");
13264
14190
  const marker = JSON.parse(raw);
13265
14191
  const firstUse = new Date(marker.firstFallbackUse).getTime();
13266
14192
  const now = Date.now();
@@ -13272,8 +14198,8 @@ async function checkAndWarnStaleness(projectRoot) {
13272
14198
  }
13273
14199
  } catch {
13274
14200
  try {
13275
- await fs24.mkdir(path23.dirname(markerPath), { recursive: true });
13276
- await fs24.writeFile(
14201
+ await fs26.mkdir(path25.dirname(markerPath), { recursive: true });
14202
+ await fs26.writeFile(
13277
14203
  markerPath,
13278
14204
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
13279
14205
  );
@@ -13283,7 +14209,7 @@ async function checkAndWarnStaleness(projectRoot) {
13283
14209
  }
13284
14210
  async function clearStalenessMarker(projectRoot) {
13285
14211
  try {
13286
- await fs24.unlink(getStalenessMarkerPath(projectRoot));
14212
+ await fs26.unlink(getStalenessMarkerPath(projectRoot));
13287
14213
  } catch {
13288
14214
  }
13289
14215
  }
@@ -13485,10 +14411,10 @@ function parseLine(line, lineNumber) {
13485
14411
  return record;
13486
14412
  }
13487
14413
  function readCostRecords(projectRoot) {
13488
- const costsFile = path24.join(projectRoot, ".harness", "metrics", "costs.jsonl");
14414
+ const costsFile = path26.join(projectRoot, ".harness", "metrics", "costs.jsonl");
13489
14415
  let raw;
13490
14416
  try {
13491
- raw = fs25.readFileSync(costsFile, "utf-8");
14417
+ raw = fs27.readFileSync(costsFile, "utf-8");
13492
14418
  } catch {
13493
14419
  return [];
13494
14420
  }
@@ -13535,7 +14461,7 @@ function parseCCLine(line, filePath, lineNumber) {
13535
14461
  entry = JSON.parse(line);
13536
14462
  } catch {
13537
14463
  console.warn(
13538
- `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path25.basename(filePath)}`
14464
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path27.basename(filePath)}`
13539
14465
  );
13540
14466
  return null;
13541
14467
  }
@@ -13549,7 +14475,7 @@ function parseCCLine(line, filePath, lineNumber) {
13549
14475
  function readCCFile(filePath) {
13550
14476
  let raw;
13551
14477
  try {
13552
- raw = fs26.readFileSync(filePath, "utf-8");
14478
+ raw = fs28.readFileSync(filePath, "utf-8");
13553
14479
  } catch {
13554
14480
  return [];
13555
14481
  }
@@ -13571,10 +14497,10 @@ function readCCFile(filePath) {
13571
14497
  }
13572
14498
  function parseCCRecords() {
13573
14499
  const homeDir = process.env.HOME ?? os2.homedir();
13574
- const projectsDir = path25.join(homeDir, ".claude", "projects");
14500
+ const projectsDir = path27.join(homeDir, ".claude", "projects");
13575
14501
  let projectDirs;
13576
14502
  try {
13577
- projectDirs = fs26.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path25.join(projectsDir, d.name));
14503
+ projectDirs = fs28.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path27.join(projectsDir, d.name));
13578
14504
  } catch {
13579
14505
  return [];
13580
14506
  }
@@ -13582,7 +14508,7 @@ function parseCCRecords() {
13582
14508
  for (const dir of projectDirs) {
13583
14509
  let files;
13584
14510
  try {
13585
- files = fs26.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path25.join(dir, f));
14511
+ files = fs28.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path27.join(dir, f));
13586
14512
  } catch {
13587
14513
  continue;
13588
14514
  }
@@ -13711,6 +14637,33 @@ export {
13711
14637
  syncConstraintNodes,
13712
14638
  detectStaleConstraints,
13713
14639
  resolveThresholds2 as resolveThresholds,
14640
+ CategorySnapshotSchema,
14641
+ TimelineSnapshotSchema,
14642
+ TimelineFileSchema,
14643
+ TrendLineSchema,
14644
+ TrendResultSchema,
14645
+ DEFAULT_STABILITY_THRESHOLDS,
14646
+ TimelineManager,
14647
+ ConfidenceTierSchema,
14648
+ RegressionResultSchema,
14649
+ DirectionSchema,
14650
+ CategoryForecastSchema,
14651
+ SpecImpactSignalsSchema,
14652
+ SpecImpactEstimateSchema,
14653
+ ContributingFeatureSchema,
14654
+ AdjustedForecastSchema,
14655
+ PredictionWarningSchema,
14656
+ StabilityForecastSchema,
14657
+ PredictionResultSchema,
14658
+ PredictionOptionsSchema,
14659
+ weightedLinearRegression,
14660
+ applyRecencyWeights,
14661
+ projectValue,
14662
+ weeksUntilThreshold,
14663
+ classifyConfidence,
14664
+ parseRoadmap,
14665
+ PredictionEngine,
14666
+ SpecImpactEstimator,
13714
14667
  FailureEntrySchema,
13715
14668
  HandoffSchema,
13716
14669
  GateResultSchema,
@@ -13734,7 +14687,7 @@ export {
13734
14687
  updateSessionIndex,
13735
14688
  loadState,
13736
14689
  saveState,
13737
- parseFrontmatter,
14690
+ parseFrontmatter2,
13738
14691
  extractIndexEntry,
13739
14692
  clearLearningsCache,
13740
14693
  appendLearning,
@@ -13839,7 +14792,6 @@ export {
13839
14792
  checkEvidenceCoverage,
13840
14793
  tagUncitedFindings,
13841
14794
  runReviewPipeline,
13842
- parseRoadmap,
13843
14795
  serializeRoadmap,
13844
14796
  STATUS_RANK,
13845
14797
  isRegression,