@harness-engineering/cli 2.2.0 → 2.3.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 (51) hide show
  1. package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +13 -7
  2. package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +13 -7
  3. package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +13 -7
  4. package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +13 -7
  5. package/dist/{agents-md-L4X4MOWT.js → agents-md-N7YBQPE3.js} +2 -2
  6. package/dist/{architecture-WJRIYQ45.js → architecture-PP7WERSV.js} +3 -3
  7. package/dist/{assess-project-PHC2HO3V.js → assess-project-WXFCJQTX.js} +1 -1
  8. package/dist/bin/harness-mcp.js +13 -13
  9. package/dist/bin/harness.js +22 -22
  10. package/dist/{check-phase-gate-646KY67H.js → check-phase-gate-R3KQFBAH.js} +3 -3
  11. package/dist/{chunk-GOOTYTTV.js → chunk-35FI23SO.js} +855 -428
  12. package/dist/{chunk-L2IDE7WS.js → chunk-3LPOTTPX.js} +255 -89
  13. package/dist/{chunk-K4WRQTEF.js → chunk-4DWOMGUM.js} +1661 -434
  14. package/dist/{chunk-W4AFGFFB.js → chunk-4NFV5UZO.js} +2 -2
  15. package/dist/{chunk-224XKVLP.js → chunk-5KTTKXQN.js} +1 -1
  16. package/dist/{chunk-KV7A5OZT.js → chunk-5PPV4WYY.js} +3 -3
  17. package/dist/{chunk-CPYL6LJR.js → chunk-6CHNY3NJ.js} +1 -1
  18. package/dist/{chunk-VKDBM3RX.js → chunk-7PGYJO3L.js} +3 -3
  19. package/dist/{chunk-E7W3YOXL.js → chunk-D2LZUJLP.js} +2 -2
  20. package/dist/{chunk-4HKVZO7R.js → chunk-DLBMYLYR.js} +1 -1
  21. package/dist/{chunk-YWD6WXXM.js → chunk-EJPR5VCT.js} +6 -6
  22. package/dist/{chunk-2ZURBWEV.js → chunk-GTXUUGFG.js} +3 -3
  23. package/dist/{chunk-OJMO4JBQ.js → chunk-GUIX2TMZ.js} +1 -1
  24. package/dist/{chunk-TNZYQTEE.js → chunk-KUVPSUG4.js} +1 -1
  25. package/dist/{chunk-YLTRFZAT.js → chunk-KWD6FF7M.js} +6 -6
  26. package/dist/{chunk-VJQLUS5I.js → chunk-L3CEN42B.js} +1 -1
  27. package/dist/{chunk-YLOAQZLA.js → chunk-LM4F5KBK.js} +2 -2
  28. package/dist/{chunk-JPWICLXQ.js → chunk-MKPRB4PL.js} +1 -1
  29. package/dist/{chunk-2QPHCR22.js → chunk-MT2MMBUA.js} +5 -5
  30. package/dist/{chunk-QTUPHTTK.js → chunk-NP2ZI6E3.js} +1 -1
  31. package/dist/{chunk-LO7ZLV7S.js → chunk-PW3U4YJV.js} +4 -2
  32. package/dist/{chunk-36X7TRUI.js → chunk-YEBUFRTT.js} +2 -2
  33. package/dist/{ci-workflow-TOS5X527.js → ci-workflow-GFCXFJ4G.js} +2 -2
  34. package/dist/{dist-6RT2AVYU.js → dist-I4LWG3IN.js} +23 -1
  35. package/dist/{docs-HOJMQYDZ.js → docs-UXKDKPJF.js} +3 -3
  36. package/dist/{engine-D6UYH6DB.js → engine-A63FRDLF.js} +2 -2
  37. package/dist/{entropy-LZ4IDGZR.js → entropy-IFIP4ZP4.js} +2 -2
  38. package/dist/{feedback-WV7EQVK7.js → feedback-W36DSJQA.js} +1 -1
  39. package/dist/{generate-agent-definitions-GFNXJCBY.js → generate-agent-definitions-F4YMKQLP.js} +3 -3
  40. package/dist/hooks/block-no-verify.js +19 -1
  41. package/dist/index.d.ts +15 -9
  42. package/dist/index.js +22 -22
  43. package/dist/{loader-Z23JTXIZ.js → loader-4UAOGUSS.js} +2 -2
  44. package/dist/{mcp-JDJGELGD.js → mcp-YW7PLXVQ.js} +13 -13
  45. package/dist/{performance-7ATLHTQU.js → performance-AZ5UYPOQ.js} +3 -3
  46. package/dist/{review-pipeline-COBGGXHJ.js → review-pipeline-OLQVGBWJ.js} +2 -2
  47. package/dist/{runtime-KCBNQUXK.js → runtime-GG3U5HH5.js} +2 -2
  48. package/dist/{security-MAPRIFQT.js → security-MYTRFSR7.js} +1 -1
  49. package/dist/{validate-74XTQR7Q.js → validate-QI4V4JY6.js} +3 -3
  50. package/dist/{validate-cross-check-MI5MZ7MB.js → validate-cross-check-XRSSAXJP.js} +2 -2
  51. package/package.json +4 -4
@@ -19,7 +19,7 @@ import {
19
19
  __export
20
20
  } from "./chunk-KFQGP6VL.js";
21
21
 
22
- // ../core/dist/chunk-CE225WRK.mjs
22
+ // ../core/dist/chunk-NC4RPKD4.mjs
23
23
  import { z } from "zod";
24
24
  import { createHash } from "crypto";
25
25
  import { minimatch } from "minimatch";
@@ -69,17 +69,17 @@ function resolveFileToLayer(file, layers) {
69
69
  }
70
70
  return void 0;
71
71
  }
72
- async function fileExists(path42) {
72
+ async function fileExists(path46) {
73
73
  try {
74
- await accessAsync(path42, constants.F_OK);
74
+ await accessAsync(path46, constants.F_OK);
75
75
  return true;
76
76
  } catch {
77
77
  return false;
78
78
  }
79
79
  }
80
- async function readFileContent(path42) {
80
+ async function readFileContent(path46) {
81
81
  try {
82
- const content = await readFileAsync(path42, "utf-8");
82
+ const content = await readFileAsync(path46, "utf-8");
83
83
  return Ok(content);
84
84
  } catch (error) {
85
85
  return Err(error);
@@ -485,9 +485,9 @@ async function resolveWasmPath(grammarName) {
485
485
  const { createRequire } = await import("module");
486
486
  const require2 = createRequire(import.meta.url ?? __filename);
487
487
  const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
488
- const path42 = await import("path");
489
- const pkgDir = path42.dirname(pkgPath);
490
- return path42.join(pkgDir, "out", `${grammarName}.wasm`);
488
+ const path46 = await import("path");
489
+ const pkgDir = path46.dirname(pkgPath);
490
+ return path46.join(pkgDir, "out", `${grammarName}.wasm`);
491
491
  }
492
492
  async function loadLanguage(lang) {
493
493
  const grammarName = GRAMMAR_MAP[lang];
@@ -1745,11 +1745,16 @@ async function toHaveMaxDepDepth(received, maxDepth) {
1745
1745
  message: () => pass ? `Expected dependency depth in '${received.scope}' to exceed ${maxDepth} but it was ${maxActual}` : `Module '${received.scope}' has dependency depth ${maxActual} (limit: ${maxDepth})`
1746
1746
  };
1747
1747
  }
1748
- var ArchMetricCategorySchema, ViolationSchema, MetricResultSchema, CategoryBaselineSchema, ArchBaselineSchema, CategoryRegressionSchema, ArchDiffResultSchema, ThresholdConfigSchema, ArchConfigSchema, ConstraintRuleSchema, ViolationSnapshotSchema, ViolationHistorySchema, EmergenceConfidenceSchema, EmergentConstraintSuggestionSchema, EmergenceResultSchema, accessAsync, readFileAsync, EXTENSION_BY_LANG, JS_EXT_FALLBACKS, CircularDepsCollector, TypeScriptParser, EXTENSION_MAP, parserCache, initialized, GRAMMAR_MAP, TOP_LEVEL_TYPES, METHOD_TYPES, IDENTIFIER_TYPES, NAME_EXTRACTORS, pythonStrategy, goStrategy, RUST_USE_ARG_TYPES, RUST_PUB_ITEM_TYPES, rustStrategy, JAVA_IMPORT_TYPES, JAVA_EXPORT_TYPES, javaStrategy, STRATEGIES, TreeSitterParser, ParserRegistry, defaultRegistry, LayerViolationCollector, ArchBaselineManager, DEFAULT_THRESHOLDS, FUNCTION_PATTERNS, DECISION_PATTERNS, ComplexityCollector, DEFAULT_THRESHOLDS2, CouplingCollector, ForbiddenImportCollector, ModuleSizeCollector, DepDepthCollector, defaultCollectors, archMatchers;
1749
- var init_chunk_CE225WRK = __esm({
1750
- "../core/dist/chunk-CE225WRK.mjs"() {
1748
+ var __defProp, __export2, ArchMetricCategorySchema, ViolationSchema, MetricResultSchema, CategoryBaselineSchema, ArchBaselineSchema, CategoryRegressionSchema, ArchDiffResultSchema, ThresholdConfigSchema, ArchConfigSchema, ConstraintRuleSchema, ViolationSnapshotSchema, ViolationHistorySchema, EmergenceConfidenceSchema, EmergentConstraintSuggestionSchema, EmergenceResultSchema, accessAsync, readFileAsync, EXTENSION_BY_LANG, JS_EXT_FALLBACKS, CircularDepsCollector, TypeScriptParser, EXTENSION_MAP, parserCache, initialized, GRAMMAR_MAP, TOP_LEVEL_TYPES, METHOD_TYPES, IDENTIFIER_TYPES, NAME_EXTRACTORS, pythonStrategy, goStrategy, RUST_USE_ARG_TYPES, RUST_PUB_ITEM_TYPES, rustStrategy, JAVA_IMPORT_TYPES, JAVA_EXPORT_TYPES, javaStrategy, STRATEGIES, TreeSitterParser, ParserRegistry, defaultRegistry, LayerViolationCollector, ArchBaselineManager, DEFAULT_THRESHOLDS, FUNCTION_PATTERNS, DECISION_PATTERNS, ComplexityCollector, DEFAULT_THRESHOLDS2, CouplingCollector, ForbiddenImportCollector, ModuleSizeCollector, DepDepthCollector, defaultCollectors, archMatchers;
1749
+ var init_chunk_NC4RPKD4 = __esm({
1750
+ "../core/dist/chunk-NC4RPKD4.mjs"() {
1751
1751
  "use strict";
1752
1752
  init_dist();
1753
+ __defProp = Object.defineProperty;
1754
+ __export2 = (target, all) => {
1755
+ for (var name in all)
1756
+ __defProp(target, name, { get: all[name], enumerable: true });
1757
+ };
1753
1758
  ArchMetricCategorySchema = z.enum([
1754
1759
  "circular-deps",
1755
1760
  "layer-violations",
@@ -1926,11 +1931,11 @@ var init_chunk_CE225WRK = __esm({
1926
1931
  TypeScriptParser = class {
1927
1932
  name = "typescript";
1928
1933
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1929
- async parseFile(path42) {
1930
- const contentResult = await readFileContent(path42);
1934
+ async parseFile(path46) {
1935
+ const contentResult = await readFileContent(path46);
1931
1936
  if (!contentResult.ok) {
1932
1937
  return Err(
1933
- createParseError("NOT_FOUND", `File not found: ${path42}`, { path: path42 }, [
1938
+ createParseError("NOT_FOUND", `File not found: ${path46}`, { path: path46 }, [
1934
1939
  "Check that the file exists",
1935
1940
  "Verify the path is correct"
1936
1941
  ])
@@ -1940,7 +1945,7 @@ var init_chunk_CE225WRK = __esm({
1940
1945
  const ast = parse(contentResult.value, {
1941
1946
  loc: true,
1942
1947
  range: true,
1943
- jsx: path42.endsWith(".tsx"),
1948
+ jsx: path46.endsWith(".tsx"),
1944
1949
  errorOnUnknownASTType: false
1945
1950
  });
1946
1951
  return Ok({
@@ -1951,7 +1956,7 @@ var init_chunk_CE225WRK = __esm({
1951
1956
  } catch (e) {
1952
1957
  const error = e;
1953
1958
  return Err(
1954
- createParseError("SYNTAX_ERROR", `Failed to parse ${path42}: ${error.message}`, { path: path42 }, [
1959
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path46}: ${error.message}`, { path: path46 }, [
1955
1960
  "Check for syntax errors in the file",
1956
1961
  "Ensure valid TypeScript syntax"
1957
1962
  ])
@@ -2266,11 +2271,11 @@ var init_chunk_CE225WRK = __esm({
2266
2271
  this.extensions = extensions;
2267
2272
  this.strategy = strategy;
2268
2273
  }
2269
- async parseFile(path42) {
2270
- const contentResult = await readFileContent(path42);
2274
+ async parseFile(path46) {
2275
+ const contentResult = await readFileContent(path46);
2271
2276
  if (!contentResult.ok) {
2272
2277
  return Err(
2273
- createParseError("NOT_FOUND", `File not found: ${path42}`, { path: path42 }, [
2278
+ createParseError("NOT_FOUND", `File not found: ${path46}`, { path: path46 }, [
2274
2279
  "Check that the file exists"
2275
2280
  ])
2276
2281
  );
@@ -2286,7 +2291,7 @@ var init_chunk_CE225WRK = __esm({
2286
2291
  } catch (e) {
2287
2292
  const error = e;
2288
2293
  return Err(
2289
- createParseError("SYNTAX_ERROR", `Failed to parse ${path42}: ${error.message}`, { path: path42 }, [
2294
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path46}: ${error.message}`, { path: path46 }, [
2290
2295
  "Check for syntax errors in the file"
2291
2296
  ])
2292
2297
  );
@@ -2854,6 +2859,7 @@ __export(dist_exports, {
2854
2859
  CompoundLockHeldError: () => CompoundLockHeldError,
2855
2860
  ConfidenceTierSchema: () => ConfidenceTierSchema,
2856
2861
  ConfirmationSchema: () => ConfirmationSchema,
2862
+ ConflictError: () => ConflictError,
2857
2863
  ConsoleSink: () => ConsoleSink,
2858
2864
  ConstraintRuleSchema: () => ConstraintRuleSchema,
2859
2865
  ContributingFeatureSchema: () => ContributingFeatureSchema,
@@ -2873,6 +2879,7 @@ __export(dist_exports, {
2873
2879
  DepDepthCollector: () => DepDepthCollector,
2874
2880
  DirectionSchema: () => DirectionSchema,
2875
2881
  EMPTY_SUPPLY_CHAIN: () => EMPTY_SUPPLY_CHAIN,
2882
+ ETagStore: () => ETagStore,
2876
2883
  EVIDENCE_SATURATION: () => EVIDENCE_SATURATION,
2877
2884
  EXTENSION_MAP: () => EXTENSION_MAP,
2878
2885
  EmergenceConfidenceSchema: () => EmergenceConfidenceSchema,
@@ -3044,6 +3051,7 @@ __export(dist_exports, {
3044
3051
  createRegionMap: () => createRegionMap,
3045
3052
  createSelfReview: () => createSelfReview,
3046
3053
  createStream: () => createStream,
3054
+ createTrackerClient: () => createTrackerClient,
3047
3055
  crossReferenceUndocumentedFixes: () => crossReferenceUndocumentedFixes,
3048
3056
  cryptoRules: () => cryptoRules,
3049
3057
  deduplicateCleanupFindings: () => deduplicateCleanupFindings,
@@ -3104,6 +3112,7 @@ __export(dist_exports, {
3104
3112
  getParser: () => getParser,
3105
3113
  getPhaseCategories: () => getPhaseCategories,
3106
3114
  getPulseAdapter: () => getPulseAdapter,
3115
+ getRoadmapMode: () => getRoadmapMode,
3107
3116
  getStreamForBranch: () => getStreamForBranch,
3108
3117
  getTaintFilePath: () => getTaintFilePath,
3109
3118
  getTrustLevel: () => getTrustLevel,
@@ -3130,17 +3139,21 @@ __export(dist_exports, {
3130
3139
  loadHandoff: () => loadHandoff,
3131
3140
  loadIndexEntries: () => loadIndexEntries,
3132
3141
  loadPricingData: () => loadPricingData,
3142
+ loadProjectRoadmapMode: () => loadProjectRoadmapMode,
3133
3143
  loadRelevantLearnings: () => loadRelevantLearnings,
3134
3144
  loadSessionSummary: () => loadSessionSummary,
3135
3145
  loadState: () => loadState,
3136
3146
  loadStreamIndex: () => loadStreamIndex,
3147
+ loadTrackerClientConfigFromProject: () => loadTrackerClientConfigFromProject,
3137
3148
  loadTrackerSyncConfig: () => loadTrackerSyncConfig,
3138
3149
  logAgentAction: () => logAgentAction,
3150
+ makeTrackerConflictBody: () => makeTrackerConflictBody,
3139
3151
  mapInjectionFindings: () => mapInjectionFindings,
3140
3152
  mapSecurityFindings: () => mapSecurityFindings,
3141
3153
  mapSecuritySeverity: () => mapSecuritySeverity,
3142
3154
  markProtectedFindings: () => markProtectedFindings,
3143
3155
  mcpRules: () => mcpRules,
3156
+ migrate: () => migrate_exports,
3144
3157
  migrateToStreams: () => migrateToStreams,
3145
3158
  networkRules: () => networkRules,
3146
3159
  nodeRules: () => nodeRules,
@@ -3212,6 +3225,8 @@ __export(dist_exports, {
3212
3225
  scanForInjection: () => scanForInjection,
3213
3226
  scopeContext: () => scopeContext,
3214
3227
  scoreRoadmapCandidates: () => scoreRoadmapCandidates,
3228
+ scoreRoadmapCandidatesFileLess: () => scoreRoadmapCandidatesFileLess,
3229
+ scoreRoadmapCandidatesForMode: () => scoreRoadmapCandidatesForMode,
3215
3230
  searchSymbols: () => searchSymbols,
3216
3231
  secretRules: () => secretRules,
3217
3232
  securityFindingId: () => securityFindingId,
@@ -3249,6 +3264,7 @@ __export(dist_exports, {
3249
3264
  validateKnowledgeMap: () => validateKnowledgeMap,
3250
3265
  validatePatternConfig: () => validatePatternConfig,
3251
3266
  validatePulseConfig: () => validatePulseConfig,
3267
+ validateRoadmapMode: () => validateRoadmapMode,
3252
3268
  validateSolutionsDir: () => validateSolutionsDir,
3253
3269
  violationId: () => violationId,
3254
3270
  weeksUntilThreshold: () => weeksUntilThreshold,
@@ -3294,63 +3310,63 @@ import { existsSync as existsSync9 } from "fs";
3294
3310
  import { join as join11 } from "path";
3295
3311
  import { existsSync as existsSync10 } from "fs";
3296
3312
  import { join as join12 } from "path";
3313
+ import * as fs3 from "fs";
3314
+ import * as path3 from "path";
3297
3315
  import { minimatch as minimatch2 } from "minimatch";
3298
3316
  import { basename as basename2 } from "path";
3299
- import { join as join13, basename as basename3 } from "path";
3317
+ import { join as join14, basename as basename3 } from "path";
3300
3318
  import { basename as basename4, dirname as dirname42 } from "path";
3301
3319
  import { z as z3 } from "zod";
3302
- import * as fs3 from "fs/promises";
3303
3320
  import * as fs4 from "fs/promises";
3304
- import { join as join14, resolve as resolve32 } from "path";
3321
+ import * as fs5 from "fs/promises";
3322
+ import { join as join15, resolve as resolve42 } from "path";
3305
3323
  import { minimatch as minimatch22 } from "minimatch";
3306
- import { dirname as dirname5, resolve as resolve4 } from "path";
3307
- import { dirname as dirname6, extname as extname2, resolve as resolve5 } from "path";
3324
+ import { dirname as dirname5, resolve as resolve5 } from "path";
3325
+ import { dirname as dirname6, extname as extname2, resolve as resolve6 } from "path";
3308
3326
  import { minimatch as minimatch3 } from "minimatch";
3309
3327
  import { readdirSync, statSync as statSync2 } from "fs";
3310
- import { join as join15 } from "path";
3311
- import * as fs5 from "fs";
3328
+ import { join as join16 } from "path";
3329
+ import * as fs6 from "fs";
3312
3330
  import { promisify as promisify2 } from "util";
3313
- import { dirname as dirname7, basename as basename5, join as join16 } from "path";
3331
+ import { dirname as dirname7, basename as basename5, join as join17 } from "path";
3314
3332
  import { z as z4 } from "zod";
3315
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync11 } from "fs";
3316
- import { join as join17, dirname as dirname8 } from "path";
3333
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
3334
+ import { join as join18, dirname as dirname8 } from "path";
3317
3335
  import { execFileSync } from "child_process";
3318
- import * as fs6 from "fs";
3319
- import * as path3 from "path";
3320
- import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync12, mkdirSync as mkdirSync22 } from "fs";
3336
+ import * as fs7 from "fs";
3337
+ import * as path4 from "path";
3338
+ import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync13, mkdirSync as mkdirSync22 } from "fs";
3321
3339
  import { dirname as dirname9 } from "path";
3322
3340
  import { z as z5 } from "zod";
3323
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, mkdirSync as mkdirSync3, existsSync as existsSync13 } from "fs";
3341
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, mkdirSync as mkdirSync3, existsSync as existsSync14 } from "fs";
3324
3342
  import { randomBytes as randomBytes2 } from "crypto";
3325
- import { join as join19, dirname as dirname10 } from "path";
3343
+ import { join as join20, dirname as dirname10 } from "path";
3326
3344
  import { z as z6 } from "zod";
3327
- import * as fs7 from "fs";
3328
- import * as path4 from "path";
3329
3345
  import * as fs8 from "fs";
3330
3346
  import * as path5 from "path";
3331
3347
  import * as fs9 from "fs";
3332
3348
  import * as path6 from "path";
3349
+ import * as fs10 from "fs";
3350
+ import * as path7 from "path";
3333
3351
  import { z as z7 } from "zod";
3352
+ import * as fs14 from "fs";
3353
+ import * as path11 from "path";
3334
3354
  import * as fs13 from "fs";
3335
3355
  import * as path10 from "path";
3336
- import * as fs12 from "fs";
3337
- import * as path9 from "path";
3338
- import * as fs10 from "fs";
3339
- import * as path7 from "path";
3340
- import { execSync } from "child_process";
3341
- import { z as z8 } from "zod";
3342
3356
  import * as fs11 from "fs";
3343
3357
  import * as path8 from "path";
3344
- import * as fs14 from "fs";
3345
- import * as path11 from "path";
3346
- import * as crypto2 from "crypto";
3358
+ import { execSync } from "child_process";
3359
+ import { z as z8 } from "zod";
3360
+ import * as fs12 from "fs";
3361
+ import * as path9 from "path";
3347
3362
  import * as fs15 from "fs";
3348
3363
  import * as path12 from "path";
3364
+ import * as crypto2 from "crypto";
3349
3365
  import * as fs16 from "fs";
3350
3366
  import * as path13 from "path";
3351
- import * as crypto3 from "crypto";
3352
3367
  import * as fs17 from "fs";
3353
3368
  import * as path14 from "path";
3369
+ import * as crypto3 from "crypto";
3354
3370
  import * as fs18 from "fs";
3355
3371
  import * as path15 from "path";
3356
3372
  import * as fs19 from "fs";
@@ -3359,77 +3375,86 @@ import * as fs20 from "fs";
3359
3375
  import * as path17 from "path";
3360
3376
  import * as fs21 from "fs";
3361
3377
  import * as path18 from "path";
3362
- import { execSync as execSync2 } from "child_process";
3363
3378
  import * as fs22 from "fs";
3364
3379
  import * as path19 from "path";
3380
+ import { execSync as execSync2 } from "child_process";
3365
3381
  import * as fs23 from "fs";
3366
3382
  import * as path20 from "path";
3367
3383
  import * as fs24 from "fs";
3368
3384
  import * as path21 from "path";
3369
3385
  import * as fs25 from "fs";
3370
3386
  import * as path22 from "path";
3387
+ import * as fs26 from "fs";
3388
+ import * as path23 from "path";
3371
3389
  import { z as z9 } from "zod";
3372
- import * as fs27 from "fs/promises";
3390
+ import * as fs28 from "fs/promises";
3373
3391
  import { minimatch as minimatch4 } from "minimatch";
3374
3392
  import { z as z10 } from "zod";
3375
- import * as fs26 from "fs";
3376
- import * as path23 from "path";
3393
+ import * as fs27 from "fs";
3394
+ import * as path24 from "path";
3377
3395
  import { readFileSync as readFileSync222, writeFileSync as writeFileSync15, unlinkSync as unlinkSync2, mkdirSync as mkdirSync14, readdirSync as readdirSync3 } from "fs";
3378
- import { join as join382, dirname as dirname12 } from "path";
3396
+ import { join as join392, dirname as dirname12 } from "path";
3379
3397
  import { createHash as createHash32 } from "crypto";
3380
3398
  import { z as z11 } from "zod";
3381
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync16, renameSync as renameSync82, mkdirSync as mkdirSync15, existsSync as existsSync332 } from "fs";
3399
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync16, renameSync as renameSync82, mkdirSync as mkdirSync15, existsSync as existsSync342 } from "fs";
3382
3400
  import { execSync as execSync3 } from "child_process";
3383
3401
  import { randomBytes as randomBytes22 } from "crypto";
3384
- import { isAbsolute as isAbsolute4, join as join39, relative as relative32, dirname as dirname13 } from "path";
3385
- import * as path24 from "path";
3402
+ import { isAbsolute as isAbsolute4, join as join40, relative as relative32, dirname as dirname13 } from "path";
3386
3403
  import * as path25 from "path";
3387
3404
  import * as path26 from "path";
3388
3405
  import * as path27 from "path";
3389
- import * as fs28 from "fs";
3390
3406
  import * as path28 from "path";
3391
3407
  import * as fs29 from "fs";
3392
3408
  import * as path29 from "path";
3393
3409
  import * as fs30 from "fs";
3394
- import { z as z12 } from "zod";
3395
- import * as fs31 from "fs/promises";
3396
3410
  import * as path30 from "path";
3397
- import * as fs32 from "fs/promises";
3411
+ import * as fs31 from "fs";
3412
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
3413
+ import * as fs32 from "fs";
3398
3414
  import * as path31 from "path";
3399
- import * as ejs from "ejs";
3400
3415
  import * as fs33 from "fs";
3401
3416
  import * as path32 from "path";
3402
- import * as os from "os";
3403
- import { spawn as spawn2 } from "child_process";
3404
- import * as fs34 from "fs/promises";
3417
+ import { createHash as createHash4 } from "crypto";
3405
3418
  import * as path33 from "path";
3406
- import * as fs35 from "fs";
3419
+ import { z as z12 } from "zod";
3420
+ import * as fs34 from "fs/promises";
3407
3421
  import * as path34 from "path";
3408
- import * as fs36 from "fs";
3422
+ import * as fs35 from "fs/promises";
3409
3423
  import * as path35 from "path";
3410
- import * as os2 from "os";
3411
- import * as fs37 from "fs";
3424
+ import * as ejs from "ejs";
3425
+ import * as fs36 from "fs";
3412
3426
  import * as path36 from "path";
3413
- import * as fs39 from "fs";
3414
- import * as path38 from "path";
3415
- import { execFileSync as execFileSync2 } from "child_process";
3416
- import * as fs38 from "fs";
3427
+ import * as os from "os";
3428
+ import { spawn as spawn2 } from "child_process";
3429
+ import * as fs37 from "fs/promises";
3417
3430
  import * as path37 from "path";
3418
- import * as crypto4 from "crypto";
3419
- import * as fs40 from "fs";
3431
+ import * as fs38 from "fs";
3432
+ import * as path38 from "path";
3433
+ import * as fs39 from "fs";
3420
3434
  import * as path39 from "path";
3421
- import * as fs41 from "fs";
3422
- import * as fs42 from "fs";
3435
+ import * as os2 from "os";
3436
+ import * as fs40 from "fs";
3423
3437
  import * as path40 from "path";
3424
- import { readFileSync as readFileSync36 } from "fs";
3438
+ import * as fs42 from "fs";
3439
+ import * as path42 from "path";
3440
+ import { execFileSync as execFileSync2 } from "child_process";
3441
+ import * as fs41 from "fs";
3442
+ import * as path41 from "path";
3443
+ import * as crypto4 from "crypto";
3444
+ import * as fs43 from "fs";
3445
+ import * as path43 from "path";
3446
+ import * as fs44 from "fs";
3447
+ import * as fs45 from "fs";
3448
+ import * as path44 from "path";
3449
+ import { readFileSync as readFileSync38 } from "fs";
3425
3450
  import { fileURLToPath } from "url";
3426
- import { dirname as dirname18, join as join572 } from "path";
3451
+ import { dirname as dirname18, join as join612 } from "path";
3427
3452
  import { execFile } from "child_process";
3428
3453
  import { promisify as promisify22 } from "util";
3429
3454
  import { execFile as execFile2 } from "child_process";
3430
3455
  import { promisify as promisify3 } from "util";
3431
- import * as fs43 from "fs/promises";
3432
- import * as path41 from "path";
3456
+ import * as fs46 from "fs/promises";
3457
+ import * as path45 from "path";
3433
3458
  async function validateFileStructure(projectPath, conventions) {
3434
3459
  const missing = [];
3435
3460
  const unexpected = [];
@@ -3465,15 +3490,15 @@ function validateConfig(data, schema) {
3465
3490
  let message = "Configuration validation failed";
3466
3491
  const suggestions = [];
3467
3492
  if (firstError) {
3468
- const path42 = firstError.path.join(".");
3469
- const pathDisplay = path42 ? ` at "${path42}"` : "";
3493
+ const path46 = firstError.path.join(".");
3494
+ const pathDisplay = path46 ? ` at "${path46}"` : "";
3470
3495
  if (firstError.code === "invalid_type") {
3471
3496
  const received = firstError.received;
3472
3497
  const expected = firstError.expected;
3473
3498
  if (received === "undefined") {
3474
3499
  code = "MISSING_FIELD";
3475
3500
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
3476
- suggestions.push(`Field "${path42}" is required and must be of type "${expected}"`);
3501
+ suggestions.push(`Field "${path46}" is required and must be of type "${expected}"`);
3477
3502
  } else {
3478
3503
  code = "INVALID_TYPE";
3479
3504
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -3689,7 +3714,7 @@ function isAgnixDisabled() {
3689
3714
  return v === "1" || v === "true";
3690
3715
  }
3691
3716
  function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, spawnFn = spawn) {
3692
- return new Promise((resolve8) => {
3717
+ return new Promise((resolve9) => {
3693
3718
  const args = ["--format", "json"];
3694
3719
  if (strict) args.push("--strict");
3695
3720
  args.push(cwd);
@@ -3703,7 +3728,7 @@ function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, sp
3703
3728
  if (settled) return;
3704
3729
  settled = true;
3705
3730
  clearTimeout(timer);
3706
- resolve8(outcome);
3731
+ resolve9(outcome);
3707
3732
  };
3708
3733
  const timer = setTimeout(() => {
3709
3734
  child.kill("SIGKILL");
@@ -3807,16 +3832,16 @@ function makeFinding(input) {
3807
3832
  if (input.suggestion) finding.suggestion = input.suggestion;
3808
3833
  return finding;
3809
3834
  }
3810
- function readTextSafe(path42) {
3835
+ function readTextSafe(path46) {
3811
3836
  try {
3812
- return readFileSync2(path42, "utf-8");
3837
+ return readFileSync2(path46, "utf-8");
3813
3838
  } catch {
3814
3839
  return null;
3815
3840
  }
3816
3841
  }
3817
- function safeFileSize(path42) {
3842
+ function safeFileSize(path46) {
3818
3843
  try {
3819
- return statSync(path42).size;
3844
+ return statSync(path46).size;
3820
3845
  } catch {
3821
3846
  return null;
3822
3847
  }
@@ -4361,27 +4386,27 @@ function extractSections(content) {
4361
4386
  }
4362
4387
  return sections.map((section) => buildAgentMapSection(section, lines));
4363
4388
  }
4364
- function isExternalLink(path42) {
4365
- return path42.startsWith("http://") || path42.startsWith("https://") || path42.startsWith("#") || path42.startsWith("mailto:");
4389
+ function isExternalLink(path46) {
4390
+ return path46.startsWith("http://") || path46.startsWith("https://") || path46.startsWith("#") || path46.startsWith("mailto:");
4366
4391
  }
4367
4392
  function resolveLinkPath(linkPath, baseDir) {
4368
4393
  return linkPath.startsWith(".") ? join8(baseDir, linkPath) : linkPath;
4369
4394
  }
4370
- async function validateAgentsMap(path42 = "./AGENTS.md") {
4371
- const contentResult = await readFileContent(path42);
4395
+ async function validateAgentsMap(path46 = "./AGENTS.md") {
4396
+ const contentResult = await readFileContent(path46);
4372
4397
  if (!contentResult.ok) {
4373
4398
  return Err(
4374
4399
  createError(
4375
4400
  "PARSE_ERROR",
4376
4401
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
4377
- { path: path42 },
4402
+ { path: path46 },
4378
4403
  ["Ensure the file exists", "Check file permissions"]
4379
4404
  )
4380
4405
  );
4381
4406
  }
4382
4407
  const content = contentResult.value;
4383
4408
  const sections = extractSections(content);
4384
- const baseDir = dirname22(path42);
4409
+ const baseDir = dirname22(path46);
4385
4410
  const sectionTitles = sections.map((s) => s.title);
4386
4411
  const missingSections = REQUIRED_SECTIONS.filter(
4387
4412
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -4725,6 +4750,45 @@ function promoteWarningsToErrors(findings) {
4725
4750
  function hasErrors(findings) {
4726
4751
  return findings.some((f) => f.severity === "error");
4727
4752
  }
4753
+ function getRoadmapMode(config) {
4754
+ if (!config || !config.roadmap) return "file-backed";
4755
+ const mode = config.roadmap.mode;
4756
+ return mode === "file-less" ? "file-less" : "file-backed";
4757
+ }
4758
+ function validateRoadmapMode(config, projectRoot) {
4759
+ const root = path3.resolve(projectRoot);
4760
+ const mode = getRoadmapMode(config);
4761
+ if (mode === "file-backed") return Ok(void 0);
4762
+ const tracker = config.roadmap?.tracker;
4763
+ if (!tracker || typeof tracker !== "object") {
4764
+ return Err(
4765
+ createError(
4766
+ "ROADMAP_MODE_MISSING_TRACKER",
4767
+ 'roadmap.mode is "file-less" but roadmap.tracker is not configured. File-less mode requires an external tracker as the source of truth.',
4768
+ { mode },
4769
+ [
4770
+ 'Configure roadmap.tracker in harness.config.json (e.g., kind: "github", repo: "owner/name").',
4771
+ 'Or set roadmap.mode to "file-backed" if you want to keep using docs/roadmap.md.'
4772
+ ]
4773
+ )
4774
+ );
4775
+ }
4776
+ const roadmapPath = path3.join(root, "docs", "roadmap.md");
4777
+ if (fs3.existsSync(roadmapPath)) {
4778
+ return Err(
4779
+ createError(
4780
+ "ROADMAP_MODE_FILE_PRESENT",
4781
+ 'roadmap.mode is "file-less" but docs/roadmap.md still exists. In file-less mode the tracker is canonical; the markdown file must be migrated. Run `harness roadmap migrate --to=file-less` to migrate features into the tracker.',
4782
+ { mode, roadmapPath },
4783
+ [
4784
+ "Run `harness roadmap migrate --to=file-less` to migrate features into the tracker.",
4785
+ 'Or set roadmap.mode to "file-backed" if you want to keep docs/roadmap.md.'
4786
+ ]
4787
+ )
4788
+ );
4789
+ }
4790
+ return Ok(void 0);
4791
+ }
4728
4792
  function determineImportance(filePath) {
4729
4793
  const name = basename2(filePath).toLowerCase();
4730
4794
  if (name === "index.ts" || name === "index.js" || name === "main.ts") {
@@ -4821,8 +4885,8 @@ async function checkDocCoverage(domain, options = {}) {
4821
4885
  );
4822
4886
  }
4823
4887
  }
4824
- function suggestFix(path42, existingFiles) {
4825
- const targetName = basename3(path42).toLowerCase();
4888
+ function suggestFix(path46, existingFiles) {
4889
+ const targetName = basename3(path46).toLowerCase();
4826
4890
  const similar = existingFiles.find((file) => {
4827
4891
  const fileName = basename3(file).toLowerCase();
4828
4892
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -4830,10 +4894,10 @@ function suggestFix(path42, existingFiles) {
4830
4894
  if (similar) {
4831
4895
  return `Did you mean "${similar}"?`;
4832
4896
  }
4833
- return `Create the file "${path42}" or remove the link`;
4897
+ return `Create the file "${path46}" or remove the link`;
4834
4898
  }
4835
4899
  async function validateKnowledgeMap(rootDir = process.cwd()) {
4836
- const agentsPath = join13(rootDir, "AGENTS.md");
4900
+ const agentsPath = join14(rootDir, "AGENTS.md");
4837
4901
  const agentsResult = await validateAgentsMap(agentsPath);
4838
4902
  if (!agentsResult.ok) {
4839
4903
  return Err(agentsResult.error);
@@ -5188,8 +5252,8 @@ function createBoundaryValidator(schema, name) {
5188
5252
  return Ok(result.data);
5189
5253
  }
5190
5254
  const suggestions = result.error.issues.map((issue) => {
5191
- const path42 = issue.path.join(".");
5192
- return path42 ? `${path42}: ${issue.message}` : issue.message;
5255
+ const path46 = issue.path.join(".");
5256
+ return path46 ? `${path46}: ${issue.message}` : issue.message;
5193
5257
  });
5194
5258
  return Err(
5195
5259
  createError(
@@ -5235,7 +5299,7 @@ function validateBoundaries(boundaries, data) {
5235
5299
  async function writeConfig(filePath, content) {
5236
5300
  try {
5237
5301
  const json = JSON.stringify(content, null, 2) + "\n";
5238
- await fs3.writeFile(filePath, json, "utf-8");
5302
+ await fs4.writeFile(filePath, json, "utf-8");
5239
5303
  return Ok(void 0);
5240
5304
  } catch (error) {
5241
5305
  return Err(error instanceof Error ? error : new Error(String(error)));
@@ -5509,7 +5573,7 @@ function deepMergeConstraints(localConfig, bundleConstraints, _existingContribut
5509
5573
  async function readLockfile(lockfilePath) {
5510
5574
  let raw;
5511
5575
  try {
5512
- raw = await fs4.readFile(lockfilePath, "utf-8");
5576
+ raw = await fs5.readFile(lockfilePath, "utf-8");
5513
5577
  } catch (err) {
5514
5578
  if (isNodeError(err) && err.code === "ENOENT") {
5515
5579
  return { ok: true, value: null };
@@ -5791,9 +5855,9 @@ function parseProtectedRegions(files) {
5791
5855
  };
5792
5856
  }
5793
5857
  function collectFieldEntries(rootDir, field) {
5794
- if (typeof field === "string") return [resolve32(rootDir, field)];
5858
+ if (typeof field === "string") return [resolve42(rootDir, field)];
5795
5859
  if (typeof field === "object" && field !== null) {
5796
- return Object.values(field).filter((v) => typeof v === "string").map((v) => resolve32(rootDir, v));
5860
+ return Object.values(field).filter((v) => typeof v === "string").map((v) => resolve42(rootDir, v));
5797
5861
  }
5798
5862
  return [];
5799
5863
  }
@@ -5801,7 +5865,7 @@ function extractPackageEntries(rootDir, pkg) {
5801
5865
  const entries = [];
5802
5866
  entries.push(...collectFieldEntries(rootDir, pkg["exports"]));
5803
5867
  if (entries.length === 0 && typeof pkg["main"] === "string") {
5804
- entries.push(resolve32(rootDir, pkg["main"]));
5868
+ entries.push(resolve42(rootDir, pkg["main"]));
5805
5869
  }
5806
5870
  if (pkg["bin"]) {
5807
5871
  entries.push(...collectFieldEntries(rootDir, pkg["bin"]));
@@ -5810,9 +5874,9 @@ function extractPackageEntries(rootDir, pkg) {
5810
5874
  }
5811
5875
  async function resolveEntryPoints(rootDir, explicitEntries) {
5812
5876
  if (explicitEntries && explicitEntries.length > 0) {
5813
- return Ok(explicitEntries.map((e) => resolve32(rootDir, e)));
5877
+ return Ok(explicitEntries.map((e) => resolve42(rootDir, e)));
5814
5878
  }
5815
- const pkgPath = join14(rootDir, "package.json");
5879
+ const pkgPath = join15(rootDir, "package.json");
5816
5880
  if (await fileExists(pkgPath)) {
5817
5881
  const pkgContent = await readFileContent(pkgPath);
5818
5882
  if (pkgContent.ok) {
@@ -5826,7 +5890,7 @@ async function resolveEntryPoints(rootDir, explicitEntries) {
5826
5890
  }
5827
5891
  const conventions = ["src/index.ts", "src/main.ts", "index.ts", "main.ts"];
5828
5892
  for (const conv of conventions) {
5829
- const convPath = join14(rootDir, conv);
5893
+ const convPath = join15(rootDir, conv);
5830
5894
  if (await fileExists(convPath)) {
5831
5895
  return Ok([convPath]);
5832
5896
  }
@@ -5893,22 +5957,22 @@ function extractInlineRefs(content) {
5893
5957
  }
5894
5958
  return refs;
5895
5959
  }
5896
- async function parseDocumentationFile(path42) {
5897
- const contentResult = await readFileContent(path42);
5960
+ async function parseDocumentationFile(path46) {
5961
+ const contentResult = await readFileContent(path46);
5898
5962
  if (!contentResult.ok) {
5899
5963
  return Err(
5900
5964
  createEntropyError(
5901
5965
  "PARSE_ERROR",
5902
- `Failed to read documentation file: ${path42}`,
5903
- { file: path42 },
5966
+ `Failed to read documentation file: ${path46}`,
5967
+ { file: path46 },
5904
5968
  ["Check that the file exists"]
5905
5969
  )
5906
5970
  );
5907
5971
  }
5908
5972
  const content = contentResult.value;
5909
- const type = path42.endsWith(".md") ? "markdown" : "text";
5973
+ const type = path46.endsWith(".md") ? "markdown" : "text";
5910
5974
  return Ok({
5911
- path: path42,
5975
+ path: path46,
5912
5976
  type,
5913
5977
  content,
5914
5978
  codeBlocks: extractCodeBlocks(content),
@@ -6005,7 +6069,7 @@ function extractAllCodeReferences(docs) {
6005
6069
  async function buildSnapshot2(config) {
6006
6070
  const startTime = Date.now();
6007
6071
  const parser = config.parser || new TypeScriptParser();
6008
- const rootDir = resolve32(config.rootDir);
6072
+ const rootDir = resolve42(config.rootDir);
6009
6073
  const entryPointsResult = await resolveEntryPoints(rootDir, config.entryPoints);
6010
6074
  if (!entryPointsResult.ok) {
6011
6075
  return Err(entryPointsResult.error);
@@ -6178,7 +6242,7 @@ async function checkStructureDrift(snapshot, _config) {
6178
6242
  for (const doc of snapshot.docs) {
6179
6243
  const fileLinks = extractFileLinks(doc.content);
6180
6244
  for (const { link, line } of fileLinks) {
6181
- const resolvedPath = resolve4(dirname5(doc.path), link);
6245
+ const resolvedPath = resolve5(dirname5(doc.path), link);
6182
6246
  const exists = await fileExists(resolvedPath);
6183
6247
  if (!exists) {
6184
6248
  drifts.push({
@@ -6280,7 +6344,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
6280
6344
  }
6281
6345
  const hasFile = fileIndex ? (p) => fileIndex.has(p) : (p) => snapshot.files.some((f) => f.path === p);
6282
6346
  const fromDir = dirname6(fromFile);
6283
- const resolved = resolve5(fromDir, importSource);
6347
+ const resolved = resolve6(fromDir, importSource);
6284
6348
  const sourceExt = extname2(resolved);
6285
6349
  const fallbacks = JS_EXT_FALLBACKS2[sourceExt];
6286
6350
  if (fallbacks) {
@@ -6290,7 +6354,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
6290
6354
  if (hasFile(candidate)) return candidate;
6291
6355
  }
6292
6356
  for (const indexExt of [".ts", ".tsx", ".jsx"]) {
6293
- const indexPath2 = resolve5(base, "index" + indexExt);
6357
+ const indexPath2 = resolve6(base, "index" + indexExt);
6294
6358
  if (hasFile(indexPath2)) return indexPath2;
6295
6359
  }
6296
6360
  }
@@ -6301,7 +6365,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
6301
6365
  if (hasFile(candidate)) return candidate;
6302
6366
  }
6303
6367
  for (const indexExt of [".ts", ".tsx"]) {
6304
- const indexPath2 = resolve5(resolved, "index" + indexExt);
6368
+ const indexPath2 = resolve6(resolved, "index" + indexExt);
6305
6369
  if (hasFile(indexPath2)) return indexPath2;
6306
6370
  }
6307
6371
  }
@@ -6818,7 +6882,7 @@ function dirSize(dirPath) {
6818
6882
  }
6819
6883
  for (const entry of entries) {
6820
6884
  if (entry === "node_modules" || entry === ".git") continue;
6821
- const fullPath = join15(dirPath, entry);
6885
+ const fullPath = join16(dirPath, entry);
6822
6886
  try {
6823
6887
  const stat2 = statSync2(fullPath);
6824
6888
  if (stat2.isDirectory()) {
@@ -6838,7 +6902,7 @@ async function detectSizeBudgetViolations(rootDir, config) {
6838
6902
  let packagesChecked = 0;
6839
6903
  for (const [pkgPath, budget] of Object.entries(budgets)) {
6840
6904
  packagesChecked++;
6841
- const distPath = join15(rootDir, pkgPath, "dist");
6905
+ const distPath = join16(rootDir, pkgPath, "dist");
6842
6906
  const currentSize = dirSize(distPath);
6843
6907
  if (budget.warn) {
6844
6908
  const budgetBytes = parseSize(budget.warn);
@@ -7082,7 +7146,7 @@ function previewFix(fix) {
7082
7146
  }
7083
7147
  }
7084
7148
  async function createBackup(filePath, backupDir) {
7085
- const backupPath = join16(backupDir, `${Date.now()}-${basename5(filePath)}`);
7149
+ const backupPath = join17(backupDir, `${Date.now()}-${basename5(filePath)}`);
7086
7150
  try {
7087
7151
  await mkdir22(dirname7(backupPath), { recursive: true });
7088
7152
  await copyFile2(filePath, backupPath);
@@ -7457,12 +7521,12 @@ function parseDiffHeader(part) {
7457
7521
  return headerMatch[2];
7458
7522
  }
7459
7523
  function parseDiffPart(part) {
7460
- const path42 = parseDiffHeader(part);
7461
- if (!path42) return null;
7524
+ const path46 = parseDiffHeader(part);
7525
+ if (!path46) return null;
7462
7526
  const additionRegex = /^\+(?!\+\+)/gm;
7463
7527
  const deletionRegex = /^-(?!--)/gm;
7464
7528
  return {
7465
- path: path42,
7529
+ path: path46,
7466
7530
  status: detectFileStatus(part),
7467
7531
  additions: (part.match(additionRegex) || []).length,
7468
7532
  deletions: (part.match(deletionRegex) || []).length
@@ -8247,10 +8311,10 @@ function detectEmergentConstraints(history, options) {
8247
8311
  return { suggestions, totalViolationsAnalyzed, windowWeeks, minOccurrences };
8248
8312
  }
8249
8313
  function streamsDir(projectPath) {
8250
- return path7.join(projectPath, HARNESS_DIR, STREAMS_DIR);
8314
+ return path8.join(projectPath, HARNESS_DIR, STREAMS_DIR);
8251
8315
  }
8252
8316
  function indexPath(projectPath) {
8253
- return path7.join(streamsDir(projectPath), INDEX_FILE);
8317
+ return path8.join(streamsDir(projectPath), INDEX_FILE);
8254
8318
  }
8255
8319
  function validateStreamName(name) {
8256
8320
  if (!STREAM_NAME_REGEX.test(name)) {
@@ -8264,11 +8328,11 @@ function validateStreamName(name) {
8264
8328
  }
8265
8329
  async function loadStreamIndex(projectPath) {
8266
8330
  const idxPath = indexPath(projectPath);
8267
- if (!fs10.existsSync(idxPath)) {
8331
+ if (!fs11.existsSync(idxPath)) {
8268
8332
  return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
8269
8333
  }
8270
8334
  try {
8271
- const raw = fs10.readFileSync(idxPath, "utf-8");
8335
+ const raw = fs11.readFileSync(idxPath, "utf-8");
8272
8336
  const parsed = JSON.parse(raw);
8273
8337
  const result = StreamIndexSchema.safeParse(parsed);
8274
8338
  if (!result.success) {
@@ -8286,8 +8350,8 @@ async function loadStreamIndex(projectPath) {
8286
8350
  async function saveStreamIndex(projectPath, index) {
8287
8351
  const dir = streamsDir(projectPath);
8288
8352
  try {
8289
- fs10.mkdirSync(dir, { recursive: true });
8290
- fs10.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
8353
+ fs11.mkdirSync(dir, { recursive: true });
8354
+ fs11.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
8291
8355
  return Ok(void 0);
8292
8356
  } catch (error) {
8293
8357
  return Err(
@@ -8326,18 +8390,18 @@ async function resolveStreamPath(projectPath, options) {
8326
8390
  )
8327
8391
  );
8328
8392
  }
8329
- return Ok(path7.join(streamsDir(projectPath), options.stream));
8393
+ return Ok(path8.join(streamsDir(projectPath), options.stream));
8330
8394
  }
8331
8395
  const branch = getCurrentBranch(projectPath);
8332
8396
  if (branch && branch !== "main" && branch !== "master") {
8333
8397
  for (const [name, info] of Object.entries(index.streams)) {
8334
8398
  if (info.branch === branch) {
8335
- return Ok(path7.join(streamsDir(projectPath), name));
8399
+ return Ok(path8.join(streamsDir(projectPath), name));
8336
8400
  }
8337
8401
  }
8338
8402
  }
8339
8403
  if (index.activeStream && index.streams[index.activeStream]) {
8340
- return Ok(path7.join(streamsDir(projectPath), index.activeStream));
8404
+ return Ok(path8.join(streamsDir(projectPath), index.activeStream));
8341
8405
  }
8342
8406
  return Err(
8343
8407
  new Error(
@@ -8365,9 +8429,9 @@ async function createStream(projectPath, name, branch) {
8365
8429
  if (index.streams[name]) {
8366
8430
  return Err(new Error(`Stream '${name}' already exists`));
8367
8431
  }
8368
- const streamPath = path7.join(streamsDir(projectPath), name);
8432
+ const streamPath = path8.join(streamsDir(projectPath), name);
8369
8433
  try {
8370
- fs10.mkdirSync(streamPath, { recursive: true });
8434
+ fs11.mkdirSync(streamPath, { recursive: true });
8371
8435
  } catch (error) {
8372
8436
  return Err(
8373
8437
  new Error(
@@ -8408,12 +8472,12 @@ async function archiveStream(projectPath, name) {
8408
8472
  if (!index.streams[name]) {
8409
8473
  return Err(new Error(`Stream '${name}' not found`));
8410
8474
  }
8411
- const streamPath = path7.join(streamsDir(projectPath), name);
8412
- const archiveDir = path7.join(projectPath, HARNESS_DIR, "archive", "streams");
8475
+ const streamPath = path8.join(streamsDir(projectPath), name);
8476
+ const archiveDir = path8.join(projectPath, HARNESS_DIR, "archive", "streams");
8413
8477
  try {
8414
- fs10.mkdirSync(archiveDir, { recursive: true });
8478
+ fs11.mkdirSync(archiveDir, { recursive: true });
8415
8479
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8416
- fs10.renameSync(streamPath, path7.join(archiveDir, `${name}-${date}`));
8480
+ fs11.renameSync(streamPath, path8.join(archiveDir, `${name}-${date}`));
8417
8481
  } catch (error) {
8418
8482
  return Err(
8419
8483
  new Error(
@@ -8434,19 +8498,19 @@ function getStreamForBranch(index, branch) {
8434
8498
  return null;
8435
8499
  }
8436
8500
  async function migrateToStreams(projectPath) {
8437
- const harnessDir = path7.join(projectPath, HARNESS_DIR);
8438
- if (fs10.existsSync(indexPath(projectPath))) {
8501
+ const harnessDir = path8.join(projectPath, HARNESS_DIR);
8502
+ if (fs11.existsSync(indexPath(projectPath))) {
8439
8503
  return Ok(void 0);
8440
8504
  }
8441
- const filesToMove = STATE_FILES.filter((f) => fs10.existsSync(path7.join(harnessDir, f)));
8505
+ const filesToMove = STATE_FILES.filter((f) => fs11.existsSync(path8.join(harnessDir, f)));
8442
8506
  if (filesToMove.length === 0) {
8443
8507
  return Ok(void 0);
8444
8508
  }
8445
- const defaultDir = path7.join(streamsDir(projectPath), "default");
8509
+ const defaultDir = path8.join(streamsDir(projectPath), "default");
8446
8510
  try {
8447
- fs10.mkdirSync(defaultDir, { recursive: true });
8511
+ fs11.mkdirSync(defaultDir, { recursive: true });
8448
8512
  for (const file of filesToMove) {
8449
- fs10.renameSync(path7.join(harnessDir, file), path7.join(defaultDir, file));
8513
+ fs11.renameSync(path8.join(harnessDir, file), path8.join(defaultDir, file));
8450
8514
  }
8451
8515
  } catch (error) {
8452
8516
  return Err(
@@ -8476,26 +8540,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
8476
8540
  new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
8477
8541
  );
8478
8542
  }
8479
- const sessionDir = path8.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
8543
+ const sessionDir = path9.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
8480
8544
  if (options?.create) {
8481
- fs11.mkdirSync(sessionDir, { recursive: true });
8545
+ fs12.mkdirSync(sessionDir, { recursive: true });
8482
8546
  }
8483
8547
  return Ok(sessionDir);
8484
8548
  }
8485
8549
  function updateSessionIndex(projectPath, sessionSlug, description) {
8486
- const sessionsDir = path8.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
8487
- fs11.mkdirSync(sessionsDir, { recursive: true });
8488
- const indexPath2 = path8.join(sessionsDir, SESSION_INDEX_FILE);
8550
+ const sessionsDir = path9.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
8551
+ fs12.mkdirSync(sessionsDir, { recursive: true });
8552
+ const indexPath2 = path9.join(sessionsDir, SESSION_INDEX_FILE);
8489
8553
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8490
8554
  const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
8491
- if (!fs11.existsSync(indexPath2)) {
8492
- fs11.writeFileSync(indexPath2, `## Active Sessions
8555
+ if (!fs12.existsSync(indexPath2)) {
8556
+ fs12.writeFileSync(indexPath2, `## Active Sessions
8493
8557
 
8494
8558
  ${newLine}
8495
8559
  `);
8496
8560
  return;
8497
8561
  }
8498
- const content = fs11.readFileSync(indexPath2, "utf-8");
8562
+ const content = fs12.readFileSync(indexPath2, "utf-8");
8499
8563
  const lines = content.split("\n");
8500
8564
  const slugPattern = `- [${sessionSlug}]`;
8501
8565
  const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
@@ -8505,7 +8569,7 @@ ${newLine}
8505
8569
  const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
8506
8570
  lines.splice(lastNonEmpty + 1, 0, newLine);
8507
8571
  }
8508
- fs11.writeFileSync(indexPath2, lines.join("\n"));
8572
+ fs12.writeFileSync(indexPath2, lines.join("\n"));
8509
8573
  }
8510
8574
  function evictIfNeeded(map) {
8511
8575
  if (map.size > MAX_CACHE_ENTRIES) {
@@ -8518,8 +8582,8 @@ async function getStateDir(projectPath, stream, session) {
8518
8582
  const sessionResult = resolveSessionDir(projectPath, session, { create: true });
8519
8583
  return sessionResult;
8520
8584
  }
8521
- const streamsIndexPath = path9.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
8522
- const hasStreams = fs12.existsSync(streamsIndexPath);
8585
+ const streamsIndexPath = path10.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
8586
+ const hasStreams = fs13.existsSync(streamsIndexPath);
8523
8587
  if (stream || hasStreams) {
8524
8588
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
8525
8589
  if (result.ok) {
@@ -8529,18 +8593,18 @@ async function getStateDir(projectPath, stream, session) {
8529
8593
  return result;
8530
8594
  }
8531
8595
  }
8532
- return Ok(path9.join(projectPath, HARNESS_DIR));
8596
+ return Ok(path10.join(projectPath, HARNESS_DIR));
8533
8597
  }
8534
8598
  async function loadState(projectPath, stream, session) {
8535
8599
  try {
8536
8600
  const dirResult = await getStateDir(projectPath, stream, session);
8537
8601
  if (!dirResult.ok) return dirResult;
8538
8602
  const stateDir = dirResult.value;
8539
- const statePath = path10.join(stateDir, STATE_FILE);
8540
- if (!fs13.existsSync(statePath)) {
8603
+ const statePath = path11.join(stateDir, STATE_FILE);
8604
+ if (!fs14.existsSync(statePath)) {
8541
8605
  return Ok({ ...DEFAULT_STATE });
8542
8606
  }
8543
- const raw = fs13.readFileSync(statePath, "utf-8");
8607
+ const raw = fs14.readFileSync(statePath, "utf-8");
8544
8608
  const parsed = JSON.parse(raw);
8545
8609
  const result = HarnessStateSchema.safeParse(parsed);
8546
8610
  if (!result.success) {
@@ -8558,11 +8622,11 @@ async function saveState(projectPath, state, stream, session) {
8558
8622
  const dirResult = await getStateDir(projectPath, stream, session);
8559
8623
  if (!dirResult.ok) return dirResult;
8560
8624
  const stateDir = dirResult.value;
8561
- const statePath = path10.join(stateDir, STATE_FILE);
8562
- fs13.mkdirSync(stateDir, { recursive: true });
8625
+ const statePath = path11.join(stateDir, STATE_FILE);
8626
+ fs14.mkdirSync(stateDir, { recursive: true });
8563
8627
  const tmpPath = statePath + ".tmp";
8564
- fs13.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
8565
- fs13.renameSync(tmpPath, statePath);
8628
+ fs14.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
8629
+ fs14.renameSync(tmpPath, statePath);
8566
8630
  return Ok(void 0);
8567
8631
  } catch (error) {
8568
8632
  return Err(
@@ -8648,10 +8712,10 @@ function computeContentHash(text) {
8648
8712
  return crypto2.createHash("sha256").update(text).digest("hex").slice(0, 16);
8649
8713
  }
8650
8714
  function loadContentHashes(stateDir) {
8651
- const hashesPath = path11.join(stateDir, CONTENT_HASHES_FILE);
8652
- if (!fs14.existsSync(hashesPath)) return {};
8715
+ const hashesPath = path12.join(stateDir, CONTENT_HASHES_FILE);
8716
+ if (!fs15.existsSync(hashesPath)) return {};
8653
8717
  try {
8654
- const raw = fs14.readFileSync(hashesPath, "utf-8");
8718
+ const raw = fs15.readFileSync(hashesPath, "utf-8");
8655
8719
  const parsed = JSON.parse(raw);
8656
8720
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
8657
8721
  return parsed;
@@ -8660,15 +8724,15 @@ function loadContentHashes(stateDir) {
8660
8724
  }
8661
8725
  }
8662
8726
  function saveContentHashes(stateDir, index) {
8663
- const hashesPath = path11.join(stateDir, CONTENT_HASHES_FILE);
8727
+ const hashesPath = path12.join(stateDir, CONTENT_HASHES_FILE);
8664
8728
  const tmpPath = hashesPath + ".tmp";
8665
- fs14.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
8666
- fs14.renameSync(tmpPath, hashesPath);
8729
+ fs15.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
8730
+ fs15.renameSync(tmpPath, hashesPath);
8667
8731
  }
8668
8732
  function rebuildContentHashes(stateDir, learningsFile) {
8669
- const learningsPath = path11.join(stateDir, learningsFile);
8670
- if (!fs14.existsSync(learningsPath)) return {};
8671
- const content = fs14.readFileSync(learningsPath, "utf-8");
8733
+ const learningsPath = path12.join(stateDir, learningsFile);
8734
+ if (!fs15.existsSync(learningsPath)) return {};
8735
+ const content = fs15.readFileSync(learningsPath, "utf-8");
8672
8736
  const lines = content.split("\n");
8673
8737
  const index = {};
8674
8738
  for (let i = 0; i < lines.length; i++) {
@@ -8727,18 +8791,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
8727
8791
  const dirResult = await getStateDir(projectPath, stream, session);
8728
8792
  if (!dirResult.ok) return dirResult;
8729
8793
  const stateDir = dirResult.value;
8730
- const learningsPath = path12.join(stateDir, LEARNINGS_FILE);
8731
- if (!fs15.existsSync(learningsPath)) {
8794
+ const learningsPath = path13.join(stateDir, LEARNINGS_FILE);
8795
+ if (!fs16.existsSync(learningsPath)) {
8732
8796
  return Ok([]);
8733
8797
  }
8734
- const stats = fs15.statSync(learningsPath);
8798
+ const stats = fs16.statSync(learningsPath);
8735
8799
  const cacheKey = learningsPath;
8736
8800
  const cached = learningsCacheMap.get(cacheKey);
8737
8801
  let entries;
8738
8802
  if (cached && cached.mtimeMs === stats.mtimeMs) {
8739
8803
  entries = cached.entries;
8740
8804
  } else {
8741
- const content = fs15.readFileSync(learningsPath, "utf-8");
8805
+ const content = fs16.readFileSync(learningsPath, "utf-8");
8742
8806
  const lines = content.split("\n");
8743
8807
  entries = [];
8744
8808
  let currentBlock = [];
@@ -8874,16 +8938,16 @@ function checkOverlap(newEntry, existingEntries, options) {
8874
8938
  };
8875
8939
  }
8876
8940
  function sleep(ms) {
8877
- return new Promise((resolve8) => setTimeout(resolve8, ms));
8941
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
8878
8942
  }
8879
8943
  async function acquireFileLock(lockPath) {
8880
8944
  for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
8881
8945
  try {
8882
- const fd = fs16.openSync(
8946
+ const fd = fs17.openSync(
8883
8947
  lockPath,
8884
- fs16.constants.O_CREAT | fs16.constants.O_EXCL | fs16.constants.O_WRONLY
8948
+ fs17.constants.O_CREAT | fs17.constants.O_EXCL | fs17.constants.O_WRONLY
8885
8949
  );
8886
- fs16.closeSync(fd);
8950
+ fs17.closeSync(fd);
8887
8951
  return;
8888
8952
  } catch (err) {
8889
8953
  const isExist = err instanceof Error && "code" in err && err.code === "EEXIST";
@@ -8897,20 +8961,20 @@ async function acquireFileLock(lockPath) {
8897
8961
  }
8898
8962
  function releaseFileLock(lockPath) {
8899
8963
  try {
8900
- fs16.unlinkSync(lockPath);
8964
+ fs17.unlinkSync(lockPath);
8901
8965
  } catch {
8902
8966
  }
8903
8967
  }
8904
8968
  function loadOrRebuildHashes(stateDir, learningsPath) {
8905
- const hashesPath = path13.join(stateDir, CONTENT_HASHES_FILE);
8906
- if (fs16.existsSync(hashesPath)) {
8969
+ const hashesPath = path14.join(stateDir, CONTENT_HASHES_FILE);
8970
+ if (fs17.existsSync(hashesPath)) {
8907
8971
  const hashes = loadContentHashes(stateDir);
8908
- if (Object.keys(hashes).length === 0 && fs16.existsSync(learningsPath)) {
8972
+ if (Object.keys(hashes).length === 0 && fs17.existsSync(learningsPath)) {
8909
8973
  return rebuildContentHashes(stateDir, LEARNINGS_FILE);
8910
8974
  }
8911
8975
  return hashes;
8912
8976
  }
8913
- if (fs16.existsSync(learningsPath)) {
8977
+ if (fs17.existsSync(learningsPath)) {
8914
8978
  return rebuildContentHashes(stateDir, LEARNINGS_FILE);
8915
8979
  }
8916
8980
  return {};
@@ -8934,8 +8998,8 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
8934
8998
  const dirResult = await getStateDir(projectPath, stream, session);
8935
8999
  if (!dirResult.ok) return dirResult;
8936
9000
  const stateDir = dirResult.value;
8937
- const learningsPath = path13.join(stateDir, LEARNINGS_FILE);
8938
- fs16.mkdirSync(stateDir, { recursive: true });
9001
+ const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
9002
+ fs17.mkdirSync(stateDir, { recursive: true });
8939
9003
  const lockPath = learningsPath + ".lock";
8940
9004
  await acquireFileLock(lockPath);
8941
9005
  try {
@@ -8965,17 +9029,17 @@ ${bulletLine}
8965
9029
  `;
8966
9030
  let overlapResult;
8967
9031
  let existingLineCount;
8968
- if (!fs16.existsSync(learningsPath)) {
8969
- fs16.writeFileSync(learningsPath, `# Learnings
9032
+ if (!fs17.existsSync(learningsPath)) {
9033
+ fs17.writeFileSync(learningsPath, `# Learnings
8970
9034
  ${entry}`);
8971
9035
  existingLineCount = 1;
8972
9036
  } else {
8973
- const existingContent = fs16.readFileSync(learningsPath, "utf-8");
9037
+ const existingContent = fs17.readFileSync(learningsPath, "utf-8");
8974
9038
  const existingEntries = existingContent.split("\n").filter((line) => /^- \*\*\d{4}-\d{2}-\d{2}/.test(line));
8975
9039
  const overlap = checkOverlap(bulletLine, existingEntries);
8976
9040
  if (overlap.score >= 0.7) overlapResult = overlap;
8977
9041
  existingLineCount = existingContent.split("\n").length;
8978
- fs16.appendFileSync(learningsPath, entry);
9042
+ fs17.appendFileSync(learningsPath, entry);
8979
9043
  }
8980
9044
  const bulletLineNum = existingLineCount + 2;
8981
9045
  contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLineNum };
@@ -9080,11 +9144,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
9080
9144
  const dirResult = await getStateDir(projectPath, stream, session);
9081
9145
  if (!dirResult.ok) return dirResult;
9082
9146
  const stateDir = dirResult.value;
9083
- const learningsPath = path13.join(stateDir, LEARNINGS_FILE);
9084
- if (!fs16.existsSync(learningsPath)) {
9147
+ const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
9148
+ if (!fs17.existsSync(learningsPath)) {
9085
9149
  return Ok([]);
9086
9150
  }
9087
- const content = fs16.readFileSync(learningsPath, "utf-8");
9151
+ const content = fs17.readFileSync(learningsPath, "utf-8");
9088
9152
  const lines = content.split("\n");
9089
9153
  const indexEntries = [];
9090
9154
  let pendingFrontmatter = null;
@@ -9126,16 +9190,16 @@ async function archiveLearnings(projectPath, entries, stream) {
9126
9190
  const dirResult = await getStateDir(projectPath, stream);
9127
9191
  if (!dirResult.ok) return dirResult;
9128
9192
  const stateDir = dirResult.value;
9129
- const archiveDir = path14.join(stateDir, "learnings-archive");
9130
- fs17.mkdirSync(archiveDir, { recursive: true });
9193
+ const archiveDir = path15.join(stateDir, "learnings-archive");
9194
+ fs18.mkdirSync(archiveDir, { recursive: true });
9131
9195
  const now = /* @__PURE__ */ new Date();
9132
9196
  const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
9133
- const archivePath = path14.join(archiveDir, `${yearMonth}.md`);
9197
+ const archivePath = path15.join(archiveDir, `${yearMonth}.md`);
9134
9198
  const archiveContent = entries.join("\n\n") + "\n";
9135
- if (fs17.existsSync(archivePath)) {
9136
- fs17.appendFileSync(archivePath, "\n" + archiveContent);
9199
+ if (fs18.existsSync(archivePath)) {
9200
+ fs18.appendFileSync(archivePath, "\n" + archiveContent);
9137
9201
  } else {
9138
- fs17.writeFileSync(archivePath, `# Learnings Archive
9202
+ fs18.writeFileSync(archivePath, `# Learnings Archive
9139
9203
 
9140
9204
  ${archiveContent}`);
9141
9205
  }
@@ -9153,8 +9217,8 @@ async function pruneLearnings(projectPath, stream) {
9153
9217
  const dirResult = await getStateDir(projectPath, stream);
9154
9218
  if (!dirResult.ok) return dirResult;
9155
9219
  const stateDir = dirResult.value;
9156
- const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
9157
- if (!fs17.existsSync(learningsPath)) {
9220
+ const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
9221
+ if (!fs18.existsSync(learningsPath)) {
9158
9222
  return Ok({ kept: 0, archived: 0, patterns: [] });
9159
9223
  }
9160
9224
  const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
@@ -9185,7 +9249,7 @@ async function pruneLearnings(projectPath, stream) {
9185
9249
  if (!archiveResult.ok) return archiveResult;
9186
9250
  }
9187
9251
  const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
9188
- fs17.writeFileSync(learningsPath, newContent);
9252
+ fs18.writeFileSync(learningsPath, newContent);
9189
9253
  invalidateLearningsCacheEntry(learningsPath);
9190
9254
  return Ok({
9191
9255
  kept: toKeep.length,
@@ -9229,19 +9293,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
9229
9293
  const dirResult = await getStateDir(projectPath, stream);
9230
9294
  if (!dirResult.ok) return dirResult;
9231
9295
  const stateDir = dirResult.value;
9232
- const globalPath = path14.join(stateDir, LEARNINGS_FILE);
9233
- const existingGlobal = fs17.existsSync(globalPath) ? fs17.readFileSync(globalPath, "utf-8") : "";
9296
+ const globalPath = path15.join(stateDir, LEARNINGS_FILE);
9297
+ const existingGlobal = fs18.existsSync(globalPath) ? fs18.readFileSync(globalPath, "utf-8") : "";
9234
9298
  const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
9235
9299
  if (newEntries.length === 0) {
9236
9300
  return Ok({ promoted: 0, skipped: skipped + toPromote.length });
9237
9301
  }
9238
9302
  const promotedContent = newEntries.join("\n\n") + "\n";
9239
9303
  if (!existingGlobal) {
9240
- fs17.writeFileSync(globalPath, `# Learnings
9304
+ fs18.writeFileSync(globalPath, `# Learnings
9241
9305
 
9242
9306
  ${promotedContent}`);
9243
9307
  } else {
9244
- fs17.appendFileSync(globalPath, "\n\n" + promotedContent);
9308
+ fs18.appendFileSync(globalPath, "\n\n" + promotedContent);
9245
9309
  }
9246
9310
  invalidateLearningsCacheEntry(globalPath);
9247
9311
  return Ok({
@@ -9277,13 +9341,13 @@ async function detectStaleLearnings(projectPath, stream, session) {
9277
9341
  freshCount++;
9278
9342
  continue;
9279
9343
  }
9280
- const resolvedRoot = path15.resolve(projectPath);
9344
+ const resolvedRoot = path16.resolve(projectPath);
9281
9345
  const missing = refs.filter((ref) => {
9282
- const absPath = path15.resolve(projectPath, ref);
9283
- if (!absPath.startsWith(resolvedRoot + path15.sep) && absPath !== resolvedRoot) {
9346
+ const absPath = path16.resolve(projectPath, ref);
9347
+ if (!absPath.startsWith(resolvedRoot + path16.sep) && absPath !== resolvedRoot) {
9284
9348
  return false;
9285
9349
  }
9286
- return !fs18.existsSync(absPath);
9350
+ return !fs19.existsSync(absPath);
9287
9351
  });
9288
9352
  if (missing.length > 0) {
9289
9353
  const idx = extractIndexEntry(entry);
@@ -9318,17 +9382,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
9318
9382
  const dirResult = await getStateDir(projectPath, stream, session);
9319
9383
  if (!dirResult.ok) return dirResult;
9320
9384
  const stateDir = dirResult.value;
9321
- const failuresPath = path16.join(stateDir, FAILURES_FILE);
9322
- fs19.mkdirSync(stateDir, { recursive: true });
9385
+ const failuresPath = path17.join(stateDir, FAILURES_FILE);
9386
+ fs20.mkdirSync(stateDir, { recursive: true });
9323
9387
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9324
9388
  const entry = `
9325
9389
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
9326
9390
  `;
9327
- if (!fs19.existsSync(failuresPath)) {
9328
- fs19.writeFileSync(failuresPath, `# Failures
9391
+ if (!fs20.existsSync(failuresPath)) {
9392
+ fs20.writeFileSync(failuresPath, `# Failures
9329
9393
  ${entry}`);
9330
9394
  } else {
9331
- fs19.appendFileSync(failuresPath, entry);
9395
+ fs20.appendFileSync(failuresPath, entry);
9332
9396
  }
9333
9397
  failuresCacheMap.delete(failuresPath);
9334
9398
  return Ok(void 0);
@@ -9345,17 +9409,17 @@ async function loadFailures(projectPath, stream, session) {
9345
9409
  const dirResult = await getStateDir(projectPath, stream, session);
9346
9410
  if (!dirResult.ok) return dirResult;
9347
9411
  const stateDir = dirResult.value;
9348
- const failuresPath = path16.join(stateDir, FAILURES_FILE);
9349
- if (!fs19.existsSync(failuresPath)) {
9412
+ const failuresPath = path17.join(stateDir, FAILURES_FILE);
9413
+ if (!fs20.existsSync(failuresPath)) {
9350
9414
  return Ok([]);
9351
9415
  }
9352
- const stats = fs19.statSync(failuresPath);
9416
+ const stats = fs20.statSync(failuresPath);
9353
9417
  const cacheKey = failuresPath;
9354
9418
  const cached = failuresCacheMap.get(cacheKey);
9355
9419
  if (cached && cached.mtimeMs === stats.mtimeMs) {
9356
9420
  return Ok(cached.entries);
9357
9421
  }
9358
- const content = fs19.readFileSync(failuresPath, "utf-8");
9422
+ const content = fs20.readFileSync(failuresPath, "utf-8");
9359
9423
  const entries = [];
9360
9424
  for (const line of content.split("\n")) {
9361
9425
  const match = line.match(FAILURE_LINE_REGEX);
@@ -9384,20 +9448,20 @@ async function archiveFailures(projectPath, stream, session) {
9384
9448
  const dirResult = await getStateDir(projectPath, stream, session);
9385
9449
  if (!dirResult.ok) return dirResult;
9386
9450
  const stateDir = dirResult.value;
9387
- const failuresPath = path16.join(stateDir, FAILURES_FILE);
9388
- if (!fs19.existsSync(failuresPath)) {
9451
+ const failuresPath = path17.join(stateDir, FAILURES_FILE);
9452
+ if (!fs20.existsSync(failuresPath)) {
9389
9453
  return Ok(void 0);
9390
9454
  }
9391
- const archiveDir = path16.join(stateDir, "archive");
9392
- fs19.mkdirSync(archiveDir, { recursive: true });
9455
+ const archiveDir = path17.join(stateDir, "archive");
9456
+ fs20.mkdirSync(archiveDir, { recursive: true });
9393
9457
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9394
9458
  let archiveName = `failures-${date}.md`;
9395
9459
  let counter = 2;
9396
- while (fs19.existsSync(path16.join(archiveDir, archiveName))) {
9460
+ while (fs20.existsSync(path17.join(archiveDir, archiveName))) {
9397
9461
  archiveName = `failures-${date}-${counter}.md`;
9398
9462
  counter++;
9399
9463
  }
9400
- fs19.renameSync(failuresPath, path16.join(archiveDir, archiveName));
9464
+ fs20.renameSync(failuresPath, path17.join(archiveDir, archiveName));
9401
9465
  failuresCacheMap.delete(failuresPath);
9402
9466
  return Ok(void 0);
9403
9467
  } catch (error) {
@@ -9413,11 +9477,11 @@ async function saveHandoff(projectPath, handoff, stream, session) {
9413
9477
  const dirResult = await getStateDir(projectPath, stream, session);
9414
9478
  if (!dirResult.ok) return dirResult;
9415
9479
  const stateDir = dirResult.value;
9416
- const handoffPath = path17.join(stateDir, HANDOFF_FILE);
9417
- fs20.mkdirSync(stateDir, { recursive: true });
9480
+ const handoffPath = path18.join(stateDir, HANDOFF_FILE);
9481
+ fs21.mkdirSync(stateDir, { recursive: true });
9418
9482
  const tmpPath = handoffPath + ".tmp";
9419
- fs20.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
9420
- fs20.renameSync(tmpPath, handoffPath);
9483
+ fs21.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
9484
+ fs21.renameSync(tmpPath, handoffPath);
9421
9485
  return Ok(void 0);
9422
9486
  } catch (error) {
9423
9487
  return Err(
@@ -9430,11 +9494,11 @@ async function loadHandoff(projectPath, stream, session) {
9430
9494
  const dirResult = await getStateDir(projectPath, stream, session);
9431
9495
  if (!dirResult.ok) return dirResult;
9432
9496
  const stateDir = dirResult.value;
9433
- const handoffPath = path17.join(stateDir, HANDOFF_FILE);
9434
- if (!fs20.existsSync(handoffPath)) {
9497
+ const handoffPath = path18.join(stateDir, HANDOFF_FILE);
9498
+ if (!fs21.existsSync(handoffPath)) {
9435
9499
  return Ok(null);
9436
9500
  }
9437
- const raw = fs20.readFileSync(handoffPath, "utf-8");
9501
+ const raw = fs21.readFileSync(handoffPath, "utf-8");
9438
9502
  const parsed = JSON.parse(raw);
9439
9503
  const result = HandoffSchema.safeParse(parsed);
9440
9504
  if (!result.success) {
@@ -9448,28 +9512,28 @@ async function loadHandoff(projectPath, stream, session) {
9448
9512
  }
9449
9513
  }
9450
9514
  function loadChecksFromConfig(gateConfigPath) {
9451
- if (!fs21.existsSync(gateConfigPath)) return [];
9452
- const raw = JSON.parse(fs21.readFileSync(gateConfigPath, "utf-8"));
9515
+ if (!fs22.existsSync(gateConfigPath)) return [];
9516
+ const raw = JSON.parse(fs22.readFileSync(gateConfigPath, "utf-8"));
9453
9517
  const config = GateConfigSchema.safeParse(raw);
9454
9518
  if (config.success && config.data.checks) return config.data.checks;
9455
9519
  return [];
9456
9520
  }
9457
9521
  function discoverChecksFromProject(projectPath) {
9458
9522
  const checks = [];
9459
- const packageJsonPath = path18.join(projectPath, "package.json");
9460
- if (fs21.existsSync(packageJsonPath)) {
9461
- const pkg = JSON.parse(fs21.readFileSync(packageJsonPath, "utf-8"));
9523
+ const packageJsonPath = path19.join(projectPath, "package.json");
9524
+ if (fs22.existsSync(packageJsonPath)) {
9525
+ const pkg = JSON.parse(fs22.readFileSync(packageJsonPath, "utf-8"));
9462
9526
  const scripts = pkg.scripts || {};
9463
9527
  if (scripts.test) checks.push({ name: "test", command: "npm test" });
9464
9528
  if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
9465
9529
  if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
9466
9530
  if (scripts.build) checks.push({ name: "build", command: "npm run build" });
9467
9531
  }
9468
- if (fs21.existsSync(path18.join(projectPath, "go.mod"))) {
9532
+ if (fs22.existsSync(path19.join(projectPath, "go.mod"))) {
9469
9533
  checks.push({ name: "test", command: "go test ./..." });
9470
9534
  checks.push({ name: "build", command: "go build ./..." });
9471
9535
  }
9472
- if (fs21.existsSync(path18.join(projectPath, "pyproject.toml")) || fs21.existsSync(path18.join(projectPath, "setup.py"))) {
9536
+ if (fs22.existsSync(path19.join(projectPath, "pyproject.toml")) || fs22.existsSync(path19.join(projectPath, "setup.py"))) {
9473
9537
  checks.push({ name: "test", command: "python -m pytest" });
9474
9538
  }
9475
9539
  return checks;
@@ -9509,8 +9573,8 @@ function executeCheck(check, projectPath) {
9509
9573
  }
9510
9574
  }
9511
9575
  async function runMechanicalGate(projectPath) {
9512
- const harnessDir = path18.join(projectPath, HARNESS_DIR);
9513
- const gateConfigPath = path18.join(harnessDir, GATE_CONFIG_FILE);
9576
+ const harnessDir = path19.join(projectPath, HARNESS_DIR);
9577
+ const gateConfigPath = path19.join(harnessDir, GATE_CONFIG_FILE);
9514
9578
  try {
9515
9579
  let checks = loadChecksFromConfig(gateConfigPath);
9516
9580
  if (checks.length === 0) {
@@ -9566,9 +9630,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
9566
9630
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
9567
9631
  if (!dirResult.ok) return dirResult;
9568
9632
  const sessionDir = dirResult.value;
9569
- const summaryPath = path19.join(sessionDir, SUMMARY_FILE);
9633
+ const summaryPath = path20.join(sessionDir, SUMMARY_FILE);
9570
9634
  const content = formatSummary(data);
9571
- fs22.writeFileSync(summaryPath, content);
9635
+ fs23.writeFileSync(summaryPath, content);
9572
9636
  const description = deriveIndexDescription(data);
9573
9637
  updateSessionIndex(projectPath, sessionSlug, description);
9574
9638
  return Ok(void 0);
@@ -9585,11 +9649,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
9585
9649
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9586
9650
  if (!dirResult.ok) return dirResult;
9587
9651
  const sessionDir = dirResult.value;
9588
- const summaryPath = path19.join(sessionDir, SUMMARY_FILE);
9589
- if (!fs22.existsSync(summaryPath)) {
9652
+ const summaryPath = path20.join(sessionDir, SUMMARY_FILE);
9653
+ if (!fs23.existsSync(summaryPath)) {
9590
9654
  return Ok(null);
9591
9655
  }
9592
- const content = fs22.readFileSync(summaryPath, "utf-8");
9656
+ const content = fs23.readFileSync(summaryPath, "utf-8");
9593
9657
  return Ok(content);
9594
9658
  } catch (error) {
9595
9659
  return Err(
@@ -9601,11 +9665,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
9601
9665
  }
9602
9666
  function listActiveSessions(projectPath) {
9603
9667
  try {
9604
- const indexPath2 = path19.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
9605
- if (!fs22.existsSync(indexPath2)) {
9668
+ const indexPath2 = path20.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
9669
+ if (!fs23.existsSync(indexPath2)) {
9606
9670
  return Ok(null);
9607
9671
  }
9608
- const content = fs22.readFileSync(indexPath2, "utf-8");
9672
+ const content = fs23.readFileSync(indexPath2, "utf-8");
9609
9673
  return Ok(content);
9610
9674
  } catch (error) {
9611
9675
  return Err(
@@ -9626,12 +9690,12 @@ async function loadSessionState(projectPath, sessionSlug) {
9626
9690
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9627
9691
  if (!dirResult.ok) return dirResult;
9628
9692
  const sessionDir = dirResult.value;
9629
- const filePath = path20.join(sessionDir, SESSION_STATE_FILE);
9630
- if (!fs23.existsSync(filePath)) {
9693
+ const filePath = path21.join(sessionDir, SESSION_STATE_FILE);
9694
+ if (!fs24.existsSync(filePath)) {
9631
9695
  return Ok(emptySections());
9632
9696
  }
9633
9697
  try {
9634
- const raw = fs23.readFileSync(filePath, "utf-8");
9698
+ const raw = fs24.readFileSync(filePath, "utf-8");
9635
9699
  const parsed = JSON.parse(raw);
9636
9700
  const sections = emptySections();
9637
9701
  for (const name of SESSION_SECTION_NAMES) {
@@ -9652,9 +9716,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
9652
9716
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
9653
9717
  if (!dirResult.ok) return dirResult;
9654
9718
  const sessionDir = dirResult.value;
9655
- const filePath = path20.join(sessionDir, SESSION_STATE_FILE);
9719
+ const filePath = path21.join(sessionDir, SESSION_STATE_FILE);
9656
9720
  try {
9657
- fs23.writeFileSync(filePath, JSON.stringify(sections, null, 2));
9721
+ fs24.writeFileSync(filePath, JSON.stringify(sections, null, 2));
9658
9722
  return Ok(void 0);
9659
9723
  } catch (error) {
9660
9724
  return Err(
@@ -9710,26 +9774,26 @@ async function archiveSession(projectPath, sessionSlug) {
9710
9774
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9711
9775
  if (!dirResult.ok) return dirResult;
9712
9776
  const sessionDir = dirResult.value;
9713
- if (!fs24.existsSync(sessionDir)) {
9777
+ if (!fs25.existsSync(sessionDir)) {
9714
9778
  return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
9715
9779
  }
9716
- const archiveBase = path21.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
9780
+ const archiveBase = path22.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
9717
9781
  try {
9718
- fs24.mkdirSync(archiveBase, { recursive: true });
9782
+ fs25.mkdirSync(archiveBase, { recursive: true });
9719
9783
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9720
9784
  let archiveName = `${sessionSlug}-${date}`;
9721
9785
  let counter = 1;
9722
- while (fs24.existsSync(path21.join(archiveBase, archiveName))) {
9786
+ while (fs25.existsSync(path22.join(archiveBase, archiveName))) {
9723
9787
  archiveName = `${sessionSlug}-${date}-${counter}`;
9724
9788
  counter++;
9725
9789
  }
9726
- const dest = path21.join(archiveBase, archiveName);
9790
+ const dest = path22.join(archiveBase, archiveName);
9727
9791
  try {
9728
- fs24.renameSync(sessionDir, dest);
9792
+ fs25.renameSync(sessionDir, dest);
9729
9793
  } catch (renameErr) {
9730
9794
  if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
9731
- fs24.cpSync(sessionDir, dest, { recursive: true });
9732
- fs24.rmSync(sessionDir, { recursive: true });
9795
+ fs25.cpSync(sessionDir, dest, { recursive: true });
9796
+ fs25.rmSync(sessionDir, { recursive: true });
9733
9797
  } else {
9734
9798
  throw renameErr;
9735
9799
  }
@@ -9751,8 +9815,8 @@ function loadKnownHashes(eventsPath) {
9751
9815
  const cached = knownHashesCache.get(eventsPath);
9752
9816
  if (cached) return cached;
9753
9817
  const hashes = /* @__PURE__ */ new Set();
9754
- if (fs25.existsSync(eventsPath)) {
9755
- const content = fs25.readFileSync(eventsPath, "utf-8");
9818
+ if (fs26.existsSync(eventsPath)) {
9819
+ const content = fs26.readFileSync(eventsPath, "utf-8");
9756
9820
  const lines = content.split("\n").filter((line) => line.trim() !== "");
9757
9821
  for (const line of lines) {
9758
9822
  try {
@@ -9775,8 +9839,8 @@ async function emitEvent(projectPath, event, options) {
9775
9839
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
9776
9840
  if (!dirResult.ok) return dirResult;
9777
9841
  const stateDir = dirResult.value;
9778
- const eventsPath = path22.join(stateDir, EVENTS_FILE);
9779
- fs25.mkdirSync(stateDir, { recursive: true });
9842
+ const eventsPath = path23.join(stateDir, EVENTS_FILE);
9843
+ fs26.mkdirSync(stateDir, { recursive: true });
9780
9844
  const contentHash = computeEventHash(event, options?.session);
9781
9845
  const knownHashes = loadKnownHashes(eventsPath);
9782
9846
  if (knownHashes.has(contentHash)) {
@@ -9790,7 +9854,7 @@ async function emitEvent(projectPath, event, options) {
9790
9854
  if (options?.session) {
9791
9855
  fullEvent.session = options.session;
9792
9856
  }
9793
- fs25.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
9857
+ fs26.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
9794
9858
  knownHashes.add(contentHash);
9795
9859
  return Ok({ written: true });
9796
9860
  } catch (error) {
@@ -9804,11 +9868,11 @@ async function loadEvents(projectPath, options) {
9804
9868
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
9805
9869
  if (!dirResult.ok) return dirResult;
9806
9870
  const stateDir = dirResult.value;
9807
- const eventsPath = path22.join(stateDir, EVENTS_FILE);
9808
- if (!fs25.existsSync(eventsPath)) {
9871
+ const eventsPath = path23.join(stateDir, EVENTS_FILE);
9872
+ if (!fs26.existsSync(eventsPath)) {
9809
9873
  return Ok([]);
9810
9874
  }
9811
- const content = fs25.readFileSync(eventsPath, "utf-8");
9875
+ const content = fs26.readFileSync(eventsPath, "utf-8");
9812
9876
  const lines = content.split("\n").filter((line) => line.trim() !== "");
9813
9877
  const events = [];
9814
9878
  for (const line of lines) {
@@ -10057,11 +10121,11 @@ function nodeSubStacks(allDeps) {
10057
10121
  return found;
10058
10122
  }
10059
10123
  function detectNodeStacks(projectRoot) {
10060
- const pkgJsonPath = path23.join(projectRoot, "package.json");
10061
- if (!fs26.existsSync(pkgJsonPath)) return [];
10124
+ const pkgJsonPath = path24.join(projectRoot, "package.json");
10125
+ if (!fs27.existsSync(pkgJsonPath)) return [];
10062
10126
  const stacks = ["node"];
10063
10127
  try {
10064
- const pkgJson = JSON.parse(fs26.readFileSync(pkgJsonPath, "utf-8"));
10128
+ const pkgJson = JSON.parse(fs27.readFileSync(pkgJsonPath, "utf-8"));
10065
10129
  const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
10066
10130
  stacks.push(...nodeSubStacks(allDeps));
10067
10131
  } catch {
@@ -10070,10 +10134,10 @@ function detectNodeStacks(projectRoot) {
10070
10134
  }
10071
10135
  function detectStack(projectRoot) {
10072
10136
  const stacks = [...detectNodeStacks(projectRoot)];
10073
- if (fs26.existsSync(path23.join(projectRoot, "go.mod"))) {
10137
+ if (fs27.existsSync(path24.join(projectRoot, "go.mod"))) {
10074
10138
  stacks.push("go");
10075
10139
  }
10076
- const hasPython = fs26.existsSync(path23.join(projectRoot, "requirements.txt")) || fs26.existsSync(path23.join(projectRoot, "pyproject.toml"));
10140
+ const hasPython = fs27.existsSync(path24.join(projectRoot, "requirements.txt")) || fs27.existsSync(path24.join(projectRoot, "pyproject.toml"));
10077
10141
  if (hasPython) {
10078
10142
  stacks.push("python");
10079
10143
  }
@@ -10116,7 +10180,7 @@ function getInjectionPatterns() {
10116
10180
  }
10117
10181
  function getTaintFilePath(projectRoot, sessionId) {
10118
10182
  const id = sessionId || DEFAULT_SESSION_ID;
10119
- return join382(projectRoot, ".harness", `session-taint-${id}.json`);
10183
+ return join392(projectRoot, ".harness", `session-taint-${id}.json`);
10120
10184
  }
10121
10185
  function readTaint(projectRoot, sessionId) {
10122
10186
  const filePath = getTaintFilePath(projectRoot, sessionId);
@@ -10198,14 +10262,14 @@ function clearTaint(projectRoot, sessionId) {
10198
10262
  return 0;
10199
10263
  }
10200
10264
  }
10201
- const harnessDir = join382(projectRoot, ".harness");
10265
+ const harnessDir = join392(projectRoot, ".harness");
10202
10266
  let count = 0;
10203
10267
  try {
10204
10268
  const files = readdirSync3(harnessDir);
10205
10269
  for (const file of files) {
10206
10270
  if (file.startsWith("session-taint-") && file.endsWith(".json")) {
10207
10271
  try {
10208
- unlinkSync2(join382(harnessDir, file));
10272
+ unlinkSync2(join392(harnessDir, file));
10209
10273
  count++;
10210
10274
  } catch {
10211
10275
  }
@@ -10216,7 +10280,7 @@ function clearTaint(projectRoot, sessionId) {
10216
10280
  return count;
10217
10281
  }
10218
10282
  function listTaintedSessions(projectRoot) {
10219
- const harnessDir = join382(projectRoot, ".harness");
10283
+ const harnessDir = join392(projectRoot, ".harness");
10220
10284
  const sessions = [];
10221
10285
  try {
10222
10286
  const files = readdirSync3(harnessDir);
@@ -10288,7 +10352,7 @@ function securityFindingId(finding) {
10288
10352
  }
10289
10353
  async function runValidateCheck(projectRoot, config) {
10290
10354
  const issues = [];
10291
- const agentsPath = path24.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10355
+ const agentsPath = path25.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10292
10356
  const result = await validateAgentsMap(agentsPath);
10293
10357
  if (!result.ok) {
10294
10358
  issues.push({ severity: "error", message: result.error.message });
@@ -10345,7 +10409,7 @@ async function runDepsCheck(projectRoot, config) {
10345
10409
  }
10346
10410
  async function runDocsCheck(projectRoot, config) {
10347
10411
  const issues = [];
10348
- const docsDir = path24.join(projectRoot, config.docsDir ?? "docs");
10412
+ const docsDir = path25.join(projectRoot, config.docsDir ?? "docs");
10349
10413
  const entropyConfig = config.entropy || {};
10350
10414
  const result = await checkDocCoverage("project", {
10351
10415
  docsDir,
@@ -10530,7 +10594,7 @@ async function runTraceabilityCheck(projectRoot, config) {
10530
10594
  const issues = [];
10531
10595
  const traceConfig = config.traceability || {};
10532
10596
  if (traceConfig.enabled === false) return issues;
10533
- const graphDir = path24.join(projectRoot, ".harness", "graph");
10597
+ const graphDir = path25.join(projectRoot, ".harness", "graph");
10534
10598
  const store = new GraphStore();
10535
10599
  const loaded = await store.load(graphDir);
10536
10600
  if (!loaded) {
@@ -10708,7 +10772,7 @@ async function runMechanicalChecks(options) {
10708
10772
  };
10709
10773
  if (!skip.includes("validate")) {
10710
10774
  try {
10711
- const agentsPath = path25.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10775
+ const agentsPath = path26.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10712
10776
  const result = await validateAgentsMap(agentsPath);
10713
10777
  if (!result.ok) {
10714
10778
  statuses.validate = "fail";
@@ -10745,7 +10809,7 @@ async function runMechanicalChecks(options) {
10745
10809
  statuses.validate = "fail";
10746
10810
  findings.push({
10747
10811
  tool: "validate",
10748
- file: path25.join(projectRoot, "AGENTS.md"),
10812
+ file: path26.join(projectRoot, "AGENTS.md"),
10749
10813
  message: err instanceof Error ? err.message : String(err),
10750
10814
  severity: "error"
10751
10815
  });
@@ -10809,7 +10873,7 @@ async function runMechanicalChecks(options) {
10809
10873
  (async () => {
10810
10874
  const localFindings = [];
10811
10875
  try {
10812
- const docsDir = path25.join(projectRoot, config.docsDir ?? "docs");
10876
+ const docsDir = path26.join(projectRoot, config.docsDir ?? "docs");
10813
10877
  const result = await checkDocCoverage("project", { docsDir });
10814
10878
  if (!result.ok) {
10815
10879
  statuses["check-docs"] = "warn";
@@ -10836,7 +10900,7 @@ async function runMechanicalChecks(options) {
10836
10900
  statuses["check-docs"] = "warn";
10837
10901
  localFindings.push({
10838
10902
  tool: "check-docs",
10839
- file: path25.join(projectRoot, "docs"),
10903
+ file: path26.join(projectRoot, "docs"),
10840
10904
  message: err instanceof Error ? err.message : String(err),
10841
10905
  severity: "warning"
10842
10906
  });
@@ -10933,18 +10997,18 @@ function computeContextBudget(diffLines) {
10933
10997
  return diffLines;
10934
10998
  }
10935
10999
  function isWithinProject(absPath, projectRoot) {
10936
- const resolvedRoot = path26.resolve(projectRoot) + path26.sep;
10937
- const resolvedPath = path26.resolve(absPath);
10938
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path26.resolve(projectRoot);
11000
+ const resolvedRoot = path27.resolve(projectRoot) + path27.sep;
11001
+ const resolvedPath = path27.resolve(absPath);
11002
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path27.resolve(projectRoot);
10939
11003
  }
10940
11004
  async function readContextFile(projectRoot, filePath, reason) {
10941
- const absPath = path26.isAbsolute(filePath) ? filePath : path26.join(projectRoot, filePath);
11005
+ const absPath = path27.isAbsolute(filePath) ? filePath : path27.join(projectRoot, filePath);
10942
11006
  if (!isWithinProject(absPath, projectRoot)) return null;
10943
11007
  const result = await readFileContent(absPath);
10944
11008
  if (!result.ok) return null;
10945
11009
  const content = result.value;
10946
11010
  const lines = content.split("\n").length;
10947
- const relPath2 = path26.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
11011
+ const relPath2 = path27.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
10948
11012
  return { path: relPath2, content, reason, lines };
10949
11013
  }
10950
11014
  function extractImportSources2(content) {
@@ -10959,24 +11023,24 @@ function extractImportSources2(content) {
10959
11023
  }
10960
11024
  async function resolveImportPath2(projectRoot, fromFile, importSource) {
10961
11025
  if (!importSource.startsWith(".")) return null;
10962
- const fromDir = path26.dirname(path26.join(projectRoot, fromFile));
10963
- const basePath = path26.resolve(fromDir, importSource);
11026
+ const fromDir = path27.dirname(path27.join(projectRoot, fromFile));
11027
+ const basePath = path27.resolve(fromDir, importSource);
10964
11028
  if (!isWithinProject(basePath, projectRoot)) return null;
10965
11029
  const relBase = relativePosix(projectRoot, basePath);
10966
- const sourceExt = path26.extname(relBase);
11030
+ const sourceExt = path27.extname(relBase);
10967
11031
  const candidates = [];
10968
11032
  const fallbacks = JS_EXT_FALLBACKS22[sourceExt];
10969
11033
  if (fallbacks) {
10970
11034
  const stripped = relBase.slice(0, -sourceExt.length);
10971
11035
  for (const ext of fallbacks) candidates.push(stripped + ext);
10972
- candidates.push(path26.join(stripped, "index.ts"));
10973
- candidates.push(path26.join(stripped, "index.tsx"));
10974
- candidates.push(path26.join(stripped, "index.jsx"));
11036
+ candidates.push(path27.join(stripped, "index.ts"));
11037
+ candidates.push(path27.join(stripped, "index.tsx"));
11038
+ candidates.push(path27.join(stripped, "index.jsx"));
10975
11039
  }
10976
11040
  candidates.push(relBase + ".ts", relBase + ".tsx", relBase + ".mts");
10977
- candidates.push(path26.join(relBase, "index.ts"));
11041
+ candidates.push(path27.join(relBase, "index.ts"));
10978
11042
  for (const candidate of candidates) {
10979
- const absCandidate = path26.join(projectRoot, candidate);
11043
+ const absCandidate = path27.join(projectRoot, candidate);
10980
11044
  if (await fileExists(absCandidate)) {
10981
11045
  return candidate;
10982
11046
  }
@@ -10984,7 +11048,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
10984
11048
  return null;
10985
11049
  }
10986
11050
  async function findTestFiles(projectRoot, sourceFile) {
10987
- const baseName = path26.basename(sourceFile, path26.extname(sourceFile));
11051
+ const baseName = path27.basename(sourceFile, path27.extname(sourceFile));
10988
11052
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
10989
11053
  const results = await findFiles(pattern, projectRoot);
10990
11054
  return results.map((f) => relativePosix(projectRoot, f));
@@ -11752,7 +11816,7 @@ function normalizePath(filePath, projectRoot) {
11752
11816
  let normalized = filePath;
11753
11817
  normalized = normalized.replace(/\\/g, "/");
11754
11818
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
11755
- if (path27.isAbsolute(normalized)) {
11819
+ if (path28.isAbsolute(normalized)) {
11756
11820
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
11757
11821
  if (normalized.startsWith(root)) {
11758
11822
  normalized = normalized.slice(root.length);
@@ -11764,12 +11828,12 @@ function normalizePath(filePath, projectRoot) {
11764
11828
  return normalized;
11765
11829
  }
11766
11830
  function resolveImportPath22(currentFile, importPath) {
11767
- const dir = path27.dirname(currentFile);
11768
- let resolved = path27.join(dir, importPath).replace(/\\/g, "/");
11831
+ const dir = path28.dirname(currentFile);
11832
+ let resolved = path28.join(dir, importPath).replace(/\\/g, "/");
11769
11833
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
11770
11834
  resolved += ".ts";
11771
11835
  }
11772
- return path27.normalize(resolved).replace(/\\/g, "/");
11836
+ return path28.normalize(resolved).replace(/\\/g, "/");
11773
11837
  }
11774
11838
  function enqueueImports(content, current, visited, queue, maxDepth) {
11775
11839
  const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
@@ -11801,7 +11865,7 @@ function isMechanicallyExcluded(finding, exclusionSet, projectRoot) {
11801
11865
  const normalizedFile = normalizePath(finding.file, projectRoot);
11802
11866
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange)) return true;
11803
11867
  if (exclusionSet.isExcluded(finding.file, finding.lineRange)) return true;
11804
- const absoluteFile = path27.isAbsolute(finding.file) ? finding.file : path27.join(projectRoot, finding.file).replace(/\\/g, "/");
11868
+ const absoluteFile = path28.isAbsolute(finding.file) ? finding.file : path28.join(projectRoot, finding.file).replace(/\\/g, "/");
11805
11869
  return exclusionSet.isExcluded(absoluteFile, finding.lineRange);
11806
11870
  }
11807
11871
  async function validateWithGraph(crossFileRefs, graph) {
@@ -12809,7 +12873,7 @@ function isRegression(from, to) {
12809
12873
  }
12810
12874
  function collectAutopilotStatuses(autopilotPath, featurePlans, allTaskStatuses) {
12811
12875
  try {
12812
- const raw = fs28.readFileSync(autopilotPath, "utf-8");
12876
+ const raw = fs29.readFileSync(autopilotPath, "utf-8");
12813
12877
  const autopilot = JSON.parse(raw);
12814
12878
  if (!autopilot.phases) return;
12815
12879
  const linkedPhases = autopilot.phases.filter(
@@ -12841,10 +12905,10 @@ function inferStatus(feature, projectPath, allFeatures) {
12841
12905
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
12842
12906
  const useRootState = featuresWithPlans.length <= 1;
12843
12907
  if (useRootState) {
12844
- const rootStatePath = path28.join(projectPath, ".harness", "state.json");
12845
- if (fs28.existsSync(rootStatePath)) {
12908
+ const rootStatePath = path29.join(projectPath, ".harness", "state.json");
12909
+ if (fs29.existsSync(rootStatePath)) {
12846
12910
  try {
12847
- const raw = fs28.readFileSync(rootStatePath, "utf-8");
12911
+ const raw = fs29.readFileSync(rootStatePath, "utf-8");
12848
12912
  const state = JSON.parse(raw);
12849
12913
  if (state.progress) {
12850
12914
  for (const status of Object.values(state.progress)) {
@@ -12855,14 +12919,14 @@ function inferStatus(feature, projectPath, allFeatures) {
12855
12919
  }
12856
12920
  }
12857
12921
  }
12858
- const sessionsDir = path28.join(projectPath, ".harness", "sessions");
12859
- if (fs28.existsSync(sessionsDir)) {
12922
+ const sessionsDir = path29.join(projectPath, ".harness", "sessions");
12923
+ if (fs29.existsSync(sessionsDir)) {
12860
12924
  try {
12861
- const sessionDirs = fs28.readdirSync(sessionsDir, { withFileTypes: true });
12925
+ const sessionDirs = fs29.readdirSync(sessionsDir, { withFileTypes: true });
12862
12926
  for (const entry of sessionDirs) {
12863
12927
  if (!entry.isDirectory()) continue;
12864
- const autopilotPath = path28.join(sessionsDir, entry.name, "autopilot-state.json");
12865
- if (!fs28.existsSync(autopilotPath)) continue;
12928
+ const autopilotPath = path29.join(sessionsDir, entry.name, "autopilot-state.json");
12929
+ if (!fs29.existsSync(autopilotPath)) continue;
12866
12930
  collectAutopilotStatuses(autopilotPath, feature.plans, allTaskStatuses);
12867
12931
  }
12868
12932
  } catch {
@@ -12944,7 +13008,7 @@ function parseRepoParts(repo) {
12944
13008
  return { owner: parts[0], repo: parts[1] };
12945
13009
  }
12946
13010
  function sleep2(ms) {
12947
- return new Promise((resolve8) => setTimeout(resolve8, ms));
13011
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
12948
13012
  }
12949
13013
  async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
12950
13014
  let lastResponse;
@@ -12977,9 +13041,9 @@ function isValidTrackerShape(tracker) {
12977
13041
  }
12978
13042
  function loadTrackerSyncConfig(projectRoot) {
12979
13043
  try {
12980
- const configPath = path29.join(projectRoot, "harness.config.json");
12981
- if (!fs29.existsSync(configPath)) return null;
12982
- const raw = fs29.readFileSync(configPath, "utf-8");
13044
+ const configPath = path30.join(projectRoot, "harness.config.json");
13045
+ if (!fs30.existsSync(configPath)) return null;
13046
+ const raw = fs30.readFileSync(configPath, "utf-8");
12983
13047
  const config = JSON.parse(raw);
12984
13048
  const tracker = config.roadmap?.tracker;
12985
13049
  if (!isValidTrackerShape(tracker)) return null;
@@ -13094,12 +13158,12 @@ async function syncFromExternal(roadmap, adapter2, config, options, prefetchedTi
13094
13158
  async function fullSync(roadmapPath, adapter2, config, options) {
13095
13159
  const previousSync = syncMutex;
13096
13160
  let releaseMutex;
13097
- syncMutex = new Promise((resolve8) => {
13098
- releaseMutex = resolve8;
13161
+ syncMutex = new Promise((resolve9) => {
13162
+ releaseMutex = resolve9;
13099
13163
  });
13100
13164
  await previousSync;
13101
13165
  try {
13102
- const raw = fs30.readFileSync(roadmapPath, "utf-8");
13166
+ const raw = fs31.readFileSync(roadmapPath, "utf-8");
13103
13167
  const parseResult = parseRoadmap(raw);
13104
13168
  if (!parseResult.ok) {
13105
13169
  return {
@@ -13112,7 +13176,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
13112
13176
  const tickets = fetchResult.ok ? fetchResult.value : void 0;
13113
13177
  const pushResult = await syncToExternal(roadmap, adapter2, config, tickets);
13114
13178
  const pullResult = await syncFromExternal(roadmap, adapter2, config, options);
13115
- fs30.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13179
+ fs31.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13116
13180
  return {
13117
13181
  created: pushResult.created,
13118
13182
  updated: pushResult.updated,
@@ -13123,6 +13187,32 @@ async function fullSync(roadmapPath, adapter2, config, options) {
13123
13187
  releaseMutex();
13124
13188
  }
13125
13189
  }
13190
+ function scoreRoadmapCandidatesFileLess(features, _options) {
13191
+ const allNames = new Set(features.map((f) => f.name.toLowerCase()));
13192
+ const doneNames = new Set(
13193
+ features.filter((f) => f.status === "done").map((f) => f.name.toLowerCase())
13194
+ );
13195
+ const eligible = features.filter((f) => {
13196
+ if (f.status !== "planned" && f.status !== "backlog") return false;
13197
+ return !f.blockedBy.some((b) => {
13198
+ const k = b.toLowerCase();
13199
+ return allNames.has(k) && !doneNames.has(k);
13200
+ });
13201
+ });
13202
+ const candidates = eligible.map((f) => ({
13203
+ feature: f,
13204
+ priorityTier: f.priority ? PRIORITY_RANK[f.priority] : null
13205
+ }));
13206
+ candidates.sort((a, b) => {
13207
+ const ap = a.priorityTier;
13208
+ const bp = b.priorityTier;
13209
+ if (ap !== null && bp === null) return -1;
13210
+ if (ap === null && bp !== null) return 1;
13211
+ if (ap !== null && bp !== null && ap !== bp) return ap - bp;
13212
+ return a.feature.createdAt.localeCompare(b.feature.createdAt);
13213
+ });
13214
+ return candidates;
13215
+ }
13126
13216
  function isEligibleCandidate(feature, allFeatureNames, doneFeatures) {
13127
13217
  if (feature.status !== "planned" && feature.status !== "backlog") return false;
13128
13218
  const isBlocked = feature.blockedBy.some((blocker) => {
@@ -13192,7 +13282,7 @@ function scoreRoadmapCandidates(roadmap, options) {
13192
13282
  userCompletedFeatures
13193
13283
  );
13194
13284
  const weightedScore = POSITION_WEIGHT * positionScore + DEPENDENTS_WEIGHT * dependentsScore + AFFINITY_WEIGHT * affinityScore;
13195
- const priorityTier = feature.priority ? PRIORITY_RANK[feature.priority] : null;
13285
+ const priorityTier = feature.priority ? PRIORITY_RANK2[feature.priority] : null;
13196
13286
  candidates.push({
13197
13287
  feature,
13198
13288
  milestone: ms.name,
@@ -13232,9 +13322,550 @@ function assignFeature(roadmap, feature, assignee, date) {
13232
13322
  date
13233
13323
  });
13234
13324
  }
13325
+ function scoreRoadmapCandidatesForMode(roadmap, options, config) {
13326
+ const mode = getRoadmapMode(config);
13327
+ if (mode === "file-less") {
13328
+ const flatFeatures = roadmap.milestones.flatMap((m) => m.features);
13329
+ const tracked = flatFeatures.map(featureToTrackedFeature);
13330
+ const fileLess = scoreRoadmapCandidatesFileLess(tracked, options);
13331
+ const featuresByName = new Map(flatFeatures.map((f) => [f.name, f]));
13332
+ return fileLess.map((c) => {
13333
+ const feature = featuresByName.get(c.feature.name);
13334
+ if (!feature) {
13335
+ throw new Error(`Internal: feature ${c.feature.name} lost during mode dispatch`);
13336
+ }
13337
+ return {
13338
+ feature,
13339
+ milestone: "",
13340
+ positionScore: 0,
13341
+ dependentsScore: 0,
13342
+ affinityScore: 0,
13343
+ weightedScore: 0,
13344
+ priorityTier: c.priorityTier
13345
+ };
13346
+ });
13347
+ }
13348
+ return scoreRoadmapCandidates(roadmap, options);
13349
+ }
13350
+ function featureToTrackedFeature(f) {
13351
+ return {
13352
+ externalId: f.externalId ?? "",
13353
+ name: f.name,
13354
+ status: f.status,
13355
+ summary: f.summary,
13356
+ spec: f.spec,
13357
+ plans: f.plans,
13358
+ blockedBy: f.blockedBy,
13359
+ assignee: f.assignee,
13360
+ priority: f.priority,
13361
+ milestone: null,
13362
+ createdAt: "1970-01-01T00:00:00Z",
13363
+ updatedAt: f.updatedAt
13364
+ };
13365
+ }
13366
+ function parseExternalId2(externalId) {
13367
+ const m = externalId.match(/^github:([^/]+)\/([^#]+)#(\d+)$/);
13368
+ if (!m) return null;
13369
+ return { owner: m[1], repo: m[2], number: parseInt(m[3], 10) };
13370
+ }
13371
+ function buildExternalId2(owner, repo, n) {
13372
+ return `github:${owner}/${repo}#${n}`;
13373
+ }
13374
+ function parseBodyBlock(body) {
13375
+ const startIdx = body.indexOf(START_MARKER);
13376
+ if (startIdx === -1) return { summary: body.trim(), meta: {} };
13377
+ const endIdx = body.indexOf(END_MARKER, startIdx + START_MARKER.length);
13378
+ if (endIdx === -1) return { summary: body.trim(), meta: {} };
13379
+ const secondStart = body.indexOf(START_MARKER, endIdx + END_MARKER.length);
13380
+ if (secondStart !== -1) {
13381
+ console.warn("harness-meta: multiple blocks found; first wins.");
13382
+ }
13383
+ const yamlText = body.slice(startIdx + START_MARKER.length, endIdx).trim();
13384
+ let meta = {};
13385
+ try {
13386
+ const parsed = parseYaml(yamlText);
13387
+ if (parsed && typeof parsed === "object") {
13388
+ meta = normalizeBodyMeta(parsed);
13389
+ }
13390
+ } catch (e) {
13391
+ console.warn(
13392
+ `harness-meta: malformed YAML, treating block as missing: ${e.message}`
13393
+ );
13394
+ meta = {};
13395
+ }
13396
+ const summary = body.slice(0, startIdx).trim();
13397
+ return { summary, meta };
13398
+ }
13399
+ function normalizeBodyMeta(raw) {
13400
+ const out = {};
13401
+ if (typeof raw.spec === "string") out.spec = raw.spec;
13402
+ if (typeof raw.plan === "string") out.plan = raw.plan;
13403
+ if (typeof raw.blocked_by === "string") {
13404
+ out.blocked_by = raw.blocked_by.split(",").map((s) => s.trim()).filter(Boolean);
13405
+ } else if (Array.isArray(raw.blocked_by)) {
13406
+ out.blocked_by = raw.blocked_by.filter(
13407
+ (v) => typeof v === "string"
13408
+ );
13409
+ }
13410
+ if (typeof raw.priority === "string" && ["P0", "P1", "P2", "P3"].includes(raw.priority)) {
13411
+ out.priority = raw.priority;
13412
+ }
13413
+ if (typeof raw.milestone === "string") out.milestone = raw.milestone;
13414
+ return out;
13415
+ }
13416
+ function serializeBodyBlock(summary, meta) {
13417
+ const ordered = {};
13418
+ for (const [key, yamlKey, mapValue] of SERIALIZER_FIELDS) {
13419
+ const raw = meta[key];
13420
+ if (raw === void 0 || raw === null) continue;
13421
+ const out = mapValue(raw);
13422
+ if (out === void 0) continue;
13423
+ ordered[yamlKey] = out;
13424
+ }
13425
+ const trimmed = summary.trim();
13426
+ if (Object.keys(ordered).length === 0) return trimmed;
13427
+ const yamlBody = stringifyYaml(ordered).trimEnd();
13428
+ const block = `${START_MARKER}
13429
+ ${yamlBody}
13430
+ ${END_MARKER}`;
13431
+ return trimmed.length > 0 ? `${trimmed}
13432
+
13433
+ ${block}` : block;
13434
+ }
13435
+ function compareStatus(server, patch) {
13436
+ if (patch.status === void 0) return { idempotent: true };
13437
+ if (server.status === "done" && patch.status !== "done") {
13438
+ return { diff: { ours: patch.status, theirs: server.status }, idempotent: false };
13439
+ }
13440
+ return { idempotent: server.status === patch.status };
13441
+ }
13442
+ function compareAssignee(server, patch) {
13443
+ if (patch.assignee === void 0) return { idempotent: true };
13444
+ if (server.assignee !== null && server.assignee !== patch.assignee) {
13445
+ return { diff: { ours: patch.assignee, theirs: server.assignee }, idempotent: false };
13446
+ }
13447
+ return { idempotent: server.assignee === patch.assignee };
13448
+ }
13449
+ function compareScalar(field, server, patch) {
13450
+ const next = patch[field];
13451
+ if (next === void 0) return { idempotent: true };
13452
+ const cur = server[field];
13453
+ if (cur !== null && cur !== next) {
13454
+ return { diff: { ours: next, theirs: cur }, idempotent: false };
13455
+ }
13456
+ return { idempotent: cur === next };
13457
+ }
13458
+ function compareArray(field, server, patch) {
13459
+ const next = patch[field];
13460
+ if (next === void 0) return { idempotent: true };
13461
+ const cur = server[field];
13462
+ if (arraysEqual2(next, cur)) return { idempotent: true };
13463
+ if (cur.length > 0 && cur.some((v) => !next.includes(v))) {
13464
+ return { diff: { ours: next, theirs: cur }, idempotent: false };
13465
+ }
13466
+ return { idempotent: false };
13467
+ }
13468
+ function refetchAndCompare(server, patch) {
13469
+ const verdicts = [
13470
+ ["status", compareStatus(server, patch)],
13471
+ ["assignee", compareAssignee(server, patch)],
13472
+ ["priority", compareScalar("priority", server, patch)],
13473
+ ["milestone", compareScalar("milestone", server, patch)],
13474
+ ["spec", compareScalar("spec", server, patch)],
13475
+ ["plans", compareArray("plans", server, patch)],
13476
+ ["blockedBy", compareArray("blockedBy", server, patch)]
13477
+ ];
13478
+ const diff2 = {};
13479
+ let idempotent = true;
13480
+ for (const [field, verdict] of verdicts) {
13481
+ if (verdict.diff) diff2[field] = verdict.diff;
13482
+ if (!verdict.idempotent) idempotent = false;
13483
+ }
13484
+ if (Object.keys(diff2).length > 0) return { ok: false, diff: diff2 };
13485
+ return { ok: true, idempotent };
13486
+ }
13487
+ function arraysEqual2(a, b) {
13488
+ if (a.length !== b.length) return false;
13489
+ const sa = [...a].sort();
13490
+ const sb = [...b].sort();
13491
+ return sa.every((v, i) => v === sb[i]);
13492
+ }
13493
+ function metaFromFeatureFields(src) {
13494
+ const meta = {};
13495
+ if (src.spec !== void 0 && src.spec !== null) meta.spec = src.spec;
13496
+ if (src.plans && src.plans.length > 0) meta.plan = src.plans[0];
13497
+ if (src.blockedBy && src.blockedBy.length > 0) meta.blocked_by = src.blockedBy;
13498
+ if (src.priority !== void 0 && src.priority !== null) meta.priority = src.priority;
13499
+ if (src.milestone !== void 0 && src.milestone !== null) meta.milestone = src.milestone;
13500
+ return meta;
13501
+ }
13502
+ function buildCreateMeta(feature) {
13503
+ return metaFromFeatureFields(feature);
13504
+ }
13505
+ function createTrackerClient(config) {
13506
+ if (config.kind !== "github-issues") {
13507
+ return Err(new Error(`Unsupported tracker kind: "${String(config.kind)}"`));
13508
+ }
13509
+ const token = config.token ?? process.env.GITHUB_TOKEN;
13510
+ if (!token) {
13511
+ return Err(
13512
+ new Error("createTrackerClient: missing GitHub token (config.token or GITHUB_TOKEN env)")
13513
+ );
13514
+ }
13515
+ const opts = { token, repo: config.repo };
13516
+ if (config.apiBase !== void 0) opts.apiBase = config.apiBase;
13517
+ if (config.selectorLabel !== void 0) opts.selectorLabel = config.selectorLabel;
13518
+ if (config.etagStore !== void 0) opts.etagStore = config.etagStore;
13519
+ return Ok(new GitHubIssuesTrackerAdapter(opts));
13520
+ }
13521
+ function makeTrackerConflictBody(err, opts = {}) {
13522
+ return {
13523
+ error: err.message,
13524
+ code: "TRACKER_CONFLICT",
13525
+ externalId: err.externalId,
13526
+ conflictedWith: opts.conflictedWith ?? err.diff,
13527
+ refreshHint: "reload-roadmap"
13528
+ };
13529
+ }
13530
+ function loadProjectRoadmapMode(projectRoot) {
13531
+ try {
13532
+ const configPath = path31.join(projectRoot, "harness.config.json");
13533
+ if (!fs32.existsSync(configPath)) return getRoadmapMode(null);
13534
+ const raw = fs32.readFileSync(configPath, "utf-8");
13535
+ const parsed = JSON.parse(raw);
13536
+ return getRoadmapMode(parsed);
13537
+ } catch {
13538
+ return getRoadmapMode(null);
13539
+ }
13540
+ }
13541
+ function loadTrackerClientConfigFromProject(projectRoot) {
13542
+ try {
13543
+ const configPath = path32.join(projectRoot, "harness.config.json");
13544
+ if (!fs33.existsSync(configPath)) {
13545
+ return Err(new Error("harness.config.json not found"));
13546
+ }
13547
+ const cfg = JSON.parse(fs33.readFileSync(configPath, "utf-8"));
13548
+ const tracker = cfg.roadmap?.tracker;
13549
+ if (!tracker) {
13550
+ return Err(
13551
+ new Error(
13552
+ "file-less tracker config missing: set roadmap.tracker.kind in harness.config.json"
13553
+ )
13554
+ );
13555
+ }
13556
+ if (tracker.kind !== "github") {
13557
+ return Err(
13558
+ new Error(`file-less tracker only supports kind: "github" today; got "${tracker.kind}"`)
13559
+ );
13560
+ }
13561
+ if (!tracker.repo) {
13562
+ return Err(
13563
+ new Error(
13564
+ "roadmap.tracker.repo is required for file-less mode (set it in harness.config.json)"
13565
+ )
13566
+ );
13567
+ }
13568
+ return Ok({ kind: "github-issues", repo: tracker.repo });
13569
+ } catch (e) {
13570
+ return Err(e instanceof Error ? e : new Error(String(e)));
13571
+ }
13572
+ }
13573
+ function bodyMetaMatches(a, b) {
13574
+ return eqOpt(a.spec, b.spec) && eqOpt(a.plan, b.plan) && eqOpt(a.priority, b.priority) && eqOpt(a.milestone, b.milestone) && eqList(a.blocked_by, b.blocked_by);
13575
+ }
13576
+ function eqOpt(x, y) {
13577
+ const xx = x ?? null;
13578
+ const yy = y ?? null;
13579
+ return xx === yy;
13580
+ }
13581
+ function eqList(x, y) {
13582
+ const xx = (x ?? []).slice().sort();
13583
+ const yy = (y ?? []).slice().sort();
13584
+ if (xx.length !== yy.length) return false;
13585
+ for (let i = 0; i < xx.length; i++) if (xx[i] !== yy[i]) return false;
13586
+ return true;
13587
+ }
13588
+ function normalizeAt(at) {
13589
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(at)) return at;
13590
+ const d = new Date(at);
13591
+ if (Number.isNaN(d.getTime())) return at;
13592
+ const seconds = Math.floor(d.getTime() / 1e3) * 1e3;
13593
+ return new Date(seconds).toISOString().replace(/\.\d{3}Z$/, "Z");
13594
+ }
13595
+ function hashHistoryEvent(event) {
13596
+ const detailsStr = JSON.stringify(event.details ?? {});
13597
+ const at = normalizeAt(event.at);
13598
+ const input = `${event.type}|${event.actor}|${at}|${detailsStr}`;
13599
+ return createHash4("sha256").update(input).digest("hex").slice(0, 8);
13600
+ }
13601
+ function parseHashFromCommentBody(commentBody) {
13602
+ const match = commentBody.match(HASH_RE);
13603
+ return match ? match[1] : null;
13604
+ }
13605
+ function buildHistoryCommentBody(event) {
13606
+ const hash = hashHistoryEvent(event);
13607
+ return `<!-- harness-history hash:${hash} -->
13608
+ ${JSON.stringify(event)}`;
13609
+ }
13610
+ async function buildMigrationPlan(roadmap, existingFeatures, fetchHistoryHashes, getRawBodyForExternalId) {
13611
+ const plan = {
13612
+ toCreate: [],
13613
+ toUpdate: [],
13614
+ unchanged: [],
13615
+ historyToAppend: [],
13616
+ ambiguous: []
13617
+ };
13618
+ const byExternalId = /* @__PURE__ */ new Map();
13619
+ const byNameLower = /* @__PURE__ */ new Map();
13620
+ for (const t of existingFeatures) {
13621
+ byExternalId.set(t.externalId, t);
13622
+ byNameLower.set(t.name.toLowerCase(), t);
13623
+ }
13624
+ const featureToExternalId = /* @__PURE__ */ new Map();
13625
+ for (const ms of roadmap.milestones) {
13626
+ for (const f of ms.features) {
13627
+ if (f.externalId) featureToExternalId.set(f.name, f.externalId);
13628
+ }
13629
+ }
13630
+ for (const ms of roadmap.milestones) {
13631
+ for (const feature of ms.features) {
13632
+ if (feature.externalId == null) {
13633
+ const existing = byNameLower.get(feature.name.toLowerCase());
13634
+ if (existing) {
13635
+ plan.ambiguous.push({ name: feature.name, existingIssueRef: existing.externalId });
13636
+ } else {
13637
+ plan.toCreate.push({
13638
+ name: feature.name,
13639
+ input: featureToNewInput(feature, ms)
13640
+ });
13641
+ }
13642
+ continue;
13643
+ }
13644
+ const tracked = byExternalId.get(feature.externalId);
13645
+ if (!tracked) {
13646
+ plan.ambiguous.push({
13647
+ name: feature.name,
13648
+ existingIssueRef: `${feature.externalId} (recorded external id not found)`
13649
+ });
13650
+ continue;
13651
+ }
13652
+ const expectedMeta = featureToExpectedMeta(feature, ms);
13653
+ const rawBody = await getRawBodyForExternalId(feature.externalId);
13654
+ const actualMeta = rawBody == null ? {} : parseBodyBlock(rawBody).meta;
13655
+ if (rawBody != null && bodyMetaMatches(actualMeta, expectedMeta)) {
13656
+ plan.unchanged.push({ externalId: feature.externalId, name: feature.name });
13657
+ } else {
13658
+ const diff2 = formatDiff(actualMeta, expectedMeta);
13659
+ const patch = metaToPatch(feature, expectedMeta);
13660
+ plan.toUpdate.push({
13661
+ externalId: feature.externalId,
13662
+ name: feature.name,
13663
+ patch,
13664
+ diff: diff2
13665
+ });
13666
+ }
13667
+ }
13668
+ }
13669
+ for (const record of roadmap.assignmentHistory) {
13670
+ const externalId = featureToExternalId.get(record.feature);
13671
+ if (!externalId) {
13672
+ console.warn(
13673
+ `harness-migrate: cannot resolve external id for assignment-history feature "${record.feature}"; skipping.`
13674
+ );
13675
+ continue;
13676
+ }
13677
+ const type = mapAction(record.action);
13678
+ const event = {
13679
+ type,
13680
+ actor: record.assignee,
13681
+ at: record.date
13682
+ };
13683
+ const hash = hashHistoryEvent(event);
13684
+ const existingHashes = await fetchHistoryHashes(externalId);
13685
+ if (existingHashes.has(hash)) continue;
13686
+ plan.historyToAppend.push({ externalId, event, hash });
13687
+ }
13688
+ return plan;
13689
+ }
13690
+ function featureToExpectedMeta(feature, milestone) {
13691
+ const meta = {};
13692
+ if (feature.spec != null) meta.spec = feature.spec;
13693
+ if (feature.plans.length > 0) meta.plan = feature.plans[0] ?? null;
13694
+ if (feature.blockedBy.length > 0) meta.blocked_by = feature.blockedBy.slice().sort();
13695
+ if (feature.priority != null) meta.priority = feature.priority;
13696
+ if (!milestone.isBacklog) meta.milestone = milestone.name;
13697
+ return meta;
13698
+ }
13699
+ function featureToNewInput(feature, milestone) {
13700
+ const input = {
13701
+ name: feature.name,
13702
+ summary: feature.summary,
13703
+ status: feature.status,
13704
+ spec: feature.spec,
13705
+ plans: feature.plans,
13706
+ blockedBy: feature.blockedBy,
13707
+ priority: feature.priority,
13708
+ milestone: milestone.isBacklog ? null : milestone.name,
13709
+ assignee: feature.assignee
13710
+ };
13711
+ return input;
13712
+ }
13713
+ function metaToPatch(feature, expected) {
13714
+ const patch = {};
13715
+ patch.summary = feature.summary;
13716
+ if (expected.spec !== void 0) patch.spec = expected.spec ?? null;
13717
+ if (expected.plan !== void 0 && expected.plan != null) patch.plans = [expected.plan];
13718
+ if (expected.blocked_by !== void 0) patch.blockedBy = expected.blocked_by ?? [];
13719
+ if (expected.priority !== void 0) patch.priority = expected.priority ?? null;
13720
+ if (expected.milestone !== void 0) patch.milestone = expected.milestone ?? null;
13721
+ return patch;
13722
+ }
13723
+ function formatDiff(actual, expected) {
13724
+ const keys = /* @__PURE__ */ new Set();
13725
+ const collect = (m) => {
13726
+ for (const k of Object.keys(m)) {
13727
+ const v = m[k];
13728
+ if (v !== void 0 && v !== null && !(Array.isArray(v) && v.length === 0)) keys.add(k);
13729
+ }
13730
+ };
13731
+ collect(actual);
13732
+ collect(expected);
13733
+ const changed = [];
13734
+ for (const key of [...keys].sort()) {
13735
+ const a = actual[key];
13736
+ const e = expected[key];
13737
+ if (JSON.stringify(a ?? null) !== JSON.stringify(e ?? null)) changed.push(key);
13738
+ }
13739
+ return changed.join(",");
13740
+ }
13741
+ function mapAction(action) {
13742
+ switch (action) {
13743
+ case "assigned":
13744
+ return "claimed";
13745
+ case "completed":
13746
+ return "completed";
13747
+ case "unassigned":
13748
+ return "released";
13749
+ }
13750
+ }
13751
+ async function runMigrationPlan(plan, deps, opts) {
13752
+ const projectRoot = opts.projectRoot;
13753
+ const roadmapPath = path33.join(projectRoot, "docs", "roadmap.md");
13754
+ const archivedPath = path33.join(projectRoot, "docs", "roadmap.md.archived");
13755
+ const configPath = path33.join(projectRoot, "harness.config.json");
13756
+ const configBackupPath = path33.join(projectRoot, "harness.config.json.pre-migration");
13757
+ const createdSoFar = [];
13758
+ const report = {
13759
+ created: 0,
13760
+ updated: 0,
13761
+ unchanged: plan.unchanged.length,
13762
+ historyAppended: 0,
13763
+ archivedFrom: null,
13764
+ archivedTo: null,
13765
+ configBackup: null,
13766
+ mode: "applied"
13767
+ };
13768
+ if (plan.ambiguous.length > 0) {
13769
+ const names = plan.ambiguous.map((a) => a.name).join(", ");
13770
+ return Ok({
13771
+ ...report,
13772
+ mode: "aborted",
13773
+ abortReason: `ambiguous features (title-collision or dangling external-id): ${names}`
13774
+ });
13775
+ }
13776
+ if (opts.dryRun) {
13777
+ return Ok({ ...report, mode: "dry-run" });
13778
+ }
13779
+ for (const entry of plan.toCreate) {
13780
+ const r = await deps.client.create(entry.input);
13781
+ if (!r.ok) {
13782
+ return Ok({
13783
+ ...report,
13784
+ created: createdSoFar.length,
13785
+ createdSoFar,
13786
+ mode: "aborted",
13787
+ abortReason: `create failed for "${entry.name}": ${r.error.message}`
13788
+ });
13789
+ }
13790
+ createdSoFar.push({ name: entry.name, externalId: r.value.externalId });
13791
+ }
13792
+ report.created = createdSoFar.length;
13793
+ for (const entry of plan.toUpdate) {
13794
+ const r = await deps.client.update(entry.externalId, entry.patch);
13795
+ if (!r.ok) {
13796
+ return Ok({
13797
+ ...report,
13798
+ createdSoFar,
13799
+ mode: "aborted",
13800
+ abortReason: `update failed for "${entry.name}" (${entry.externalId}): ${r.error.message}`
13801
+ });
13802
+ }
13803
+ report.updated++;
13804
+ }
13805
+ for (const entry of plan.historyToAppend) {
13806
+ const r = await deps.client.appendHistory(entry.externalId, entry.event);
13807
+ if (!r.ok) {
13808
+ return Ok({
13809
+ ...report,
13810
+ createdSoFar,
13811
+ mode: "aborted",
13812
+ abortReason: `appendHistory failed for ${entry.externalId} (${entry.event.type}): ${r.error.message}`
13813
+ });
13814
+ }
13815
+ report.historyAppended++;
13816
+ }
13817
+ if (deps.existsFile(archivedPath)) {
13818
+ return Ok({
13819
+ ...report,
13820
+ createdSoFar,
13821
+ mode: "aborted",
13822
+ abortReason: `archive-collision: ${archivedPath} already exists; refusing to overwrite`
13823
+ });
13824
+ }
13825
+ if (deps.existsFile(roadmapPath)) {
13826
+ try {
13827
+ deps.renameFile(roadmapPath, archivedPath);
13828
+ report.archivedFrom = roadmapPath;
13829
+ report.archivedTo = archivedPath;
13830
+ } catch (err) {
13831
+ return Ok({
13832
+ ...report,
13833
+ createdSoFar,
13834
+ mode: "aborted",
13835
+ abortReason: `archive failed: ${err.message}`
13836
+ });
13837
+ }
13838
+ }
13839
+ try {
13840
+ if (!deps.existsFile(configPath)) {
13841
+ return Ok({
13842
+ ...report,
13843
+ createdSoFar,
13844
+ mode: "aborted",
13845
+ abortReason: `config rewrite failed: ${configPath} not found`
13846
+ });
13847
+ }
13848
+ const original = deps.readFile(configPath);
13849
+ deps.writeFile(configBackupPath, original);
13850
+ report.configBackup = configBackupPath;
13851
+ const parsed = JSON.parse(original);
13852
+ const roadmapSection = parsed.roadmap ?? {};
13853
+ roadmapSection.mode = "file-less";
13854
+ parsed.roadmap = roadmapSection;
13855
+ deps.writeFile(configPath, JSON.stringify(parsed, null, 2) + "\n");
13856
+ } catch (err) {
13857
+ return Ok({
13858
+ ...report,
13859
+ createdSoFar,
13860
+ mode: "aborted",
13861
+ abortReason: `config rewrite failed: ${err.message}`
13862
+ });
13863
+ }
13864
+ return Ok(report);
13865
+ }
13235
13866
  function getStatePath() {
13236
13867
  const home = process.env["HOME"] || os.homedir();
13237
- return path32.join(home, ".harness", "update-check.json");
13868
+ return path36.join(home, ".harness", "update-check.json");
13238
13869
  }
13239
13870
  function isUpdateCheckEnabled(configInterval) {
13240
13871
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -13247,7 +13878,7 @@ function shouldRunCheck(state, intervalMs) {
13247
13878
  }
13248
13879
  function readCheckState() {
13249
13880
  try {
13250
- const raw = fs33.readFileSync(getStatePath(), "utf-8");
13881
+ const raw = fs36.readFileSync(getStatePath(), "utf-8");
13251
13882
  const parsed = JSON.parse(raw);
13252
13883
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
13253
13884
  const state = parsed;
@@ -13264,7 +13895,7 @@ function readCheckState() {
13264
13895
  }
13265
13896
  function spawnBackgroundCheck(currentVersion) {
13266
13897
  const statePath = getStatePath();
13267
- const stateDir = path32.dirname(statePath);
13898
+ const stateDir = path36.dirname(statePath);
13268
13899
  const script = `
13269
13900
  const { execSync } = require('child_process');
13270
13901
  const fs = require('fs');
@@ -13507,14 +14138,14 @@ function getModelPrice(model, dataset) {
13507
14138
  return pricing;
13508
14139
  }
13509
14140
  function getCachePath(projectRoot) {
13510
- return path33.join(projectRoot, ".harness", "cache", "pricing.json");
14141
+ return path37.join(projectRoot, ".harness", "cache", "pricing.json");
13511
14142
  }
13512
14143
  function getStalenessMarkerPath(projectRoot) {
13513
- return path33.join(projectRoot, ".harness", "cache", "staleness-marker.json");
14144
+ return path37.join(projectRoot, ".harness", "cache", "staleness-marker.json");
13514
14145
  }
13515
14146
  async function readDiskCache(projectRoot) {
13516
14147
  try {
13517
- const raw = await fs34.readFile(getCachePath(projectRoot), "utf-8");
14148
+ const raw = await fs37.readFile(getCachePath(projectRoot), "utf-8");
13518
14149
  return JSON.parse(raw);
13519
14150
  } catch {
13520
14151
  return null;
@@ -13522,8 +14153,8 @@ async function readDiskCache(projectRoot) {
13522
14153
  }
13523
14154
  async function writeDiskCache(projectRoot, data) {
13524
14155
  const cachePath = getCachePath(projectRoot);
13525
- await fs34.mkdir(path33.dirname(cachePath), { recursive: true });
13526
- await fs34.writeFile(cachePath, JSON.stringify(data, null, 2));
14156
+ await fs37.mkdir(path37.dirname(cachePath), { recursive: true });
14157
+ await fs37.writeFile(cachePath, JSON.stringify(data, null, 2));
13527
14158
  }
13528
14159
  async function fetchFromNetwork() {
13529
14160
  try {
@@ -13550,7 +14181,7 @@ function loadFallbackDataset() {
13550
14181
  async function checkAndWarnStaleness(projectRoot) {
13551
14182
  const markerPath = getStalenessMarkerPath(projectRoot);
13552
14183
  try {
13553
- const raw = await fs34.readFile(markerPath, "utf-8");
14184
+ const raw = await fs37.readFile(markerPath, "utf-8");
13554
14185
  const marker = JSON.parse(raw);
13555
14186
  const firstUse = new Date(marker.firstFallbackUse).getTime();
13556
14187
  const now = Date.now();
@@ -13562,8 +14193,8 @@ async function checkAndWarnStaleness(projectRoot) {
13562
14193
  }
13563
14194
  } catch {
13564
14195
  try {
13565
- await fs34.mkdir(path33.dirname(markerPath), { recursive: true });
13566
- await fs34.writeFile(
14196
+ await fs37.mkdir(path37.dirname(markerPath), { recursive: true });
14197
+ await fs37.writeFile(
13567
14198
  markerPath,
13568
14199
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
13569
14200
  );
@@ -13573,7 +14204,7 @@ async function checkAndWarnStaleness(projectRoot) {
13573
14204
  }
13574
14205
  async function clearStalenessMarker(projectRoot) {
13575
14206
  try {
13576
- await fs34.unlink(getStalenessMarkerPath(projectRoot));
14207
+ await fs37.unlink(getStalenessMarkerPath(projectRoot));
13577
14208
  } catch {
13578
14209
  }
13579
14210
  }
@@ -13810,10 +14441,10 @@ function parseLine(line, lineNumber) {
13810
14441
  return record;
13811
14442
  }
13812
14443
  function readCostRecords(projectRoot) {
13813
- const costsFile = path34.join(projectRoot, ".harness", "metrics", "costs.jsonl");
14444
+ const costsFile = path38.join(projectRoot, ".harness", "metrics", "costs.jsonl");
13814
14445
  let raw;
13815
14446
  try {
13816
- raw = fs35.readFileSync(costsFile, "utf-8");
14447
+ raw = fs38.readFileSync(costsFile, "utf-8");
13817
14448
  } catch {
13818
14449
  return [];
13819
14450
  }
@@ -13863,7 +14494,7 @@ function parseCCLine(line, filePath, lineNumber) {
13863
14494
  entry = JSON.parse(line);
13864
14495
  } catch {
13865
14496
  console.warn(
13866
- `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path35.basename(filePath)}`
14497
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path39.basename(filePath)}`
13867
14498
  );
13868
14499
  return null;
13869
14500
  }
@@ -13877,7 +14508,7 @@ function parseCCLine(line, filePath, lineNumber) {
13877
14508
  function readCCFile(filePath) {
13878
14509
  let raw;
13879
14510
  try {
13880
- raw = fs36.readFileSync(filePath, "utf-8");
14511
+ raw = fs39.readFileSync(filePath, "utf-8");
13881
14512
  } catch {
13882
14513
  return [];
13883
14514
  }
@@ -13899,10 +14530,10 @@ function readCCFile(filePath) {
13899
14530
  }
13900
14531
  function parseCCRecords() {
13901
14532
  const homeDir = process.env.HOME ?? os2.homedir();
13902
- const projectsDir = path35.join(homeDir, ".claude", "projects");
14533
+ const projectsDir = path39.join(homeDir, ".claude", "projects");
13903
14534
  let projectDirs;
13904
14535
  try {
13905
- projectDirs = fs36.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path35.join(projectsDir, d.name));
14536
+ projectDirs = fs39.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path39.join(projectsDir, d.name));
13906
14537
  } catch {
13907
14538
  return [];
13908
14539
  }
@@ -13910,7 +14541,7 @@ function parseCCRecords() {
13910
14541
  for (const dir of projectDirs) {
13911
14542
  let files;
13912
14543
  try {
13913
- files = fs36.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path35.join(dir, f));
14544
+ files = fs39.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path39.join(dir, f));
13914
14545
  } catch {
13915
14546
  continue;
13916
14547
  }
@@ -13938,10 +14569,10 @@ function parseLine2(line, lineNumber) {
13938
14569
  }
13939
14570
  }
13940
14571
  function readAdoptionRecords(projectRoot) {
13941
- const adoptionFile = path36.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
14572
+ const adoptionFile = path40.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
13942
14573
  let raw;
13943
14574
  try {
13944
- raw = fs37.readFileSync(adoptionFile, "utf-8");
14575
+ raw = fs40.readFileSync(adoptionFile, "utf-8");
13945
14576
  } catch {
13946
14577
  return [];
13947
14578
  }
@@ -14089,25 +14720,25 @@ function resolveStability(contentType) {
14089
14720
  return STABILITY_LOOKUP[contentType] ?? "ephemeral";
14090
14721
  }
14091
14722
  function getOrCreateInstallId(projectRoot) {
14092
- const harnessDir = path37.join(projectRoot, ".harness");
14093
- const installIdFile = path37.join(harnessDir, ".install-id");
14723
+ const harnessDir = path41.join(projectRoot, ".harness");
14724
+ const installIdFile = path41.join(harnessDir, ".install-id");
14094
14725
  const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
14095
14726
  try {
14096
- const existing = fs38.readFileSync(installIdFile, "utf-8").trim();
14727
+ const existing = fs41.readFileSync(installIdFile, "utf-8").trim();
14097
14728
  if (UUID_V4_RE.test(existing)) {
14098
14729
  return existing;
14099
14730
  }
14100
14731
  } catch {
14101
14732
  }
14102
14733
  const id = crypto4.randomUUID();
14103
- fs38.mkdirSync(harnessDir, { recursive: true });
14104
- fs38.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
14734
+ fs41.mkdirSync(harnessDir, { recursive: true });
14735
+ fs41.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
14105
14736
  return id;
14106
14737
  }
14107
14738
  function parseIdentityFromTelemetryFile(filePath) {
14108
14739
  const identity = {};
14109
14740
  try {
14110
- const raw = fs39.readFileSync(filePath, "utf-8");
14741
+ const raw = fs42.readFileSync(filePath, "utf-8");
14111
14742
  const parsed = JSON.parse(raw);
14112
14743
  const src = parsed?.identity;
14113
14744
  if (!src || typeof src !== "object") return identity;
@@ -14120,7 +14751,7 @@ function parseIdentityFromTelemetryFile(filePath) {
14120
14751
  }
14121
14752
  function readProjectNameFallback(configPath) {
14122
14753
  try {
14123
- const raw = fs39.readFileSync(configPath, "utf-8");
14754
+ const raw = fs42.readFileSync(configPath, "utf-8");
14124
14755
  const config = JSON.parse(raw);
14125
14756
  return typeof config?.name === "string" ? config.name : void 0;
14126
14757
  } catch {
@@ -14141,10 +14772,10 @@ function readGitAliasFallback(cwd) {
14141
14772
  }
14142
14773
  }
14143
14774
  function readIdentity(projectRoot) {
14144
- const filePath = path38.join(projectRoot, ".harness", "telemetry.json");
14775
+ const filePath = path42.join(projectRoot, ".harness", "telemetry.json");
14145
14776
  const identity = parseIdentityFromTelemetryFile(filePath);
14146
14777
  if (!identity.project) {
14147
- const fallbackProject = readProjectNameFallback(path38.join(projectRoot, "harness.config.json"));
14778
+ const fallbackProject = readProjectNameFallback(path42.join(projectRoot, "harness.config.json"));
14148
14779
  if (fallbackProject) identity.project = fallbackProject;
14149
14780
  }
14150
14781
  if (!identity.alias) {
@@ -14191,7 +14822,7 @@ function collectEvents(projectRoot, consent) {
14191
14822
  );
14192
14823
  }
14193
14824
  function sleep3(ms) {
14194
- return new Promise((resolve8) => setTimeout(resolve8, ms));
14825
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
14195
14826
  }
14196
14827
  async function send(events, apiKey) {
14197
14828
  if (events.length === 0) return;
@@ -14221,12 +14852,12 @@ function acquireCompoundLock(category, opts = {}) {
14221
14852
  );
14222
14853
  }
14223
14854
  const cwd = opts.cwd ?? process.cwd();
14224
- const lockDir = path39.join(cwd, ".harness", "locks");
14225
- fs40.mkdirSync(lockDir, { recursive: true });
14226
- const lockPath = path39.join(lockDir, `compound-${category}.lock`);
14855
+ const lockDir = path43.join(cwd, ".harness", "locks");
14856
+ fs43.mkdirSync(lockDir, { recursive: true });
14857
+ const lockPath = path43.join(lockDir, `compound-${category}.lock`);
14227
14858
  let fd;
14228
14859
  try {
14229
- fd = fs40.openSync(lockPath, "wx");
14860
+ fd = fs43.openSync(lockPath, "wx");
14230
14861
  } catch (e) {
14231
14862
  const err = e;
14232
14863
  if (err.code === "EEXIST") {
@@ -14235,14 +14866,14 @@ function acquireCompoundLock(category, opts = {}) {
14235
14866
  }
14236
14867
  throw err;
14237
14868
  }
14238
- fs40.writeSync(fd, String(process.pid));
14239
- fs40.closeSync(fd);
14869
+ fs43.writeSync(fd, String(process.pid));
14870
+ fs43.closeSync(fd);
14240
14871
  let released = false;
14241
14872
  const release = () => {
14242
14873
  if (released) return;
14243
14874
  released = true;
14244
14875
  try {
14245
- fs40.unlinkSync(lockPath);
14876
+ fs43.unlinkSync(lockPath);
14246
14877
  } catch {
14247
14878
  }
14248
14879
  process.removeListener("exit", onExit);
@@ -14268,7 +14899,7 @@ function acquireCompoundLock(category, opts = {}) {
14268
14899
  }
14269
14900
  function readHolderPid(lockPath) {
14270
14901
  try {
14271
- const raw = fs40.readFileSync(lockPath, "utf-8").trim();
14902
+ const raw = fs43.readFileSync(lockPath, "utf-8").trim();
14272
14903
  const n = Number.parseInt(raw, 10);
14273
14904
  return Number.isFinite(n) ? n : -1;
14274
14905
  } catch {
@@ -14293,10 +14924,10 @@ function assertSanitized(value) {
14293
14924
  }
14294
14925
  function writePulseConfig(config, opts) {
14295
14926
  PulseConfigSchema.parse(config);
14296
- if (!fs41.existsSync(opts.configPath)) {
14927
+ if (!fs44.existsSync(opts.configPath)) {
14297
14928
  throw new Error(`harness.config.json not found at ${opts.configPath}`);
14298
14929
  }
14299
- const raw = fs41.readFileSync(opts.configPath, "utf-8");
14930
+ const raw = fs44.readFileSync(opts.configPath, "utf-8");
14300
14931
  let parsed;
14301
14932
  try {
14302
14933
  parsed = JSON.parse(raw);
@@ -14304,18 +14935,18 @@ function writePulseConfig(config, opts) {
14304
14935
  throw new Error(`Invalid JSON in ${opts.configPath}: ${e.message}`, { cause: e });
14305
14936
  }
14306
14937
  const bakPath = `${opts.configPath}.bak`;
14307
- if (!opts.skipBackup && !fs41.existsSync(bakPath)) {
14308
- fs41.writeFileSync(bakPath, raw, "utf-8");
14938
+ if (!opts.skipBackup && !fs44.existsSync(bakPath)) {
14939
+ fs44.writeFileSync(bakPath, raw, "utf-8");
14309
14940
  }
14310
14941
  parsed.pulse = config;
14311
14942
  const serialized = JSON.stringify(parsed, null, 2) + "\n";
14312
14943
  const tmpPath = `${opts.configPath}.tmp-${process.pid}`;
14313
- fs41.writeFileSync(tmpPath, serialized, "utf-8");
14944
+ fs44.writeFileSync(tmpPath, serialized, "utf-8");
14314
14945
  try {
14315
- fs41.renameSync(tmpPath, opts.configPath);
14946
+ fs44.renameSync(tmpPath, opts.configPath);
14316
14947
  } catch (e) {
14317
14948
  try {
14318
- fs41.unlinkSync(tmpPath);
14949
+ fs44.unlinkSync(tmpPath);
14319
14950
  } catch {
14320
14951
  }
14321
14952
  throw e;
@@ -14323,12 +14954,12 @@ function writePulseConfig(config, opts) {
14323
14954
  }
14324
14955
  function seedFromStrategy(opts = {}) {
14325
14956
  const cwd = opts.cwd ?? process.cwd();
14326
- const strategyPath = path40.join(cwd, "STRATEGY.md");
14957
+ const strategyPath = path44.join(cwd, "STRATEGY.md");
14327
14958
  const warnings = [];
14328
- if (!fs42.existsSync(strategyPath)) {
14959
+ if (!fs45.existsSync(strategyPath)) {
14329
14960
  return { name: null, keyMetrics: [], warnings: ["STRATEGY.md not found"] };
14330
14961
  }
14331
- const raw = fs42.readFileSync(strategyPath, "utf-8");
14962
+ const raw = fs45.readFileSync(strategyPath, "utf-8");
14332
14963
  let name = null;
14333
14964
  const fmMatch = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
14334
14965
  const fm = fmMatch?.[1];
@@ -14480,7 +15111,7 @@ function loadTemplate() {
14480
15111
  const url = import.meta?.url;
14481
15112
  if (!url) return INLINE_TEMPLATE;
14482
15113
  const here = dirname18(fileURLToPath(url));
14483
- return readFileSync36(join572(here, "template.md"), "utf-8");
15114
+ return readFileSync38(join612(here, "template.md"), "utf-8");
14484
15115
  } catch {
14485
15116
  return INLINE_TEMPLATE;
14486
15117
  }
@@ -14669,11 +15300,11 @@ async function computeHotspots(opts) {
14669
15300
  }
14670
15301
  const counts = /* @__PURE__ */ new Map();
14671
15302
  for (const line of stdout.split("\n")) {
14672
- const path42 = line.trim();
14673
- if (path42.length === 0) continue;
14674
- counts.set(path42, (counts.get(path42) ?? 0) + 1);
15303
+ const path46 = line.trim();
15304
+ if (path46.length === 0) continue;
15305
+ counts.set(path46, (counts.get(path46) ?? 0) + 1);
14675
15306
  }
14676
- return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([path42, churn]) => ({ path: path42, churn })).sort((a, b) => b.churn - a.churn);
15307
+ return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([path46, churn]) => ({ path: path46, churn })).sort((a, b) => b.churn - a.churn);
14677
15308
  }
14678
15309
  function tokenize(text) {
14679
15310
  return new Set(
@@ -14683,12 +15314,12 @@ function tokenize(text) {
14683
15314
  async function* walk22(dir) {
14684
15315
  let entries;
14685
15316
  try {
14686
- entries = await fs43.readdir(dir, { withFileTypes: true });
15317
+ entries = await fs46.readdir(dir, { withFileTypes: true });
14687
15318
  } catch {
14688
15319
  return;
14689
15320
  }
14690
15321
  for (const e of entries) {
14691
- const p = path41.join(dir, e.name);
15322
+ const p = path45.join(dir, e.name);
14692
15323
  if (e.isDirectory()) yield* walk22(p);
14693
15324
  else if (e.isFile() && e.name.endsWith(".md")) yield p;
14694
15325
  }
@@ -14697,11 +15328,11 @@ async function readDocumentedTokens(solutionsDir) {
14697
15328
  const docs = [];
14698
15329
  for (const [track, categories] of TRACK_CATEGORIES2) {
14699
15330
  for (const category of categories) {
14700
- const dir = path41.join(solutionsDir, track, category);
15331
+ const dir = path45.join(solutionsDir, track, category);
14701
15332
  for await (const file of walk22(dir)) {
14702
- const raw = await fs43.readFile(file, "utf-8");
15333
+ const raw = await fs46.readFile(file, "utf-8");
14703
15334
  const m = /^#\s+(.+)$/m.exec(raw);
14704
- const title = m?.[1] ?? path41.basename(file, ".md");
15335
+ const title = m?.[1] ?? path45.basename(file, ".md");
14705
15336
  docs.push(tokenize(title));
14706
15337
  }
14707
15338
  }
@@ -14764,16 +15395,20 @@ function assembleCandidateReport(input) {
14764
15395
  }
14765
15396
  return lines.join("\n");
14766
15397
  }
14767
- var VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE, BugTrackSchema, KnowledgeTrackSchema, BaseFrontmatter, SolutionDocFrontmatterSchema, TRACK_CATEGORIES, DEFAULT_AGNIX_TIMEOUT_MS, HARNESS_AGNIX_DISABLE, HARNESS_AGNIX_BIN, CLAUDE_MD_WARN_BYTES, CLAUDE_MD_ERROR_BYTES, CONTEXT_FILES, MIN_DESCRIPTION_LENGTH, SETTINGS_PATHS, KNOWN_HOOK_EVENTS, MCP_SETTINGS_FILES, REQUIRED_SECTIONS, SETTINGS_FILES, KNOWN_AGNIX_TOOLS, DEFAULT_SECTIONS, DEFAULT_RATIOS, NODE_TYPE_TO_CATEGORY, SECTION_LEVEL_MAP, DEFAULT_LEVEL, DEFAULT_BUDGET, DEFAULT_LOADER_CONFIG, LEVEL_FRACTIONS, PHASE_PRIORITIES, ManifestSchema, BundleConstraintsSchema, BundleSchema, ContributionsSchema, LockfilePackageSchema, LockfileSchema, SharableLayerSchema, SharableForbiddenImportSchema, SharableBoundaryConfigSchema, SharableSecurityRulesSchema, VALID_SCOPES, LINE_PATTERN, START_PATTERN, END_PATTERN, CODE_BLOCK_LANGUAGES, DEFAULT_DRIFT_CONFIG, JS_EXT_FALLBACKS2, FILE_TYPES, EXPORT_TYPES, CONVENTION_DESCRIPTIONS, RULE_CHECKERS, EntropyAnalyzer, readFile62, writeFile32, unlink22, mkdir22, copyFile2, DEFAULT_FIX_CONFIG, EXPORT_TYPE_KEYWORD, ALWAYS_UNSAFE_TYPES, idCounter, DEAD_CODE_FIX_ACTIONS, MustExportRuleSchema, MustExportDefaultRuleSchema, NoExportRuleSchema, MustImportRuleSchema, NoImportRuleSchema, NamingRuleSchema, MaxExportsRuleSchema, MaxLinesRuleSchema, RequireJSDocRuleSchema, RuleSchema, ConfigPatternSchema, PatternConfigSchema, DriftConfigSchema, DeadCodeConfigSchema, EntropyConfigSchema, BaselineManager, BenchmarkRunner, RegressionDetector, SKIP_DIRS, SOURCE_EXTENSIONS, FUNCTION_DECL_RE, CONST_DECL_RE, CriticalPathResolver, NoOpTelemetryAdapter, NoOpExecutor, ConsoleSink, feedbackConfig, ChecklistBuilder, AgentActionEmitter, globalEmitter, FileSink, NoOpSink, CategorySnapshotSchema, TimelineSnapshotSchema, TimelineFileSchema, TrendLineSchema, TrendResultSchema, DEFAULT_STABILITY_THRESHOLDS, ALL_CATEGORIES, TimelineManager, ConfidenceTierSchema, RegressionResultSchema, DirectionSchema, CategoryForecastSchema, SpecImpactSignalsSchema, SpecImpactEstimateSchema, ContributingFeatureSchema, AdjustedForecastSchema, PredictionWarningSchema, StabilityForecastSchema, PredictionResultSchema, PredictionOptionsSchema, VALID_STATUSES, EM_DASH, VALID_PRIORITIES, ALL_CATEGORIES2, DIRECTION_THRESHOLD, PredictionEngine, DEFAULT_COEFFICIENTS, SpecImpactEstimator, EMPTY_HISTORY, ViolationHistoryManager, NORMALIZERS, MAX_SAMPLES, DEFAULT_WINDOW_WEEKS, DEFAULT_MIN_OCCURRENCES, FailureEntrySchema, HandoffSchema, GateCheckSchema, GateResultSchema, GateConfigSchema, HarnessStateSchema, DEFAULT_STATE, StreamInfoSchema, StreamIndexSchema, DEFAULT_STREAM_INDEX, HARNESS_DIR, STATE_FILE, LEARNINGS_FILE, FAILURES_FILE, HANDOFF_FILE, GATE_CONFIG_FILE, INDEX_FILE, SESSIONS_DIR, SESSION_INDEX_FILE, SUMMARY_FILE, SESSION_STATE_FILE, ARCHIVE_DIR, CONTENT_HASHES_FILE, EVENTS_FILE, STREAMS_DIR, STREAM_NAME_REGEX, branchCache, BRANCH_CACHE_TTL_MS, STATE_FILES, MAX_CACHE_ENTRIES, learningsCacheMap, WEIGHTS, LOCK_RETRIES, LOCK_BACKOFFS, PROMOTABLE_OUTCOMES, failuresCacheMap, FAILURE_LINE_REGEX, SAFE_GATE_COMMAND, SkillEventSchema, knownHashesCache, EVENT_FORMATTERS, RuleRegistry, DEFAULT_SECURITY_CONFIG, RuleOverrideSchema, SecurityConfigSchema, secretRules, injectionRules, xssRules, cryptoRules, pathTraversalRules, networkRules, deserializationRules, agentConfigRules, mcpRules, insecureDefaultsRules, sharpEdgesRules, nodeRules, expressRules, reactRules, goRules, SecurityScanner, hiddenUnicodePatterns, reRolingPatterns, permissionEscalationPatterns, encodedPayloadPatterns, indirectInjectionPatterns, contextManipulationPatterns, socialEngineeringPatterns, suspiciousPatterns, ALL_PATTERNS, DESTRUCTIVE_BASH, TAINT_DURATION_MS, DEFAULT_SESSION_ID, SecurityCategorySnapshotSchema, SupplyChainSnapshotSchema, SecurityTimelineSnapshotSchema, FindingLifecycleSchema, SecurityTimelineFileSchema, DirectionSchema2, SecurityTrendLineSchema, TrendAttributionSchema, SecurityTrendResultSchema, TimeToFixStatsSchema, TimeToFixResultSchema, EMPTY_SUPPLY_CHAIN, SecurityTimelineManager, ALL_CHECKS, STATUS_ICONS, CINotifier, ExclusionSet, PREFIX_PATTERNS, TEST_FILE_PATTERN, MD_FILE_PATTERN, ALL_DOMAINS, SECURITY_PATTERNS, JS_EXT_FALLBACKS22, SEVERITY_RANK, SEVERITY_ORDER, SEVERITY_LABELS, VALIDATED_BY_RANK, VALIDATION_SCORES, DOMAIN_BASELINES, FACTOR_WEIGHTS, EVIDENCE_SATURATION, CORROBORATED_AGREEMENT, STANDALONE_AGREEMENT, AGREEMENT_LINE_GAP, COMPLIANCE_DESCRIPTOR, EXPORT_RE, BUG_DETECTION_DESCRIPTOR, SECURITY_DESCRIPTOR, EVAL_PATTERN, SECRET_PATTERNS, SQL_CONCAT_PATTERN, SHELL_EXEC_PATTERN, ARCHITECTURE_DESCRIPTOR, LARGE_FILE_THRESHOLD, VIOLATION_FILE_RE, LEARNINGS_DESCRIPTOR, RELEVANCE_THRESHOLD, AGENT_DESCRIPTORS, AGENT_RUNNERS, DOWNGRADE_MAP, DEFAULT_PROVIDER_TIERS, SMALL_SUGGESTION_LINE_LIMIT, FILE_LINE_RANGE_PATTERN, FILE_LINE_PATTERN, FILE_ONLY_PATTERN, SECURITY_SENSITIVE_PATH, MIGRATION_PATH, TEST_PATH, CONFIG_PATH, STAGE_DOMAINS, EM_DASH2, STATUS_RANK, RETRY_DEFAULTS, GitHubIssuesSyncAdapter, syncMutex, PRIORITY_RANK, POSITION_WEIGHT, DEPENDENTS_WEIGHT, AFFINITY_WEIGHT, InteractionTypeSchema, QuestionSchema, ConfirmationSchema, TransitionSchema, EmitInteractionInputSchema, ProjectScanner, SHELL_TEMPLATE, STYLES, SCRIPTS, MockLLMService, llmService, ContentPipeline, BlueprintGenerator, TOKENS_PER_MILLION, fallback_default, LITELLM_PRICING_URL, CACHE_TTL_MS, STALENESS_WARNING_DAYS, MICRODOLLARS_PER_DOLLAR, TOKENS_PER_MILLION2, StructuralStrategy, DEFAULT_TOKEN_BUDGET, CHARS_PER_TOKEN, TRUNCATION_MARKER, TruncationStrategy, CompactionPipeline, STABILITY_LOOKUP, TIER_ORDER, AnthropicCacheAdapter, TIER_ORDER2, OpenAICacheAdapter, TIER_ORDER3, CACHED_CONTENT_MARKER, GeminiCacheAdapter, VERSION, POSTHOG_BATCH_URL, MAX_ATTEMPTS, TIMEOUT_MS, CompoundLockHeldError, KNOWN_CATEGORIES, ALLOWED_FIELD_KEYS, PII_TOKENS, PII_FIELD_DENYLIST, PII_LINE_RE, ALLOWED_SET, PulseAdapterAlreadyRegisteredError, REGISTRY, MOCK_ADAPTER_NAME, ALLOWED_FIELD_SET, adapter, LOOKBACK_RE, MAX_LINES, INLINE_TEMPLATE, TRUNCATION_MARKER2, execFileAsync, FIX_RE, EMPTY_REPO_RE, execFileAsync2, TRACK_CATEGORIES2, STOPWORDS, KEYWORD_TO_CATEGORY;
15398
+ var VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE, BugTrackSchema, KnowledgeTrackSchema, BaseFrontmatter, SolutionDocFrontmatterSchema, TRACK_CATEGORIES, DEFAULT_AGNIX_TIMEOUT_MS, HARNESS_AGNIX_DISABLE, HARNESS_AGNIX_BIN, CLAUDE_MD_WARN_BYTES, CLAUDE_MD_ERROR_BYTES, CONTEXT_FILES, MIN_DESCRIPTION_LENGTH, SETTINGS_PATHS, KNOWN_HOOK_EVENTS, MCP_SETTINGS_FILES, REQUIRED_SECTIONS, SETTINGS_FILES, KNOWN_AGNIX_TOOLS, DEFAULT_SECTIONS, DEFAULT_RATIOS, NODE_TYPE_TO_CATEGORY, SECTION_LEVEL_MAP, DEFAULT_LEVEL, DEFAULT_BUDGET, DEFAULT_LOADER_CONFIG, LEVEL_FRACTIONS, PHASE_PRIORITIES, ManifestSchema, BundleConstraintsSchema, BundleSchema, ContributionsSchema, LockfilePackageSchema, LockfileSchema, SharableLayerSchema, SharableForbiddenImportSchema, SharableBoundaryConfigSchema, SharableSecurityRulesSchema, VALID_SCOPES, LINE_PATTERN, START_PATTERN, END_PATTERN, CODE_BLOCK_LANGUAGES, DEFAULT_DRIFT_CONFIG, JS_EXT_FALLBACKS2, FILE_TYPES, EXPORT_TYPES, CONVENTION_DESCRIPTIONS, RULE_CHECKERS, EntropyAnalyzer, readFile62, writeFile32, unlink22, mkdir22, copyFile2, DEFAULT_FIX_CONFIG, EXPORT_TYPE_KEYWORD, ALWAYS_UNSAFE_TYPES, idCounter, DEAD_CODE_FIX_ACTIONS, MustExportRuleSchema, MustExportDefaultRuleSchema, NoExportRuleSchema, MustImportRuleSchema, NoImportRuleSchema, NamingRuleSchema, MaxExportsRuleSchema, MaxLinesRuleSchema, RequireJSDocRuleSchema, RuleSchema, ConfigPatternSchema, PatternConfigSchema, DriftConfigSchema, DeadCodeConfigSchema, EntropyConfigSchema, BaselineManager, BenchmarkRunner, RegressionDetector, SKIP_DIRS, SOURCE_EXTENSIONS, FUNCTION_DECL_RE, CONST_DECL_RE, CriticalPathResolver, NoOpTelemetryAdapter, NoOpExecutor, ConsoleSink, feedbackConfig, ChecklistBuilder, AgentActionEmitter, globalEmitter, FileSink, NoOpSink, CategorySnapshotSchema, TimelineSnapshotSchema, TimelineFileSchema, TrendLineSchema, TrendResultSchema, DEFAULT_STABILITY_THRESHOLDS, ALL_CATEGORIES, TimelineManager, ConfidenceTierSchema, RegressionResultSchema, DirectionSchema, CategoryForecastSchema, SpecImpactSignalsSchema, SpecImpactEstimateSchema, ContributingFeatureSchema, AdjustedForecastSchema, PredictionWarningSchema, StabilityForecastSchema, PredictionResultSchema, PredictionOptionsSchema, VALID_STATUSES, EM_DASH, VALID_PRIORITIES, ALL_CATEGORIES2, DIRECTION_THRESHOLD, PredictionEngine, DEFAULT_COEFFICIENTS, SpecImpactEstimator, EMPTY_HISTORY, ViolationHistoryManager, NORMALIZERS, MAX_SAMPLES, DEFAULT_WINDOW_WEEKS, DEFAULT_MIN_OCCURRENCES, FailureEntrySchema, HandoffSchema, GateCheckSchema, GateResultSchema, GateConfigSchema, HarnessStateSchema, DEFAULT_STATE, StreamInfoSchema, StreamIndexSchema, DEFAULT_STREAM_INDEX, HARNESS_DIR, STATE_FILE, LEARNINGS_FILE, FAILURES_FILE, HANDOFF_FILE, GATE_CONFIG_FILE, INDEX_FILE, SESSIONS_DIR, SESSION_INDEX_FILE, SUMMARY_FILE, SESSION_STATE_FILE, ARCHIVE_DIR, CONTENT_HASHES_FILE, EVENTS_FILE, STREAMS_DIR, STREAM_NAME_REGEX, branchCache, BRANCH_CACHE_TTL_MS, STATE_FILES, MAX_CACHE_ENTRIES, learningsCacheMap, WEIGHTS, LOCK_RETRIES, LOCK_BACKOFFS, PROMOTABLE_OUTCOMES, failuresCacheMap, FAILURE_LINE_REGEX, SAFE_GATE_COMMAND, SkillEventSchema, knownHashesCache, EVENT_FORMATTERS, RuleRegistry, DEFAULT_SECURITY_CONFIG, RuleOverrideSchema, SecurityConfigSchema, secretRules, injectionRules, xssRules, cryptoRules, pathTraversalRules, networkRules, deserializationRules, agentConfigRules, mcpRules, insecureDefaultsRules, sharpEdgesRules, nodeRules, expressRules, reactRules, goRules, SecurityScanner, hiddenUnicodePatterns, reRolingPatterns, permissionEscalationPatterns, encodedPayloadPatterns, indirectInjectionPatterns, contextManipulationPatterns, socialEngineeringPatterns, suspiciousPatterns, ALL_PATTERNS, DESTRUCTIVE_BASH, TAINT_DURATION_MS, DEFAULT_SESSION_ID, SecurityCategorySnapshotSchema, SupplyChainSnapshotSchema, SecurityTimelineSnapshotSchema, FindingLifecycleSchema, SecurityTimelineFileSchema, DirectionSchema2, SecurityTrendLineSchema, TrendAttributionSchema, SecurityTrendResultSchema, TimeToFixStatsSchema, TimeToFixResultSchema, EMPTY_SUPPLY_CHAIN, SecurityTimelineManager, ALL_CHECKS, STATUS_ICONS, CINotifier, ExclusionSet, PREFIX_PATTERNS, TEST_FILE_PATTERN, MD_FILE_PATTERN, ALL_DOMAINS, SECURITY_PATTERNS, JS_EXT_FALLBACKS22, SEVERITY_RANK, SEVERITY_ORDER, SEVERITY_LABELS, VALIDATED_BY_RANK, VALIDATION_SCORES, DOMAIN_BASELINES, FACTOR_WEIGHTS, EVIDENCE_SATURATION, CORROBORATED_AGREEMENT, STANDALONE_AGREEMENT, AGREEMENT_LINE_GAP, COMPLIANCE_DESCRIPTOR, EXPORT_RE, BUG_DETECTION_DESCRIPTOR, SECURITY_DESCRIPTOR, EVAL_PATTERN, SECRET_PATTERNS, SQL_CONCAT_PATTERN, SHELL_EXEC_PATTERN, ARCHITECTURE_DESCRIPTOR, LARGE_FILE_THRESHOLD, VIOLATION_FILE_RE, LEARNINGS_DESCRIPTOR, RELEVANCE_THRESHOLD, AGENT_DESCRIPTORS, AGENT_RUNNERS, DOWNGRADE_MAP, DEFAULT_PROVIDER_TIERS, SMALL_SUGGESTION_LINE_LIMIT, FILE_LINE_RANGE_PATTERN, FILE_LINE_PATTERN, FILE_ONLY_PATTERN, SECURITY_SENSITIVE_PATH, MIGRATION_PATH, TEST_PATH, CONFIG_PATH, STAGE_DOMAINS, EM_DASH2, STATUS_RANK, RETRY_DEFAULTS, GitHubIssuesSyncAdapter, syncMutex, PRIORITY_RANK, PRIORITY_RANK2, POSITION_WEIGHT, DEPENDENTS_WEIGHT, AFFINITY_WEIGHT, ConflictError, DEFAULTS, GitHubHttp, ETagStore, START_MARKER, END_MARKER, SERIALIZER_FIELDS, GitHubIssuesTrackerAdapter, migrate_exports, HASH_RE, InteractionTypeSchema, QuestionSchema, ConfirmationSchema, TransitionSchema, EmitInteractionInputSchema, ProjectScanner, SHELL_TEMPLATE, STYLES, SCRIPTS, MockLLMService, llmService, ContentPipeline, BlueprintGenerator, TOKENS_PER_MILLION, fallback_default, LITELLM_PRICING_URL, CACHE_TTL_MS, STALENESS_WARNING_DAYS, MICRODOLLARS_PER_DOLLAR, TOKENS_PER_MILLION2, StructuralStrategy, DEFAULT_TOKEN_BUDGET, CHARS_PER_TOKEN, TRUNCATION_MARKER, TruncationStrategy, CompactionPipeline, STABILITY_LOOKUP, TIER_ORDER, AnthropicCacheAdapter, TIER_ORDER2, OpenAICacheAdapter, TIER_ORDER3, CACHED_CONTENT_MARKER, GeminiCacheAdapter, VERSION, POSTHOG_BATCH_URL, MAX_ATTEMPTS, TIMEOUT_MS, CompoundLockHeldError, KNOWN_CATEGORIES, ALLOWED_FIELD_KEYS, PII_TOKENS, PII_FIELD_DENYLIST, PII_LINE_RE, ALLOWED_SET, PulseAdapterAlreadyRegisteredError, REGISTRY, MOCK_ADAPTER_NAME, ALLOWED_FIELD_SET, adapter, LOOKBACK_RE, MAX_LINES, INLINE_TEMPLATE, TRUNCATION_MARKER2, execFileAsync, FIX_RE, EMPTY_REPO_RE, execFileAsync2, TRACK_CATEGORIES2, STOPWORDS, KEYWORD_TO_CATEGORY;
14768
15399
  var init_dist3 = __esm({
14769
15400
  "../core/dist/index.mjs"() {
14770
- init_chunk_CE225WRK();
15401
+ init_chunk_NC4RPKD4();
14771
15402
  init_dist();
14772
15403
  init_dist();
14773
15404
  init_dist();
14774
15405
  init_dist2();
14775
15406
  init_dist();
14776
15407
  init_dist();
15408
+ init_dist();
15409
+ init_dist();
15410
+ init_dist();
15411
+ init_dist();
14777
15412
  init_dist2();
14778
15413
  VALID_TYPES = [
14779
15414
  "feat",
@@ -15356,11 +15991,11 @@ var init_dist3 = __esm({
15356
15991
  return detectPatternViolations(snapshotResult.value, config);
15357
15992
  }
15358
15993
  };
15359
- readFile62 = promisify2(fs5.readFile);
15360
- writeFile32 = promisify2(fs5.writeFile);
15361
- unlink22 = promisify2(fs5.unlink);
15362
- mkdir22 = promisify2(fs5.mkdir);
15363
- copyFile2 = promisify2(fs5.copyFile);
15994
+ readFile62 = promisify2(fs6.readFile);
15995
+ writeFile32 = promisify2(fs6.writeFile);
15996
+ unlink22 = promisify2(fs6.unlink);
15997
+ mkdir22 = promisify2(fs6.mkdir);
15998
+ copyFile2 = promisify2(fs6.copyFile);
15364
15999
  DEFAULT_FIX_CONFIG = {
15365
16000
  dryRun: false,
15366
16001
  fixTypes: ["unused-imports", "dead-files"],
@@ -15482,14 +16117,14 @@ var init_dist3 = __esm({
15482
16117
  BaselineManager = class {
15483
16118
  baselinesPath;
15484
16119
  constructor(projectRoot) {
15485
- this.baselinesPath = join17(projectRoot, ".harness", "perf", "baselines.json");
16120
+ this.baselinesPath = join18(projectRoot, ".harness", "perf", "baselines.json");
15486
16121
  }
15487
16122
  /**
15488
16123
  * Load the baselines file from disk.
15489
16124
  * Returns null if the file does not exist or contains invalid JSON.
15490
16125
  */
15491
16126
  load() {
15492
- if (!existsSync11(this.baselinesPath)) {
16127
+ if (!existsSync12(this.baselinesPath)) {
15493
16128
  return null;
15494
16129
  }
15495
16130
  try {
@@ -15523,7 +16158,7 @@ var init_dist3 = __esm({
15523
16158
  benchmarks
15524
16159
  };
15525
16160
  const dir = dirname8(this.baselinesPath);
15526
- if (!existsSync11(dir)) {
16161
+ if (!existsSync12(dir)) {
15527
16162
  mkdirSync2(dir, { recursive: true });
15528
16163
  }
15529
16164
  writeFileSync2(this.baselinesPath, JSON.stringify(file, null, 2));
@@ -15757,16 +16392,16 @@ var init_dist3 = __esm({
15757
16392
  walkDir(dir, entries) {
15758
16393
  let items;
15759
16394
  try {
15760
- items = fs6.readdirSync(dir, { withFileTypes: true });
16395
+ items = fs7.readdirSync(dir, { withFileTypes: true });
15761
16396
  } catch {
15762
16397
  return;
15763
16398
  }
15764
16399
  for (const item of items) {
15765
16400
  if (item.isDirectory()) {
15766
16401
  if (SKIP_DIRS.has(item.name)) continue;
15767
- this.walkDir(path3.join(dir, item.name), entries);
15768
- } else if (item.isFile() && SOURCE_EXTENSIONS.has(path3.extname(item.name))) {
15769
- this.scanFile(path3.join(dir, item.name), entries);
16402
+ this.walkDir(path4.join(dir, item.name), entries);
16403
+ } else if (item.isFile() && SOURCE_EXTENSIONS.has(path4.extname(item.name))) {
16404
+ this.scanFile(path4.join(dir, item.name), entries);
15770
16405
  }
15771
16406
  }
15772
16407
  }
@@ -15781,12 +16416,12 @@ var init_dist3 = __esm({
15781
16416
  scanFile(filePath, entries) {
15782
16417
  let content;
15783
16418
  try {
15784
- content = fs6.readFileSync(filePath, "utf-8");
16419
+ content = fs7.readFileSync(filePath, "utf-8");
15785
16420
  } catch {
15786
16421
  return;
15787
16422
  }
15788
16423
  const lines = content.split("\n");
15789
- const relativePath = path3.relative(this.projectRoot, filePath).replace(/\\/g, "/");
16424
+ const relativePath = path4.relative(this.projectRoot, filePath).replace(/\\/g, "/");
15790
16425
  for (let i = 0; i < lines.length; i++) {
15791
16426
  if (!lines[i].includes("@perf-critical")) continue;
15792
16427
  const fnName = this.resolveFunctionName(lines, i + 1);
@@ -16126,7 +16761,7 @@ var init_dist3 = __esm({
16126
16761
  ensureDirectory() {
16127
16762
  if (!this.initialized) {
16128
16763
  const dir = dirname9(this.filePath);
16129
- if (!existsSync12(dir)) {
16764
+ if (!existsSync13(dir)) {
16130
16765
  mkdirSync22(dir, { recursive: true });
16131
16766
  }
16132
16767
  this.initialized = true;
@@ -16157,7 +16792,7 @@ var init_dist3 = __esm({
16157
16792
  this.ensureDirectory();
16158
16793
  const content = this.buffer.join("");
16159
16794
  this.buffer = [];
16160
- if (this.options.mode === "overwrite" && !existsSync12(this.filePath)) {
16795
+ if (this.options.mode === "overwrite" && !existsSync13(this.filePath)) {
16161
16796
  writeFileSync22(this.filePath, content);
16162
16797
  } else {
16163
16798
  appendFileSync(this.filePath, content);
@@ -16239,14 +16874,14 @@ var init_dist3 = __esm({
16239
16874
  TimelineManager = class {
16240
16875
  timelinePath;
16241
16876
  constructor(rootDir) {
16242
- this.timelinePath = join19(rootDir, ".harness", "arch", "timeline.json");
16877
+ this.timelinePath = join20(rootDir, ".harness", "arch", "timeline.json");
16243
16878
  }
16244
16879
  /**
16245
16880
  * Load timeline from disk.
16246
16881
  * Returns empty TimelineFile if file does not exist or is invalid.
16247
16882
  */
16248
16883
  load() {
16249
- if (!existsSync13(this.timelinePath)) {
16884
+ if (!existsSync14(this.timelinePath)) {
16250
16885
  return { version: 1, snapshots: [] };
16251
16886
  }
16252
16887
  try {
@@ -16272,7 +16907,7 @@ var init_dist3 = __esm({
16272
16907
  */
16273
16908
  save(timeline) {
16274
16909
  const dir = dirname10(this.timelinePath);
16275
- if (!existsSync13(dir)) {
16910
+ if (!existsSync14(dir)) {
16276
16911
  mkdirSync3(dir, { recursive: true });
16277
16912
  }
16278
16913
  const tmp = this.timelinePath + "." + randomBytes2(4).toString("hex") + ".tmp";
@@ -16829,8 +17464,8 @@ var init_dist3 = __esm({
16829
17464
  return null;
16830
17465
  }
16831
17466
  try {
16832
- const roadmapPath = path4.join(this.rootDir, "roadmap.md");
16833
- const raw = fs7.readFileSync(roadmapPath, "utf-8");
17467
+ const roadmapPath = path5.join(this.rootDir, "roadmap.md");
17468
+ const raw = fs8.readFileSync(roadmapPath, "utf-8");
16834
17469
  const parseResult = parseRoadmap(raw);
16835
17470
  if (!parseResult.ok) return null;
16836
17471
  const features = parseResult.value.milestones.flatMap(
@@ -16865,8 +17500,8 @@ var init_dist3 = __esm({
16865
17500
  * @param specPath - Relative path from rootDir to the spec file.
16866
17501
  */
16867
17502
  estimate(specPath) {
16868
- const absolutePath = path5.join(this.rootDir, specPath);
16869
- const content = fs8.readFileSync(absolutePath, "utf-8");
17503
+ const absolutePath = path6.join(this.rootDir, specPath);
17504
+ const content = fs9.readFileSync(absolutePath, "utf-8");
16870
17505
  const newFileCount = this.extractNewFileCount(content);
16871
17506
  const affectedLayers = this.extractAffectedLayers(content);
16872
17507
  const newDependencies = this.extractNewDependencies(content);
@@ -16878,7 +17513,7 @@ var init_dist3 = __esm({
16878
17513
  phaseCount
16879
17514
  );
16880
17515
  const h1Match = content.match(/^#\s+(.+)$/m);
16881
- const featureName = h1Match ? h1Match[1].trim() : path5.basename(specPath, ".md");
17516
+ const featureName = h1Match ? h1Match[1].trim() : path6.basename(specPath, ".md");
16882
17517
  return {
16883
17518
  specPath,
16884
17519
  featureName,
@@ -16899,8 +17534,8 @@ var init_dist3 = __esm({
16899
17534
  const results = [];
16900
17535
  for (const feature of features) {
16901
17536
  if (!feature.spec) continue;
16902
- const absolutePath = path5.join(this.rootDir, feature.spec);
16903
- if (!fs8.existsSync(absolutePath)) continue;
17537
+ const absolutePath = path6.join(this.rootDir, feature.spec);
17538
+ if (!fs9.existsSync(absolutePath)) continue;
16904
17539
  const estimate = this.estimate(feature.spec);
16905
17540
  results.push({ ...estimate, featureName: feature.name });
16906
17541
  }
@@ -16928,8 +17563,8 @@ var init_dist3 = __esm({
16928
17563
  }
16929
17564
  let count = 0;
16930
17565
  for (const fp of filePaths) {
16931
- const absolute = path5.join(this.rootDir, fp);
16932
- if (!fs8.existsSync(absolute)) {
17566
+ const absolute = path6.join(this.rootDir, fp);
17567
+ if (!fs9.existsSync(absolute)) {
16933
17568
  count++;
16934
17569
  }
16935
17570
  }
@@ -16999,8 +17634,8 @@ var init_dist3 = __esm({
16999
17634
  // --- Private: Config Loading ---
17000
17635
  loadLayerNames() {
17001
17636
  try {
17002
- const configPath = path5.join(this.rootDir, "harness.config.json");
17003
- const raw = fs8.readFileSync(configPath, "utf-8");
17637
+ const configPath = path6.join(this.rootDir, "harness.config.json");
17638
+ const raw = fs9.readFileSync(configPath, "utf-8");
17004
17639
  const config = JSON.parse(raw);
17005
17640
  return (config.layers ?? []).map((l) => l.name);
17006
17641
  } catch {
@@ -17019,8 +17654,8 @@ var init_dist3 = __esm({
17019
17654
  historyPath;
17020
17655
  /** Load history from disk, returning empty history if file doesn't exist. */
17021
17656
  load() {
17022
- if (!fs9.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
17023
- const raw = fs9.readFileSync(this.historyPath, "utf-8");
17657
+ if (!fs10.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
17658
+ const raw = fs10.readFileSync(this.historyPath, "utf-8");
17024
17659
  return JSON.parse(raw);
17025
17660
  }
17026
17661
  /** Append a timestamped snapshot of current violations. */
@@ -17034,18 +17669,18 @@ var init_dist3 = __esm({
17034
17669
  }
17035
17670
  /** Prune snapshots older than retentionDays. */
17036
17671
  prune(retentionDays) {
17037
- if (!fs9.existsSync(this.historyPath)) return;
17672
+ if (!fs10.existsSync(this.historyPath)) return;
17038
17673
  const history = this.load();
17039
17674
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
17040
17675
  history.snapshots = history.snapshots.filter((s) => new Date(s.timestamp).getTime() >= cutoff);
17041
17676
  this.write(history);
17042
17677
  }
17043
17678
  write(history) {
17044
- const dir = path6.dirname(this.historyPath);
17045
- if (!fs9.existsSync(dir)) {
17046
- fs9.mkdirSync(dir, { recursive: true });
17679
+ const dir = path7.dirname(this.historyPath);
17680
+ if (!fs10.existsSync(dir)) {
17681
+ fs10.mkdirSync(dir, { recursive: true });
17047
17682
  }
17048
- fs9.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
17683
+ fs10.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
17049
17684
  }
17050
17685
  };
17051
17686
  NORMALIZERS = {
@@ -18047,7 +18682,7 @@ var init_dist3 = __esm({
18047
18682
  }
18048
18683
  async scanFile(filePath) {
18049
18684
  if (!this.config.enabled) return [];
18050
- const content = await fs27.readFile(filePath, "utf-8");
18685
+ const content = await fs28.readFile(filePath, "utf-8");
18051
18686
  return this.scanContentForFile(content, filePath, 1);
18052
18687
  }
18053
18688
  scanContentForFile(content, filePath, startLine = 1) {
@@ -18452,7 +19087,7 @@ var init_dist3 = __esm({
18452
19087
  timelinePath;
18453
19088
  constructor(rootDir) {
18454
19089
  this.rootDir = rootDir;
18455
- this.timelinePath = join39(rootDir, ".harness", "security", "timeline.json");
19090
+ this.timelinePath = join40(rootDir, ".harness", "security", "timeline.json");
18456
19091
  }
18457
19092
  /**
18458
19093
  * Load timeline from disk.
@@ -18463,7 +19098,7 @@ var init_dist3 = __esm({
18463
19098
  * fixup is one-shot.
18464
19099
  */
18465
19100
  load() {
18466
- if (!existsSync332(this.timelinePath)) {
19101
+ if (!existsSync342(this.timelinePath)) {
18467
19102
  return { version: 1, snapshots: [], findingLifecycles: [] };
18468
19103
  }
18469
19104
  try {
@@ -18497,7 +19132,7 @@ var init_dist3 = __esm({
18497
19132
  */
18498
19133
  save(timeline) {
18499
19134
  const dir = dirname13(this.timelinePath);
18500
- if (!existsSync332(dir)) {
19135
+ if (!existsSync342(dir)) {
18501
19136
  mkdirSync15(dir, { recursive: true });
18502
19137
  }
18503
19138
  const tmp = this.timelinePath + "." + randomBytes22(4).toString("hex") + ".tmp";
@@ -19511,7 +20146,8 @@ var init_dist3 = __esm({
19511
20146
  }
19512
20147
  };
19513
20148
  syncMutex = Promise.resolve();
19514
- PRIORITY_RANK = {
20149
+ PRIORITY_RANK = { P0: 0, P1: 1, P2: 2, P3: 3 };
20150
+ PRIORITY_RANK2 = {
19515
20151
  P0: 0,
19516
20152
  P1: 1,
19517
20153
  P2: 2,
@@ -19520,6 +20156,586 @@ var init_dist3 = __esm({
19520
20156
  POSITION_WEIGHT = 0.5;
19521
20157
  DEPENDENTS_WEIGHT = 0.3;
19522
20158
  AFFINITY_WEIGHT = 0.2;
20159
+ ConflictError = class extends Error {
20160
+ code = "TRACKER_CONFLICT";
20161
+ externalId;
20162
+ diff;
20163
+ serverUpdatedAt;
20164
+ constructor(externalId, diff2, serverUpdatedAt = null, message) {
20165
+ super(message ?? `Conflict on ${externalId}: ${Object.keys(diff2).join(", ")}`);
20166
+ this.name = "ConflictError";
20167
+ this.externalId = externalId;
20168
+ this.diff = diff2;
20169
+ this.serverUpdatedAt = serverUpdatedAt;
20170
+ }
20171
+ };
20172
+ DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
20173
+ GitHubHttp = class {
20174
+ token;
20175
+ fetchFn;
20176
+ apiBase;
20177
+ retryOpts;
20178
+ constructor(opts) {
20179
+ this.token = opts.token;
20180
+ this.fetchFn = opts.fetchFn ?? globalThis.fetch;
20181
+ this.apiBase = opts.apiBase ?? "https://api.github.com";
20182
+ this.retryOpts = {
20183
+ maxRetries: opts.maxRetries ?? DEFAULTS.maxRetries,
20184
+ baseDelayMs: opts.baseDelayMs ?? DEFAULTS.baseDelayMs
20185
+ };
20186
+ }
20187
+ headers(extra) {
20188
+ return {
20189
+ Authorization: `Bearer ${this.token}`,
20190
+ Accept: "application/vnd.github+json",
20191
+ "Content-Type": "application/json",
20192
+ "X-GitHub-Api-Version": "2022-11-28",
20193
+ ...extra ?? {}
20194
+ };
20195
+ }
20196
+ async request(url, init) {
20197
+ const { extraHeaders, ...rest } = init;
20198
+ const merged = {
20199
+ ...rest,
20200
+ headers: this.headers(extraHeaders)
20201
+ };
20202
+ return this.fetchWithRetry(url, merged);
20203
+ }
20204
+ async fetchWithRetry(url, init) {
20205
+ let last;
20206
+ for (let attempt = 0; attempt <= this.retryOpts.maxRetries; attempt++) {
20207
+ const res = await this.fetchFn(url, init);
20208
+ if (res.status !== 403 && res.status !== 429 && res.status < 500) return res;
20209
+ last = res;
20210
+ if (attempt === this.retryOpts.maxRetries) break;
20211
+ const retryAfter = res.headers.get("Retry-After");
20212
+ let delayMs;
20213
+ if (retryAfter) {
20214
+ const seconds = parseInt(retryAfter, 10);
20215
+ delayMs = isNaN(seconds) ? this.retryOpts.baseDelayMs : seconds * 1e3;
20216
+ } else {
20217
+ delayMs = this.retryOpts.baseDelayMs * Math.pow(2, attempt) + Math.random() * 500;
20218
+ }
20219
+ await new Promise((r) => setTimeout(r, delayMs));
20220
+ }
20221
+ return last;
20222
+ }
20223
+ /**
20224
+ * Walk all pages of a paginated GET. Stops when a page returns < perPage items.
20225
+ *
20226
+ * Status semantics:
20227
+ * - `status: 304` is returned ONLY when the first page returned 304 (the
20228
+ * server confirmed the caller's If-None-Match matched and there are no
20229
+ * items to read). Callers may then serve cached data.
20230
+ * - `status: 200` is returned in all other terminal cases, including the
20231
+ * mid-walk case where page 1 returned 200 (with items) and a later page
20232
+ * returned 304. Mid-walk 304 is treated as "no further items"; already-
20233
+ * accumulated items are preserved and `lastEtag` is the latest ETag seen.
20234
+ */
20235
+ async paginate(buildUrl, perPage = 100, extraHeaders) {
20236
+ const items = [];
20237
+ let page = 1;
20238
+ let lastEtag = null;
20239
+ let status = 200;
20240
+ for (; ; ) {
20241
+ const init = { method: "GET" };
20242
+ if (extraHeaders) init.extraHeaders = extraHeaders;
20243
+ const res = await this.request(buildUrl(page), init);
20244
+ const etag = res.headers.get("ETag");
20245
+ if (res.status === 304) {
20246
+ if (page === 1) return { items, lastEtag: etag, status: 304 };
20247
+ if (etag) lastEtag = etag;
20248
+ break;
20249
+ }
20250
+ if (!res.ok) {
20251
+ throw new Error(`GitHub ${res.status}: ${await res.text()}`);
20252
+ }
20253
+ const data = await res.json();
20254
+ items.push(...data);
20255
+ lastEtag = etag;
20256
+ status = res.status;
20257
+ if (data.length < perPage) break;
20258
+ page++;
20259
+ }
20260
+ return { items, lastEtag, status };
20261
+ }
20262
+ };
20263
+ ETagStore = class {
20264
+ max;
20265
+ cache = /* @__PURE__ */ new Map();
20266
+ constructor(max = 500) {
20267
+ this.max = max;
20268
+ }
20269
+ get(key) {
20270
+ const entry = this.cache.get(key);
20271
+ if (!entry) return null;
20272
+ this.cache.delete(key);
20273
+ this.cache.set(key, entry);
20274
+ return entry;
20275
+ }
20276
+ set(key, etag, data) {
20277
+ if (this.cache.has(key)) this.cache.delete(key);
20278
+ this.cache.set(key, { etag, data });
20279
+ if (this.cache.size > this.max) {
20280
+ const oldestKey = this.cache.keys().next().value;
20281
+ if (oldestKey !== void 0) this.cache.delete(oldestKey);
20282
+ }
20283
+ }
20284
+ invalidate(key) {
20285
+ this.cache.delete(key);
20286
+ }
20287
+ invalidateAll() {
20288
+ this.cache.clear();
20289
+ }
20290
+ /** Invalidate all keys matching a prefix. Used when writes invalidate `list:*`. */
20291
+ invalidatePrefix(prefix) {
20292
+ for (const key of [...this.cache.keys()]) {
20293
+ if (key.startsWith(prefix)) this.cache.delete(key);
20294
+ }
20295
+ }
20296
+ get size() {
20297
+ return this.cache.size;
20298
+ }
20299
+ };
20300
+ START_MARKER = "<!-- harness-meta:start -->";
20301
+ END_MARKER = "<!-- harness-meta:end -->";
20302
+ SERIALIZER_FIELDS = [
20303
+ ["spec", "spec", (v) => v],
20304
+ ["plan", "plan", (v) => v],
20305
+ [
20306
+ "blocked_by",
20307
+ "blocked_by",
20308
+ (v) => Array.isArray(v) && v.length > 0 ? v : void 0
20309
+ ],
20310
+ ["priority", "priority", (v) => v],
20311
+ ["milestone", "milestone", (v) => v]
20312
+ ];
20313
+ GitHubIssuesTrackerAdapter = class _GitHubIssuesTrackerAdapter {
20314
+ http;
20315
+ owner;
20316
+ repo;
20317
+ cache;
20318
+ selectorLabel;
20319
+ constructor(opts) {
20320
+ this.http = new GitHubHttp(opts);
20321
+ const [owner, repo] = opts.repo.split("/");
20322
+ if (!owner || !repo) throw new Error(`Invalid repo "${opts.repo}", expected "owner/repo"`);
20323
+ this.owner = owner;
20324
+ this.repo = repo;
20325
+ this.cache = opts.etagStore ?? new ETagStore(500);
20326
+ this.selectorLabel = opts.selectorLabel ?? "harness-managed";
20327
+ }
20328
+ // --- Reads ---
20329
+ async fetchAll() {
20330
+ try {
20331
+ const cacheKey = "list:all";
20332
+ const cached = this.cache.get(cacheKey);
20333
+ const buildUrl = (page) => {
20334
+ const params = new URLSearchParams({
20335
+ state: "all",
20336
+ per_page: "100",
20337
+ page: String(page),
20338
+ labels: this.selectorLabel
20339
+ });
20340
+ return `${this.http.apiBase}/repos/${this.owner}/${this.repo}/issues?${params.toString()}`;
20341
+ };
20342
+ const headers = cached ? { "If-None-Match": cached.etag } : void 0;
20343
+ const { items, lastEtag, status } = await this.http.paginate(
20344
+ buildUrl,
20345
+ 100,
20346
+ headers
20347
+ );
20348
+ if (status === 304 && cached) {
20349
+ return Ok({ features: cached.data, etag: cached.etag });
20350
+ }
20351
+ const issues = items.filter((i) => !i.pull_request);
20352
+ const nameIndex = /* @__PURE__ */ new Map();
20353
+ for (const i of issues) {
20354
+ nameIndex.set(i.title, buildExternalId2(this.owner, this.repo, i.number));
20355
+ }
20356
+ const features = issues.map((i) => this.mapIssue(i, nameIndex));
20357
+ features.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
20358
+ if (lastEtag) this.cache.set(cacheKey, lastEtag, features);
20359
+ return Ok({ features, etag: lastEtag });
20360
+ } catch (err) {
20361
+ return Err(err instanceof Error ? err : new Error(String(err)));
20362
+ }
20363
+ }
20364
+ /**
20365
+ * Fetches a feature by externalId.
20366
+ *
20367
+ * Confused-deputy guard: the externalId's owner/repo MUST match the
20368
+ * adapter's configured owner/repo. If they differ we return Err rather
20369
+ * than silently issuing a request against the externalId's repo (which
20370
+ * the adapter is not configured to manage). The adapter's configured
20371
+ * owner/repo always wins.
20372
+ */
20373
+ async fetchById(externalId) {
20374
+ try {
20375
+ const parsed = parseExternalId2(externalId);
20376
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20377
+ if (parsed.owner !== this.owner || parsed.repo !== this.repo) {
20378
+ return Err(new Error("externalId repo does not match adapter repo"));
20379
+ }
20380
+ const cacheKey = `feature:${externalId}`;
20381
+ const cached = this.cache.get(cacheKey);
20382
+ const init = { method: "GET" };
20383
+ if (cached) init.extraHeaders = { "If-None-Match": cached.etag };
20384
+ const res = await this.http.request(
20385
+ `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
20386
+ init
20387
+ );
20388
+ if (res.status === 404) return Ok(null);
20389
+ if (res.status === 304 && cached) {
20390
+ return Ok({ feature: cached.data, etag: cached.etag });
20391
+ }
20392
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20393
+ const data = await res.json();
20394
+ const etag = res.headers.get("ETag");
20395
+ if (data.pull_request) return Ok(null);
20396
+ const feature = this.mapIssue(data, /* @__PURE__ */ new Map());
20397
+ if (etag) this.cache.set(cacheKey, etag, feature);
20398
+ return Ok({ feature, etag: etag ?? "" });
20399
+ } catch (err) {
20400
+ return Err(err instanceof Error ? err : new Error(String(err)));
20401
+ }
20402
+ }
20403
+ async fetchByStatus(statuses) {
20404
+ const all = await this.fetchAll();
20405
+ if (!all.ok) return all;
20406
+ return Ok(all.value.features.filter((f) => statuses.includes(f.status)));
20407
+ }
20408
+ // --- Writes ---
20409
+ async create(feature) {
20410
+ try {
20411
+ const meta = buildCreateMeta(feature);
20412
+ const body = serializeBodyBlock(feature.summary ?? "", meta);
20413
+ const labels = this.buildCreateLabels(feature);
20414
+ const payload = { title: feature.name, body, labels };
20415
+ if (feature.assignee) payload.assignees = [feature.assignee.replace(/^@/, "")];
20416
+ const res = await this.http.request(
20417
+ `${this.http.apiBase}/repos/${this.owner}/${this.repo}/issues`,
20418
+ { method: "POST", body: JSON.stringify(payload) }
20419
+ );
20420
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20421
+ const data = await res.json();
20422
+ this.cache.invalidatePrefix("list:");
20423
+ return Ok(this.mapIssue(data, /* @__PURE__ */ new Map()));
20424
+ } catch (err) {
20425
+ return Err(err instanceof Error ? err : new Error(String(err)));
20426
+ }
20427
+ }
20428
+ buildCreateLabels(feature) {
20429
+ const labels = [this.selectorLabel];
20430
+ if (feature.status && feature.status !== "backlog") labels.push(feature.status);
20431
+ return labels;
20432
+ }
20433
+ /**
20434
+ * Patches a feature.
20435
+ *
20436
+ * Confused-deputy guard: the externalId's owner/repo MUST match the
20437
+ * adapter's configured owner/repo. If they differ we return Err rather
20438
+ * than silently writing to the externalId's repo. The adapter's
20439
+ * configured owner/repo always wins.
20440
+ */
20441
+ async update(externalId, patch, ifMatch) {
20442
+ const r = await this.updateInternal(externalId, patch, ifMatch);
20443
+ if (!r.ok) return r;
20444
+ return Ok(r.value.feature);
20445
+ }
20446
+ /**
20447
+ * Internal update that also reports whether a real PATCH was issued.
20448
+ * Returns wrote=false on the idempotent path so callers (claim/release/complete)
20449
+ * can skip best-effort history logging when no state change occurred.
20450
+ *
20451
+ * When ifMatch is supplied, updateInternal performs a refetch GET; the
20452
+ * refetched feature is returned as `priorFeature` so callers (release/complete)
20453
+ * can extract the pre-PATCH assignee for history attribution without a second GET.
20454
+ */
20455
+ async updateInternal(externalId, patch, ifMatch) {
20456
+ try {
20457
+ const parsed = parseExternalId2(externalId);
20458
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20459
+ if (parsed.owner !== this.owner || parsed.repo !== this.repo) {
20460
+ return Err(new Error("externalId repo does not match adapter repo"));
20461
+ }
20462
+ let priorFeature;
20463
+ if (ifMatch) {
20464
+ const cur = await this.fetchById(externalId);
20465
+ if (!cur.ok) return cur;
20466
+ if (!cur.value) return Err(new Error(`Not found: ${externalId}`));
20467
+ priorFeature = cur.value.feature;
20468
+ const cmp = refetchAndCompare(cur.value.feature, patch);
20469
+ if (!cmp.ok) {
20470
+ return Err(new ConflictError(externalId, cmp.diff, cur.value.feature.updatedAt));
20471
+ }
20472
+ if (cmp.idempotent) return Ok({ feature: cur.value.feature, wrote: false, priorFeature });
20473
+ }
20474
+ const reqBody = await this.buildIssuePatchBody(externalId, patch, priorFeature);
20475
+ const res = await this.http.request(
20476
+ `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
20477
+ { method: "PATCH", body: JSON.stringify(reqBody) }
20478
+ );
20479
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20480
+ const data = await res.json();
20481
+ this.cache.invalidate(`feature:${externalId}`);
20482
+ this.cache.invalidatePrefix("list:");
20483
+ return Ok({
20484
+ feature: this.mapIssue(data, /* @__PURE__ */ new Map()),
20485
+ wrote: true,
20486
+ ...priorFeature !== void 0 ? { priorFeature } : {}
20487
+ });
20488
+ } catch (err) {
20489
+ return Err(err instanceof Error ? err : new Error(String(err)));
20490
+ }
20491
+ }
20492
+ /** Status labels recognized by mapStatus; 'backlog' and 'done' are state-driven. */
20493
+ static STATUS_LABELS = /* @__PURE__ */ new Set([
20494
+ "planned",
20495
+ "in-progress",
20496
+ "blocked",
20497
+ "needs-human"
20498
+ ]);
20499
+ async buildIssuePatchBody(externalId, patch, priorFeature) {
20500
+ const out = {};
20501
+ if (patch.name !== void 0) out.title = patch.name;
20502
+ if (patch.assignee !== void 0) {
20503
+ out.assignees = patch.assignee ? [patch.assignee.replace(/^@/, "")] : [];
20504
+ }
20505
+ if (patch.status !== void 0) {
20506
+ if (patch.status === "done") out.state = "closed";
20507
+ else out.state = "open";
20508
+ const labelsR = await this.fetchRawLabels(externalId);
20509
+ if (labelsR.ok) {
20510
+ const filtered = labelsR.value.filter(
20511
+ (l) => !_GitHubIssuesTrackerAdapter.STATUS_LABELS.has(l)
20512
+ );
20513
+ if (patch.status !== "backlog" && patch.status !== "done" && _GitHubIssuesTrackerAdapter.STATUS_LABELS.has(patch.status)) {
20514
+ filtered.push(patch.status);
20515
+ }
20516
+ out.labels = filtered;
20517
+ } else {
20518
+ console.warn(
20519
+ `harness-tracker: skipping label sync on ${externalId} due to GET error: ${labelsR.error.message}`
20520
+ );
20521
+ }
20522
+ }
20523
+ const bodyTouches = [
20524
+ "summary",
20525
+ "spec",
20526
+ "plans",
20527
+ "blockedBy",
20528
+ "priority",
20529
+ "milestone"
20530
+ ];
20531
+ if (bodyTouches.some((k) => patch[k] !== void 0)) {
20532
+ let current;
20533
+ if (priorFeature) {
20534
+ current = priorFeature;
20535
+ } else {
20536
+ const cur = await this.fetchById(externalId);
20537
+ if (!cur.ok || !cur.value) throw new Error("Cannot rebuild body without current state");
20538
+ current = cur.value.feature;
20539
+ }
20540
+ const meta = metaFromFeatureFields({
20541
+ spec: patch.spec !== void 0 ? patch.spec : current.spec,
20542
+ plans: patch.plans !== void 0 ? patch.plans : current.plans,
20543
+ blockedBy: patch.blockedBy !== void 0 ? patch.blockedBy : current.blockedBy,
20544
+ priority: patch.priority !== void 0 ? patch.priority : current.priority,
20545
+ milestone: patch.milestone !== void 0 ? patch.milestone : current.milestone
20546
+ });
20547
+ const summaryText = patch.summary !== void 0 ? patch.summary : current.summary;
20548
+ out.body = serializeBodyBlock(summaryText, meta);
20549
+ }
20550
+ return out;
20551
+ }
20552
+ /**
20553
+ * Lightweight GET that returns the raw label name list.
20554
+ *
20555
+ * P2-RR2-IMP-1: returns Result so callers can distinguish a real Err (network
20556
+ * failure / non-200 response) from an empty label set. The previous shape
20557
+ * silently swallowed errors and returned [], which combined with the
20558
+ * unconditional `out.labels = filtered` assignment in buildIssuePatchBody
20559
+ * caused PATCH requests to wipe ALL labels (including the selectorLabel and
20560
+ * user-added labels) on a transient GET blip.
20561
+ */
20562
+ async fetchRawLabels(externalId) {
20563
+ try {
20564
+ const parsed = parseExternalId2(externalId);
20565
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20566
+ const res = await this.http.request(
20567
+ `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
20568
+ { method: "GET" }
20569
+ );
20570
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20571
+ const data = await res.json();
20572
+ return Ok(data.labels.map((l) => l.name));
20573
+ } catch (err) {
20574
+ return Err(err instanceof Error ? err : new Error(String(err)));
20575
+ }
20576
+ }
20577
+ async claim(externalId, assignee, ifMatch) {
20578
+ const r = await this.updateInternal(externalId, { assignee, status: "in-progress" }, ifMatch);
20579
+ if (!r.ok) return r;
20580
+ if (r.value.wrote) await this.logEvent(externalId, "claimed", assignee);
20581
+ return Ok(r.value.feature);
20582
+ }
20583
+ async release(externalId, ifMatch) {
20584
+ const priorAssignee = await this.fetchPriorAssigneeIfNeeded(externalId, ifMatch);
20585
+ const r = await this.updateInternal(externalId, { assignee: null, status: "backlog" }, ifMatch);
20586
+ if (!r.ok) return r;
20587
+ if (r.value.wrote) {
20588
+ const actor = r.value.priorFeature?.assignee ?? priorAssignee ?? null;
20589
+ await this.logEvent(externalId, "released", actor ?? "unknown");
20590
+ }
20591
+ return Ok(r.value.feature);
20592
+ }
20593
+ async complete(externalId, ifMatch) {
20594
+ const priorAssignee = await this.fetchPriorAssigneeIfNeeded(externalId, ifMatch);
20595
+ const r = await this.updateInternal(externalId, { status: "done" }, ifMatch);
20596
+ if (!r.ok) return r;
20597
+ if (r.value.wrote) {
20598
+ const actor = r.value.priorFeature?.assignee ?? priorAssignee ?? null;
20599
+ await this.logEvent(externalId, "completed", actor ?? "unknown");
20600
+ }
20601
+ return Ok(r.value.feature);
20602
+ }
20603
+ /**
20604
+ * Best-effort GET to capture the assignee BEFORE a PATCH that may clear it.
20605
+ * Skipped when ifMatch is supplied because updateInternal will refetch and
20606
+ * thread the prior feature through.
20607
+ */
20608
+ async fetchPriorAssigneeIfNeeded(externalId, ifMatch) {
20609
+ if (ifMatch) return null;
20610
+ const cur = await this.fetchById(externalId);
20611
+ if (cur.ok && cur.value) return cur.value.feature.assignee;
20612
+ return null;
20613
+ }
20614
+ /**
20615
+ * Trust model for history events.
20616
+ *
20617
+ * `HISTORY_PREFIX` (`<!-- harness-history -->`) marks issue comments as
20618
+ * authentic harness events. fetchHistory reads only comments that begin
20619
+ * with this prefix; appendHistory writes the prefix unconditionally.
20620
+ *
20621
+ * Trust is per-repo: only repository contributors authorized to post
20622
+ * comments on a given issue can synthesize harness history events.
20623
+ * GitHub's per-repo permissions are the authorization boundary; the
20624
+ * prefix itself is not a cryptographic credential — a contributor with
20625
+ * comment access can forge events by hand-writing the prefix.
20626
+ *
20627
+ * HMAC-signing of event payloads is a future hardening (post-GA) for
20628
+ * deployments that need cross-actor tamper-evidence within the same
20629
+ * repo. The current shape is intentional: optimized for transparency
20630
+ * (events are human-readable, no key distribution required).
20631
+ */
20632
+ static HISTORY_PREFIX = "<!-- harness-history -->";
20633
+ async appendHistory(externalId, event) {
20634
+ try {
20635
+ const parsed = parseExternalId2(externalId);
20636
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20637
+ const body = `${_GitHubIssuesTrackerAdapter.HISTORY_PREFIX}
20638
+ ${JSON.stringify(event)}`;
20639
+ const res = await this.http.request(
20640
+ `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/comments`,
20641
+ { method: "POST", body: JSON.stringify({ body }) }
20642
+ );
20643
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20644
+ return Ok(void 0);
20645
+ } catch (err) {
20646
+ return Err(err instanceof Error ? err : new Error(String(err)));
20647
+ }
20648
+ }
20649
+ async fetchHistory(externalId, limit) {
20650
+ try {
20651
+ const parsed = parseExternalId2(externalId);
20652
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20653
+ const buildUrl = (page) => `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/comments?per_page=100&page=${page}`;
20654
+ const { items } = await this.http.paginate(buildUrl);
20655
+ const events = [];
20656
+ for (const c of items) {
20657
+ if (!c.body.startsWith(_GitHubIssuesTrackerAdapter.HISTORY_PREFIX)) continue;
20658
+ const json = c.body.slice(_GitHubIssuesTrackerAdapter.HISTORY_PREFIX.length).trim();
20659
+ try {
20660
+ events.push(JSON.parse(json));
20661
+ } catch (e) {
20662
+ console.warn(`harness-history: malformed JSON in ${externalId}: ${e.message}`);
20663
+ }
20664
+ }
20665
+ events.sort((a, b) => a.at.localeCompare(b.at));
20666
+ return Ok(limit ? events.slice(-limit) : events);
20667
+ } catch (err) {
20668
+ return Err(err instanceof Error ? err : new Error(String(err)));
20669
+ }
20670
+ }
20671
+ async logEvent(externalId, type, actor) {
20672
+ const event = {
20673
+ type,
20674
+ actor,
20675
+ at: (/* @__PURE__ */ new Date()).toISOString()
20676
+ };
20677
+ const r = await this.appendHistory(externalId, event);
20678
+ if (!r.ok) {
20679
+ console.warn(`harness-history: append failed for ${externalId}: ${r.error.message}`);
20680
+ }
20681
+ }
20682
+ // --- Helpers ---
20683
+ // The nameIndex parameter is retained for backwards-compatibility with the
20684
+ // fetchAll signature but is no longer consulted: blockedBy holds feature
20685
+ // names verbatim from the body-meta block (see client.ts TrackedFeature.blockedBy).
20686
+ mapIssue(issue, _nameIndex) {
20687
+ const { summary, meta } = parseBodyBlock(issue.body ?? "");
20688
+ const status = this.mapStatus(issue, meta);
20689
+ return {
20690
+ externalId: buildExternalId2(this.owner, this.repo, issue.number),
20691
+ name: issue.title,
20692
+ status,
20693
+ summary,
20694
+ spec: meta.spec ?? null,
20695
+ plans: meta.plan ? [meta.plan] : [],
20696
+ blockedBy: meta.blocked_by ?? [],
20697
+ assignee: issue.assignees[0]?.login ? `@${issue.assignees[0].login}` : null,
20698
+ priority: meta.priority ?? null,
20699
+ milestone: issue.milestone?.title ?? meta.milestone ?? null,
20700
+ createdAt: issue.created_at,
20701
+ updatedAt: issue.updated_at ?? null
20702
+ };
20703
+ }
20704
+ /**
20705
+ * Status precedence (highest wins):
20706
+ * 1. issue.state === 'closed' → 'done'
20707
+ * 2. 'blocked' label → 'blocked'
20708
+ * 3. 'needs-human' label → 'needs-human'
20709
+ * 4. 'in-progress' label OR any assignee → 'in-progress'
20710
+ * 5. 'planned' label → 'planned'
20711
+ * 6. otherwise → 'backlog'
20712
+ *
20713
+ * Note step 4: an assignment overrides a 'planned' label. This is
20714
+ * intentional — a feature that someone has picked up is "in-progress"
20715
+ * regardless of whether the planning label was cleared first. The reverse
20716
+ * (planned label + assignee) is the common case where the orchestrator
20717
+ * (or a human) starts work without scrubbing the planning label.
20718
+ */
20719
+ mapStatus(issue, _meta) {
20720
+ if (issue.state === "closed") return "done";
20721
+ const labels = issue.labels.map((l) => l.name);
20722
+ if (labels.includes("blocked")) return "blocked";
20723
+ if (labels.includes("needs-human")) return "needs-human";
20724
+ if (labels.includes("in-progress") || issue.assignees.length > 0) return "in-progress";
20725
+ if (labels.includes("planned")) return "planned";
20726
+ return "backlog";
20727
+ }
20728
+ };
20729
+ migrate_exports = {};
20730
+ __export2(migrate_exports, {
20731
+ bodyMetaMatches: () => bodyMetaMatches,
20732
+ buildHistoryCommentBody: () => buildHistoryCommentBody,
20733
+ buildMigrationPlan: () => buildMigrationPlan,
20734
+ hashHistoryEvent: () => hashHistoryEvent,
20735
+ parseHashFromCommentBody: () => parseHashFromCommentBody,
20736
+ runMigrationPlan: () => runMigrationPlan
20737
+ });
20738
+ HASH_RE = /<!--\s*harness-history\s+hash:([0-9a-f]{8})\s*-->/i;
19523
20739
  InteractionTypeSchema = z12.enum(["question", "confirmation", "transition"]);
19524
20740
  QuestionSchema = z12.object({
19525
20741
  text: z12.string(),
@@ -19552,10 +20768,10 @@ var init_dist3 = __esm({
19552
20768
  }
19553
20769
  rootDir;
19554
20770
  async scan() {
19555
- let projectName = path30.basename(this.rootDir);
20771
+ let projectName = path34.basename(this.rootDir);
19556
20772
  try {
19557
- const pkgPath = path30.join(this.rootDir, "package.json");
19558
- const pkgRaw = await fs31.readFile(pkgPath, "utf-8");
20773
+ const pkgPath = path34.join(this.rootDir, "package.json");
20774
+ const pkgRaw = await fs34.readFile(pkgPath, "utf-8");
19559
20775
  const pkg = JSON.parse(pkgRaw);
19560
20776
  if (pkg.name) projectName = pkg.name;
19561
20777
  } catch {
@@ -19668,8 +20884,8 @@ console.log('Blueprint player initialized.');
19668
20884
  styles: STYLES,
19669
20885
  scripts: SCRIPTS
19670
20886
  });
19671
- await fs32.mkdir(options.outputDir, { recursive: true });
19672
- await fs32.writeFile(path31.join(options.outputDir, "index.html"), html);
20887
+ await fs35.mkdir(options.outputDir, { recursive: true });
20888
+ await fs35.writeFile(path35.join(options.outputDir, "index.html"), html);
19673
20889
  }
19674
20890
  };
19675
20891
  TOKENS_PER_MILLION = 1e6;
@@ -20102,6 +21318,8 @@ export {
20102
21318
  validateAgentsMap,
20103
21319
  runFallbackRules,
20104
21320
  validateAgentConfigs,
21321
+ getRoadmapMode,
21322
+ validateRoadmapMode,
20105
21323
  checkDocCoverage,
20106
21324
  validateKnowledgeMap,
20107
21325
  generateAgentsMap,
@@ -20391,8 +21609,17 @@ export {
20391
21609
  syncToExternal,
20392
21610
  syncFromExternal,
20393
21611
  fullSync,
21612
+ scoreRoadmapCandidatesFileLess,
20394
21613
  scoreRoadmapCandidates,
20395
21614
  assignFeature,
21615
+ scoreRoadmapCandidatesForMode,
21616
+ ConflictError,
21617
+ ETagStore,
21618
+ createTrackerClient,
21619
+ makeTrackerConflictBody,
21620
+ loadProjectRoadmapMode,
21621
+ loadTrackerClientConfigFromProject,
21622
+ migrate_exports,
20396
21623
  InteractionTypeSchema,
20397
21624
  QuestionSchema,
20398
21625
  ConfirmationSchema,