@harness-engineering/cli 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) 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-5JG3AE5A.js} +3 -3
  6. package/dist/{architecture-WJRIYQ45.js → architecture-544ISXDI.js} +4 -4
  7. package/dist/{assess-project-PHC2HO3V.js → assess-project-PBO6XFJC.js} +1 -1
  8. package/dist/bin/harness-mcp.js +15 -15
  9. package/dist/bin/harness.js +25 -25
  10. package/dist/{check-phase-gate-646KY67H.js → check-phase-gate-BZU4DGF3.js} +4 -4
  11. package/dist/{chunk-2QPHCR22.js → chunk-22OKI5QI.js} +9 -9
  12. package/dist/{chunk-CPYL6LJR.js → chunk-2564TBT6.js} +1 -1
  13. package/dist/{chunk-YG4ZBGEH.js → chunk-7BNK4M23.js} +1 -1
  14. package/dist/{chunk-L2IDE7WS.js → chunk-A36MGB3V.js} +277 -111
  15. package/dist/{chunk-LO7ZLV7S.js → chunk-DBJC4ULT.js} +12 -5
  16. package/dist/{chunk-JPWICLXQ.js → chunk-DHWFOHPX.js} +1 -1
  17. package/dist/{chunk-W4AFGFFB.js → chunk-EMJFHTPZ.js} +3 -3
  18. package/dist/{chunk-4HKVZO7R.js → chunk-IYA3NS5P.js} +1 -1
  19. package/dist/{chunk-YLOAQZLA.js → chunk-J4MX5DIA.js} +5 -5
  20. package/dist/{chunk-224XKVLP.js → chunk-JMACX5FN.js} +1 -1
  21. package/dist/{chunk-YLTRFZAT.js → chunk-KJR4Z2YC.js} +8 -8
  22. package/dist/{chunk-VJQLUS5I.js → chunk-KT5PWWWO.js} +1 -1
  23. package/dist/{chunk-OJMO4JBQ.js → chunk-LBYGGNEZ.js} +1 -1
  24. package/dist/{chunk-36X7TRUI.js → chunk-LYMPKO6N.js} +2 -2
  25. package/dist/{chunk-GOOTYTTV.js → chunk-N6DVSOLZ.js} +933 -500
  26. package/dist/{chunk-VKDBM3RX.js → chunk-QZ67TDLJ.js} +3 -3
  27. package/dist/{chunk-2ZURBWEV.js → chunk-R6GLLMRZ.js} +9 -9
  28. package/dist/{chunk-QTUPHTTK.js → chunk-T2ZMHXCX.js} +1 -1
  29. package/dist/{chunk-YWD6WXXM.js → chunk-TIJXG4UB.js} +6 -6
  30. package/dist/{chunk-KTWKW6KJ.js → chunk-TIQU5VY7.js} +1 -1
  31. package/dist/{chunk-E7W3YOXL.js → chunk-U2SF3KAQ.js} +2 -2
  32. package/dist/{chunk-TNZYQTEE.js → chunk-WMZKZCCE.js} +1 -1
  33. package/dist/{chunk-BMM3GXV6.js → chunk-XPRGWHBL.js} +80 -37
  34. package/dist/{chunk-KV7A5OZT.js → chunk-YI6NTL72.js} +5 -5
  35. package/dist/{chunk-K4WRQTEF.js → chunk-YNG7BWGB.js} +2034 -490
  36. package/dist/{ci-workflow-TOS5X527.js → ci-workflow-ZO6FPO3V.js} +3 -3
  37. package/dist/{dist-D27LJR2T.js → dist-SQAOKWXM.js} +5 -3
  38. package/dist/{dist-6RT2AVYU.js → dist-ZS3Y3YWP.js} +30 -2
  39. package/dist/{docs-HOJMQYDZ.js → docs-VW2KDPI3.js} +4 -4
  40. package/dist/{engine-D6UYH6DB.js → engine-LFQ2JSHY.js} +3 -3
  41. package/dist/{entropy-LZ4IDGZR.js → entropy-P62LCVJI.js} +3 -3
  42. package/dist/{feedback-WV7EQVK7.js → feedback-O3TZAPGX.js} +1 -1
  43. package/dist/{generate-agent-definitions-GFNXJCBY.js → generate-agent-definitions-WVCOZDKI.js} +4 -4
  44. package/dist/{glob-helper-FZPPTGJN.js → glob-helper-JE3MO4UD.js} +1 -1
  45. package/dist/{graph-loader-5MKHAJJF.js → graph-loader-TJU4E7KL.js} +1 -1
  46. package/dist/index.d.ts +15 -9
  47. package/dist/index.js +25 -25
  48. package/dist/{loader-Z23JTXIZ.js → loader-UB2A65DN.js} +3 -3
  49. package/dist/{mcp-JDJGELGD.js → mcp-ST26QMDK.js} +15 -15
  50. package/dist/{performance-7ATLHTQU.js → performance-5HQG3IU6.js} +4 -4
  51. package/dist/{review-pipeline-COBGGXHJ.js → review-pipeline-VRDL5A7J.js} +3 -3
  52. package/dist/{runtime-KCBNQUXK.js → runtime-TQQT2MWQ.js} +3 -3
  53. package/dist/{scan-BIJYHOE7.js → scan-SFX5U3TE.js} +1 -1
  54. package/dist/{security-MAPRIFQT.js → security-IMFAE6MW.js} +1 -1
  55. package/dist/{validate-74XTQR7Q.js → validate-GW2DG64P.js} +4 -4
  56. package/dist/{validate-cross-check-MI5MZ7MB.js → validate-cross-check-F5YQVXLT.js} +3 -3
  57. package/package.json +6 -6
@@ -9,17 +9,19 @@ import {
9
9
  isOk
10
10
  } from "./chunk-RL4VMEXL.js";
11
11
  import {
12
+ DEFAULT_SKIP_DIRS,
12
13
  GraphStore,
13
14
  NODE_STABILITY,
14
15
  init_dist as init_dist2,
15
- queryTraceability
16
- } from "./chunk-BMM3GXV6.js";
16
+ queryTraceability,
17
+ skipDirGlobs
18
+ } from "./chunk-XPRGWHBL.js";
17
19
  import {
18
20
  __esm,
19
21
  __export
20
22
  } from "./chunk-KFQGP6VL.js";
21
23
 
22
- // ../core/dist/chunk-CE225WRK.mjs
24
+ // ../core/dist/chunk-4UI65RLE.mjs
23
25
  import { z } from "zod";
24
26
  import { createHash } from "crypto";
25
27
  import { minimatch } from "minimatch";
@@ -69,17 +71,17 @@ function resolveFileToLayer(file, layers) {
69
71
  }
70
72
  return void 0;
71
73
  }
72
- async function fileExists(path42) {
74
+ async function fileExists(path46) {
73
75
  try {
74
- await accessAsync(path42, constants.F_OK);
76
+ await accessAsync(path46, constants.F_OK);
75
77
  return true;
76
78
  } catch {
77
79
  return false;
78
80
  }
79
81
  }
80
- async function readFileContent(path42) {
82
+ async function readFileContent(path46) {
81
83
  try {
82
- const content = await readFileAsync(path42, "utf-8");
84
+ const content = await readFileAsync(path46, "utf-8");
83
85
  return Ok(content);
84
86
  } catch (error) {
85
87
  return Err(error);
@@ -485,9 +487,9 @@ async function resolveWasmPath(grammarName) {
485
487
  const { createRequire } = await import("module");
486
488
  const require2 = createRequire(import.meta.url ?? __filename);
487
489
  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`);
490
+ const path46 = await import("path");
491
+ const pkgDir = path46.dirname(pkgPath);
492
+ return path46.join(pkgDir, "out", `${grammarName}.wasm`);
491
493
  }
492
494
  async function loadLanguage(lang) {
493
495
  const grammarName = GRAMMAR_MAP[lang];
@@ -1421,7 +1423,7 @@ function mapForbiddenImportViolations(forbidden, rootDir, category) {
1421
1423
  });
1422
1424
  }
1423
1425
  function isSkippedEntry(name) {
1424
- return name.startsWith(".") || name === "node_modules" || name === "dist";
1426
+ return name.startsWith(".") || DEFAULT_SKIP_DIRS.has(name);
1425
1427
  }
1426
1428
  function isTsSourceFile(name) {
1427
1429
  if (!name.endsWith(".ts") && !name.endsWith(".tsx")) return false;
@@ -1510,7 +1512,7 @@ function extractImportSources(content, filePath) {
1510
1512
  return sources;
1511
1513
  }
1512
1514
  function isSkippedEntry2(name) {
1513
- return name.startsWith(".") || name === "node_modules" || name === "dist";
1515
+ return name.startsWith(".") || DEFAULT_SKIP_DIRS.has(name);
1514
1516
  }
1515
1517
  function isTsSourceFile2(name) {
1516
1518
  if (!name.endsWith(".ts") && !name.endsWith(".tsx")) return false;
@@ -1745,11 +1747,18 @@ async function toHaveMaxDepDepth(received, maxDepth) {
1745
1747
  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
1748
  };
1747
1749
  }
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"() {
1750
+ 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;
1751
+ var init_chunk_4UI65RLE = __esm({
1752
+ "../core/dist/chunk-4UI65RLE.mjs"() {
1751
1753
  "use strict";
1752
1754
  init_dist();
1755
+ init_dist2();
1756
+ init_dist2();
1757
+ __defProp = Object.defineProperty;
1758
+ __export2 = (target, all) => {
1759
+ for (var name in all)
1760
+ __defProp(target, name, { get: all[name], enumerable: true });
1761
+ };
1753
1762
  ArchMetricCategorySchema = z.enum([
1754
1763
  "circular-deps",
1755
1764
  "layer-violations",
@@ -1926,11 +1935,11 @@ var init_chunk_CE225WRK = __esm({
1926
1935
  TypeScriptParser = class {
1927
1936
  name = "typescript";
1928
1937
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1929
- async parseFile(path42) {
1930
- const contentResult = await readFileContent(path42);
1938
+ async parseFile(path46) {
1939
+ const contentResult = await readFileContent(path46);
1931
1940
  if (!contentResult.ok) {
1932
1941
  return Err(
1933
- createParseError("NOT_FOUND", `File not found: ${path42}`, { path: path42 }, [
1942
+ createParseError("NOT_FOUND", `File not found: ${path46}`, { path: path46 }, [
1934
1943
  "Check that the file exists",
1935
1944
  "Verify the path is correct"
1936
1945
  ])
@@ -1940,7 +1949,7 @@ var init_chunk_CE225WRK = __esm({
1940
1949
  const ast = parse(contentResult.value, {
1941
1950
  loc: true,
1942
1951
  range: true,
1943
- jsx: path42.endsWith(".tsx"),
1952
+ jsx: path46.endsWith(".tsx"),
1944
1953
  errorOnUnknownASTType: false
1945
1954
  });
1946
1955
  return Ok({
@@ -1951,7 +1960,7 @@ var init_chunk_CE225WRK = __esm({
1951
1960
  } catch (e) {
1952
1961
  const error = e;
1953
1962
  return Err(
1954
- createParseError("SYNTAX_ERROR", `Failed to parse ${path42}: ${error.message}`, { path: path42 }, [
1963
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path46}: ${error.message}`, { path: path46 }, [
1955
1964
  "Check for syntax errors in the file",
1956
1965
  "Ensure valid TypeScript syntax"
1957
1966
  ])
@@ -2266,11 +2275,11 @@ var init_chunk_CE225WRK = __esm({
2266
2275
  this.extensions = extensions;
2267
2276
  this.strategy = strategy;
2268
2277
  }
2269
- async parseFile(path42) {
2270
- const contentResult = await readFileContent(path42);
2278
+ async parseFile(path46) {
2279
+ const contentResult = await readFileContent(path46);
2271
2280
  if (!contentResult.ok) {
2272
2281
  return Err(
2273
- createParseError("NOT_FOUND", `File not found: ${path42}`, { path: path42 }, [
2282
+ createParseError("NOT_FOUND", `File not found: ${path46}`, { path: path46 }, [
2274
2283
  "Check that the file exists"
2275
2284
  ])
2276
2285
  );
@@ -2286,7 +2295,7 @@ var init_chunk_CE225WRK = __esm({
2286
2295
  } catch (e) {
2287
2296
  const error = e;
2288
2297
  return Err(
2289
- createParseError("SYNTAX_ERROR", `Failed to parse ${path42}: ${error.message}`, { path: path42 }, [
2298
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path46}: ${error.message}`, { path: path46 }, [
2290
2299
  "Check for syntax errors in the file"
2291
2300
  ])
2292
2301
  );
@@ -2854,6 +2863,7 @@ __export(dist_exports, {
2854
2863
  CompoundLockHeldError: () => CompoundLockHeldError,
2855
2864
  ConfidenceTierSchema: () => ConfidenceTierSchema,
2856
2865
  ConfirmationSchema: () => ConfirmationSchema,
2866
+ ConflictError: () => ConflictError,
2857
2867
  ConsoleSink: () => ConsoleSink,
2858
2868
  ConstraintRuleSchema: () => ConstraintRuleSchema,
2859
2869
  ContributingFeatureSchema: () => ContributingFeatureSchema,
@@ -2873,6 +2883,7 @@ __export(dist_exports, {
2873
2883
  DepDepthCollector: () => DepDepthCollector,
2874
2884
  DirectionSchema: () => DirectionSchema,
2875
2885
  EMPTY_SUPPLY_CHAIN: () => EMPTY_SUPPLY_CHAIN,
2886
+ ETagStore: () => ETagStore,
2876
2887
  EVIDENCE_SATURATION: () => EVIDENCE_SATURATION,
2877
2888
  EXTENSION_MAP: () => EXTENSION_MAP,
2878
2889
  EmergenceConfidenceSchema: () => EmergenceConfidenceSchema,
@@ -2975,6 +2986,7 @@ __export(dist_exports, {
2975
2986
  ViolationHistorySchema: () => ViolationHistorySchema,
2976
2987
  ViolationSchema: () => ViolationSchema,
2977
2988
  ViolationSnapshotSchema: () => ViolationSnapshotSchema,
2989
+ WHATWG_BAD_PORTS: () => WHATWG_BAD_PORTS,
2978
2990
  acquireCompoundLock: () => acquireCompoundLock,
2979
2991
  addProvenance: () => addProvenance,
2980
2992
  agentConfigRules: () => agentConfigRules,
@@ -3000,6 +3012,7 @@ __export(dist_exports, {
3000
3012
  archiveStream: () => archiveStream,
3001
3013
  assembleCandidateReport: () => assembleCandidateReport,
3002
3014
  assembleReport: () => assembleReport,
3015
+ assertPortUsable: () => assertPortUsable,
3003
3016
  assertSanitized: () => assertSanitized,
3004
3017
  assignFeature: () => assignFeature,
3005
3018
  buildDependencyGraph: () => buildDependencyGraph,
@@ -3044,6 +3057,7 @@ __export(dist_exports, {
3044
3057
  createRegionMap: () => createRegionMap,
3045
3058
  createSelfReview: () => createSelfReview,
3046
3059
  createStream: () => createStream,
3060
+ createTrackerClient: () => createTrackerClient,
3047
3061
  crossReferenceUndocumentedFixes: () => crossReferenceUndocumentedFixes,
3048
3062
  cryptoRules: () => cryptoRules,
3049
3063
  deduplicateCleanupFindings: () => deduplicateCleanupFindings,
@@ -3104,6 +3118,7 @@ __export(dist_exports, {
3104
3118
  getParser: () => getParser,
3105
3119
  getPhaseCategories: () => getPhaseCategories,
3106
3120
  getPulseAdapter: () => getPulseAdapter,
3121
+ getRoadmapMode: () => getRoadmapMode,
3107
3122
  getStreamForBranch: () => getStreamForBranch,
3108
3123
  getTaintFilePath: () => getTaintFilePath,
3109
3124
  getTrustLevel: () => getTrustLevel,
@@ -3112,6 +3127,7 @@ __export(dist_exports, {
3112
3127
  goRules: () => goRules,
3113
3128
  injectionRules: () => injectionRules,
3114
3129
  insecureDefaultsRules: () => insecureDefaultsRules,
3130
+ isBadPort: () => isBadPort,
3115
3131
  isDuplicateFinding: () => isDuplicateFinding,
3116
3132
  isErr: () => isErr,
3117
3133
  isOk: () => isOk,
@@ -3130,17 +3146,21 @@ __export(dist_exports, {
3130
3146
  loadHandoff: () => loadHandoff,
3131
3147
  loadIndexEntries: () => loadIndexEntries,
3132
3148
  loadPricingData: () => loadPricingData,
3149
+ loadProjectRoadmapMode: () => loadProjectRoadmapMode,
3133
3150
  loadRelevantLearnings: () => loadRelevantLearnings,
3134
3151
  loadSessionSummary: () => loadSessionSummary,
3135
3152
  loadState: () => loadState,
3136
3153
  loadStreamIndex: () => loadStreamIndex,
3154
+ loadTrackerClientConfigFromProject: () => loadTrackerClientConfigFromProject,
3137
3155
  loadTrackerSyncConfig: () => loadTrackerSyncConfig,
3138
3156
  logAgentAction: () => logAgentAction,
3157
+ makeTrackerConflictBody: () => makeTrackerConflictBody,
3139
3158
  mapInjectionFindings: () => mapInjectionFindings,
3140
3159
  mapSecurityFindings: () => mapSecurityFindings,
3141
3160
  mapSecuritySeverity: () => mapSecuritySeverity,
3142
3161
  markProtectedFindings: () => markProtectedFindings,
3143
3162
  mcpRules: () => mcpRules,
3163
+ migrate: () => migrate_exports,
3144
3164
  migrateToStreams: () => migrateToStreams,
3145
3165
  networkRules: () => networkRules,
3146
3166
  nodeRules: () => nodeRules,
@@ -3212,6 +3232,8 @@ __export(dist_exports, {
3212
3232
  scanForInjection: () => scanForInjection,
3213
3233
  scopeContext: () => scopeContext,
3214
3234
  scoreRoadmapCandidates: () => scoreRoadmapCandidates,
3235
+ scoreRoadmapCandidatesFileLess: () => scoreRoadmapCandidatesFileLess,
3236
+ scoreRoadmapCandidatesForMode: () => scoreRoadmapCandidatesForMode,
3215
3237
  searchSymbols: () => searchSymbols,
3216
3238
  secretRules: () => secretRules,
3217
3239
  securityFindingId: () => securityFindingId,
@@ -3249,6 +3271,7 @@ __export(dist_exports, {
3249
3271
  validateKnowledgeMap: () => validateKnowledgeMap,
3250
3272
  validatePatternConfig: () => validatePatternConfig,
3251
3273
  validatePulseConfig: () => validatePulseConfig,
3274
+ validateRoadmapMode: () => validateRoadmapMode,
3252
3275
  validateSolutionsDir: () => validateSolutionsDir,
3253
3276
  violationId: () => violationId,
3254
3277
  weeksUntilThreshold: () => weeksUntilThreshold,
@@ -3294,63 +3317,64 @@ import { existsSync as existsSync9 } from "fs";
3294
3317
  import { join as join11 } from "path";
3295
3318
  import { existsSync as existsSync10 } from "fs";
3296
3319
  import { join as join12 } from "path";
3320
+ import * as fs3 from "fs";
3321
+ import * as path3 from "path";
3297
3322
  import { minimatch as minimatch2 } from "minimatch";
3298
3323
  import { basename as basename2 } from "path";
3299
- import { join as join13, basename as basename3 } from "path";
3324
+ import { join as join14, basename as basename3 } from "path";
3300
3325
  import { basename as basename4, dirname as dirname42 } from "path";
3301
3326
  import { z as z3 } from "zod";
3302
- import * as fs3 from "fs/promises";
3303
3327
  import * as fs4 from "fs/promises";
3304
- import { join as join14, resolve as resolve32 } from "path";
3328
+ import * as fs5 from "fs/promises";
3329
+ import { resolve as resolve5 } from "path";
3305
3330
  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";
3331
+ import { join as join15, resolve as resolve42 } from "path";
3332
+ import { dirname as dirname5, resolve as resolve6 } from "path";
3333
+ import { dirname as dirname6, extname as extname2, resolve as resolve7 } from "path";
3308
3334
  import { minimatch as minimatch3 } from "minimatch";
3309
3335
  import { readdirSync, statSync as statSync2 } from "fs";
3310
- import { join as join15 } from "path";
3311
- import * as fs5 from "fs";
3336
+ import { join as join16 } from "path";
3337
+ import * as fs6 from "fs";
3312
3338
  import { promisify as promisify2 } from "util";
3313
- import { dirname as dirname7, basename as basename5, join as join16 } from "path";
3339
+ import { dirname as dirname7, basename as basename5, join as join17 } from "path";
3314
3340
  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";
3341
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
3342
+ import { join as join18, dirname as dirname8 } from "path";
3317
3343
  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";
3344
+ import * as fs7 from "fs";
3345
+ import * as path4 from "path";
3346
+ import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync13, mkdirSync as mkdirSync22 } from "fs";
3321
3347
  import { dirname as dirname9 } from "path";
3322
3348
  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";
3349
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, mkdirSync as mkdirSync3, existsSync as existsSync14 } from "fs";
3324
3350
  import { randomBytes as randomBytes2 } from "crypto";
3325
- import { join as join19, dirname as dirname10 } from "path";
3351
+ import { join as join20, dirname as dirname10 } from "path";
3326
3352
  import { z as z6 } from "zod";
3327
- import * as fs7 from "fs";
3328
- import * as path4 from "path";
3329
3353
  import * as fs8 from "fs";
3330
3354
  import * as path5 from "path";
3331
3355
  import * as fs9 from "fs";
3332
3356
  import * as path6 from "path";
3357
+ import * as fs10 from "fs";
3358
+ import * as path7 from "path";
3333
3359
  import { z as z7 } from "zod";
3360
+ import * as fs14 from "fs";
3361
+ import * as path11 from "path";
3334
3362
  import * as fs13 from "fs";
3335
3363
  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
3364
  import * as fs11 from "fs";
3343
3365
  import * as path8 from "path";
3344
- import * as fs14 from "fs";
3345
- import * as path11 from "path";
3346
- import * as crypto2 from "crypto";
3366
+ import { execSync } from "child_process";
3367
+ import { z as z8 } from "zod";
3368
+ import * as fs12 from "fs";
3369
+ import * as path9 from "path";
3347
3370
  import * as fs15 from "fs";
3348
3371
  import * as path12 from "path";
3372
+ import * as crypto2 from "crypto";
3349
3373
  import * as fs16 from "fs";
3350
3374
  import * as path13 from "path";
3351
- import * as crypto3 from "crypto";
3352
3375
  import * as fs17 from "fs";
3353
3376
  import * as path14 from "path";
3377
+ import * as crypto3 from "crypto";
3354
3378
  import * as fs18 from "fs";
3355
3379
  import * as path15 from "path";
3356
3380
  import * as fs19 from "fs";
@@ -3359,77 +3383,96 @@ import * as fs20 from "fs";
3359
3383
  import * as path17 from "path";
3360
3384
  import * as fs21 from "fs";
3361
3385
  import * as path18 from "path";
3362
- import { execSync as execSync2 } from "child_process";
3363
3386
  import * as fs22 from "fs";
3364
3387
  import * as path19 from "path";
3388
+ import { execSync as execSync2 } from "child_process";
3365
3389
  import * as fs23 from "fs";
3366
3390
  import * as path20 from "path";
3367
3391
  import * as fs24 from "fs";
3368
3392
  import * as path21 from "path";
3369
3393
  import * as fs25 from "fs";
3370
3394
  import * as path22 from "path";
3395
+ import * as fs26 from "fs";
3396
+ import * as path23 from "path";
3371
3397
  import { z as z9 } from "zod";
3372
- import * as fs27 from "fs/promises";
3398
+ import * as fs28 from "fs/promises";
3373
3399
  import { minimatch as minimatch4 } from "minimatch";
3374
3400
  import { z as z10 } from "zod";
3375
- import * as fs26 from "fs";
3376
- import * as path23 from "path";
3401
+ import * as fs27 from "fs";
3402
+ import * as path24 from "path";
3377
3403
  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";
3404
+ import { join as join392, dirname as dirname12 } from "path";
3379
3405
  import { createHash as createHash32 } from "crypto";
3380
3406
  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";
3407
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync16, renameSync as renameSync82, mkdirSync as mkdirSync15, existsSync as existsSync342 } from "fs";
3382
3408
  import { execSync as execSync3 } from "child_process";
3383
3409
  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";
3410
+ import { isAbsolute as isAbsolute4, join as join40, relative as relative32, dirname as dirname13 } from "path";
3386
3411
  import * as path25 from "path";
3387
3412
  import * as path26 from "path";
3388
3413
  import * as path27 from "path";
3389
- import * as fs28 from "fs";
3390
3414
  import * as path28 from "path";
3391
3415
  import * as fs29 from "fs";
3392
3416
  import * as path29 from "path";
3393
3417
  import * as fs30 from "fs";
3394
- import { z as z12 } from "zod";
3395
- import * as fs31 from "fs/promises";
3396
3418
  import * as path30 from "path";
3397
- import * as fs32 from "fs/promises";
3419
+ import * as fs31 from "fs";
3420
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
3421
+ import * as fs32 from "fs";
3398
3422
  import * as path31 from "path";
3399
- import * as ejs from "ejs";
3400
3423
  import * as fs33 from "fs";
3401
3424
  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";
3425
+ import { createHash as createHash4 } from "crypto";
3405
3426
  import * as path33 from "path";
3406
- import * as fs35 from "fs";
3427
+ import { z as z12 } from "zod";
3428
+ import * as fs34 from "fs/promises";
3407
3429
  import * as path34 from "path";
3408
- import * as fs36 from "fs";
3430
+ import * as fs35 from "fs/promises";
3409
3431
  import * as path35 from "path";
3410
- import * as os2 from "os";
3411
- import * as fs37 from "fs";
3432
+ import * as ejs from "ejs";
3433
+ import * as fs36 from "fs";
3412
3434
  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";
3435
+ import * as os from "os";
3436
+ import { spawn as spawn2 } from "child_process";
3437
+ import * as fs37 from "fs/promises";
3417
3438
  import * as path37 from "path";
3418
- import * as crypto4 from "crypto";
3419
- import * as fs40 from "fs";
3439
+ import * as fs38 from "fs";
3440
+ import * as path38 from "path";
3441
+ import * as fs39 from "fs";
3420
3442
  import * as path39 from "path";
3421
- import * as fs41 from "fs";
3422
- import * as fs42 from "fs";
3443
+ import * as os2 from "os";
3444
+ import * as fs40 from "fs";
3423
3445
  import * as path40 from "path";
3424
- import { readFileSync as readFileSync36 } from "fs";
3446
+ import * as fs42 from "fs";
3447
+ import * as path42 from "path";
3448
+ import { execFileSync as execFileSync2 } from "child_process";
3449
+ import * as fs41 from "fs";
3450
+ import * as path41 from "path";
3451
+ import * as crypto4 from "crypto";
3452
+ import * as fs43 from "fs";
3453
+ import * as path43 from "path";
3454
+ import * as fs44 from "fs";
3455
+ import * as fs45 from "fs";
3456
+ import * as path44 from "path";
3457
+ import { readFileSync as readFileSync38 } from "fs";
3425
3458
  import { fileURLToPath } from "url";
3426
- import { dirname as dirname18, join as join572 } from "path";
3459
+ import { dirname as dirname18, join as join612 } from "path";
3427
3460
  import { execFile } from "child_process";
3428
3461
  import { promisify as promisify22 } from "util";
3429
3462
  import { execFile as execFile2 } from "child_process";
3430
3463
  import { promisify as promisify3 } from "util";
3431
- import * as fs43 from "fs/promises";
3432
- import * as path41 from "path";
3464
+ import * as fs46 from "fs/promises";
3465
+ import * as path45 from "path";
3466
+ function isBadPort(port) {
3467
+ return BAD_PORT_SET.has(port);
3468
+ }
3469
+ function assertPortUsable(port, label = "server") {
3470
+ if (isBadPort(port)) {
3471
+ throw new Error(
3472
+ `Refusing to bind ${label} to port ${port}: this port is on the WHATWG fetch bad-ports list, so browsers and Node's fetch() will reject every connection with "bad port". Choose a different port. See https://fetch.spec.whatwg.org/#port-blocking for the full list.`
3473
+ );
3474
+ }
3475
+ }
3433
3476
  async function validateFileStructure(projectPath, conventions) {
3434
3477
  const missing = [];
3435
3478
  const unexpected = [];
@@ -3465,15 +3508,15 @@ function validateConfig(data, schema) {
3465
3508
  let message = "Configuration validation failed";
3466
3509
  const suggestions = [];
3467
3510
  if (firstError) {
3468
- const path42 = firstError.path.join(".");
3469
- const pathDisplay = path42 ? ` at "${path42}"` : "";
3511
+ const path46 = firstError.path.join(".");
3512
+ const pathDisplay = path46 ? ` at "${path46}"` : "";
3470
3513
  if (firstError.code === "invalid_type") {
3471
3514
  const received = firstError.received;
3472
3515
  const expected = firstError.expected;
3473
3516
  if (received === "undefined") {
3474
3517
  code = "MISSING_FIELD";
3475
3518
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
3476
- suggestions.push(`Field "${path42}" is required and must be of type "${expected}"`);
3519
+ suggestions.push(`Field "${path46}" is required and must be of type "${expected}"`);
3477
3520
  } else {
3478
3521
  code = "INVALID_TYPE";
3479
3522
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -3689,7 +3732,7 @@ function isAgnixDisabled() {
3689
3732
  return v === "1" || v === "true";
3690
3733
  }
3691
3734
  function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, spawnFn = spawn) {
3692
- return new Promise((resolve8) => {
3735
+ return new Promise((resolve10) => {
3693
3736
  const args = ["--format", "json"];
3694
3737
  if (strict) args.push("--strict");
3695
3738
  args.push(cwd);
@@ -3703,7 +3746,7 @@ function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, sp
3703
3746
  if (settled) return;
3704
3747
  settled = true;
3705
3748
  clearTimeout(timer);
3706
- resolve8(outcome);
3749
+ resolve10(outcome);
3707
3750
  };
3708
3751
  const timer = setTimeout(() => {
3709
3752
  child.kill("SIGKILL");
@@ -3807,16 +3850,16 @@ function makeFinding(input) {
3807
3850
  if (input.suggestion) finding.suggestion = input.suggestion;
3808
3851
  return finding;
3809
3852
  }
3810
- function readTextSafe(path42) {
3853
+ function readTextSafe(path46) {
3811
3854
  try {
3812
- return readFileSync2(path42, "utf-8");
3855
+ return readFileSync2(path46, "utf-8");
3813
3856
  } catch {
3814
3857
  return null;
3815
3858
  }
3816
3859
  }
3817
- function safeFileSize(path42) {
3860
+ function safeFileSize(path46) {
3818
3861
  try {
3819
- return statSync(path42).size;
3862
+ return statSync(path46).size;
3820
3863
  } catch {
3821
3864
  return null;
3822
3865
  }
@@ -4361,27 +4404,27 @@ function extractSections(content) {
4361
4404
  }
4362
4405
  return sections.map((section) => buildAgentMapSection(section, lines));
4363
4406
  }
4364
- function isExternalLink(path42) {
4365
- return path42.startsWith("http://") || path42.startsWith("https://") || path42.startsWith("#") || path42.startsWith("mailto:");
4407
+ function isExternalLink(path46) {
4408
+ return path46.startsWith("http://") || path46.startsWith("https://") || path46.startsWith("#") || path46.startsWith("mailto:");
4366
4409
  }
4367
4410
  function resolveLinkPath(linkPath, baseDir) {
4368
4411
  return linkPath.startsWith(".") ? join8(baseDir, linkPath) : linkPath;
4369
4412
  }
4370
- async function validateAgentsMap(path42 = "./AGENTS.md") {
4371
- const contentResult = await readFileContent(path42);
4413
+ async function validateAgentsMap(path46 = "./AGENTS.md") {
4414
+ const contentResult = await readFileContent(path46);
4372
4415
  if (!contentResult.ok) {
4373
4416
  return Err(
4374
4417
  createError(
4375
4418
  "PARSE_ERROR",
4376
4419
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
4377
- { path: path42 },
4420
+ { path: path46 },
4378
4421
  ["Ensure the file exists", "Check file permissions"]
4379
4422
  )
4380
4423
  );
4381
4424
  }
4382
4425
  const content = contentResult.value;
4383
4426
  const sections = extractSections(content);
4384
- const baseDir = dirname22(path42);
4427
+ const baseDir = dirname22(path46);
4385
4428
  const sectionTitles = sections.map((s) => s.title);
4386
4429
  const missingSections = REQUIRED_SECTIONS.filter(
4387
4430
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -4725,6 +4768,45 @@ function promoteWarningsToErrors(findings) {
4725
4768
  function hasErrors(findings) {
4726
4769
  return findings.some((f) => f.severity === "error");
4727
4770
  }
4771
+ function getRoadmapMode(config) {
4772
+ if (!config || !config.roadmap) return "file-backed";
4773
+ const mode = config.roadmap.mode;
4774
+ return mode === "file-less" ? "file-less" : "file-backed";
4775
+ }
4776
+ function validateRoadmapMode(config, projectRoot) {
4777
+ const root = path3.resolve(projectRoot);
4778
+ const mode = getRoadmapMode(config);
4779
+ if (mode === "file-backed") return Ok(void 0);
4780
+ const tracker = config.roadmap?.tracker;
4781
+ if (!tracker || typeof tracker !== "object") {
4782
+ return Err(
4783
+ createError(
4784
+ "ROADMAP_MODE_MISSING_TRACKER",
4785
+ 'roadmap.mode is "file-less" but roadmap.tracker is not configured. File-less mode requires an external tracker as the source of truth.',
4786
+ { mode },
4787
+ [
4788
+ 'Configure roadmap.tracker in harness.config.json (e.g., kind: "github", repo: "owner/name").',
4789
+ 'Or set roadmap.mode to "file-backed" if you want to keep using docs/roadmap.md.'
4790
+ ]
4791
+ )
4792
+ );
4793
+ }
4794
+ const roadmapPath = path3.join(root, "docs", "roadmap.md");
4795
+ if (fs3.existsSync(roadmapPath)) {
4796
+ return Err(
4797
+ createError(
4798
+ "ROADMAP_MODE_FILE_PRESENT",
4799
+ '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.',
4800
+ { mode, roadmapPath },
4801
+ [
4802
+ "Run `harness roadmap migrate --to=file-less` to migrate features into the tracker.",
4803
+ 'Or set roadmap.mode to "file-backed" if you want to keep docs/roadmap.md.'
4804
+ ]
4805
+ )
4806
+ );
4807
+ }
4808
+ return Ok(void 0);
4809
+ }
4728
4810
  function determineImportance(filePath) {
4729
4811
  const name = basename2(filePath).toLowerCase();
4730
4812
  if (name === "index.ts" || name === "index.js" || name === "main.ts") {
@@ -4821,8 +4903,8 @@ async function checkDocCoverage(domain, options = {}) {
4821
4903
  );
4822
4904
  }
4823
4905
  }
4824
- function suggestFix(path42, existingFiles) {
4825
- const targetName = basename3(path42).toLowerCase();
4906
+ function suggestFix(path46, existingFiles) {
4907
+ const targetName = basename3(path46).toLowerCase();
4826
4908
  const similar = existingFiles.find((file) => {
4827
4909
  const fileName = basename3(file).toLowerCase();
4828
4910
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -4830,10 +4912,10 @@ function suggestFix(path42, existingFiles) {
4830
4912
  if (similar) {
4831
4913
  return `Did you mean "${similar}"?`;
4832
4914
  }
4833
- return `Create the file "${path42}" or remove the link`;
4915
+ return `Create the file "${path46}" or remove the link`;
4834
4916
  }
4835
4917
  async function validateKnowledgeMap(rootDir = process.cwd()) {
4836
- const agentsPath = join13(rootDir, "AGENTS.md");
4918
+ const agentsPath = join14(rootDir, "AGENTS.md");
4837
4919
  const agentsResult = await validateAgentsMap(agentsPath);
4838
4920
  if (!agentsResult.ok) {
4839
4921
  return Err(agentsResult.error);
@@ -5188,8 +5270,8 @@ function createBoundaryValidator(schema, name) {
5188
5270
  return Ok(result.data);
5189
5271
  }
5190
5272
  const suggestions = result.error.issues.map((issue) => {
5191
- const path42 = issue.path.join(".");
5192
- return path42 ? `${path42}: ${issue.message}` : issue.message;
5273
+ const path46 = issue.path.join(".");
5274
+ return path46 ? `${path46}: ${issue.message}` : issue.message;
5193
5275
  });
5194
5276
  return Err(
5195
5277
  createError(
@@ -5235,7 +5317,7 @@ function validateBoundaries(boundaries, data) {
5235
5317
  async function writeConfig(filePath, content) {
5236
5318
  try {
5237
5319
  const json = JSON.stringify(content, null, 2) + "\n";
5238
- await fs3.writeFile(filePath, json, "utf-8");
5320
+ await fs4.writeFile(filePath, json, "utf-8");
5239
5321
  return Ok(void 0);
5240
5322
  } catch (error) {
5241
5323
  return Err(error instanceof Error ? error : new Error(String(error)));
@@ -5509,7 +5591,7 @@ function deepMergeConstraints(localConfig, bundleConstraints, _existingContribut
5509
5591
  async function readLockfile(lockfilePath) {
5510
5592
  let raw;
5511
5593
  try {
5512
- raw = await fs4.readFile(lockfilePath, "utf-8");
5594
+ raw = await fs5.readFile(lockfilePath, "utf-8");
5513
5595
  } catch (err) {
5514
5596
  if (isNodeError(err) && err.code === "ENOENT") {
5515
5597
  return { ok: true, value: null };
@@ -5791,9 +5873,9 @@ function parseProtectedRegions(files) {
5791
5873
  };
5792
5874
  }
5793
5875
  function collectFieldEntries(rootDir, field) {
5794
- if (typeof field === "string") return [resolve32(rootDir, field)];
5876
+ if (typeof field === "string") return [resolve42(rootDir, field)];
5795
5877
  if (typeof field === "object" && field !== null) {
5796
- return Object.values(field).filter((v) => typeof v === "string").map((v) => resolve32(rootDir, v));
5878
+ return Object.values(field).filter((v) => typeof v === "string").map((v) => resolve42(rootDir, v));
5797
5879
  }
5798
5880
  return [];
5799
5881
  }
@@ -5801,46 +5883,234 @@ function extractPackageEntries(rootDir, pkg) {
5801
5883
  const entries = [];
5802
5884
  entries.push(...collectFieldEntries(rootDir, pkg["exports"]));
5803
5885
  if (entries.length === 0 && typeof pkg["main"] === "string") {
5804
- entries.push(resolve32(rootDir, pkg["main"]));
5886
+ entries.push(resolve42(rootDir, pkg["main"]));
5805
5887
  }
5806
- if (pkg["bin"]) {
5807
- entries.push(...collectFieldEntries(rootDir, pkg["bin"]));
5888
+ if (pkg["bin"]) entries.push(...collectFieldEntries(rootDir, pkg["bin"]));
5889
+ return entries;
5890
+ }
5891
+ async function readPackageJsonEntries(rootDir, pkgPath) {
5892
+ const content = await readFileContent(pkgPath);
5893
+ if (!content.ok) return [];
5894
+ try {
5895
+ const pkg = JSON.parse(content.value);
5896
+ return extractPackageEntries(rootDir, pkg);
5897
+ } catch {
5898
+ return [];
5899
+ }
5900
+ }
5901
+ async function resolveTypeScript(rootDir) {
5902
+ const pkgPath = join15(rootDir, "package.json");
5903
+ const detected = await fileExists(pkgPath);
5904
+ if (detected) {
5905
+ const entries = await readPackageJsonEntries(rootDir, pkgPath);
5906
+ if (entries.length > 0) {
5907
+ return { language: "typescript", detected: true, entries, hints: TS_HINTS };
5908
+ }
5909
+ }
5910
+ for (const conv of TS_CONVENTIONS) {
5911
+ const p = join15(rootDir, conv);
5912
+ if (await fileExists(p)) {
5913
+ return { language: "typescript", detected: true, entries: [p], hints: TS_HINTS };
5914
+ }
5915
+ }
5916
+ return { language: "typescript", detected, entries: [], hints: TS_HINTS };
5917
+ }
5918
+ async function detectPython(rootDir) {
5919
+ return await fileExists(join15(rootDir, "pyproject.toml")) || await fileExists(join15(rootDir, "setup.py")) || await fileExists(join15(rootDir, "requirements.txt"));
5920
+ }
5921
+ async function readPyProject(rootDir) {
5922
+ const pyproject = join15(rootDir, "pyproject.toml");
5923
+ if (!await fileExists(pyproject)) return { scriptTargets: [] };
5924
+ const content = await readFileContent(pyproject);
5925
+ if (!content.ok) return { scriptTargets: [] };
5926
+ return parsePyProject(content.value);
5927
+ }
5928
+ async function resolveScriptTargetEntry(rootDir, target) {
5929
+ const mod = target.split(":")[0];
5930
+ if (!mod) return void 0;
5931
+ const relPath2 = mod.replaceAll(".", "/") + ".py";
5932
+ for (const candidate of [join15(rootDir, relPath2), join15(rootDir, "src", relPath2)]) {
5933
+ if (await fileExists(candidate)) return candidate;
5934
+ }
5935
+ return void 0;
5936
+ }
5937
+ async function resolvePythonFromScripts(rootDir, targets) {
5938
+ const entries = [];
5939
+ for (const target of targets) {
5940
+ const entry = await resolveScriptTargetEntry(rootDir, target);
5941
+ if (entry) entries.push(entry);
5808
5942
  }
5809
5943
  return entries;
5810
5944
  }
5811
- async function resolveEntryPoints(rootDir, explicitEntries) {
5812
- if (explicitEntries && explicitEntries.length > 0) {
5813
- return Ok(explicitEntries.map((e) => resolve32(rootDir, e)));
5945
+ async function resolvePythonFromProjectName(rootDir, projectName) {
5946
+ if (!projectName) return [];
5947
+ const normalized = projectName.replaceAll("-", "_");
5948
+ const candidates = [
5949
+ join15(rootDir, normalized, "__init__.py"),
5950
+ join15(rootDir, normalized, "__main__.py"),
5951
+ join15(rootDir, "src", normalized, "__init__.py"),
5952
+ join15(rootDir, "src", normalized, "__main__.py")
5953
+ ];
5954
+ const entries = [];
5955
+ for (const c of candidates) {
5956
+ if (await fileExists(c)) entries.push(c);
5814
5957
  }
5815
- const pkgPath = join14(rootDir, "package.json");
5816
- if (await fileExists(pkgPath)) {
5817
- const pkgContent = await readFileContent(pkgPath);
5818
- if (pkgContent.ok) {
5819
- try {
5820
- const pkg = JSON.parse(pkgContent.value);
5821
- const entries = extractPackageEntries(rootDir, pkg);
5822
- if (entries.length > 0) return Ok(entries);
5823
- } catch {
5824
- }
5958
+ return entries;
5959
+ }
5960
+ async function resolvePythonConventions(rootDir) {
5961
+ const entries = [];
5962
+ for (const conv of PYTHON_CONVENTIONS) {
5963
+ const p = join15(rootDir, conv);
5964
+ if (await fileExists(p)) entries.push(p);
5965
+ }
5966
+ return entries;
5967
+ }
5968
+ async function findPythonTopLevelPackages(rootDir) {
5969
+ const found = await findFiles("*/__init__.py", rootDir);
5970
+ if (found.length > 0) return found;
5971
+ return findFiles("src/*/__init__.py", rootDir);
5972
+ }
5973
+ async function resolvePython(rootDir) {
5974
+ if (!await detectPython(rootDir)) {
5975
+ return { language: "python", detected: false, entries: [], hints: PYTHON_HINTS };
5976
+ }
5977
+ const info = await readPyProject(rootDir);
5978
+ const strategies = [
5979
+ () => resolvePythonFromScripts(rootDir, info.scriptTargets),
5980
+ () => resolvePythonFromProjectName(rootDir, info.projectName),
5981
+ () => resolvePythonConventions(rootDir),
5982
+ () => findPythonTopLevelPackages(rootDir)
5983
+ ];
5984
+ for (const strategy of strategies) {
5985
+ const entries = await strategy();
5986
+ if (entries.length > 0) {
5987
+ return { language: "python", detected: true, entries, hints: PYTHON_HINTS };
5988
+ }
5989
+ }
5990
+ return { language: "python", detected: true, entries: [], hints: PYTHON_HINTS };
5991
+ }
5992
+ async function resolveGo(rootDir) {
5993
+ const hints = ["Create main.go at the project root, or use the cmd/<name>/main.go layout"];
5994
+ const detected = await fileExists(join15(rootDir, "go.mod"));
5995
+ if (!detected) return { language: "go", detected: false, entries: [], hints };
5996
+ const entries = [];
5997
+ const mainGo = join15(rootDir, "main.go");
5998
+ if (await fileExists(mainGo)) entries.push(mainGo);
5999
+ entries.push(...await findFiles("cmd/*/main.go", rootDir));
6000
+ return { language: "go", detected: true, entries, hints };
6001
+ }
6002
+ async function resolveRust(rootDir) {
6003
+ const hints = [
6004
+ "Create src/main.rs or src/lib.rs",
6005
+ "Declare [[bin]] entries with a `path` in Cargo.toml"
6006
+ ];
6007
+ const cargoPath = join15(rootDir, "Cargo.toml");
6008
+ const detected = await fileExists(cargoPath);
6009
+ if (!detected) return { language: "rust", detected: false, entries: [], hints };
6010
+ const entries = [];
6011
+ const content = await readFileContent(cargoPath);
6012
+ if (content.ok) {
6013
+ for (const bp of parseCargoBinPaths(content.value)) {
6014
+ const abs = resolve42(rootDir, bp);
6015
+ if (await fileExists(abs)) entries.push(abs);
6016
+ }
6017
+ }
6018
+ if (entries.length === 0) {
6019
+ for (const conv of ["src/main.rs", "src/lib.rs"]) {
6020
+ const p = join15(rootDir, conv);
6021
+ if (await fileExists(p)) entries.push(p);
6022
+ }
6023
+ entries.push(...await findFiles("src/bin/*.rs", rootDir));
6024
+ }
6025
+ return { language: "rust", detected: true, entries, hints };
6026
+ }
6027
+ async function resolveJava(rootDir) {
6028
+ const hints = [
6029
+ "Place an entry class at src/main/java/**/Main.java (or *Application.java for Spring Boot)"
6030
+ ];
6031
+ const detected = await fileExists(join15(rootDir, "pom.xml")) || await fileExists(join15(rootDir, "build.gradle")) || await fileExists(join15(rootDir, "build.gradle.kts"));
6032
+ if (!detected) return { language: "java", detected: false, entries: [], hints };
6033
+ const entries = [];
6034
+ entries.push(...await findFiles("src/main/java/**/Main.java", rootDir));
6035
+ entries.push(...await findFiles("src/main/java/**/*Application.java", rootDir));
6036
+ return { language: "java", detected: true, entries, hints };
6037
+ }
6038
+ function parseTomlLine(raw) {
6039
+ const line = raw.replace(/(^|\s)#.*$/, "").trim();
6040
+ if (!line) return {};
6041
+ const sectionMatch = /^\[([^\]]+)\]$/.exec(line);
6042
+ if (sectionMatch) return { section: sectionMatch[1] ?? "" };
6043
+ const eq = line.indexOf("=");
6044
+ if (eq <= 0) return {};
6045
+ return {
6046
+ key: line.slice(0, eq).trim(),
6047
+ value: stripTomlString2(line.slice(eq + 1).trim())
6048
+ };
6049
+ }
6050
+ function parsePyProject(content) {
6051
+ const result = { scriptTargets: [] };
6052
+ let section = null;
6053
+ for (const raw of content.split(/\r?\n/)) {
6054
+ const parsed = parseTomlLine(raw);
6055
+ if (parsed.section !== void 0) {
6056
+ section = parsed.section;
6057
+ continue;
5825
6058
  }
6059
+ if (parsed.key === void 0 || parsed.value === void 0) continue;
6060
+ if (section === "project" && parsed.key === "name") result.projectName = parsed.value;
6061
+ else if (section === "project.scripts") result.scriptTargets.push(parsed.value);
5826
6062
  }
5827
- const conventions = ["src/index.ts", "src/main.ts", "index.ts", "main.ts"];
5828
- for (const conv of conventions) {
5829
- const convPath = join14(rootDir, conv);
5830
- if (await fileExists(convPath)) {
5831
- return Ok([convPath]);
6063
+ return result;
6064
+ }
6065
+ function parseCargoBinPaths(content) {
6066
+ const paths = [];
6067
+ let inBin = false;
6068
+ for (const raw of content.split(/\r?\n/)) {
6069
+ const line = raw.replace(/(^|\s)#.*$/, "").trim();
6070
+ if (!line) continue;
6071
+ if (line === "[[bin]]") {
6072
+ inBin = true;
6073
+ continue;
6074
+ }
6075
+ if (line.startsWith("[")) {
6076
+ inBin = false;
6077
+ continue;
5832
6078
  }
6079
+ if (!inBin) continue;
6080
+ const eq = line.indexOf("=");
6081
+ if (eq <= 0) continue;
6082
+ const key = line.slice(0, eq).trim();
6083
+ if (key === "path") paths.push(stripTomlString2(line.slice(eq + 1).trim()));
5833
6084
  }
6085
+ return paths;
6086
+ }
6087
+ function stripTomlString2(value) {
6088
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
6089
+ return value.slice(1, -1);
6090
+ }
6091
+ return value;
6092
+ }
6093
+ async function resolveEntryPoints(rootDir, explicitEntries) {
6094
+ if (explicitEntries && explicitEntries.length > 0) {
6095
+ return Ok(explicitEntries.map((e) => resolve42(rootDir, e)));
6096
+ }
6097
+ const resolvers = [resolveTypeScript, resolvePython, resolveGo, resolveRust, resolveJava];
6098
+ const resolutions = [];
6099
+ for (const resolver of resolvers) {
6100
+ const res = await resolver(rootDir);
6101
+ resolutions.push(res);
6102
+ if (res.entries.length > 0) return Ok(res.entries);
6103
+ }
6104
+ const detectedLangs = resolutions.filter((r) => r.detected);
6105
+ const suggestions = detectedLangs.length > 0 ? detectedLangs.flatMap((r) => r.hints) : resolutions.flatMap((r) => r.hints);
6106
+ suggestions.push("Specify entryPoints in config");
6107
+ const reason = detectedLangs.length > 0 ? `Detected ${detectedLangs.map((r) => r.language).join(", ")} project but found no entry points` : "No language manifest (package.json, pyproject.toml, go.mod, Cargo.toml, pom.xml) and no conventional entry files found";
5834
6108
  return Err(
5835
6109
  createEntropyError(
5836
6110
  "ENTRY_POINT_NOT_FOUND",
5837
6111
  "Could not resolve entry points",
5838
- { reason: "No package.json exports/main and no conventional entry files found" },
5839
- [
5840
- 'Add "exports" or "main" to package.json',
5841
- "Create src/index.ts",
5842
- "Specify entryPoints in config"
5843
- ]
6112
+ { reason },
6113
+ suggestions
5844
6114
  )
5845
6115
  );
5846
6116
  }
@@ -5893,22 +6163,22 @@ function extractInlineRefs(content) {
5893
6163
  }
5894
6164
  return refs;
5895
6165
  }
5896
- async function parseDocumentationFile(path42) {
5897
- const contentResult = await readFileContent(path42);
6166
+ async function parseDocumentationFile(path46) {
6167
+ const contentResult = await readFileContent(path46);
5898
6168
  if (!contentResult.ok) {
5899
6169
  return Err(
5900
6170
  createEntropyError(
5901
6171
  "PARSE_ERROR",
5902
- `Failed to read documentation file: ${path42}`,
5903
- { file: path42 },
6172
+ `Failed to read documentation file: ${path46}`,
6173
+ { file: path46 },
5904
6174
  ["Check that the file exists"]
5905
6175
  )
5906
6176
  );
5907
6177
  }
5908
6178
  const content = contentResult.value;
5909
- const type = path42.endsWith(".md") ? "markdown" : "text";
6179
+ const type = path46.endsWith(".md") ? "markdown" : "text";
5910
6180
  return Ok({
5911
- path: path42,
6181
+ path: path46,
5912
6182
  type,
5913
6183
  content,
5914
6184
  codeBlocks: extractCodeBlocks(content),
@@ -5937,6 +6207,7 @@ function extractSymbolsFromNode(node) {
5937
6207
  return [];
5938
6208
  }
5939
6209
  function extractInternalSymbols(ast) {
6210
+ if (ast.language !== "typescript" && ast.language !== "javascript") return [];
5940
6211
  const body = ast.body;
5941
6212
  if (!body?.body) return [];
5942
6213
  const nodes = body.body;
@@ -5947,6 +6218,7 @@ function toJSDocComment(comment) {
5947
6218
  return { content: comment.value, line: comment.loc?.start?.line || 0 };
5948
6219
  }
5949
6220
  function extractJSDocComments(ast) {
6221
+ if (ast.language !== "typescript" && ast.language !== "javascript") return [];
5950
6222
  const body = ast.body;
5951
6223
  if (!body?.comments) return [];
5952
6224
  return body.comments.flatMap((c) => {
@@ -6004,19 +6276,16 @@ function extractAllCodeReferences(docs) {
6004
6276
  }
6005
6277
  async function buildSnapshot2(config) {
6006
6278
  const startTime = Date.now();
6007
- const parser = config.parser || new TypeScriptParser();
6008
- const rootDir = resolve32(config.rootDir);
6279
+ const rootDir = resolve5(config.rootDir);
6009
6280
  const entryPointsResult = await resolveEntryPoints(rootDir, config.entryPoints);
6010
6281
  if (!entryPointsResult.ok) {
6011
6282
  return Err(entryPointsResult.error);
6012
6283
  }
6013
- const includePatterns = config.include || ["**/*.ts", "**/*.tsx"];
6014
- const excludePatterns = config.exclude || [
6015
- "node_modules/**",
6016
- "dist/**",
6017
- "**/*.test.ts",
6018
- "**/*.spec.ts"
6019
- ];
6284
+ const registry = getDefaultRegistry();
6285
+ const singleParser = config.parser;
6286
+ const parserForFile = (filePath) => singleParser ?? registry.getForFile(filePath);
6287
+ const includePatterns = config.include || DEFAULT_INCLUDE_PATTERNS;
6288
+ const excludePatterns = config.exclude || [...skipDirGlobs(), "**/*.test.ts", "**/*.spec.ts"];
6020
6289
  let sourceFilePaths = [];
6021
6290
  for (const pattern of includePatterns) {
6022
6291
  const files2 = await findFiles(pattern, rootDir);
@@ -6028,10 +6297,12 @@ async function buildSnapshot2(config) {
6028
6297
  });
6029
6298
  const files = [];
6030
6299
  for (const filePath of sourceFilePaths) {
6031
- const parseResult = await parser.parseFile(filePath);
6300
+ const fileParser = parserForFile(filePath);
6301
+ if (!fileParser) continue;
6302
+ const parseResult = await fileParser.parseFile(filePath);
6032
6303
  if (!parseResult.ok) continue;
6033
- const importsResult = parser.extractImports(parseResult.value);
6034
- const exportsResult = parser.extractExports(parseResult.value);
6304
+ const importsResult = fileParser.extractImports(parseResult.value);
6305
+ const exportsResult = fileParser.extractExports(parseResult.value);
6035
6306
  const internalSymbols = extractInternalSymbols(parseResult.value);
6036
6307
  const jsDocComments = extractJSDocComments(parseResult.value);
6037
6308
  files.push({
@@ -6043,7 +6314,7 @@ async function buildSnapshot2(config) {
6043
6314
  jsDocComments
6044
6315
  });
6045
6316
  }
6046
- const graphResult = await buildDependencyGraph(sourceFilePaths, parser);
6317
+ const graphResult = await buildDependencyGraph(sourceFilePaths, singleParser ?? registry);
6047
6318
  const dependencyGraph = graphResult.ok ? graphResult.value : { nodes: [], edges: [] };
6048
6319
  const docPatterns = config.docPaths || ["docs/**/*.md", "README.md", "**/README.md"];
6049
6320
  let docFilePaths = [];
@@ -6178,7 +6449,7 @@ async function checkStructureDrift(snapshot, _config) {
6178
6449
  for (const doc of snapshot.docs) {
6179
6450
  const fileLinks = extractFileLinks(doc.content);
6180
6451
  for (const { link, line } of fileLinks) {
6181
- const resolvedPath = resolve4(dirname5(doc.path), link);
6452
+ const resolvedPath = resolve6(dirname5(doc.path), link);
6182
6453
  const exists = await fileExists(resolvedPath);
6183
6454
  if (!exists) {
6184
6455
  drifts.push({
@@ -6280,7 +6551,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
6280
6551
  }
6281
6552
  const hasFile = fileIndex ? (p) => fileIndex.has(p) : (p) => snapshot.files.some((f) => f.path === p);
6282
6553
  const fromDir = dirname6(fromFile);
6283
- const resolved = resolve5(fromDir, importSource);
6554
+ const resolved = resolve7(fromDir, importSource);
6284
6555
  const sourceExt = extname2(resolved);
6285
6556
  const fallbacks = JS_EXT_FALLBACKS2[sourceExt];
6286
6557
  if (fallbacks) {
@@ -6290,7 +6561,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
6290
6561
  if (hasFile(candidate)) return candidate;
6291
6562
  }
6292
6563
  for (const indexExt of [".ts", ".tsx", ".jsx"]) {
6293
- const indexPath2 = resolve5(base, "index" + indexExt);
6564
+ const indexPath2 = resolve7(base, "index" + indexExt);
6294
6565
  if (hasFile(indexPath2)) return indexPath2;
6295
6566
  }
6296
6567
  }
@@ -6301,7 +6572,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
6301
6572
  if (hasFile(candidate)) return candidate;
6302
6573
  }
6303
6574
  for (const indexExt of [".ts", ".tsx"]) {
6304
- const indexPath2 = resolve5(resolved, "index" + indexExt);
6575
+ const indexPath2 = resolve7(resolved, "index" + indexExt);
6305
6576
  if (hasFile(indexPath2)) return indexPath2;
6306
6577
  }
6307
6578
  }
@@ -6817,8 +7088,8 @@ function dirSize(dirPath) {
6817
7088
  return 0;
6818
7089
  }
6819
7090
  for (const entry of entries) {
6820
- if (entry === "node_modules" || entry === ".git") continue;
6821
- const fullPath = join15(dirPath, entry);
7091
+ if (DEFAULT_SKIP_DIRS.has(entry)) continue;
7092
+ const fullPath = join16(dirPath, entry);
6822
7093
  try {
6823
7094
  const stat2 = statSync2(fullPath);
6824
7095
  if (stat2.isDirectory()) {
@@ -6838,7 +7109,7 @@ async function detectSizeBudgetViolations(rootDir, config) {
6838
7109
  let packagesChecked = 0;
6839
7110
  for (const [pkgPath, budget] of Object.entries(budgets)) {
6840
7111
  packagesChecked++;
6841
- const distPath = join15(rootDir, pkgPath, "dist");
7112
+ const distPath = join16(rootDir, pkgPath, "dist");
6842
7113
  const currentSize = dirSize(distPath);
6843
7114
  if (budget.warn) {
6844
7115
  const budgetBytes = parseSize(budget.warn);
@@ -7082,7 +7353,7 @@ function previewFix(fix) {
7082
7353
  }
7083
7354
  }
7084
7355
  async function createBackup(filePath, backupDir) {
7085
- const backupPath = join16(backupDir, `${Date.now()}-${basename5(filePath)}`);
7356
+ const backupPath = join17(backupDir, `${Date.now()}-${basename5(filePath)}`);
7086
7357
  try {
7087
7358
  await mkdir22(dirname7(backupPath), { recursive: true });
7088
7359
  await copyFile2(filePath, backupPath);
@@ -7457,12 +7728,12 @@ function parseDiffHeader(part) {
7457
7728
  return headerMatch[2];
7458
7729
  }
7459
7730
  function parseDiffPart(part) {
7460
- const path42 = parseDiffHeader(part);
7461
- if (!path42) return null;
7731
+ const path46 = parseDiffHeader(part);
7732
+ if (!path46) return null;
7462
7733
  const additionRegex = /^\+(?!\+\+)/gm;
7463
7734
  const deletionRegex = /^-(?!--)/gm;
7464
7735
  return {
7465
- path: path42,
7736
+ path: path46,
7466
7737
  status: detectFileStatus(part),
7467
7738
  additions: (part.match(additionRegex) || []).length,
7468
7739
  deletions: (part.match(deletionRegex) || []).length
@@ -8247,10 +8518,10 @@ function detectEmergentConstraints(history, options) {
8247
8518
  return { suggestions, totalViolationsAnalyzed, windowWeeks, minOccurrences };
8248
8519
  }
8249
8520
  function streamsDir(projectPath) {
8250
- return path7.join(projectPath, HARNESS_DIR, STREAMS_DIR);
8521
+ return path8.join(projectPath, HARNESS_DIR, STREAMS_DIR);
8251
8522
  }
8252
8523
  function indexPath(projectPath) {
8253
- return path7.join(streamsDir(projectPath), INDEX_FILE);
8524
+ return path8.join(streamsDir(projectPath), INDEX_FILE);
8254
8525
  }
8255
8526
  function validateStreamName(name) {
8256
8527
  if (!STREAM_NAME_REGEX.test(name)) {
@@ -8264,11 +8535,11 @@ function validateStreamName(name) {
8264
8535
  }
8265
8536
  async function loadStreamIndex(projectPath) {
8266
8537
  const idxPath = indexPath(projectPath);
8267
- if (!fs10.existsSync(idxPath)) {
8538
+ if (!fs11.existsSync(idxPath)) {
8268
8539
  return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
8269
8540
  }
8270
8541
  try {
8271
- const raw = fs10.readFileSync(idxPath, "utf-8");
8542
+ const raw = fs11.readFileSync(idxPath, "utf-8");
8272
8543
  const parsed = JSON.parse(raw);
8273
8544
  const result = StreamIndexSchema.safeParse(parsed);
8274
8545
  if (!result.success) {
@@ -8286,8 +8557,8 @@ async function loadStreamIndex(projectPath) {
8286
8557
  async function saveStreamIndex(projectPath, index) {
8287
8558
  const dir = streamsDir(projectPath);
8288
8559
  try {
8289
- fs10.mkdirSync(dir, { recursive: true });
8290
- fs10.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
8560
+ fs11.mkdirSync(dir, { recursive: true });
8561
+ fs11.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
8291
8562
  return Ok(void 0);
8292
8563
  } catch (error) {
8293
8564
  return Err(
@@ -8326,18 +8597,18 @@ async function resolveStreamPath(projectPath, options) {
8326
8597
  )
8327
8598
  );
8328
8599
  }
8329
- return Ok(path7.join(streamsDir(projectPath), options.stream));
8600
+ return Ok(path8.join(streamsDir(projectPath), options.stream));
8330
8601
  }
8331
8602
  const branch = getCurrentBranch(projectPath);
8332
8603
  if (branch && branch !== "main" && branch !== "master") {
8333
8604
  for (const [name, info] of Object.entries(index.streams)) {
8334
8605
  if (info.branch === branch) {
8335
- return Ok(path7.join(streamsDir(projectPath), name));
8606
+ return Ok(path8.join(streamsDir(projectPath), name));
8336
8607
  }
8337
8608
  }
8338
8609
  }
8339
8610
  if (index.activeStream && index.streams[index.activeStream]) {
8340
- return Ok(path7.join(streamsDir(projectPath), index.activeStream));
8611
+ return Ok(path8.join(streamsDir(projectPath), index.activeStream));
8341
8612
  }
8342
8613
  return Err(
8343
8614
  new Error(
@@ -8365,9 +8636,9 @@ async function createStream(projectPath, name, branch) {
8365
8636
  if (index.streams[name]) {
8366
8637
  return Err(new Error(`Stream '${name}' already exists`));
8367
8638
  }
8368
- const streamPath = path7.join(streamsDir(projectPath), name);
8639
+ const streamPath = path8.join(streamsDir(projectPath), name);
8369
8640
  try {
8370
- fs10.mkdirSync(streamPath, { recursive: true });
8641
+ fs11.mkdirSync(streamPath, { recursive: true });
8371
8642
  } catch (error) {
8372
8643
  return Err(
8373
8644
  new Error(
@@ -8408,12 +8679,12 @@ async function archiveStream(projectPath, name) {
8408
8679
  if (!index.streams[name]) {
8409
8680
  return Err(new Error(`Stream '${name}' not found`));
8410
8681
  }
8411
- const streamPath = path7.join(streamsDir(projectPath), name);
8412
- const archiveDir = path7.join(projectPath, HARNESS_DIR, "archive", "streams");
8682
+ const streamPath = path8.join(streamsDir(projectPath), name);
8683
+ const archiveDir = path8.join(projectPath, HARNESS_DIR, "archive", "streams");
8413
8684
  try {
8414
- fs10.mkdirSync(archiveDir, { recursive: true });
8685
+ fs11.mkdirSync(archiveDir, { recursive: true });
8415
8686
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8416
- fs10.renameSync(streamPath, path7.join(archiveDir, `${name}-${date}`));
8687
+ fs11.renameSync(streamPath, path8.join(archiveDir, `${name}-${date}`));
8417
8688
  } catch (error) {
8418
8689
  return Err(
8419
8690
  new Error(
@@ -8434,19 +8705,19 @@ function getStreamForBranch(index, branch) {
8434
8705
  return null;
8435
8706
  }
8436
8707
  async function migrateToStreams(projectPath) {
8437
- const harnessDir = path7.join(projectPath, HARNESS_DIR);
8438
- if (fs10.existsSync(indexPath(projectPath))) {
8708
+ const harnessDir = path8.join(projectPath, HARNESS_DIR);
8709
+ if (fs11.existsSync(indexPath(projectPath))) {
8439
8710
  return Ok(void 0);
8440
8711
  }
8441
- const filesToMove = STATE_FILES.filter((f) => fs10.existsSync(path7.join(harnessDir, f)));
8712
+ const filesToMove = STATE_FILES.filter((f) => fs11.existsSync(path8.join(harnessDir, f)));
8442
8713
  if (filesToMove.length === 0) {
8443
8714
  return Ok(void 0);
8444
8715
  }
8445
- const defaultDir = path7.join(streamsDir(projectPath), "default");
8716
+ const defaultDir = path8.join(streamsDir(projectPath), "default");
8446
8717
  try {
8447
- fs10.mkdirSync(defaultDir, { recursive: true });
8718
+ fs11.mkdirSync(defaultDir, { recursive: true });
8448
8719
  for (const file of filesToMove) {
8449
- fs10.renameSync(path7.join(harnessDir, file), path7.join(defaultDir, file));
8720
+ fs11.renameSync(path8.join(harnessDir, file), path8.join(defaultDir, file));
8450
8721
  }
8451
8722
  } catch (error) {
8452
8723
  return Err(
@@ -8476,26 +8747,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
8476
8747
  new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
8477
8748
  );
8478
8749
  }
8479
- const sessionDir = path8.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
8750
+ const sessionDir = path9.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
8480
8751
  if (options?.create) {
8481
- fs11.mkdirSync(sessionDir, { recursive: true });
8752
+ fs12.mkdirSync(sessionDir, { recursive: true });
8482
8753
  }
8483
8754
  return Ok(sessionDir);
8484
8755
  }
8485
8756
  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);
8757
+ const sessionsDir = path9.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
8758
+ fs12.mkdirSync(sessionsDir, { recursive: true });
8759
+ const indexPath2 = path9.join(sessionsDir, SESSION_INDEX_FILE);
8489
8760
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8490
8761
  const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
8491
- if (!fs11.existsSync(indexPath2)) {
8492
- fs11.writeFileSync(indexPath2, `## Active Sessions
8762
+ if (!fs12.existsSync(indexPath2)) {
8763
+ fs12.writeFileSync(indexPath2, `## Active Sessions
8493
8764
 
8494
8765
  ${newLine}
8495
8766
  `);
8496
8767
  return;
8497
8768
  }
8498
- const content = fs11.readFileSync(indexPath2, "utf-8");
8769
+ const content = fs12.readFileSync(indexPath2, "utf-8");
8499
8770
  const lines = content.split("\n");
8500
8771
  const slugPattern = `- [${sessionSlug}]`;
8501
8772
  const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
@@ -8505,7 +8776,7 @@ ${newLine}
8505
8776
  const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
8506
8777
  lines.splice(lastNonEmpty + 1, 0, newLine);
8507
8778
  }
8508
- fs11.writeFileSync(indexPath2, lines.join("\n"));
8779
+ fs12.writeFileSync(indexPath2, lines.join("\n"));
8509
8780
  }
8510
8781
  function evictIfNeeded(map) {
8511
8782
  if (map.size > MAX_CACHE_ENTRIES) {
@@ -8518,8 +8789,8 @@ async function getStateDir(projectPath, stream, session) {
8518
8789
  const sessionResult = resolveSessionDir(projectPath, session, { create: true });
8519
8790
  return sessionResult;
8520
8791
  }
8521
- const streamsIndexPath = path9.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
8522
- const hasStreams = fs12.existsSync(streamsIndexPath);
8792
+ const streamsIndexPath = path10.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
8793
+ const hasStreams = fs13.existsSync(streamsIndexPath);
8523
8794
  if (stream || hasStreams) {
8524
8795
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
8525
8796
  if (result.ok) {
@@ -8529,18 +8800,18 @@ async function getStateDir(projectPath, stream, session) {
8529
8800
  return result;
8530
8801
  }
8531
8802
  }
8532
- return Ok(path9.join(projectPath, HARNESS_DIR));
8803
+ return Ok(path10.join(projectPath, HARNESS_DIR));
8533
8804
  }
8534
8805
  async function loadState(projectPath, stream, session) {
8535
8806
  try {
8536
8807
  const dirResult = await getStateDir(projectPath, stream, session);
8537
8808
  if (!dirResult.ok) return dirResult;
8538
8809
  const stateDir = dirResult.value;
8539
- const statePath = path10.join(stateDir, STATE_FILE);
8540
- if (!fs13.existsSync(statePath)) {
8810
+ const statePath = path11.join(stateDir, STATE_FILE);
8811
+ if (!fs14.existsSync(statePath)) {
8541
8812
  return Ok({ ...DEFAULT_STATE });
8542
8813
  }
8543
- const raw = fs13.readFileSync(statePath, "utf-8");
8814
+ const raw = fs14.readFileSync(statePath, "utf-8");
8544
8815
  const parsed = JSON.parse(raw);
8545
8816
  const result = HarnessStateSchema.safeParse(parsed);
8546
8817
  if (!result.success) {
@@ -8558,11 +8829,11 @@ async function saveState(projectPath, state, stream, session) {
8558
8829
  const dirResult = await getStateDir(projectPath, stream, session);
8559
8830
  if (!dirResult.ok) return dirResult;
8560
8831
  const stateDir = dirResult.value;
8561
- const statePath = path10.join(stateDir, STATE_FILE);
8562
- fs13.mkdirSync(stateDir, { recursive: true });
8832
+ const statePath = path11.join(stateDir, STATE_FILE);
8833
+ fs14.mkdirSync(stateDir, { recursive: true });
8563
8834
  const tmpPath = statePath + ".tmp";
8564
- fs13.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
8565
- fs13.renameSync(tmpPath, statePath);
8835
+ fs14.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
8836
+ fs14.renameSync(tmpPath, statePath);
8566
8837
  return Ok(void 0);
8567
8838
  } catch (error) {
8568
8839
  return Err(
@@ -8648,10 +8919,10 @@ function computeContentHash(text) {
8648
8919
  return crypto2.createHash("sha256").update(text).digest("hex").slice(0, 16);
8649
8920
  }
8650
8921
  function loadContentHashes(stateDir) {
8651
- const hashesPath = path11.join(stateDir, CONTENT_HASHES_FILE);
8652
- if (!fs14.existsSync(hashesPath)) return {};
8922
+ const hashesPath = path12.join(stateDir, CONTENT_HASHES_FILE);
8923
+ if (!fs15.existsSync(hashesPath)) return {};
8653
8924
  try {
8654
- const raw = fs14.readFileSync(hashesPath, "utf-8");
8925
+ const raw = fs15.readFileSync(hashesPath, "utf-8");
8655
8926
  const parsed = JSON.parse(raw);
8656
8927
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
8657
8928
  return parsed;
@@ -8660,15 +8931,15 @@ function loadContentHashes(stateDir) {
8660
8931
  }
8661
8932
  }
8662
8933
  function saveContentHashes(stateDir, index) {
8663
- const hashesPath = path11.join(stateDir, CONTENT_HASHES_FILE);
8934
+ const hashesPath = path12.join(stateDir, CONTENT_HASHES_FILE);
8664
8935
  const tmpPath = hashesPath + ".tmp";
8665
- fs14.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
8666
- fs14.renameSync(tmpPath, hashesPath);
8936
+ fs15.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
8937
+ fs15.renameSync(tmpPath, hashesPath);
8667
8938
  }
8668
8939
  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");
8940
+ const learningsPath = path12.join(stateDir, learningsFile);
8941
+ if (!fs15.existsSync(learningsPath)) return {};
8942
+ const content = fs15.readFileSync(learningsPath, "utf-8");
8672
8943
  const lines = content.split("\n");
8673
8944
  const index = {};
8674
8945
  for (let i = 0; i < lines.length; i++) {
@@ -8727,18 +8998,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
8727
8998
  const dirResult = await getStateDir(projectPath, stream, session);
8728
8999
  if (!dirResult.ok) return dirResult;
8729
9000
  const stateDir = dirResult.value;
8730
- const learningsPath = path12.join(stateDir, LEARNINGS_FILE);
8731
- if (!fs15.existsSync(learningsPath)) {
9001
+ const learningsPath = path13.join(stateDir, LEARNINGS_FILE);
9002
+ if (!fs16.existsSync(learningsPath)) {
8732
9003
  return Ok([]);
8733
9004
  }
8734
- const stats = fs15.statSync(learningsPath);
9005
+ const stats = fs16.statSync(learningsPath);
8735
9006
  const cacheKey = learningsPath;
8736
9007
  const cached = learningsCacheMap.get(cacheKey);
8737
9008
  let entries;
8738
9009
  if (cached && cached.mtimeMs === stats.mtimeMs) {
8739
9010
  entries = cached.entries;
8740
9011
  } else {
8741
- const content = fs15.readFileSync(learningsPath, "utf-8");
9012
+ const content = fs16.readFileSync(learningsPath, "utf-8");
8742
9013
  const lines = content.split("\n");
8743
9014
  entries = [];
8744
9015
  let currentBlock = [];
@@ -8874,16 +9145,16 @@ function checkOverlap(newEntry, existingEntries, options) {
8874
9145
  };
8875
9146
  }
8876
9147
  function sleep(ms) {
8877
- return new Promise((resolve8) => setTimeout(resolve8, ms));
9148
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
8878
9149
  }
8879
9150
  async function acquireFileLock(lockPath) {
8880
9151
  for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
8881
9152
  try {
8882
- const fd = fs16.openSync(
9153
+ const fd = fs17.openSync(
8883
9154
  lockPath,
8884
- fs16.constants.O_CREAT | fs16.constants.O_EXCL | fs16.constants.O_WRONLY
9155
+ fs17.constants.O_CREAT | fs17.constants.O_EXCL | fs17.constants.O_WRONLY
8885
9156
  );
8886
- fs16.closeSync(fd);
9157
+ fs17.closeSync(fd);
8887
9158
  return;
8888
9159
  } catch (err) {
8889
9160
  const isExist = err instanceof Error && "code" in err && err.code === "EEXIST";
@@ -8897,20 +9168,20 @@ async function acquireFileLock(lockPath) {
8897
9168
  }
8898
9169
  function releaseFileLock(lockPath) {
8899
9170
  try {
8900
- fs16.unlinkSync(lockPath);
9171
+ fs17.unlinkSync(lockPath);
8901
9172
  } catch {
8902
9173
  }
8903
9174
  }
8904
9175
  function loadOrRebuildHashes(stateDir, learningsPath) {
8905
- const hashesPath = path13.join(stateDir, CONTENT_HASHES_FILE);
8906
- if (fs16.existsSync(hashesPath)) {
9176
+ const hashesPath = path14.join(stateDir, CONTENT_HASHES_FILE);
9177
+ if (fs17.existsSync(hashesPath)) {
8907
9178
  const hashes = loadContentHashes(stateDir);
8908
- if (Object.keys(hashes).length === 0 && fs16.existsSync(learningsPath)) {
9179
+ if (Object.keys(hashes).length === 0 && fs17.existsSync(learningsPath)) {
8909
9180
  return rebuildContentHashes(stateDir, LEARNINGS_FILE);
8910
9181
  }
8911
9182
  return hashes;
8912
9183
  }
8913
- if (fs16.existsSync(learningsPath)) {
9184
+ if (fs17.existsSync(learningsPath)) {
8914
9185
  return rebuildContentHashes(stateDir, LEARNINGS_FILE);
8915
9186
  }
8916
9187
  return {};
@@ -8934,8 +9205,8 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
8934
9205
  const dirResult = await getStateDir(projectPath, stream, session);
8935
9206
  if (!dirResult.ok) return dirResult;
8936
9207
  const stateDir = dirResult.value;
8937
- const learningsPath = path13.join(stateDir, LEARNINGS_FILE);
8938
- fs16.mkdirSync(stateDir, { recursive: true });
9208
+ const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
9209
+ fs17.mkdirSync(stateDir, { recursive: true });
8939
9210
  const lockPath = learningsPath + ".lock";
8940
9211
  await acquireFileLock(lockPath);
8941
9212
  try {
@@ -8965,17 +9236,17 @@ ${bulletLine}
8965
9236
  `;
8966
9237
  let overlapResult;
8967
9238
  let existingLineCount;
8968
- if (!fs16.existsSync(learningsPath)) {
8969
- fs16.writeFileSync(learningsPath, `# Learnings
9239
+ if (!fs17.existsSync(learningsPath)) {
9240
+ fs17.writeFileSync(learningsPath, `# Learnings
8970
9241
  ${entry}`);
8971
9242
  existingLineCount = 1;
8972
9243
  } else {
8973
- const existingContent = fs16.readFileSync(learningsPath, "utf-8");
9244
+ const existingContent = fs17.readFileSync(learningsPath, "utf-8");
8974
9245
  const existingEntries = existingContent.split("\n").filter((line) => /^- \*\*\d{4}-\d{2}-\d{2}/.test(line));
8975
9246
  const overlap = checkOverlap(bulletLine, existingEntries);
8976
9247
  if (overlap.score >= 0.7) overlapResult = overlap;
8977
9248
  existingLineCount = existingContent.split("\n").length;
8978
- fs16.appendFileSync(learningsPath, entry);
9249
+ fs17.appendFileSync(learningsPath, entry);
8979
9250
  }
8980
9251
  const bulletLineNum = existingLineCount + 2;
8981
9252
  contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLineNum };
@@ -9080,11 +9351,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
9080
9351
  const dirResult = await getStateDir(projectPath, stream, session);
9081
9352
  if (!dirResult.ok) return dirResult;
9082
9353
  const stateDir = dirResult.value;
9083
- const learningsPath = path13.join(stateDir, LEARNINGS_FILE);
9084
- if (!fs16.existsSync(learningsPath)) {
9354
+ const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
9355
+ if (!fs17.existsSync(learningsPath)) {
9085
9356
  return Ok([]);
9086
9357
  }
9087
- const content = fs16.readFileSync(learningsPath, "utf-8");
9358
+ const content = fs17.readFileSync(learningsPath, "utf-8");
9088
9359
  const lines = content.split("\n");
9089
9360
  const indexEntries = [];
9090
9361
  let pendingFrontmatter = null;
@@ -9126,16 +9397,16 @@ async function archiveLearnings(projectPath, entries, stream) {
9126
9397
  const dirResult = await getStateDir(projectPath, stream);
9127
9398
  if (!dirResult.ok) return dirResult;
9128
9399
  const stateDir = dirResult.value;
9129
- const archiveDir = path14.join(stateDir, "learnings-archive");
9130
- fs17.mkdirSync(archiveDir, { recursive: true });
9400
+ const archiveDir = path15.join(stateDir, "learnings-archive");
9401
+ fs18.mkdirSync(archiveDir, { recursive: true });
9131
9402
  const now = /* @__PURE__ */ new Date();
9132
9403
  const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
9133
- const archivePath = path14.join(archiveDir, `${yearMonth}.md`);
9404
+ const archivePath = path15.join(archiveDir, `${yearMonth}.md`);
9134
9405
  const archiveContent = entries.join("\n\n") + "\n";
9135
- if (fs17.existsSync(archivePath)) {
9136
- fs17.appendFileSync(archivePath, "\n" + archiveContent);
9406
+ if (fs18.existsSync(archivePath)) {
9407
+ fs18.appendFileSync(archivePath, "\n" + archiveContent);
9137
9408
  } else {
9138
- fs17.writeFileSync(archivePath, `# Learnings Archive
9409
+ fs18.writeFileSync(archivePath, `# Learnings Archive
9139
9410
 
9140
9411
  ${archiveContent}`);
9141
9412
  }
@@ -9153,8 +9424,8 @@ async function pruneLearnings(projectPath, stream) {
9153
9424
  const dirResult = await getStateDir(projectPath, stream);
9154
9425
  if (!dirResult.ok) return dirResult;
9155
9426
  const stateDir = dirResult.value;
9156
- const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
9157
- if (!fs17.existsSync(learningsPath)) {
9427
+ const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
9428
+ if (!fs18.existsSync(learningsPath)) {
9158
9429
  return Ok({ kept: 0, archived: 0, patterns: [] });
9159
9430
  }
9160
9431
  const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
@@ -9185,7 +9456,7 @@ async function pruneLearnings(projectPath, stream) {
9185
9456
  if (!archiveResult.ok) return archiveResult;
9186
9457
  }
9187
9458
  const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
9188
- fs17.writeFileSync(learningsPath, newContent);
9459
+ fs18.writeFileSync(learningsPath, newContent);
9189
9460
  invalidateLearningsCacheEntry(learningsPath);
9190
9461
  return Ok({
9191
9462
  kept: toKeep.length,
@@ -9229,19 +9500,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
9229
9500
  const dirResult = await getStateDir(projectPath, stream);
9230
9501
  if (!dirResult.ok) return dirResult;
9231
9502
  const stateDir = dirResult.value;
9232
- const globalPath = path14.join(stateDir, LEARNINGS_FILE);
9233
- const existingGlobal = fs17.existsSync(globalPath) ? fs17.readFileSync(globalPath, "utf-8") : "";
9503
+ const globalPath = path15.join(stateDir, LEARNINGS_FILE);
9504
+ const existingGlobal = fs18.existsSync(globalPath) ? fs18.readFileSync(globalPath, "utf-8") : "";
9234
9505
  const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
9235
9506
  if (newEntries.length === 0) {
9236
9507
  return Ok({ promoted: 0, skipped: skipped + toPromote.length });
9237
9508
  }
9238
9509
  const promotedContent = newEntries.join("\n\n") + "\n";
9239
9510
  if (!existingGlobal) {
9240
- fs17.writeFileSync(globalPath, `# Learnings
9511
+ fs18.writeFileSync(globalPath, `# Learnings
9241
9512
 
9242
9513
  ${promotedContent}`);
9243
9514
  } else {
9244
- fs17.appendFileSync(globalPath, "\n\n" + promotedContent);
9515
+ fs18.appendFileSync(globalPath, "\n\n" + promotedContent);
9245
9516
  }
9246
9517
  invalidateLearningsCacheEntry(globalPath);
9247
9518
  return Ok({
@@ -9277,13 +9548,13 @@ async function detectStaleLearnings(projectPath, stream, session) {
9277
9548
  freshCount++;
9278
9549
  continue;
9279
9550
  }
9280
- const resolvedRoot = path15.resolve(projectPath);
9551
+ const resolvedRoot = path16.resolve(projectPath);
9281
9552
  const missing = refs.filter((ref) => {
9282
- const absPath = path15.resolve(projectPath, ref);
9283
- if (!absPath.startsWith(resolvedRoot + path15.sep) && absPath !== resolvedRoot) {
9553
+ const absPath = path16.resolve(projectPath, ref);
9554
+ if (!absPath.startsWith(resolvedRoot + path16.sep) && absPath !== resolvedRoot) {
9284
9555
  return false;
9285
9556
  }
9286
- return !fs18.existsSync(absPath);
9557
+ return !fs19.existsSync(absPath);
9287
9558
  });
9288
9559
  if (missing.length > 0) {
9289
9560
  const idx = extractIndexEntry(entry);
@@ -9318,17 +9589,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
9318
9589
  const dirResult = await getStateDir(projectPath, stream, session);
9319
9590
  if (!dirResult.ok) return dirResult;
9320
9591
  const stateDir = dirResult.value;
9321
- const failuresPath = path16.join(stateDir, FAILURES_FILE);
9322
- fs19.mkdirSync(stateDir, { recursive: true });
9592
+ const failuresPath = path17.join(stateDir, FAILURES_FILE);
9593
+ fs20.mkdirSync(stateDir, { recursive: true });
9323
9594
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9324
9595
  const entry = `
9325
9596
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
9326
9597
  `;
9327
- if (!fs19.existsSync(failuresPath)) {
9328
- fs19.writeFileSync(failuresPath, `# Failures
9598
+ if (!fs20.existsSync(failuresPath)) {
9599
+ fs20.writeFileSync(failuresPath, `# Failures
9329
9600
  ${entry}`);
9330
9601
  } else {
9331
- fs19.appendFileSync(failuresPath, entry);
9602
+ fs20.appendFileSync(failuresPath, entry);
9332
9603
  }
9333
9604
  failuresCacheMap.delete(failuresPath);
9334
9605
  return Ok(void 0);
@@ -9345,17 +9616,17 @@ async function loadFailures(projectPath, stream, session) {
9345
9616
  const dirResult = await getStateDir(projectPath, stream, session);
9346
9617
  if (!dirResult.ok) return dirResult;
9347
9618
  const stateDir = dirResult.value;
9348
- const failuresPath = path16.join(stateDir, FAILURES_FILE);
9349
- if (!fs19.existsSync(failuresPath)) {
9619
+ const failuresPath = path17.join(stateDir, FAILURES_FILE);
9620
+ if (!fs20.existsSync(failuresPath)) {
9350
9621
  return Ok([]);
9351
9622
  }
9352
- const stats = fs19.statSync(failuresPath);
9623
+ const stats = fs20.statSync(failuresPath);
9353
9624
  const cacheKey = failuresPath;
9354
9625
  const cached = failuresCacheMap.get(cacheKey);
9355
9626
  if (cached && cached.mtimeMs === stats.mtimeMs) {
9356
9627
  return Ok(cached.entries);
9357
9628
  }
9358
- const content = fs19.readFileSync(failuresPath, "utf-8");
9629
+ const content = fs20.readFileSync(failuresPath, "utf-8");
9359
9630
  const entries = [];
9360
9631
  for (const line of content.split("\n")) {
9361
9632
  const match = line.match(FAILURE_LINE_REGEX);
@@ -9384,20 +9655,20 @@ async function archiveFailures(projectPath, stream, session) {
9384
9655
  const dirResult = await getStateDir(projectPath, stream, session);
9385
9656
  if (!dirResult.ok) return dirResult;
9386
9657
  const stateDir = dirResult.value;
9387
- const failuresPath = path16.join(stateDir, FAILURES_FILE);
9388
- if (!fs19.existsSync(failuresPath)) {
9658
+ const failuresPath = path17.join(stateDir, FAILURES_FILE);
9659
+ if (!fs20.existsSync(failuresPath)) {
9389
9660
  return Ok(void 0);
9390
9661
  }
9391
- const archiveDir = path16.join(stateDir, "archive");
9392
- fs19.mkdirSync(archiveDir, { recursive: true });
9662
+ const archiveDir = path17.join(stateDir, "archive");
9663
+ fs20.mkdirSync(archiveDir, { recursive: true });
9393
9664
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9394
9665
  let archiveName = `failures-${date}.md`;
9395
9666
  let counter = 2;
9396
- while (fs19.existsSync(path16.join(archiveDir, archiveName))) {
9667
+ while (fs20.existsSync(path17.join(archiveDir, archiveName))) {
9397
9668
  archiveName = `failures-${date}-${counter}.md`;
9398
9669
  counter++;
9399
9670
  }
9400
- fs19.renameSync(failuresPath, path16.join(archiveDir, archiveName));
9671
+ fs20.renameSync(failuresPath, path17.join(archiveDir, archiveName));
9401
9672
  failuresCacheMap.delete(failuresPath);
9402
9673
  return Ok(void 0);
9403
9674
  } catch (error) {
@@ -9413,11 +9684,11 @@ async function saveHandoff(projectPath, handoff, stream, session) {
9413
9684
  const dirResult = await getStateDir(projectPath, stream, session);
9414
9685
  if (!dirResult.ok) return dirResult;
9415
9686
  const stateDir = dirResult.value;
9416
- const handoffPath = path17.join(stateDir, HANDOFF_FILE);
9417
- fs20.mkdirSync(stateDir, { recursive: true });
9687
+ const handoffPath = path18.join(stateDir, HANDOFF_FILE);
9688
+ fs21.mkdirSync(stateDir, { recursive: true });
9418
9689
  const tmpPath = handoffPath + ".tmp";
9419
- fs20.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
9420
- fs20.renameSync(tmpPath, handoffPath);
9690
+ fs21.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
9691
+ fs21.renameSync(tmpPath, handoffPath);
9421
9692
  return Ok(void 0);
9422
9693
  } catch (error) {
9423
9694
  return Err(
@@ -9430,11 +9701,11 @@ async function loadHandoff(projectPath, stream, session) {
9430
9701
  const dirResult = await getStateDir(projectPath, stream, session);
9431
9702
  if (!dirResult.ok) return dirResult;
9432
9703
  const stateDir = dirResult.value;
9433
- const handoffPath = path17.join(stateDir, HANDOFF_FILE);
9434
- if (!fs20.existsSync(handoffPath)) {
9704
+ const handoffPath = path18.join(stateDir, HANDOFF_FILE);
9705
+ if (!fs21.existsSync(handoffPath)) {
9435
9706
  return Ok(null);
9436
9707
  }
9437
- const raw = fs20.readFileSync(handoffPath, "utf-8");
9708
+ const raw = fs21.readFileSync(handoffPath, "utf-8");
9438
9709
  const parsed = JSON.parse(raw);
9439
9710
  const result = HandoffSchema.safeParse(parsed);
9440
9711
  if (!result.success) {
@@ -9448,28 +9719,28 @@ async function loadHandoff(projectPath, stream, session) {
9448
9719
  }
9449
9720
  }
9450
9721
  function loadChecksFromConfig(gateConfigPath) {
9451
- if (!fs21.existsSync(gateConfigPath)) return [];
9452
- const raw = JSON.parse(fs21.readFileSync(gateConfigPath, "utf-8"));
9722
+ if (!fs22.existsSync(gateConfigPath)) return [];
9723
+ const raw = JSON.parse(fs22.readFileSync(gateConfigPath, "utf-8"));
9453
9724
  const config = GateConfigSchema.safeParse(raw);
9454
9725
  if (config.success && config.data.checks) return config.data.checks;
9455
9726
  return [];
9456
9727
  }
9457
9728
  function discoverChecksFromProject(projectPath) {
9458
9729
  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"));
9730
+ const packageJsonPath = path19.join(projectPath, "package.json");
9731
+ if (fs22.existsSync(packageJsonPath)) {
9732
+ const pkg = JSON.parse(fs22.readFileSync(packageJsonPath, "utf-8"));
9462
9733
  const scripts = pkg.scripts || {};
9463
9734
  if (scripts.test) checks.push({ name: "test", command: "npm test" });
9464
9735
  if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
9465
9736
  if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
9466
9737
  if (scripts.build) checks.push({ name: "build", command: "npm run build" });
9467
9738
  }
9468
- if (fs21.existsSync(path18.join(projectPath, "go.mod"))) {
9739
+ if (fs22.existsSync(path19.join(projectPath, "go.mod"))) {
9469
9740
  checks.push({ name: "test", command: "go test ./..." });
9470
9741
  checks.push({ name: "build", command: "go build ./..." });
9471
9742
  }
9472
- if (fs21.existsSync(path18.join(projectPath, "pyproject.toml")) || fs21.existsSync(path18.join(projectPath, "setup.py"))) {
9743
+ if (fs22.existsSync(path19.join(projectPath, "pyproject.toml")) || fs22.existsSync(path19.join(projectPath, "setup.py"))) {
9473
9744
  checks.push({ name: "test", command: "python -m pytest" });
9474
9745
  }
9475
9746
  return checks;
@@ -9509,8 +9780,8 @@ function executeCheck(check, projectPath) {
9509
9780
  }
9510
9781
  }
9511
9782
  async function runMechanicalGate(projectPath) {
9512
- const harnessDir = path18.join(projectPath, HARNESS_DIR);
9513
- const gateConfigPath = path18.join(harnessDir, GATE_CONFIG_FILE);
9783
+ const harnessDir = path19.join(projectPath, HARNESS_DIR);
9784
+ const gateConfigPath = path19.join(harnessDir, GATE_CONFIG_FILE);
9514
9785
  try {
9515
9786
  let checks = loadChecksFromConfig(gateConfigPath);
9516
9787
  if (checks.length === 0) {
@@ -9566,9 +9837,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
9566
9837
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
9567
9838
  if (!dirResult.ok) return dirResult;
9568
9839
  const sessionDir = dirResult.value;
9569
- const summaryPath = path19.join(sessionDir, SUMMARY_FILE);
9840
+ const summaryPath = path20.join(sessionDir, SUMMARY_FILE);
9570
9841
  const content = formatSummary(data);
9571
- fs22.writeFileSync(summaryPath, content);
9842
+ fs23.writeFileSync(summaryPath, content);
9572
9843
  const description = deriveIndexDescription(data);
9573
9844
  updateSessionIndex(projectPath, sessionSlug, description);
9574
9845
  return Ok(void 0);
@@ -9585,11 +9856,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
9585
9856
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9586
9857
  if (!dirResult.ok) return dirResult;
9587
9858
  const sessionDir = dirResult.value;
9588
- const summaryPath = path19.join(sessionDir, SUMMARY_FILE);
9589
- if (!fs22.existsSync(summaryPath)) {
9859
+ const summaryPath = path20.join(sessionDir, SUMMARY_FILE);
9860
+ if (!fs23.existsSync(summaryPath)) {
9590
9861
  return Ok(null);
9591
9862
  }
9592
- const content = fs22.readFileSync(summaryPath, "utf-8");
9863
+ const content = fs23.readFileSync(summaryPath, "utf-8");
9593
9864
  return Ok(content);
9594
9865
  } catch (error) {
9595
9866
  return Err(
@@ -9601,11 +9872,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
9601
9872
  }
9602
9873
  function listActiveSessions(projectPath) {
9603
9874
  try {
9604
- const indexPath2 = path19.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
9605
- if (!fs22.existsSync(indexPath2)) {
9875
+ const indexPath2 = path20.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
9876
+ if (!fs23.existsSync(indexPath2)) {
9606
9877
  return Ok(null);
9607
9878
  }
9608
- const content = fs22.readFileSync(indexPath2, "utf-8");
9879
+ const content = fs23.readFileSync(indexPath2, "utf-8");
9609
9880
  return Ok(content);
9610
9881
  } catch (error) {
9611
9882
  return Err(
@@ -9626,12 +9897,12 @@ async function loadSessionState(projectPath, sessionSlug) {
9626
9897
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9627
9898
  if (!dirResult.ok) return dirResult;
9628
9899
  const sessionDir = dirResult.value;
9629
- const filePath = path20.join(sessionDir, SESSION_STATE_FILE);
9630
- if (!fs23.existsSync(filePath)) {
9900
+ const filePath = path21.join(sessionDir, SESSION_STATE_FILE);
9901
+ if (!fs24.existsSync(filePath)) {
9631
9902
  return Ok(emptySections());
9632
9903
  }
9633
9904
  try {
9634
- const raw = fs23.readFileSync(filePath, "utf-8");
9905
+ const raw = fs24.readFileSync(filePath, "utf-8");
9635
9906
  const parsed = JSON.parse(raw);
9636
9907
  const sections = emptySections();
9637
9908
  for (const name of SESSION_SECTION_NAMES) {
@@ -9652,9 +9923,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
9652
9923
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
9653
9924
  if (!dirResult.ok) return dirResult;
9654
9925
  const sessionDir = dirResult.value;
9655
- const filePath = path20.join(sessionDir, SESSION_STATE_FILE);
9926
+ const filePath = path21.join(sessionDir, SESSION_STATE_FILE);
9656
9927
  try {
9657
- fs23.writeFileSync(filePath, JSON.stringify(sections, null, 2));
9928
+ fs24.writeFileSync(filePath, JSON.stringify(sections, null, 2));
9658
9929
  return Ok(void 0);
9659
9930
  } catch (error) {
9660
9931
  return Err(
@@ -9710,26 +9981,26 @@ async function archiveSession(projectPath, sessionSlug) {
9710
9981
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
9711
9982
  if (!dirResult.ok) return dirResult;
9712
9983
  const sessionDir = dirResult.value;
9713
- if (!fs24.existsSync(sessionDir)) {
9984
+ if (!fs25.existsSync(sessionDir)) {
9714
9985
  return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
9715
9986
  }
9716
- const archiveBase = path21.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
9987
+ const archiveBase = path22.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
9717
9988
  try {
9718
- fs24.mkdirSync(archiveBase, { recursive: true });
9989
+ fs25.mkdirSync(archiveBase, { recursive: true });
9719
9990
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
9720
9991
  let archiveName = `${sessionSlug}-${date}`;
9721
9992
  let counter = 1;
9722
- while (fs24.existsSync(path21.join(archiveBase, archiveName))) {
9993
+ while (fs25.existsSync(path22.join(archiveBase, archiveName))) {
9723
9994
  archiveName = `${sessionSlug}-${date}-${counter}`;
9724
9995
  counter++;
9725
9996
  }
9726
- const dest = path21.join(archiveBase, archiveName);
9997
+ const dest = path22.join(archiveBase, archiveName);
9727
9998
  try {
9728
- fs24.renameSync(sessionDir, dest);
9999
+ fs25.renameSync(sessionDir, dest);
9729
10000
  } catch (renameErr) {
9730
10001
  if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
9731
- fs24.cpSync(sessionDir, dest, { recursive: true });
9732
- fs24.rmSync(sessionDir, { recursive: true });
10002
+ fs25.cpSync(sessionDir, dest, { recursive: true });
10003
+ fs25.rmSync(sessionDir, { recursive: true });
9733
10004
  } else {
9734
10005
  throw renameErr;
9735
10006
  }
@@ -9751,8 +10022,8 @@ function loadKnownHashes(eventsPath) {
9751
10022
  const cached = knownHashesCache.get(eventsPath);
9752
10023
  if (cached) return cached;
9753
10024
  const hashes = /* @__PURE__ */ new Set();
9754
- if (fs25.existsSync(eventsPath)) {
9755
- const content = fs25.readFileSync(eventsPath, "utf-8");
10025
+ if (fs26.existsSync(eventsPath)) {
10026
+ const content = fs26.readFileSync(eventsPath, "utf-8");
9756
10027
  const lines = content.split("\n").filter((line) => line.trim() !== "");
9757
10028
  for (const line of lines) {
9758
10029
  try {
@@ -9775,8 +10046,8 @@ async function emitEvent(projectPath, event, options) {
9775
10046
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
9776
10047
  if (!dirResult.ok) return dirResult;
9777
10048
  const stateDir = dirResult.value;
9778
- const eventsPath = path22.join(stateDir, EVENTS_FILE);
9779
- fs25.mkdirSync(stateDir, { recursive: true });
10049
+ const eventsPath = path23.join(stateDir, EVENTS_FILE);
10050
+ fs26.mkdirSync(stateDir, { recursive: true });
9780
10051
  const contentHash = computeEventHash(event, options?.session);
9781
10052
  const knownHashes = loadKnownHashes(eventsPath);
9782
10053
  if (knownHashes.has(contentHash)) {
@@ -9790,7 +10061,7 @@ async function emitEvent(projectPath, event, options) {
9790
10061
  if (options?.session) {
9791
10062
  fullEvent.session = options.session;
9792
10063
  }
9793
- fs25.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
10064
+ fs26.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
9794
10065
  knownHashes.add(contentHash);
9795
10066
  return Ok({ written: true });
9796
10067
  } catch (error) {
@@ -9804,11 +10075,11 @@ async function loadEvents(projectPath, options) {
9804
10075
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
9805
10076
  if (!dirResult.ok) return dirResult;
9806
10077
  const stateDir = dirResult.value;
9807
- const eventsPath = path22.join(stateDir, EVENTS_FILE);
9808
- if (!fs25.existsSync(eventsPath)) {
10078
+ const eventsPath = path23.join(stateDir, EVENTS_FILE);
10079
+ if (!fs26.existsSync(eventsPath)) {
9809
10080
  return Ok([]);
9810
10081
  }
9811
- const content = fs25.readFileSync(eventsPath, "utf-8");
10082
+ const content = fs26.readFileSync(eventsPath, "utf-8");
9812
10083
  const lines = content.split("\n").filter((line) => line.trim() !== "");
9813
10084
  const events = [];
9814
10085
  for (const line of lines) {
@@ -10057,11 +10328,11 @@ function nodeSubStacks(allDeps) {
10057
10328
  return found;
10058
10329
  }
10059
10330
  function detectNodeStacks(projectRoot) {
10060
- const pkgJsonPath = path23.join(projectRoot, "package.json");
10061
- if (!fs26.existsSync(pkgJsonPath)) return [];
10331
+ const pkgJsonPath = path24.join(projectRoot, "package.json");
10332
+ if (!fs27.existsSync(pkgJsonPath)) return [];
10062
10333
  const stacks = ["node"];
10063
10334
  try {
10064
- const pkgJson = JSON.parse(fs26.readFileSync(pkgJsonPath, "utf-8"));
10335
+ const pkgJson = JSON.parse(fs27.readFileSync(pkgJsonPath, "utf-8"));
10065
10336
  const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
10066
10337
  stacks.push(...nodeSubStacks(allDeps));
10067
10338
  } catch {
@@ -10070,10 +10341,10 @@ function detectNodeStacks(projectRoot) {
10070
10341
  }
10071
10342
  function detectStack(projectRoot) {
10072
10343
  const stacks = [...detectNodeStacks(projectRoot)];
10073
- if (fs26.existsSync(path23.join(projectRoot, "go.mod"))) {
10344
+ if (fs27.existsSync(path24.join(projectRoot, "go.mod"))) {
10074
10345
  stacks.push("go");
10075
10346
  }
10076
- const hasPython = fs26.existsSync(path23.join(projectRoot, "requirements.txt")) || fs26.existsSync(path23.join(projectRoot, "pyproject.toml"));
10347
+ const hasPython = fs27.existsSync(path24.join(projectRoot, "requirements.txt")) || fs27.existsSync(path24.join(projectRoot, "pyproject.toml"));
10077
10348
  if (hasPython) {
10078
10349
  stacks.push("python");
10079
10350
  }
@@ -10116,7 +10387,7 @@ function getInjectionPatterns() {
10116
10387
  }
10117
10388
  function getTaintFilePath(projectRoot, sessionId) {
10118
10389
  const id = sessionId || DEFAULT_SESSION_ID;
10119
- return join382(projectRoot, ".harness", `session-taint-${id}.json`);
10390
+ return join392(projectRoot, ".harness", `session-taint-${id}.json`);
10120
10391
  }
10121
10392
  function readTaint(projectRoot, sessionId) {
10122
10393
  const filePath = getTaintFilePath(projectRoot, sessionId);
@@ -10198,14 +10469,14 @@ function clearTaint(projectRoot, sessionId) {
10198
10469
  return 0;
10199
10470
  }
10200
10471
  }
10201
- const harnessDir = join382(projectRoot, ".harness");
10472
+ const harnessDir = join392(projectRoot, ".harness");
10202
10473
  let count = 0;
10203
10474
  try {
10204
10475
  const files = readdirSync3(harnessDir);
10205
10476
  for (const file of files) {
10206
10477
  if (file.startsWith("session-taint-") && file.endsWith(".json")) {
10207
10478
  try {
10208
- unlinkSync2(join382(harnessDir, file));
10479
+ unlinkSync2(join392(harnessDir, file));
10209
10480
  count++;
10210
10481
  } catch {
10211
10482
  }
@@ -10216,7 +10487,7 @@ function clearTaint(projectRoot, sessionId) {
10216
10487
  return count;
10217
10488
  }
10218
10489
  function listTaintedSessions(projectRoot) {
10219
- const harnessDir = join382(projectRoot, ".harness");
10490
+ const harnessDir = join392(projectRoot, ".harness");
10220
10491
  const sessions = [];
10221
10492
  try {
10222
10493
  const files = readdirSync3(harnessDir);
@@ -10288,7 +10559,7 @@ function securityFindingId(finding) {
10288
10559
  }
10289
10560
  async function runValidateCheck(projectRoot, config) {
10290
10561
  const issues = [];
10291
- const agentsPath = path24.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10562
+ const agentsPath = path25.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10292
10563
  const result = await validateAgentsMap(agentsPath);
10293
10564
  if (!result.ok) {
10294
10565
  issues.push({ severity: "error", message: result.error.message });
@@ -10345,14 +10616,13 @@ async function runDepsCheck(projectRoot, config) {
10345
10616
  }
10346
10617
  async function runDocsCheck(projectRoot, config) {
10347
10618
  const issues = [];
10348
- const docsDir = path24.join(projectRoot, config.docsDir ?? "docs");
10619
+ const docsDir = path25.join(projectRoot, config.docsDir ?? "docs");
10349
10620
  const entropyConfig = config.entropy || {};
10350
10621
  const result = await checkDocCoverage("project", {
10351
10622
  docsDir,
10352
10623
  sourceDir: projectRoot,
10353
10624
  excludePatterns: entropyConfig.excludePatterns || [
10354
- "**/node_modules/**",
10355
- "**/dist/**",
10625
+ ...skipDirGlobs(),
10356
10626
  "**/*.test.ts",
10357
10627
  "**/fixtures/**"
10358
10628
  ]
@@ -10417,12 +10687,7 @@ async function runSecurityCheck(projectRoot, config) {
10417
10687
  const { glob: globFn } = await import("glob");
10418
10688
  const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
10419
10689
  cwd: projectRoot,
10420
- ignore: securityConfig.exclude ?? [
10421
- "**/node_modules/**",
10422
- "**/dist/**",
10423
- "**/*.test.ts",
10424
- "**/fixtures/**"
10425
- ],
10690
+ ignore: securityConfig.exclude ?? [...skipDirGlobs(), "**/*.test.ts", "**/fixtures/**"],
10426
10691
  absolute: true
10427
10692
  });
10428
10693
  const scanResult = await scanner.scanFiles(sourceFiles);
@@ -10530,7 +10795,7 @@ async function runTraceabilityCheck(projectRoot, config) {
10530
10795
  const issues = [];
10531
10796
  const traceConfig = config.traceability || {};
10532
10797
  if (traceConfig.enabled === false) return issues;
10533
- const graphDir = path24.join(projectRoot, ".harness", "graph");
10798
+ const graphDir = path25.join(projectRoot, ".harness", "graph");
10534
10799
  const store = new GraphStore();
10535
10800
  const loaded = await store.load(graphDir);
10536
10801
  if (!loaded) {
@@ -10708,7 +10973,7 @@ async function runMechanicalChecks(options) {
10708
10973
  };
10709
10974
  if (!skip.includes("validate")) {
10710
10975
  try {
10711
- const agentsPath = path25.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10976
+ const agentsPath = path26.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
10712
10977
  const result = await validateAgentsMap(agentsPath);
10713
10978
  if (!result.ok) {
10714
10979
  statuses.validate = "fail";
@@ -10745,7 +11010,7 @@ async function runMechanicalChecks(options) {
10745
11010
  statuses.validate = "fail";
10746
11011
  findings.push({
10747
11012
  tool: "validate",
10748
- file: path25.join(projectRoot, "AGENTS.md"),
11013
+ file: path26.join(projectRoot, "AGENTS.md"),
10749
11014
  message: err instanceof Error ? err.message : String(err),
10750
11015
  severity: "error"
10751
11016
  });
@@ -10809,7 +11074,7 @@ async function runMechanicalChecks(options) {
10809
11074
  (async () => {
10810
11075
  const localFindings = [];
10811
11076
  try {
10812
- const docsDir = path25.join(projectRoot, config.docsDir ?? "docs");
11077
+ const docsDir = path26.join(projectRoot, config.docsDir ?? "docs");
10813
11078
  const result = await checkDocCoverage("project", { docsDir });
10814
11079
  if (!result.ok) {
10815
11080
  statuses["check-docs"] = "warn";
@@ -10836,7 +11101,7 @@ async function runMechanicalChecks(options) {
10836
11101
  statuses["check-docs"] = "warn";
10837
11102
  localFindings.push({
10838
11103
  tool: "check-docs",
10839
- file: path25.join(projectRoot, "docs"),
11104
+ file: path26.join(projectRoot, "docs"),
10840
11105
  message: err instanceof Error ? err.message : String(err),
10841
11106
  severity: "warning"
10842
11107
  });
@@ -10933,18 +11198,18 @@ function computeContextBudget(diffLines) {
10933
11198
  return diffLines;
10934
11199
  }
10935
11200
  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);
11201
+ const resolvedRoot = path27.resolve(projectRoot) + path27.sep;
11202
+ const resolvedPath = path27.resolve(absPath);
11203
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path27.resolve(projectRoot);
10939
11204
  }
10940
11205
  async function readContextFile(projectRoot, filePath, reason) {
10941
- const absPath = path26.isAbsolute(filePath) ? filePath : path26.join(projectRoot, filePath);
11206
+ const absPath = path27.isAbsolute(filePath) ? filePath : path27.join(projectRoot, filePath);
10942
11207
  if (!isWithinProject(absPath, projectRoot)) return null;
10943
11208
  const result = await readFileContent(absPath);
10944
11209
  if (!result.ok) return null;
10945
11210
  const content = result.value;
10946
11211
  const lines = content.split("\n").length;
10947
- const relPath2 = path26.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
11212
+ const relPath2 = path27.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
10948
11213
  return { path: relPath2, content, reason, lines };
10949
11214
  }
10950
11215
  function extractImportSources2(content) {
@@ -10959,24 +11224,24 @@ function extractImportSources2(content) {
10959
11224
  }
10960
11225
  async function resolveImportPath2(projectRoot, fromFile, importSource) {
10961
11226
  if (!importSource.startsWith(".")) return null;
10962
- const fromDir = path26.dirname(path26.join(projectRoot, fromFile));
10963
- const basePath = path26.resolve(fromDir, importSource);
11227
+ const fromDir = path27.dirname(path27.join(projectRoot, fromFile));
11228
+ const basePath = path27.resolve(fromDir, importSource);
10964
11229
  if (!isWithinProject(basePath, projectRoot)) return null;
10965
11230
  const relBase = relativePosix(projectRoot, basePath);
10966
- const sourceExt = path26.extname(relBase);
11231
+ const sourceExt = path27.extname(relBase);
10967
11232
  const candidates = [];
10968
11233
  const fallbacks = JS_EXT_FALLBACKS22[sourceExt];
10969
11234
  if (fallbacks) {
10970
11235
  const stripped = relBase.slice(0, -sourceExt.length);
10971
11236
  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"));
11237
+ candidates.push(path27.join(stripped, "index.ts"));
11238
+ candidates.push(path27.join(stripped, "index.tsx"));
11239
+ candidates.push(path27.join(stripped, "index.jsx"));
10975
11240
  }
10976
11241
  candidates.push(relBase + ".ts", relBase + ".tsx", relBase + ".mts");
10977
- candidates.push(path26.join(relBase, "index.ts"));
11242
+ candidates.push(path27.join(relBase, "index.ts"));
10978
11243
  for (const candidate of candidates) {
10979
- const absCandidate = path26.join(projectRoot, candidate);
11244
+ const absCandidate = path27.join(projectRoot, candidate);
10980
11245
  if (await fileExists(absCandidate)) {
10981
11246
  return candidate;
10982
11247
  }
@@ -10984,7 +11249,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
10984
11249
  return null;
10985
11250
  }
10986
11251
  async function findTestFiles(projectRoot, sourceFile) {
10987
- const baseName = path26.basename(sourceFile, path26.extname(sourceFile));
11252
+ const baseName = path27.basename(sourceFile, path27.extname(sourceFile));
10988
11253
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
10989
11254
  const results = await findFiles(pattern, projectRoot);
10990
11255
  return results.map((f) => relativePosix(projectRoot, f));
@@ -11752,7 +12017,7 @@ function normalizePath(filePath, projectRoot) {
11752
12017
  let normalized = filePath;
11753
12018
  normalized = normalized.replace(/\\/g, "/");
11754
12019
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
11755
- if (path27.isAbsolute(normalized)) {
12020
+ if (path28.isAbsolute(normalized)) {
11756
12021
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
11757
12022
  if (normalized.startsWith(root)) {
11758
12023
  normalized = normalized.slice(root.length);
@@ -11764,12 +12029,12 @@ function normalizePath(filePath, projectRoot) {
11764
12029
  return normalized;
11765
12030
  }
11766
12031
  function resolveImportPath22(currentFile, importPath) {
11767
- const dir = path27.dirname(currentFile);
11768
- let resolved = path27.join(dir, importPath).replace(/\\/g, "/");
12032
+ const dir = path28.dirname(currentFile);
12033
+ let resolved = path28.join(dir, importPath).replace(/\\/g, "/");
11769
12034
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
11770
12035
  resolved += ".ts";
11771
12036
  }
11772
- return path27.normalize(resolved).replace(/\\/g, "/");
12037
+ return path28.normalize(resolved).replace(/\\/g, "/");
11773
12038
  }
11774
12039
  function enqueueImports(content, current, visited, queue, maxDepth) {
11775
12040
  const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
@@ -11801,7 +12066,7 @@ function isMechanicallyExcluded(finding, exclusionSet, projectRoot) {
11801
12066
  const normalizedFile = normalizePath(finding.file, projectRoot);
11802
12067
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange)) return true;
11803
12068
  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, "/");
12069
+ const absoluteFile = path28.isAbsolute(finding.file) ? finding.file : path28.join(projectRoot, finding.file).replace(/\\/g, "/");
11805
12070
  return exclusionSet.isExcluded(absoluteFile, finding.lineRange);
11806
12071
  }
11807
12072
  async function validateWithGraph(crossFileRefs, graph) {
@@ -12809,7 +13074,7 @@ function isRegression(from, to) {
12809
13074
  }
12810
13075
  function collectAutopilotStatuses(autopilotPath, featurePlans, allTaskStatuses) {
12811
13076
  try {
12812
- const raw = fs28.readFileSync(autopilotPath, "utf-8");
13077
+ const raw = fs29.readFileSync(autopilotPath, "utf-8");
12813
13078
  const autopilot = JSON.parse(raw);
12814
13079
  if (!autopilot.phases) return;
12815
13080
  const linkedPhases = autopilot.phases.filter(
@@ -12841,10 +13106,10 @@ function inferStatus(feature, projectPath, allFeatures) {
12841
13106
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
12842
13107
  const useRootState = featuresWithPlans.length <= 1;
12843
13108
  if (useRootState) {
12844
- const rootStatePath = path28.join(projectPath, ".harness", "state.json");
12845
- if (fs28.existsSync(rootStatePath)) {
13109
+ const rootStatePath = path29.join(projectPath, ".harness", "state.json");
13110
+ if (fs29.existsSync(rootStatePath)) {
12846
13111
  try {
12847
- const raw = fs28.readFileSync(rootStatePath, "utf-8");
13112
+ const raw = fs29.readFileSync(rootStatePath, "utf-8");
12848
13113
  const state = JSON.parse(raw);
12849
13114
  if (state.progress) {
12850
13115
  for (const status of Object.values(state.progress)) {
@@ -12855,14 +13120,14 @@ function inferStatus(feature, projectPath, allFeatures) {
12855
13120
  }
12856
13121
  }
12857
13122
  }
12858
- const sessionsDir = path28.join(projectPath, ".harness", "sessions");
12859
- if (fs28.existsSync(sessionsDir)) {
13123
+ const sessionsDir = path29.join(projectPath, ".harness", "sessions");
13124
+ if (fs29.existsSync(sessionsDir)) {
12860
13125
  try {
12861
- const sessionDirs = fs28.readdirSync(sessionsDir, { withFileTypes: true });
13126
+ const sessionDirs = fs29.readdirSync(sessionsDir, { withFileTypes: true });
12862
13127
  for (const entry of sessionDirs) {
12863
13128
  if (!entry.isDirectory()) continue;
12864
- const autopilotPath = path28.join(sessionsDir, entry.name, "autopilot-state.json");
12865
- if (!fs28.existsSync(autopilotPath)) continue;
13129
+ const autopilotPath = path29.join(sessionsDir, entry.name, "autopilot-state.json");
13130
+ if (!fs29.existsSync(autopilotPath)) continue;
12866
13131
  collectAutopilotStatuses(autopilotPath, feature.plans, allTaskStatuses);
12867
13132
  }
12868
13133
  } catch {
@@ -12944,7 +13209,7 @@ function parseRepoParts(repo) {
12944
13209
  return { owner: parts[0], repo: parts[1] };
12945
13210
  }
12946
13211
  function sleep2(ms) {
12947
- return new Promise((resolve8) => setTimeout(resolve8, ms));
13212
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
12948
13213
  }
12949
13214
  async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
12950
13215
  let lastResponse;
@@ -12977,9 +13242,9 @@ function isValidTrackerShape(tracker) {
12977
13242
  }
12978
13243
  function loadTrackerSyncConfig(projectRoot) {
12979
13244
  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");
13245
+ const configPath = path30.join(projectRoot, "harness.config.json");
13246
+ if (!fs30.existsSync(configPath)) return null;
13247
+ const raw = fs30.readFileSync(configPath, "utf-8");
12983
13248
  const config = JSON.parse(raw);
12984
13249
  const tracker = config.roadmap?.tracker;
12985
13250
  if (!isValidTrackerShape(tracker)) return null;
@@ -13094,12 +13359,12 @@ async function syncFromExternal(roadmap, adapter2, config, options, prefetchedTi
13094
13359
  async function fullSync(roadmapPath, adapter2, config, options) {
13095
13360
  const previousSync = syncMutex;
13096
13361
  let releaseMutex;
13097
- syncMutex = new Promise((resolve8) => {
13098
- releaseMutex = resolve8;
13362
+ syncMutex = new Promise((resolve10) => {
13363
+ releaseMutex = resolve10;
13099
13364
  });
13100
13365
  await previousSync;
13101
13366
  try {
13102
- const raw = fs30.readFileSync(roadmapPath, "utf-8");
13367
+ const raw = fs31.readFileSync(roadmapPath, "utf-8");
13103
13368
  const parseResult = parseRoadmap(raw);
13104
13369
  if (!parseResult.ok) {
13105
13370
  return {
@@ -13112,7 +13377,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
13112
13377
  const tickets = fetchResult.ok ? fetchResult.value : void 0;
13113
13378
  const pushResult = await syncToExternal(roadmap, adapter2, config, tickets);
13114
13379
  const pullResult = await syncFromExternal(roadmap, adapter2, config, options);
13115
- fs30.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13380
+ fs31.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13116
13381
  return {
13117
13382
  created: pushResult.created,
13118
13383
  updated: pushResult.updated,
@@ -13123,6 +13388,32 @@ async function fullSync(roadmapPath, adapter2, config, options) {
13123
13388
  releaseMutex();
13124
13389
  }
13125
13390
  }
13391
+ function scoreRoadmapCandidatesFileLess(features, _options) {
13392
+ const allNames = new Set(features.map((f) => f.name.toLowerCase()));
13393
+ const doneNames = new Set(
13394
+ features.filter((f) => f.status === "done").map((f) => f.name.toLowerCase())
13395
+ );
13396
+ const eligible = features.filter((f) => {
13397
+ if (f.status !== "planned" && f.status !== "backlog") return false;
13398
+ return !f.blockedBy.some((b) => {
13399
+ const k = b.toLowerCase();
13400
+ return allNames.has(k) && !doneNames.has(k);
13401
+ });
13402
+ });
13403
+ const candidates = eligible.map((f) => ({
13404
+ feature: f,
13405
+ priorityTier: f.priority ? PRIORITY_RANK[f.priority] : null
13406
+ }));
13407
+ candidates.sort((a, b) => {
13408
+ const ap = a.priorityTier;
13409
+ const bp = b.priorityTier;
13410
+ if (ap !== null && bp === null) return -1;
13411
+ if (ap === null && bp !== null) return 1;
13412
+ if (ap !== null && bp !== null && ap !== bp) return ap - bp;
13413
+ return a.feature.createdAt.localeCompare(b.feature.createdAt);
13414
+ });
13415
+ return candidates;
13416
+ }
13126
13417
  function isEligibleCandidate(feature, allFeatureNames, doneFeatures) {
13127
13418
  if (feature.status !== "planned" && feature.status !== "backlog") return false;
13128
13419
  const isBlocked = feature.blockedBy.some((blocker) => {
@@ -13192,7 +13483,7 @@ function scoreRoadmapCandidates(roadmap, options) {
13192
13483
  userCompletedFeatures
13193
13484
  );
13194
13485
  const weightedScore = POSITION_WEIGHT * positionScore + DEPENDENTS_WEIGHT * dependentsScore + AFFINITY_WEIGHT * affinityScore;
13195
- const priorityTier = feature.priority ? PRIORITY_RANK[feature.priority] : null;
13486
+ const priorityTier = feature.priority ? PRIORITY_RANK2[feature.priority] : null;
13196
13487
  candidates.push({
13197
13488
  feature,
13198
13489
  milestone: ms.name,
@@ -13232,9 +13523,550 @@ function assignFeature(roadmap, feature, assignee, date) {
13232
13523
  date
13233
13524
  });
13234
13525
  }
13526
+ function scoreRoadmapCandidatesForMode(roadmap, options, config) {
13527
+ const mode = getRoadmapMode(config);
13528
+ if (mode === "file-less") {
13529
+ const flatFeatures = roadmap.milestones.flatMap((m) => m.features);
13530
+ const tracked = flatFeatures.map(featureToTrackedFeature);
13531
+ const fileLess = scoreRoadmapCandidatesFileLess(tracked, options);
13532
+ const featuresByName = new Map(flatFeatures.map((f) => [f.name, f]));
13533
+ return fileLess.map((c) => {
13534
+ const feature = featuresByName.get(c.feature.name);
13535
+ if (!feature) {
13536
+ throw new Error(`Internal: feature ${c.feature.name} lost during mode dispatch`);
13537
+ }
13538
+ return {
13539
+ feature,
13540
+ milestone: "",
13541
+ positionScore: 0,
13542
+ dependentsScore: 0,
13543
+ affinityScore: 0,
13544
+ weightedScore: 0,
13545
+ priorityTier: c.priorityTier
13546
+ };
13547
+ });
13548
+ }
13549
+ return scoreRoadmapCandidates(roadmap, options);
13550
+ }
13551
+ function featureToTrackedFeature(f) {
13552
+ return {
13553
+ externalId: f.externalId ?? "",
13554
+ name: f.name,
13555
+ status: f.status,
13556
+ summary: f.summary,
13557
+ spec: f.spec,
13558
+ plans: f.plans,
13559
+ blockedBy: f.blockedBy,
13560
+ assignee: f.assignee,
13561
+ priority: f.priority,
13562
+ milestone: null,
13563
+ createdAt: "1970-01-01T00:00:00Z",
13564
+ updatedAt: f.updatedAt
13565
+ };
13566
+ }
13567
+ function parseExternalId2(externalId) {
13568
+ const m = externalId.match(/^github:([^/]+)\/([^#]+)#(\d+)$/);
13569
+ if (!m) return null;
13570
+ return { owner: m[1], repo: m[2], number: parseInt(m[3], 10) };
13571
+ }
13572
+ function buildExternalId2(owner, repo, n) {
13573
+ return `github:${owner}/${repo}#${n}`;
13574
+ }
13575
+ function parseBodyBlock(body) {
13576
+ const startIdx = body.indexOf(START_MARKER);
13577
+ if (startIdx === -1) return { summary: body.trim(), meta: {} };
13578
+ const endIdx = body.indexOf(END_MARKER, startIdx + START_MARKER.length);
13579
+ if (endIdx === -1) return { summary: body.trim(), meta: {} };
13580
+ const secondStart = body.indexOf(START_MARKER, endIdx + END_MARKER.length);
13581
+ if (secondStart !== -1) {
13582
+ console.warn("harness-meta: multiple blocks found; first wins.");
13583
+ }
13584
+ const yamlText = body.slice(startIdx + START_MARKER.length, endIdx).trim();
13585
+ let meta = {};
13586
+ try {
13587
+ const parsed = parseYaml(yamlText);
13588
+ if (parsed && typeof parsed === "object") {
13589
+ meta = normalizeBodyMeta(parsed);
13590
+ }
13591
+ } catch (e) {
13592
+ console.warn(
13593
+ `harness-meta: malformed YAML, treating block as missing: ${e.message}`
13594
+ );
13595
+ meta = {};
13596
+ }
13597
+ const summary = body.slice(0, startIdx).trim();
13598
+ return { summary, meta };
13599
+ }
13600
+ function normalizeBodyMeta(raw) {
13601
+ const out = {};
13602
+ if (typeof raw.spec === "string") out.spec = raw.spec;
13603
+ if (typeof raw.plan === "string") out.plan = raw.plan;
13604
+ if (typeof raw.blocked_by === "string") {
13605
+ out.blocked_by = raw.blocked_by.split(",").map((s) => s.trim()).filter(Boolean);
13606
+ } else if (Array.isArray(raw.blocked_by)) {
13607
+ out.blocked_by = raw.blocked_by.filter(
13608
+ (v) => typeof v === "string"
13609
+ );
13610
+ }
13611
+ if (typeof raw.priority === "string" && ["P0", "P1", "P2", "P3"].includes(raw.priority)) {
13612
+ out.priority = raw.priority;
13613
+ }
13614
+ if (typeof raw.milestone === "string") out.milestone = raw.milestone;
13615
+ return out;
13616
+ }
13617
+ function serializeBodyBlock(summary, meta) {
13618
+ const ordered = {};
13619
+ for (const [key, yamlKey, mapValue] of SERIALIZER_FIELDS) {
13620
+ const raw = meta[key];
13621
+ if (raw === void 0 || raw === null) continue;
13622
+ const out = mapValue(raw);
13623
+ if (out === void 0) continue;
13624
+ ordered[yamlKey] = out;
13625
+ }
13626
+ const trimmed = summary.trim();
13627
+ if (Object.keys(ordered).length === 0) return trimmed;
13628
+ const yamlBody = stringifyYaml(ordered).trimEnd();
13629
+ const block = `${START_MARKER}
13630
+ ${yamlBody}
13631
+ ${END_MARKER}`;
13632
+ return trimmed.length > 0 ? `${trimmed}
13633
+
13634
+ ${block}` : block;
13635
+ }
13636
+ function compareStatus(server, patch) {
13637
+ if (patch.status === void 0) return { idempotent: true };
13638
+ if (server.status === "done" && patch.status !== "done") {
13639
+ return { diff: { ours: patch.status, theirs: server.status }, idempotent: false };
13640
+ }
13641
+ return { idempotent: server.status === patch.status };
13642
+ }
13643
+ function compareAssignee(server, patch) {
13644
+ if (patch.assignee === void 0) return { idempotent: true };
13645
+ if (server.assignee !== null && server.assignee !== patch.assignee) {
13646
+ return { diff: { ours: patch.assignee, theirs: server.assignee }, idempotent: false };
13647
+ }
13648
+ return { idempotent: server.assignee === patch.assignee };
13649
+ }
13650
+ function compareScalar(field, server, patch) {
13651
+ const next = patch[field];
13652
+ if (next === void 0) return { idempotent: true };
13653
+ const cur = server[field];
13654
+ if (cur !== null && cur !== next) {
13655
+ return { diff: { ours: next, theirs: cur }, idempotent: false };
13656
+ }
13657
+ return { idempotent: cur === next };
13658
+ }
13659
+ function compareArray(field, server, patch) {
13660
+ const next = patch[field];
13661
+ if (next === void 0) return { idempotent: true };
13662
+ const cur = server[field];
13663
+ if (arraysEqual2(next, cur)) return { idempotent: true };
13664
+ if (cur.length > 0 && cur.some((v) => !next.includes(v))) {
13665
+ return { diff: { ours: next, theirs: cur }, idempotent: false };
13666
+ }
13667
+ return { idempotent: false };
13668
+ }
13669
+ function refetchAndCompare(server, patch) {
13670
+ const verdicts = [
13671
+ ["status", compareStatus(server, patch)],
13672
+ ["assignee", compareAssignee(server, patch)],
13673
+ ["priority", compareScalar("priority", server, patch)],
13674
+ ["milestone", compareScalar("milestone", server, patch)],
13675
+ ["spec", compareScalar("spec", server, patch)],
13676
+ ["plans", compareArray("plans", server, patch)],
13677
+ ["blockedBy", compareArray("blockedBy", server, patch)]
13678
+ ];
13679
+ const diff2 = {};
13680
+ let idempotent = true;
13681
+ for (const [field, verdict] of verdicts) {
13682
+ if (verdict.diff) diff2[field] = verdict.diff;
13683
+ if (!verdict.idempotent) idempotent = false;
13684
+ }
13685
+ if (Object.keys(diff2).length > 0) return { ok: false, diff: diff2 };
13686
+ return { ok: true, idempotent };
13687
+ }
13688
+ function arraysEqual2(a, b) {
13689
+ if (a.length !== b.length) return false;
13690
+ const sa = [...a].sort();
13691
+ const sb = [...b].sort();
13692
+ return sa.every((v, i) => v === sb[i]);
13693
+ }
13694
+ function metaFromFeatureFields(src) {
13695
+ const meta = {};
13696
+ if (src.spec !== void 0 && src.spec !== null) meta.spec = src.spec;
13697
+ if (src.plans && src.plans.length > 0) meta.plan = src.plans[0];
13698
+ if (src.blockedBy && src.blockedBy.length > 0) meta.blocked_by = src.blockedBy;
13699
+ if (src.priority !== void 0 && src.priority !== null) meta.priority = src.priority;
13700
+ if (src.milestone !== void 0 && src.milestone !== null) meta.milestone = src.milestone;
13701
+ return meta;
13702
+ }
13703
+ function buildCreateMeta(feature) {
13704
+ return metaFromFeatureFields(feature);
13705
+ }
13706
+ function createTrackerClient(config) {
13707
+ if (config.kind !== "github-issues") {
13708
+ return Err(new Error(`Unsupported tracker kind: "${String(config.kind)}"`));
13709
+ }
13710
+ const token = config.token ?? process.env.GITHUB_TOKEN;
13711
+ if (!token) {
13712
+ return Err(
13713
+ new Error("createTrackerClient: missing GitHub token (config.token or GITHUB_TOKEN env)")
13714
+ );
13715
+ }
13716
+ const opts = { token, repo: config.repo };
13717
+ if (config.apiBase !== void 0) opts.apiBase = config.apiBase;
13718
+ if (config.selectorLabel !== void 0) opts.selectorLabel = config.selectorLabel;
13719
+ if (config.etagStore !== void 0) opts.etagStore = config.etagStore;
13720
+ return Ok(new GitHubIssuesTrackerAdapter(opts));
13721
+ }
13722
+ function makeTrackerConflictBody(err, opts = {}) {
13723
+ return {
13724
+ error: err.message,
13725
+ code: "TRACKER_CONFLICT",
13726
+ externalId: err.externalId,
13727
+ conflictedWith: opts.conflictedWith ?? err.diff,
13728
+ refreshHint: "reload-roadmap"
13729
+ };
13730
+ }
13731
+ function loadProjectRoadmapMode(projectRoot) {
13732
+ try {
13733
+ const configPath = path31.join(projectRoot, "harness.config.json");
13734
+ if (!fs32.existsSync(configPath)) return getRoadmapMode(null);
13735
+ const raw = fs32.readFileSync(configPath, "utf-8");
13736
+ const parsed = JSON.parse(raw);
13737
+ return getRoadmapMode(parsed);
13738
+ } catch {
13739
+ return getRoadmapMode(null);
13740
+ }
13741
+ }
13742
+ function loadTrackerClientConfigFromProject(projectRoot) {
13743
+ try {
13744
+ const configPath = path32.join(projectRoot, "harness.config.json");
13745
+ if (!fs33.existsSync(configPath)) {
13746
+ return Err(new Error("harness.config.json not found"));
13747
+ }
13748
+ const cfg = JSON.parse(fs33.readFileSync(configPath, "utf-8"));
13749
+ const tracker = cfg.roadmap?.tracker;
13750
+ if (!tracker) {
13751
+ return Err(
13752
+ new Error(
13753
+ "file-less tracker config missing: set roadmap.tracker.kind in harness.config.json"
13754
+ )
13755
+ );
13756
+ }
13757
+ if (tracker.kind !== "github") {
13758
+ return Err(
13759
+ new Error(`file-less tracker only supports kind: "github" today; got "${tracker.kind}"`)
13760
+ );
13761
+ }
13762
+ if (!tracker.repo) {
13763
+ return Err(
13764
+ new Error(
13765
+ "roadmap.tracker.repo is required for file-less mode (set it in harness.config.json)"
13766
+ )
13767
+ );
13768
+ }
13769
+ return Ok({ kind: "github-issues", repo: tracker.repo });
13770
+ } catch (e) {
13771
+ return Err(e instanceof Error ? e : new Error(String(e)));
13772
+ }
13773
+ }
13774
+ function bodyMetaMatches(a, b) {
13775
+ 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);
13776
+ }
13777
+ function eqOpt(x, y) {
13778
+ const xx = x ?? null;
13779
+ const yy = y ?? null;
13780
+ return xx === yy;
13781
+ }
13782
+ function eqList(x, y) {
13783
+ const xx = (x ?? []).slice().sort();
13784
+ const yy = (y ?? []).slice().sort();
13785
+ if (xx.length !== yy.length) return false;
13786
+ for (let i = 0; i < xx.length; i++) if (xx[i] !== yy[i]) return false;
13787
+ return true;
13788
+ }
13789
+ function normalizeAt(at) {
13790
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(at)) return at;
13791
+ const d = new Date(at);
13792
+ if (Number.isNaN(d.getTime())) return at;
13793
+ const seconds = Math.floor(d.getTime() / 1e3) * 1e3;
13794
+ return new Date(seconds).toISOString().replace(/\.\d{3}Z$/, "Z");
13795
+ }
13796
+ function hashHistoryEvent(event) {
13797
+ const detailsStr = JSON.stringify(event.details ?? {});
13798
+ const at = normalizeAt(event.at);
13799
+ const input = `${event.type}|${event.actor}|${at}|${detailsStr}`;
13800
+ return createHash4("sha256").update(input).digest("hex").slice(0, 8);
13801
+ }
13802
+ function parseHashFromCommentBody(commentBody) {
13803
+ const match = commentBody.match(HASH_RE);
13804
+ return match ? match[1] : null;
13805
+ }
13806
+ function buildHistoryCommentBody(event) {
13807
+ const hash = hashHistoryEvent(event);
13808
+ return `<!-- harness-history hash:${hash} -->
13809
+ ${JSON.stringify(event)}`;
13810
+ }
13811
+ async function buildMigrationPlan(roadmap, existingFeatures, fetchHistoryHashes, getRawBodyForExternalId) {
13812
+ const plan = {
13813
+ toCreate: [],
13814
+ toUpdate: [],
13815
+ unchanged: [],
13816
+ historyToAppend: [],
13817
+ ambiguous: []
13818
+ };
13819
+ const byExternalId = /* @__PURE__ */ new Map();
13820
+ const byNameLower = /* @__PURE__ */ new Map();
13821
+ for (const t of existingFeatures) {
13822
+ byExternalId.set(t.externalId, t);
13823
+ byNameLower.set(t.name.toLowerCase(), t);
13824
+ }
13825
+ const featureToExternalId = /* @__PURE__ */ new Map();
13826
+ for (const ms of roadmap.milestones) {
13827
+ for (const f of ms.features) {
13828
+ if (f.externalId) featureToExternalId.set(f.name, f.externalId);
13829
+ }
13830
+ }
13831
+ for (const ms of roadmap.milestones) {
13832
+ for (const feature of ms.features) {
13833
+ if (feature.externalId == null) {
13834
+ const existing = byNameLower.get(feature.name.toLowerCase());
13835
+ if (existing) {
13836
+ plan.ambiguous.push({ name: feature.name, existingIssueRef: existing.externalId });
13837
+ } else {
13838
+ plan.toCreate.push({
13839
+ name: feature.name,
13840
+ input: featureToNewInput(feature, ms)
13841
+ });
13842
+ }
13843
+ continue;
13844
+ }
13845
+ const tracked = byExternalId.get(feature.externalId);
13846
+ if (!tracked) {
13847
+ plan.ambiguous.push({
13848
+ name: feature.name,
13849
+ existingIssueRef: `${feature.externalId} (recorded external id not found)`
13850
+ });
13851
+ continue;
13852
+ }
13853
+ const expectedMeta = featureToExpectedMeta(feature, ms);
13854
+ const rawBody = await getRawBodyForExternalId(feature.externalId);
13855
+ const actualMeta = rawBody == null ? {} : parseBodyBlock(rawBody).meta;
13856
+ if (rawBody != null && bodyMetaMatches(actualMeta, expectedMeta)) {
13857
+ plan.unchanged.push({ externalId: feature.externalId, name: feature.name });
13858
+ } else {
13859
+ const diff2 = formatDiff(actualMeta, expectedMeta);
13860
+ const patch = metaToPatch(feature, expectedMeta);
13861
+ plan.toUpdate.push({
13862
+ externalId: feature.externalId,
13863
+ name: feature.name,
13864
+ patch,
13865
+ diff: diff2
13866
+ });
13867
+ }
13868
+ }
13869
+ }
13870
+ for (const record of roadmap.assignmentHistory) {
13871
+ const externalId = featureToExternalId.get(record.feature);
13872
+ if (!externalId) {
13873
+ console.warn(
13874
+ `harness-migrate: cannot resolve external id for assignment-history feature "${record.feature}"; skipping.`
13875
+ );
13876
+ continue;
13877
+ }
13878
+ const type = mapAction(record.action);
13879
+ const event = {
13880
+ type,
13881
+ actor: record.assignee,
13882
+ at: record.date
13883
+ };
13884
+ const hash = hashHistoryEvent(event);
13885
+ const existingHashes = await fetchHistoryHashes(externalId);
13886
+ if (existingHashes.has(hash)) continue;
13887
+ plan.historyToAppend.push({ externalId, event, hash });
13888
+ }
13889
+ return plan;
13890
+ }
13891
+ function featureToExpectedMeta(feature, milestone) {
13892
+ const meta = {};
13893
+ if (feature.spec != null) meta.spec = feature.spec;
13894
+ if (feature.plans.length > 0) meta.plan = feature.plans[0] ?? null;
13895
+ if (feature.blockedBy.length > 0) meta.blocked_by = feature.blockedBy.slice().sort();
13896
+ if (feature.priority != null) meta.priority = feature.priority;
13897
+ if (!milestone.isBacklog) meta.milestone = milestone.name;
13898
+ return meta;
13899
+ }
13900
+ function featureToNewInput(feature, milestone) {
13901
+ const input = {
13902
+ name: feature.name,
13903
+ summary: feature.summary,
13904
+ status: feature.status,
13905
+ spec: feature.spec,
13906
+ plans: feature.plans,
13907
+ blockedBy: feature.blockedBy,
13908
+ priority: feature.priority,
13909
+ milestone: milestone.isBacklog ? null : milestone.name,
13910
+ assignee: feature.assignee
13911
+ };
13912
+ return input;
13913
+ }
13914
+ function metaToPatch(feature, expected) {
13915
+ const patch = {};
13916
+ patch.summary = feature.summary;
13917
+ if (expected.spec !== void 0) patch.spec = expected.spec ?? null;
13918
+ if (expected.plan !== void 0 && expected.plan != null) patch.plans = [expected.plan];
13919
+ if (expected.blocked_by !== void 0) patch.blockedBy = expected.blocked_by ?? [];
13920
+ if (expected.priority !== void 0) patch.priority = expected.priority ?? null;
13921
+ if (expected.milestone !== void 0) patch.milestone = expected.milestone ?? null;
13922
+ return patch;
13923
+ }
13924
+ function formatDiff(actual, expected) {
13925
+ const keys = /* @__PURE__ */ new Set();
13926
+ const collect = (m) => {
13927
+ for (const k of Object.keys(m)) {
13928
+ const v = m[k];
13929
+ if (v !== void 0 && v !== null && !(Array.isArray(v) && v.length === 0)) keys.add(k);
13930
+ }
13931
+ };
13932
+ collect(actual);
13933
+ collect(expected);
13934
+ const changed = [];
13935
+ for (const key of [...keys].sort()) {
13936
+ const a = actual[key];
13937
+ const e = expected[key];
13938
+ if (JSON.stringify(a ?? null) !== JSON.stringify(e ?? null)) changed.push(key);
13939
+ }
13940
+ return changed.join(",");
13941
+ }
13942
+ function mapAction(action) {
13943
+ switch (action) {
13944
+ case "assigned":
13945
+ return "claimed";
13946
+ case "completed":
13947
+ return "completed";
13948
+ case "unassigned":
13949
+ return "released";
13950
+ }
13951
+ }
13952
+ async function runMigrationPlan(plan, deps, opts) {
13953
+ const projectRoot = opts.projectRoot;
13954
+ const roadmapPath = path33.join(projectRoot, "docs", "roadmap.md");
13955
+ const archivedPath = path33.join(projectRoot, "docs", "roadmap.md.archived");
13956
+ const configPath = path33.join(projectRoot, "harness.config.json");
13957
+ const configBackupPath = path33.join(projectRoot, "harness.config.json.pre-migration");
13958
+ const createdSoFar = [];
13959
+ const report = {
13960
+ created: 0,
13961
+ updated: 0,
13962
+ unchanged: plan.unchanged.length,
13963
+ historyAppended: 0,
13964
+ archivedFrom: null,
13965
+ archivedTo: null,
13966
+ configBackup: null,
13967
+ mode: "applied"
13968
+ };
13969
+ if (plan.ambiguous.length > 0) {
13970
+ const names = plan.ambiguous.map((a) => a.name).join(", ");
13971
+ return Ok({
13972
+ ...report,
13973
+ mode: "aborted",
13974
+ abortReason: `ambiguous features (title-collision or dangling external-id): ${names}`
13975
+ });
13976
+ }
13977
+ if (opts.dryRun) {
13978
+ return Ok({ ...report, mode: "dry-run" });
13979
+ }
13980
+ for (const entry of plan.toCreate) {
13981
+ const r = await deps.client.create(entry.input);
13982
+ if (!r.ok) {
13983
+ return Ok({
13984
+ ...report,
13985
+ created: createdSoFar.length,
13986
+ createdSoFar,
13987
+ mode: "aborted",
13988
+ abortReason: `create failed for "${entry.name}": ${r.error.message}`
13989
+ });
13990
+ }
13991
+ createdSoFar.push({ name: entry.name, externalId: r.value.externalId });
13992
+ }
13993
+ report.created = createdSoFar.length;
13994
+ for (const entry of plan.toUpdate) {
13995
+ const r = await deps.client.update(entry.externalId, entry.patch);
13996
+ if (!r.ok) {
13997
+ return Ok({
13998
+ ...report,
13999
+ createdSoFar,
14000
+ mode: "aborted",
14001
+ abortReason: `update failed for "${entry.name}" (${entry.externalId}): ${r.error.message}`
14002
+ });
14003
+ }
14004
+ report.updated++;
14005
+ }
14006
+ for (const entry of plan.historyToAppend) {
14007
+ const r = await deps.client.appendHistory(entry.externalId, entry.event);
14008
+ if (!r.ok) {
14009
+ return Ok({
14010
+ ...report,
14011
+ createdSoFar,
14012
+ mode: "aborted",
14013
+ abortReason: `appendHistory failed for ${entry.externalId} (${entry.event.type}): ${r.error.message}`
14014
+ });
14015
+ }
14016
+ report.historyAppended++;
14017
+ }
14018
+ if (deps.existsFile(archivedPath)) {
14019
+ return Ok({
14020
+ ...report,
14021
+ createdSoFar,
14022
+ mode: "aborted",
14023
+ abortReason: `archive-collision: ${archivedPath} already exists; refusing to overwrite`
14024
+ });
14025
+ }
14026
+ if (deps.existsFile(roadmapPath)) {
14027
+ try {
14028
+ deps.renameFile(roadmapPath, archivedPath);
14029
+ report.archivedFrom = roadmapPath;
14030
+ report.archivedTo = archivedPath;
14031
+ } catch (err) {
14032
+ return Ok({
14033
+ ...report,
14034
+ createdSoFar,
14035
+ mode: "aborted",
14036
+ abortReason: `archive failed: ${err.message}`
14037
+ });
14038
+ }
14039
+ }
14040
+ try {
14041
+ if (!deps.existsFile(configPath)) {
14042
+ return Ok({
14043
+ ...report,
14044
+ createdSoFar,
14045
+ mode: "aborted",
14046
+ abortReason: `config rewrite failed: ${configPath} not found`
14047
+ });
14048
+ }
14049
+ const original = deps.readFile(configPath);
14050
+ deps.writeFile(configBackupPath, original);
14051
+ report.configBackup = configBackupPath;
14052
+ const parsed = JSON.parse(original);
14053
+ const roadmapSection = parsed.roadmap ?? {};
14054
+ roadmapSection.mode = "file-less";
14055
+ parsed.roadmap = roadmapSection;
14056
+ deps.writeFile(configPath, JSON.stringify(parsed, null, 2) + "\n");
14057
+ } catch (err) {
14058
+ return Ok({
14059
+ ...report,
14060
+ createdSoFar,
14061
+ mode: "aborted",
14062
+ abortReason: `config rewrite failed: ${err.message}`
14063
+ });
14064
+ }
14065
+ return Ok(report);
14066
+ }
13235
14067
  function getStatePath() {
13236
14068
  const home = process.env["HOME"] || os.homedir();
13237
- return path32.join(home, ".harness", "update-check.json");
14069
+ return path36.join(home, ".harness", "update-check.json");
13238
14070
  }
13239
14071
  function isUpdateCheckEnabled(configInterval) {
13240
14072
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -13247,7 +14079,7 @@ function shouldRunCheck(state, intervalMs) {
13247
14079
  }
13248
14080
  function readCheckState() {
13249
14081
  try {
13250
- const raw = fs33.readFileSync(getStatePath(), "utf-8");
14082
+ const raw = fs36.readFileSync(getStatePath(), "utf-8");
13251
14083
  const parsed = JSON.parse(raw);
13252
14084
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
13253
14085
  const state = parsed;
@@ -13264,7 +14096,7 @@ function readCheckState() {
13264
14096
  }
13265
14097
  function spawnBackgroundCheck(currentVersion) {
13266
14098
  const statePath = getStatePath();
13267
- const stateDir = path32.dirname(statePath);
14099
+ const stateDir = path36.dirname(statePath);
13268
14100
  const script = `
13269
14101
  const { execSync } = require('child_process');
13270
14102
  const fs = require('fs');
@@ -13507,14 +14339,14 @@ function getModelPrice(model, dataset) {
13507
14339
  return pricing;
13508
14340
  }
13509
14341
  function getCachePath(projectRoot) {
13510
- return path33.join(projectRoot, ".harness", "cache", "pricing.json");
14342
+ return path37.join(projectRoot, ".harness", "cache", "pricing.json");
13511
14343
  }
13512
14344
  function getStalenessMarkerPath(projectRoot) {
13513
- return path33.join(projectRoot, ".harness", "cache", "staleness-marker.json");
14345
+ return path37.join(projectRoot, ".harness", "cache", "staleness-marker.json");
13514
14346
  }
13515
14347
  async function readDiskCache(projectRoot) {
13516
14348
  try {
13517
- const raw = await fs34.readFile(getCachePath(projectRoot), "utf-8");
14349
+ const raw = await fs37.readFile(getCachePath(projectRoot), "utf-8");
13518
14350
  return JSON.parse(raw);
13519
14351
  } catch {
13520
14352
  return null;
@@ -13522,8 +14354,8 @@ async function readDiskCache(projectRoot) {
13522
14354
  }
13523
14355
  async function writeDiskCache(projectRoot, data) {
13524
14356
  const cachePath = getCachePath(projectRoot);
13525
- await fs34.mkdir(path33.dirname(cachePath), { recursive: true });
13526
- await fs34.writeFile(cachePath, JSON.stringify(data, null, 2));
14357
+ await fs37.mkdir(path37.dirname(cachePath), { recursive: true });
14358
+ await fs37.writeFile(cachePath, JSON.stringify(data, null, 2));
13527
14359
  }
13528
14360
  async function fetchFromNetwork() {
13529
14361
  try {
@@ -13550,7 +14382,7 @@ function loadFallbackDataset() {
13550
14382
  async function checkAndWarnStaleness(projectRoot) {
13551
14383
  const markerPath = getStalenessMarkerPath(projectRoot);
13552
14384
  try {
13553
- const raw = await fs34.readFile(markerPath, "utf-8");
14385
+ const raw = await fs37.readFile(markerPath, "utf-8");
13554
14386
  const marker = JSON.parse(raw);
13555
14387
  const firstUse = new Date(marker.firstFallbackUse).getTime();
13556
14388
  const now = Date.now();
@@ -13562,8 +14394,8 @@ async function checkAndWarnStaleness(projectRoot) {
13562
14394
  }
13563
14395
  } catch {
13564
14396
  try {
13565
- await fs34.mkdir(path33.dirname(markerPath), { recursive: true });
13566
- await fs34.writeFile(
14397
+ await fs37.mkdir(path37.dirname(markerPath), { recursive: true });
14398
+ await fs37.writeFile(
13567
14399
  markerPath,
13568
14400
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
13569
14401
  );
@@ -13573,7 +14405,7 @@ async function checkAndWarnStaleness(projectRoot) {
13573
14405
  }
13574
14406
  async function clearStalenessMarker(projectRoot) {
13575
14407
  try {
13576
- await fs34.unlink(getStalenessMarkerPath(projectRoot));
14408
+ await fs37.unlink(getStalenessMarkerPath(projectRoot));
13577
14409
  } catch {
13578
14410
  }
13579
14411
  }
@@ -13810,10 +14642,10 @@ function parseLine(line, lineNumber) {
13810
14642
  return record;
13811
14643
  }
13812
14644
  function readCostRecords(projectRoot) {
13813
- const costsFile = path34.join(projectRoot, ".harness", "metrics", "costs.jsonl");
14645
+ const costsFile = path38.join(projectRoot, ".harness", "metrics", "costs.jsonl");
13814
14646
  let raw;
13815
14647
  try {
13816
- raw = fs35.readFileSync(costsFile, "utf-8");
14648
+ raw = fs38.readFileSync(costsFile, "utf-8");
13817
14649
  } catch {
13818
14650
  return [];
13819
14651
  }
@@ -13863,7 +14695,7 @@ function parseCCLine(line, filePath, lineNumber) {
13863
14695
  entry = JSON.parse(line);
13864
14696
  } catch {
13865
14697
  console.warn(
13866
- `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path35.basename(filePath)}`
14698
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path39.basename(filePath)}`
13867
14699
  );
13868
14700
  return null;
13869
14701
  }
@@ -13877,7 +14709,7 @@ function parseCCLine(line, filePath, lineNumber) {
13877
14709
  function readCCFile(filePath) {
13878
14710
  let raw;
13879
14711
  try {
13880
- raw = fs36.readFileSync(filePath, "utf-8");
14712
+ raw = fs39.readFileSync(filePath, "utf-8");
13881
14713
  } catch {
13882
14714
  return [];
13883
14715
  }
@@ -13899,10 +14731,10 @@ function readCCFile(filePath) {
13899
14731
  }
13900
14732
  function parseCCRecords() {
13901
14733
  const homeDir = process.env.HOME ?? os2.homedir();
13902
- const projectsDir = path35.join(homeDir, ".claude", "projects");
14734
+ const projectsDir = path39.join(homeDir, ".claude", "projects");
13903
14735
  let projectDirs;
13904
14736
  try {
13905
- projectDirs = fs36.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path35.join(projectsDir, d.name));
14737
+ projectDirs = fs39.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path39.join(projectsDir, d.name));
13906
14738
  } catch {
13907
14739
  return [];
13908
14740
  }
@@ -13910,7 +14742,7 @@ function parseCCRecords() {
13910
14742
  for (const dir of projectDirs) {
13911
14743
  let files;
13912
14744
  try {
13913
- files = fs36.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path35.join(dir, f));
14745
+ files = fs39.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path39.join(dir, f));
13914
14746
  } catch {
13915
14747
  continue;
13916
14748
  }
@@ -13938,10 +14770,10 @@ function parseLine2(line, lineNumber) {
13938
14770
  }
13939
14771
  }
13940
14772
  function readAdoptionRecords(projectRoot) {
13941
- const adoptionFile = path36.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
14773
+ const adoptionFile = path40.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
13942
14774
  let raw;
13943
14775
  try {
13944
- raw = fs37.readFileSync(adoptionFile, "utf-8");
14776
+ raw = fs40.readFileSync(adoptionFile, "utf-8");
13945
14777
  } catch {
13946
14778
  return [];
13947
14779
  }
@@ -14089,25 +14921,25 @@ function resolveStability(contentType) {
14089
14921
  return STABILITY_LOOKUP[contentType] ?? "ephemeral";
14090
14922
  }
14091
14923
  function getOrCreateInstallId(projectRoot) {
14092
- const harnessDir = path37.join(projectRoot, ".harness");
14093
- const installIdFile = path37.join(harnessDir, ".install-id");
14924
+ const harnessDir = path41.join(projectRoot, ".harness");
14925
+ const installIdFile = path41.join(harnessDir, ".install-id");
14094
14926
  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
14927
  try {
14096
- const existing = fs38.readFileSync(installIdFile, "utf-8").trim();
14928
+ const existing = fs41.readFileSync(installIdFile, "utf-8").trim();
14097
14929
  if (UUID_V4_RE.test(existing)) {
14098
14930
  return existing;
14099
14931
  }
14100
14932
  } catch {
14101
14933
  }
14102
14934
  const id = crypto4.randomUUID();
14103
- fs38.mkdirSync(harnessDir, { recursive: true });
14104
- fs38.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
14935
+ fs41.mkdirSync(harnessDir, { recursive: true });
14936
+ fs41.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
14105
14937
  return id;
14106
14938
  }
14107
14939
  function parseIdentityFromTelemetryFile(filePath) {
14108
14940
  const identity = {};
14109
14941
  try {
14110
- const raw = fs39.readFileSync(filePath, "utf-8");
14942
+ const raw = fs42.readFileSync(filePath, "utf-8");
14111
14943
  const parsed = JSON.parse(raw);
14112
14944
  const src = parsed?.identity;
14113
14945
  if (!src || typeof src !== "object") return identity;
@@ -14120,7 +14952,7 @@ function parseIdentityFromTelemetryFile(filePath) {
14120
14952
  }
14121
14953
  function readProjectNameFallback(configPath) {
14122
14954
  try {
14123
- const raw = fs39.readFileSync(configPath, "utf-8");
14955
+ const raw = fs42.readFileSync(configPath, "utf-8");
14124
14956
  const config = JSON.parse(raw);
14125
14957
  return typeof config?.name === "string" ? config.name : void 0;
14126
14958
  } catch {
@@ -14141,10 +14973,10 @@ function readGitAliasFallback(cwd) {
14141
14973
  }
14142
14974
  }
14143
14975
  function readIdentity(projectRoot) {
14144
- const filePath = path38.join(projectRoot, ".harness", "telemetry.json");
14976
+ const filePath = path42.join(projectRoot, ".harness", "telemetry.json");
14145
14977
  const identity = parseIdentityFromTelemetryFile(filePath);
14146
14978
  if (!identity.project) {
14147
- const fallbackProject = readProjectNameFallback(path38.join(projectRoot, "harness.config.json"));
14979
+ const fallbackProject = readProjectNameFallback(path42.join(projectRoot, "harness.config.json"));
14148
14980
  if (fallbackProject) identity.project = fallbackProject;
14149
14981
  }
14150
14982
  if (!identity.alias) {
@@ -14191,7 +15023,7 @@ function collectEvents(projectRoot, consent) {
14191
15023
  );
14192
15024
  }
14193
15025
  function sleep3(ms) {
14194
- return new Promise((resolve8) => setTimeout(resolve8, ms));
15026
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
14195
15027
  }
14196
15028
  async function send(events, apiKey) {
14197
15029
  if (events.length === 0) return;
@@ -14221,12 +15053,12 @@ function acquireCompoundLock(category, opts = {}) {
14221
15053
  );
14222
15054
  }
14223
15055
  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`);
15056
+ const lockDir = path43.join(cwd, ".harness", "locks");
15057
+ fs43.mkdirSync(lockDir, { recursive: true });
15058
+ const lockPath = path43.join(lockDir, `compound-${category}.lock`);
14227
15059
  let fd;
14228
15060
  try {
14229
- fd = fs40.openSync(lockPath, "wx");
15061
+ fd = fs43.openSync(lockPath, "wx");
14230
15062
  } catch (e) {
14231
15063
  const err = e;
14232
15064
  if (err.code === "EEXIST") {
@@ -14235,14 +15067,14 @@ function acquireCompoundLock(category, opts = {}) {
14235
15067
  }
14236
15068
  throw err;
14237
15069
  }
14238
- fs40.writeSync(fd, String(process.pid));
14239
- fs40.closeSync(fd);
15070
+ fs43.writeSync(fd, String(process.pid));
15071
+ fs43.closeSync(fd);
14240
15072
  let released = false;
14241
15073
  const release = () => {
14242
15074
  if (released) return;
14243
15075
  released = true;
14244
15076
  try {
14245
- fs40.unlinkSync(lockPath);
15077
+ fs43.unlinkSync(lockPath);
14246
15078
  } catch {
14247
15079
  }
14248
15080
  process.removeListener("exit", onExit);
@@ -14268,7 +15100,7 @@ function acquireCompoundLock(category, opts = {}) {
14268
15100
  }
14269
15101
  function readHolderPid(lockPath) {
14270
15102
  try {
14271
- const raw = fs40.readFileSync(lockPath, "utf-8").trim();
15103
+ const raw = fs43.readFileSync(lockPath, "utf-8").trim();
14272
15104
  const n = Number.parseInt(raw, 10);
14273
15105
  return Number.isFinite(n) ? n : -1;
14274
15106
  } catch {
@@ -14293,10 +15125,10 @@ function assertSanitized(value) {
14293
15125
  }
14294
15126
  function writePulseConfig(config, opts) {
14295
15127
  PulseConfigSchema.parse(config);
14296
- if (!fs41.existsSync(opts.configPath)) {
15128
+ if (!fs44.existsSync(opts.configPath)) {
14297
15129
  throw new Error(`harness.config.json not found at ${opts.configPath}`);
14298
15130
  }
14299
- const raw = fs41.readFileSync(opts.configPath, "utf-8");
15131
+ const raw = fs44.readFileSync(opts.configPath, "utf-8");
14300
15132
  let parsed;
14301
15133
  try {
14302
15134
  parsed = JSON.parse(raw);
@@ -14304,18 +15136,18 @@ function writePulseConfig(config, opts) {
14304
15136
  throw new Error(`Invalid JSON in ${opts.configPath}: ${e.message}`, { cause: e });
14305
15137
  }
14306
15138
  const bakPath = `${opts.configPath}.bak`;
14307
- if (!opts.skipBackup && !fs41.existsSync(bakPath)) {
14308
- fs41.writeFileSync(bakPath, raw, "utf-8");
15139
+ if (!opts.skipBackup && !fs44.existsSync(bakPath)) {
15140
+ fs44.writeFileSync(bakPath, raw, "utf-8");
14309
15141
  }
14310
15142
  parsed.pulse = config;
14311
15143
  const serialized = JSON.stringify(parsed, null, 2) + "\n";
14312
15144
  const tmpPath = `${opts.configPath}.tmp-${process.pid}`;
14313
- fs41.writeFileSync(tmpPath, serialized, "utf-8");
15145
+ fs44.writeFileSync(tmpPath, serialized, "utf-8");
14314
15146
  try {
14315
- fs41.renameSync(tmpPath, opts.configPath);
15147
+ fs44.renameSync(tmpPath, opts.configPath);
14316
15148
  } catch (e) {
14317
15149
  try {
14318
- fs41.unlinkSync(tmpPath);
15150
+ fs44.unlinkSync(tmpPath);
14319
15151
  } catch {
14320
15152
  }
14321
15153
  throw e;
@@ -14323,12 +15155,12 @@ function writePulseConfig(config, opts) {
14323
15155
  }
14324
15156
  function seedFromStrategy(opts = {}) {
14325
15157
  const cwd = opts.cwd ?? process.cwd();
14326
- const strategyPath = path40.join(cwd, "STRATEGY.md");
15158
+ const strategyPath = path44.join(cwd, "STRATEGY.md");
14327
15159
  const warnings = [];
14328
- if (!fs42.existsSync(strategyPath)) {
15160
+ if (!fs45.existsSync(strategyPath)) {
14329
15161
  return { name: null, keyMetrics: [], warnings: ["STRATEGY.md not found"] };
14330
15162
  }
14331
- const raw = fs42.readFileSync(strategyPath, "utf-8");
15163
+ const raw = fs45.readFileSync(strategyPath, "utf-8");
14332
15164
  let name = null;
14333
15165
  const fmMatch = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
14334
15166
  const fm = fmMatch?.[1];
@@ -14480,7 +15312,7 @@ function loadTemplate() {
14480
15312
  const url = import.meta?.url;
14481
15313
  if (!url) return INLINE_TEMPLATE;
14482
15314
  const here = dirname18(fileURLToPath(url));
14483
- return readFileSync36(join572(here, "template.md"), "utf-8");
15315
+ return readFileSync38(join612(here, "template.md"), "utf-8");
14484
15316
  } catch {
14485
15317
  return INLINE_TEMPLATE;
14486
15318
  }
@@ -14669,11 +15501,11 @@ async function computeHotspots(opts) {
14669
15501
  }
14670
15502
  const counts = /* @__PURE__ */ new Map();
14671
15503
  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);
15504
+ const path46 = line.trim();
15505
+ if (path46.length === 0) continue;
15506
+ counts.set(path46, (counts.get(path46) ?? 0) + 1);
14675
15507
  }
14676
- return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([path42, churn]) => ({ path: path42, churn })).sort((a, b) => b.churn - a.churn);
15508
+ return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([path46, churn]) => ({ path: path46, churn })).sort((a, b) => b.churn - a.churn);
14677
15509
  }
14678
15510
  function tokenize(text) {
14679
15511
  return new Set(
@@ -14683,12 +15515,12 @@ function tokenize(text) {
14683
15515
  async function* walk22(dir) {
14684
15516
  let entries;
14685
15517
  try {
14686
- entries = await fs43.readdir(dir, { withFileTypes: true });
15518
+ entries = await fs46.readdir(dir, { withFileTypes: true });
14687
15519
  } catch {
14688
15520
  return;
14689
15521
  }
14690
15522
  for (const e of entries) {
14691
- const p = path41.join(dir, e.name);
15523
+ const p = path45.join(dir, e.name);
14692
15524
  if (e.isDirectory()) yield* walk22(p);
14693
15525
  else if (e.isFile() && e.name.endsWith(".md")) yield p;
14694
15526
  }
@@ -14697,11 +15529,11 @@ async function readDocumentedTokens(solutionsDir) {
14697
15529
  const docs = [];
14698
15530
  for (const [track, categories] of TRACK_CATEGORIES2) {
14699
15531
  for (const category of categories) {
14700
- const dir = path41.join(solutionsDir, track, category);
15532
+ const dir = path45.join(solutionsDir, track, category);
14701
15533
  for await (const file of walk22(dir)) {
14702
- const raw = await fs43.readFile(file, "utf-8");
15534
+ const raw = await fs46.readFile(file, "utf-8");
14703
15535
  const m = /^#\s+(.+)$/m.exec(raw);
14704
- const title = m?.[1] ?? path41.basename(file, ".md");
15536
+ const title = m?.[1] ?? path45.basename(file, ".md");
14705
15537
  docs.push(tokenize(title));
14706
15538
  }
14707
15539
  }
@@ -14764,17 +15596,112 @@ function assembleCandidateReport(input) {
14764
15596
  }
14765
15597
  return lines.join("\n");
14766
15598
  }
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;
15599
+ var WHATWG_BAD_PORTS, BAD_PORT_SET, 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, TS_HINTS, TS_CONVENTIONS, PYTHON_HINTS, PYTHON_CONVENTIONS, DEFAULT_INCLUDE_PATTERNS, 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, 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
15600
  var init_dist3 = __esm({
14769
15601
  "../core/dist/index.mjs"() {
14770
- init_chunk_CE225WRK();
15602
+ init_chunk_4UI65RLE();
14771
15603
  init_dist();
15604
+ init_dist2();
15605
+ init_dist2();
15606
+ init_dist2();
14772
15607
  init_dist();
14773
15608
  init_dist();
14774
15609
  init_dist2();
15610
+ init_dist2();
15611
+ init_dist2();
15612
+ init_dist2();
15613
+ init_dist();
15614
+ init_dist();
15615
+ init_dist();
15616
+ init_dist();
14775
15617
  init_dist();
14776
15618
  init_dist();
14777
15619
  init_dist2();
15620
+ WHATWG_BAD_PORTS = Object.freeze([
15621
+ 1,
15622
+ 7,
15623
+ 9,
15624
+ 11,
15625
+ 13,
15626
+ 15,
15627
+ 17,
15628
+ 19,
15629
+ 20,
15630
+ 21,
15631
+ 22,
15632
+ 23,
15633
+ 25,
15634
+ 37,
15635
+ 42,
15636
+ 43,
15637
+ 53,
15638
+ 69,
15639
+ 77,
15640
+ 79,
15641
+ 87,
15642
+ 95,
15643
+ 101,
15644
+ 102,
15645
+ 103,
15646
+ 104,
15647
+ 109,
15648
+ 110,
15649
+ 111,
15650
+ 113,
15651
+ 115,
15652
+ 117,
15653
+ 119,
15654
+ 123,
15655
+ 135,
15656
+ 137,
15657
+ 139,
15658
+ 143,
15659
+ 161,
15660
+ 179,
15661
+ 389,
15662
+ 427,
15663
+ 465,
15664
+ 512,
15665
+ 513,
15666
+ 514,
15667
+ 515,
15668
+ 526,
15669
+ 530,
15670
+ 531,
15671
+ 532,
15672
+ 540,
15673
+ 548,
15674
+ 554,
15675
+ 556,
15676
+ 563,
15677
+ 587,
15678
+ 601,
15679
+ 636,
15680
+ 989,
15681
+ 990,
15682
+ 993,
15683
+ 995,
15684
+ 1719,
15685
+ 1720,
15686
+ 1723,
15687
+ 2049,
15688
+ 3659,
15689
+ 4045,
15690
+ 4190,
15691
+ 5060,
15692
+ 5061,
15693
+ 6e3,
15694
+ 6566,
15695
+ 6665,
15696
+ 6666,
15697
+ 6667,
15698
+ 6668,
15699
+ 6669,
15700
+ 6679,
15701
+ 6697,
15702
+ 10080
15703
+ ]);
15704
+ BAD_PORT_SET = new Set(WHATWG_BAD_PORTS);
14778
15705
  VALID_TYPES = [
14779
15706
  "feat",
14780
15707
  "fix",
@@ -15089,6 +16016,32 @@ var init_dist3 = __esm({
15089
16016
  LINE_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore(?!\s*(?:SEC-|-start|-end))(?:\s+([\w,]+))?(?::\s*(.+))?$/;
15090
16017
  START_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore-start(?:\s+([\w,]+))?(?::\s*(.+))?$/;
15091
16018
  END_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore-end\s*$/;
16019
+ TS_HINTS = ['Add "exports" or "main" to package.json', "Create src/index.ts"];
16020
+ TS_CONVENTIONS = ["src/index.ts", "src/main.ts", "src/index.tsx", "index.ts", "main.ts"];
16021
+ PYTHON_HINTS = [
16022
+ "Add an entry to [project.scripts] in pyproject.toml",
16023
+ "Create main.py or <package>/__main__.py"
16024
+ ];
16025
+ PYTHON_CONVENTIONS = [
16026
+ "__main__.py",
16027
+ "main.py",
16028
+ "app.py",
16029
+ "src/__main__.py",
16030
+ "src/main.py",
16031
+ "src/app.py"
16032
+ ];
16033
+ DEFAULT_INCLUDE_PATTERNS = [
16034
+ "**/*.ts",
16035
+ "**/*.tsx",
16036
+ "**/*.js",
16037
+ "**/*.jsx",
16038
+ "**/*.mjs",
16039
+ "**/*.cjs",
16040
+ "**/*.py",
16041
+ "**/*.go",
16042
+ "**/*.rs",
16043
+ "**/*.java"
16044
+ ];
15092
16045
  CODE_BLOCK_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "ts", "javascript", "js"]);
15093
16046
  DEFAULT_DRIFT_CONFIG = {
15094
16047
  docPaths: [],
@@ -15127,10 +16080,7 @@ var init_dist3 = __esm({
15127
16080
  snapshot;
15128
16081
  report;
15129
16082
  constructor(config) {
15130
- this.config = {
15131
- ...config,
15132
- parser: config.parser || new TypeScriptParser()
15133
- };
16083
+ this.config = { ...config };
15134
16084
  }
15135
16085
  /**
15136
16086
  * Run full entropy analysis.
@@ -15356,11 +16306,11 @@ var init_dist3 = __esm({
15356
16306
  return detectPatternViolations(snapshotResult.value, config);
15357
16307
  }
15358
16308
  };
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);
16309
+ readFile62 = promisify2(fs6.readFile);
16310
+ writeFile32 = promisify2(fs6.writeFile);
16311
+ unlink22 = promisify2(fs6.unlink);
16312
+ mkdir22 = promisify2(fs6.mkdir);
16313
+ copyFile2 = promisify2(fs6.copyFile);
15364
16314
  DEFAULT_FIX_CONFIG = {
15365
16315
  dryRun: false,
15366
16316
  fixTypes: ["unused-imports", "dead-files"],
@@ -15482,14 +16432,14 @@ var init_dist3 = __esm({
15482
16432
  BaselineManager = class {
15483
16433
  baselinesPath;
15484
16434
  constructor(projectRoot) {
15485
- this.baselinesPath = join17(projectRoot, ".harness", "perf", "baselines.json");
16435
+ this.baselinesPath = join18(projectRoot, ".harness", "perf", "baselines.json");
15486
16436
  }
15487
16437
  /**
15488
16438
  * Load the baselines file from disk.
15489
16439
  * Returns null if the file does not exist or contains invalid JSON.
15490
16440
  */
15491
16441
  load() {
15492
- if (!existsSync11(this.baselinesPath)) {
16442
+ if (!existsSync12(this.baselinesPath)) {
15493
16443
  return null;
15494
16444
  }
15495
16445
  try {
@@ -15523,7 +16473,7 @@ var init_dist3 = __esm({
15523
16473
  benchmarks
15524
16474
  };
15525
16475
  const dir = dirname8(this.baselinesPath);
15526
- if (!existsSync11(dir)) {
16476
+ if (!existsSync12(dir)) {
15527
16477
  mkdirSync2(dir, { recursive: true });
15528
16478
  }
15529
16479
  writeFileSync2(this.baselinesPath, JSON.stringify(file, null, 2));
@@ -15722,7 +16672,6 @@ var init_dist3 = __esm({
15722
16672
  };
15723
16673
  }
15724
16674
  };
15725
- SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
15726
16675
  SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
15727
16676
  FUNCTION_DECL_RE = /(?:export\s+)?(?:async\s+)?function\s+(\w+)/;
15728
16677
  CONST_DECL_RE = /(?:export\s+)?(?:const|let)\s+(\w+)\s*=/;
@@ -15757,16 +16706,16 @@ var init_dist3 = __esm({
15757
16706
  walkDir(dir, entries) {
15758
16707
  let items;
15759
16708
  try {
15760
- items = fs6.readdirSync(dir, { withFileTypes: true });
16709
+ items = fs7.readdirSync(dir, { withFileTypes: true });
15761
16710
  } catch {
15762
16711
  return;
15763
16712
  }
15764
16713
  for (const item of items) {
15765
16714
  if (item.isDirectory()) {
15766
- 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);
16715
+ if (DEFAULT_SKIP_DIRS.has(item.name)) continue;
16716
+ this.walkDir(path4.join(dir, item.name), entries);
16717
+ } else if (item.isFile() && SOURCE_EXTENSIONS.has(path4.extname(item.name))) {
16718
+ this.scanFile(path4.join(dir, item.name), entries);
15770
16719
  }
15771
16720
  }
15772
16721
  }
@@ -15781,12 +16730,12 @@ var init_dist3 = __esm({
15781
16730
  scanFile(filePath, entries) {
15782
16731
  let content;
15783
16732
  try {
15784
- content = fs6.readFileSync(filePath, "utf-8");
16733
+ content = fs7.readFileSync(filePath, "utf-8");
15785
16734
  } catch {
15786
16735
  return;
15787
16736
  }
15788
16737
  const lines = content.split("\n");
15789
- const relativePath = path3.relative(this.projectRoot, filePath).replace(/\\/g, "/");
16738
+ const relativePath = path4.relative(this.projectRoot, filePath).replace(/\\/g, "/");
15790
16739
  for (let i = 0; i < lines.length; i++) {
15791
16740
  if (!lines[i].includes("@perf-critical")) continue;
15792
16741
  const fnName = this.resolveFunctionName(lines, i + 1);
@@ -16126,7 +17075,7 @@ var init_dist3 = __esm({
16126
17075
  ensureDirectory() {
16127
17076
  if (!this.initialized) {
16128
17077
  const dir = dirname9(this.filePath);
16129
- if (!existsSync12(dir)) {
17078
+ if (!existsSync13(dir)) {
16130
17079
  mkdirSync22(dir, { recursive: true });
16131
17080
  }
16132
17081
  this.initialized = true;
@@ -16157,7 +17106,7 @@ var init_dist3 = __esm({
16157
17106
  this.ensureDirectory();
16158
17107
  const content = this.buffer.join("");
16159
17108
  this.buffer = [];
16160
- if (this.options.mode === "overwrite" && !existsSync12(this.filePath)) {
17109
+ if (this.options.mode === "overwrite" && !existsSync13(this.filePath)) {
16161
17110
  writeFileSync22(this.filePath, content);
16162
17111
  } else {
16163
17112
  appendFileSync(this.filePath, content);
@@ -16239,14 +17188,14 @@ var init_dist3 = __esm({
16239
17188
  TimelineManager = class {
16240
17189
  timelinePath;
16241
17190
  constructor(rootDir) {
16242
- this.timelinePath = join19(rootDir, ".harness", "arch", "timeline.json");
17191
+ this.timelinePath = join20(rootDir, ".harness", "arch", "timeline.json");
16243
17192
  }
16244
17193
  /**
16245
17194
  * Load timeline from disk.
16246
17195
  * Returns empty TimelineFile if file does not exist or is invalid.
16247
17196
  */
16248
17197
  load() {
16249
- if (!existsSync13(this.timelinePath)) {
17198
+ if (!existsSync14(this.timelinePath)) {
16250
17199
  return { version: 1, snapshots: [] };
16251
17200
  }
16252
17201
  try {
@@ -16272,7 +17221,7 @@ var init_dist3 = __esm({
16272
17221
  */
16273
17222
  save(timeline) {
16274
17223
  const dir = dirname10(this.timelinePath);
16275
- if (!existsSync13(dir)) {
17224
+ if (!existsSync14(dir)) {
16276
17225
  mkdirSync3(dir, { recursive: true });
16277
17226
  }
16278
17227
  const tmp = this.timelinePath + "." + randomBytes2(4).toString("hex") + ".tmp";
@@ -16829,8 +17778,8 @@ var init_dist3 = __esm({
16829
17778
  return null;
16830
17779
  }
16831
17780
  try {
16832
- const roadmapPath = path4.join(this.rootDir, "roadmap.md");
16833
- const raw = fs7.readFileSync(roadmapPath, "utf-8");
17781
+ const roadmapPath = path5.join(this.rootDir, "roadmap.md");
17782
+ const raw = fs8.readFileSync(roadmapPath, "utf-8");
16834
17783
  const parseResult = parseRoadmap(raw);
16835
17784
  if (!parseResult.ok) return null;
16836
17785
  const features = parseResult.value.milestones.flatMap(
@@ -16865,8 +17814,8 @@ var init_dist3 = __esm({
16865
17814
  * @param specPath - Relative path from rootDir to the spec file.
16866
17815
  */
16867
17816
  estimate(specPath) {
16868
- const absolutePath = path5.join(this.rootDir, specPath);
16869
- const content = fs8.readFileSync(absolutePath, "utf-8");
17817
+ const absolutePath = path6.join(this.rootDir, specPath);
17818
+ const content = fs9.readFileSync(absolutePath, "utf-8");
16870
17819
  const newFileCount = this.extractNewFileCount(content);
16871
17820
  const affectedLayers = this.extractAffectedLayers(content);
16872
17821
  const newDependencies = this.extractNewDependencies(content);
@@ -16878,7 +17827,7 @@ var init_dist3 = __esm({
16878
17827
  phaseCount
16879
17828
  );
16880
17829
  const h1Match = content.match(/^#\s+(.+)$/m);
16881
- const featureName = h1Match ? h1Match[1].trim() : path5.basename(specPath, ".md");
17830
+ const featureName = h1Match ? h1Match[1].trim() : path6.basename(specPath, ".md");
16882
17831
  return {
16883
17832
  specPath,
16884
17833
  featureName,
@@ -16899,8 +17848,8 @@ var init_dist3 = __esm({
16899
17848
  const results = [];
16900
17849
  for (const feature of features) {
16901
17850
  if (!feature.spec) continue;
16902
- const absolutePath = path5.join(this.rootDir, feature.spec);
16903
- if (!fs8.existsSync(absolutePath)) continue;
17851
+ const absolutePath = path6.join(this.rootDir, feature.spec);
17852
+ if (!fs9.existsSync(absolutePath)) continue;
16904
17853
  const estimate = this.estimate(feature.spec);
16905
17854
  results.push({ ...estimate, featureName: feature.name });
16906
17855
  }
@@ -16928,8 +17877,8 @@ var init_dist3 = __esm({
16928
17877
  }
16929
17878
  let count = 0;
16930
17879
  for (const fp of filePaths) {
16931
- const absolute = path5.join(this.rootDir, fp);
16932
- if (!fs8.existsSync(absolute)) {
17880
+ const absolute = path6.join(this.rootDir, fp);
17881
+ if (!fs9.existsSync(absolute)) {
16933
17882
  count++;
16934
17883
  }
16935
17884
  }
@@ -16999,8 +17948,8 @@ var init_dist3 = __esm({
16999
17948
  // --- Private: Config Loading ---
17000
17949
  loadLayerNames() {
17001
17950
  try {
17002
- const configPath = path5.join(this.rootDir, "harness.config.json");
17003
- const raw = fs8.readFileSync(configPath, "utf-8");
17951
+ const configPath = path6.join(this.rootDir, "harness.config.json");
17952
+ const raw = fs9.readFileSync(configPath, "utf-8");
17004
17953
  const config = JSON.parse(raw);
17005
17954
  return (config.layers ?? []).map((l) => l.name);
17006
17955
  } catch {
@@ -17019,8 +17968,8 @@ var init_dist3 = __esm({
17019
17968
  historyPath;
17020
17969
  /** Load history from disk, returning empty history if file doesn't exist. */
17021
17970
  load() {
17022
- if (!fs9.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
17023
- const raw = fs9.readFileSync(this.historyPath, "utf-8");
17971
+ if (!fs10.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
17972
+ const raw = fs10.readFileSync(this.historyPath, "utf-8");
17024
17973
  return JSON.parse(raw);
17025
17974
  }
17026
17975
  /** Append a timestamped snapshot of current violations. */
@@ -17034,18 +17983,18 @@ var init_dist3 = __esm({
17034
17983
  }
17035
17984
  /** Prune snapshots older than retentionDays. */
17036
17985
  prune(retentionDays) {
17037
- if (!fs9.existsSync(this.historyPath)) return;
17986
+ if (!fs10.existsSync(this.historyPath)) return;
17038
17987
  const history = this.load();
17039
17988
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
17040
17989
  history.snapshots = history.snapshots.filter((s) => new Date(s.timestamp).getTime() >= cutoff);
17041
17990
  this.write(history);
17042
17991
  }
17043
17992
  write(history) {
17044
- const dir = path6.dirname(this.historyPath);
17045
- if (!fs9.existsSync(dir)) {
17046
- fs9.mkdirSync(dir, { recursive: true });
17993
+ const dir = path7.dirname(this.historyPath);
17994
+ if (!fs10.existsSync(dir)) {
17995
+ fs10.mkdirSync(dir, { recursive: true });
17047
17996
  }
17048
- fs9.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
17997
+ fs10.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
17049
17998
  }
17050
17999
  };
17051
18000
  NORMALIZERS = {
@@ -17243,14 +18192,14 @@ var init_dist3 = __esm({
17243
18192
  enabled: true,
17244
18193
  strict: false,
17245
18194
  rules: {},
17246
- exclude: ["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]
18195
+ exclude: [...skipDirGlobs(), "**/*.test.ts", "**/fixtures/**"]
17247
18196
  };
17248
18197
  RuleOverrideSchema = z10.enum(["off", "error", "warning", "info"]);
17249
18198
  SecurityConfigSchema = z10.object({
17250
18199
  enabled: z10.boolean().default(true),
17251
18200
  strict: z10.boolean().default(false),
17252
18201
  rules: z10.record(z10.string(), RuleOverrideSchema).optional().default({}),
17253
- exclude: z10.array(z10.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
18202
+ exclude: z10.array(z10.string()).optional().default([...skipDirGlobs(), "**/*.test.ts", "**/fixtures/**"]),
17254
18203
  external: z10.object({
17255
18204
  semgrep: z10.object({
17256
18205
  enabled: z10.union([z10.literal("auto"), z10.boolean()]).default("auto"),
@@ -18047,7 +18996,7 @@ var init_dist3 = __esm({
18047
18996
  }
18048
18997
  async scanFile(filePath) {
18049
18998
  if (!this.config.enabled) return [];
18050
- const content = await fs27.readFile(filePath, "utf-8");
18999
+ const content = await fs28.readFile(filePath, "utf-8");
18051
19000
  return this.scanContentForFile(content, filePath, 1);
18052
19001
  }
18053
19002
  scanContentForFile(content, filePath, startLine = 1) {
@@ -18452,7 +19401,7 @@ var init_dist3 = __esm({
18452
19401
  timelinePath;
18453
19402
  constructor(rootDir) {
18454
19403
  this.rootDir = rootDir;
18455
- this.timelinePath = join39(rootDir, ".harness", "security", "timeline.json");
19404
+ this.timelinePath = join40(rootDir, ".harness", "security", "timeline.json");
18456
19405
  }
18457
19406
  /**
18458
19407
  * Load timeline from disk.
@@ -18463,7 +19412,7 @@ var init_dist3 = __esm({
18463
19412
  * fixup is one-shot.
18464
19413
  */
18465
19414
  load() {
18466
- if (!existsSync332(this.timelinePath)) {
19415
+ if (!existsSync342(this.timelinePath)) {
18467
19416
  return { version: 1, snapshots: [], findingLifecycles: [] };
18468
19417
  }
18469
19418
  try {
@@ -18497,7 +19446,7 @@ var init_dist3 = __esm({
18497
19446
  */
18498
19447
  save(timeline) {
18499
19448
  const dir = dirname13(this.timelinePath);
18500
- if (!existsSync332(dir)) {
19449
+ if (!existsSync342(dir)) {
18501
19450
  mkdirSync15(dir, { recursive: true });
18502
19451
  }
18503
19452
  const tmp = this.timelinePath + "." + randomBytes22(4).toString("hex") + ".tmp";
@@ -19511,7 +20460,8 @@ var init_dist3 = __esm({
19511
20460
  }
19512
20461
  };
19513
20462
  syncMutex = Promise.resolve();
19514
- PRIORITY_RANK = {
20463
+ PRIORITY_RANK = { P0: 0, P1: 1, P2: 2, P3: 3 };
20464
+ PRIORITY_RANK2 = {
19515
20465
  P0: 0,
19516
20466
  P1: 1,
19517
20467
  P2: 2,
@@ -19520,6 +20470,586 @@ var init_dist3 = __esm({
19520
20470
  POSITION_WEIGHT = 0.5;
19521
20471
  DEPENDENTS_WEIGHT = 0.3;
19522
20472
  AFFINITY_WEIGHT = 0.2;
20473
+ ConflictError = class extends Error {
20474
+ code = "TRACKER_CONFLICT";
20475
+ externalId;
20476
+ diff;
20477
+ serverUpdatedAt;
20478
+ constructor(externalId, diff2, serverUpdatedAt = null, message) {
20479
+ super(message ?? `Conflict on ${externalId}: ${Object.keys(diff2).join(", ")}`);
20480
+ this.name = "ConflictError";
20481
+ this.externalId = externalId;
20482
+ this.diff = diff2;
20483
+ this.serverUpdatedAt = serverUpdatedAt;
20484
+ }
20485
+ };
20486
+ DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
20487
+ GitHubHttp = class {
20488
+ token;
20489
+ fetchFn;
20490
+ apiBase;
20491
+ retryOpts;
20492
+ constructor(opts) {
20493
+ this.token = opts.token;
20494
+ this.fetchFn = opts.fetchFn ?? globalThis.fetch;
20495
+ this.apiBase = opts.apiBase ?? "https://api.github.com";
20496
+ this.retryOpts = {
20497
+ maxRetries: opts.maxRetries ?? DEFAULTS.maxRetries,
20498
+ baseDelayMs: opts.baseDelayMs ?? DEFAULTS.baseDelayMs
20499
+ };
20500
+ }
20501
+ headers(extra) {
20502
+ return {
20503
+ Authorization: `Bearer ${this.token}`,
20504
+ Accept: "application/vnd.github+json",
20505
+ "Content-Type": "application/json",
20506
+ "X-GitHub-Api-Version": "2022-11-28",
20507
+ ...extra ?? {}
20508
+ };
20509
+ }
20510
+ async request(url, init) {
20511
+ const { extraHeaders, ...rest } = init;
20512
+ const merged = {
20513
+ ...rest,
20514
+ headers: this.headers(extraHeaders)
20515
+ };
20516
+ return this.fetchWithRetry(url, merged);
20517
+ }
20518
+ async fetchWithRetry(url, init) {
20519
+ let last;
20520
+ for (let attempt = 0; attempt <= this.retryOpts.maxRetries; attempt++) {
20521
+ const res = await this.fetchFn(url, init);
20522
+ if (res.status !== 403 && res.status !== 429 && res.status < 500) return res;
20523
+ last = res;
20524
+ if (attempt === this.retryOpts.maxRetries) break;
20525
+ const retryAfter = res.headers.get("Retry-After");
20526
+ let delayMs;
20527
+ if (retryAfter) {
20528
+ const seconds = parseInt(retryAfter, 10);
20529
+ delayMs = isNaN(seconds) ? this.retryOpts.baseDelayMs : seconds * 1e3;
20530
+ } else {
20531
+ delayMs = this.retryOpts.baseDelayMs * Math.pow(2, attempt) + Math.random() * 500;
20532
+ }
20533
+ await new Promise((r) => setTimeout(r, delayMs));
20534
+ }
20535
+ return last;
20536
+ }
20537
+ /**
20538
+ * Walk all pages of a paginated GET. Stops when a page returns < perPage items.
20539
+ *
20540
+ * Status semantics:
20541
+ * - `status: 304` is returned ONLY when the first page returned 304 (the
20542
+ * server confirmed the caller's If-None-Match matched and there are no
20543
+ * items to read). Callers may then serve cached data.
20544
+ * - `status: 200` is returned in all other terminal cases, including the
20545
+ * mid-walk case where page 1 returned 200 (with items) and a later page
20546
+ * returned 304. Mid-walk 304 is treated as "no further items"; already-
20547
+ * accumulated items are preserved and `lastEtag` is the latest ETag seen.
20548
+ */
20549
+ async paginate(buildUrl, perPage = 100, extraHeaders) {
20550
+ const items = [];
20551
+ let page = 1;
20552
+ let lastEtag = null;
20553
+ let status = 200;
20554
+ for (; ; ) {
20555
+ const init = { method: "GET" };
20556
+ if (extraHeaders) init.extraHeaders = extraHeaders;
20557
+ const res = await this.request(buildUrl(page), init);
20558
+ const etag = res.headers.get("ETag");
20559
+ if (res.status === 304) {
20560
+ if (page === 1) return { items, lastEtag: etag, status: 304 };
20561
+ if (etag) lastEtag = etag;
20562
+ break;
20563
+ }
20564
+ if (!res.ok) {
20565
+ throw new Error(`GitHub ${res.status}: ${await res.text()}`);
20566
+ }
20567
+ const data = await res.json();
20568
+ items.push(...data);
20569
+ lastEtag = etag;
20570
+ status = res.status;
20571
+ if (data.length < perPage) break;
20572
+ page++;
20573
+ }
20574
+ return { items, lastEtag, status };
20575
+ }
20576
+ };
20577
+ ETagStore = class {
20578
+ max;
20579
+ cache = /* @__PURE__ */ new Map();
20580
+ constructor(max = 500) {
20581
+ this.max = max;
20582
+ }
20583
+ get(key) {
20584
+ const entry = this.cache.get(key);
20585
+ if (!entry) return null;
20586
+ this.cache.delete(key);
20587
+ this.cache.set(key, entry);
20588
+ return entry;
20589
+ }
20590
+ set(key, etag, data) {
20591
+ if (this.cache.has(key)) this.cache.delete(key);
20592
+ this.cache.set(key, { etag, data });
20593
+ if (this.cache.size > this.max) {
20594
+ const oldestKey = this.cache.keys().next().value;
20595
+ if (oldestKey !== void 0) this.cache.delete(oldestKey);
20596
+ }
20597
+ }
20598
+ invalidate(key) {
20599
+ this.cache.delete(key);
20600
+ }
20601
+ invalidateAll() {
20602
+ this.cache.clear();
20603
+ }
20604
+ /** Invalidate all keys matching a prefix. Used when writes invalidate `list:*`. */
20605
+ invalidatePrefix(prefix) {
20606
+ for (const key of [...this.cache.keys()]) {
20607
+ if (key.startsWith(prefix)) this.cache.delete(key);
20608
+ }
20609
+ }
20610
+ get size() {
20611
+ return this.cache.size;
20612
+ }
20613
+ };
20614
+ START_MARKER = "<!-- harness-meta:start -->";
20615
+ END_MARKER = "<!-- harness-meta:end -->";
20616
+ SERIALIZER_FIELDS = [
20617
+ ["spec", "spec", (v) => v],
20618
+ ["plan", "plan", (v) => v],
20619
+ [
20620
+ "blocked_by",
20621
+ "blocked_by",
20622
+ (v) => Array.isArray(v) && v.length > 0 ? v : void 0
20623
+ ],
20624
+ ["priority", "priority", (v) => v],
20625
+ ["milestone", "milestone", (v) => v]
20626
+ ];
20627
+ GitHubIssuesTrackerAdapter = class _GitHubIssuesTrackerAdapter {
20628
+ http;
20629
+ owner;
20630
+ repo;
20631
+ cache;
20632
+ selectorLabel;
20633
+ constructor(opts) {
20634
+ this.http = new GitHubHttp(opts);
20635
+ const [owner, repo] = opts.repo.split("/");
20636
+ if (!owner || !repo) throw new Error(`Invalid repo "${opts.repo}", expected "owner/repo"`);
20637
+ this.owner = owner;
20638
+ this.repo = repo;
20639
+ this.cache = opts.etagStore ?? new ETagStore(500);
20640
+ this.selectorLabel = opts.selectorLabel ?? "harness-managed";
20641
+ }
20642
+ // --- Reads ---
20643
+ async fetchAll() {
20644
+ try {
20645
+ const cacheKey = "list:all";
20646
+ const cached = this.cache.get(cacheKey);
20647
+ const buildUrl = (page) => {
20648
+ const params = new URLSearchParams({
20649
+ state: "all",
20650
+ per_page: "100",
20651
+ page: String(page),
20652
+ labels: this.selectorLabel
20653
+ });
20654
+ return `${this.http.apiBase}/repos/${this.owner}/${this.repo}/issues?${params.toString()}`;
20655
+ };
20656
+ const headers = cached ? { "If-None-Match": cached.etag } : void 0;
20657
+ const { items, lastEtag, status } = await this.http.paginate(
20658
+ buildUrl,
20659
+ 100,
20660
+ headers
20661
+ );
20662
+ if (status === 304 && cached) {
20663
+ return Ok({ features: cached.data, etag: cached.etag });
20664
+ }
20665
+ const issues = items.filter((i) => !i.pull_request);
20666
+ const nameIndex = /* @__PURE__ */ new Map();
20667
+ for (const i of issues) {
20668
+ nameIndex.set(i.title, buildExternalId2(this.owner, this.repo, i.number));
20669
+ }
20670
+ const features = issues.map((i) => this.mapIssue(i, nameIndex));
20671
+ features.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
20672
+ if (lastEtag) this.cache.set(cacheKey, lastEtag, features);
20673
+ return Ok({ features, etag: lastEtag });
20674
+ } catch (err) {
20675
+ return Err(err instanceof Error ? err : new Error(String(err)));
20676
+ }
20677
+ }
20678
+ /**
20679
+ * Fetches a feature by externalId.
20680
+ *
20681
+ * Confused-deputy guard: the externalId's owner/repo MUST match the
20682
+ * adapter's configured owner/repo. If they differ we return Err rather
20683
+ * than silently issuing a request against the externalId's repo (which
20684
+ * the adapter is not configured to manage). The adapter's configured
20685
+ * owner/repo always wins.
20686
+ */
20687
+ async fetchById(externalId) {
20688
+ try {
20689
+ const parsed = parseExternalId2(externalId);
20690
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20691
+ if (parsed.owner !== this.owner || parsed.repo !== this.repo) {
20692
+ return Err(new Error("externalId repo does not match adapter repo"));
20693
+ }
20694
+ const cacheKey = `feature:${externalId}`;
20695
+ const cached = this.cache.get(cacheKey);
20696
+ const init = { method: "GET" };
20697
+ if (cached) init.extraHeaders = { "If-None-Match": cached.etag };
20698
+ const res = await this.http.request(
20699
+ `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
20700
+ init
20701
+ );
20702
+ if (res.status === 404) return Ok(null);
20703
+ if (res.status === 304 && cached) {
20704
+ return Ok({ feature: cached.data, etag: cached.etag });
20705
+ }
20706
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20707
+ const data = await res.json();
20708
+ const etag = res.headers.get("ETag");
20709
+ if (data.pull_request) return Ok(null);
20710
+ const feature = this.mapIssue(data, /* @__PURE__ */ new Map());
20711
+ if (etag) this.cache.set(cacheKey, etag, feature);
20712
+ return Ok({ feature, etag: etag ?? "" });
20713
+ } catch (err) {
20714
+ return Err(err instanceof Error ? err : new Error(String(err)));
20715
+ }
20716
+ }
20717
+ async fetchByStatus(statuses) {
20718
+ const all = await this.fetchAll();
20719
+ if (!all.ok) return all;
20720
+ return Ok(all.value.features.filter((f) => statuses.includes(f.status)));
20721
+ }
20722
+ // --- Writes ---
20723
+ async create(feature) {
20724
+ try {
20725
+ const meta = buildCreateMeta(feature);
20726
+ const body = serializeBodyBlock(feature.summary ?? "", meta);
20727
+ const labels = this.buildCreateLabels(feature);
20728
+ const payload = { title: feature.name, body, labels };
20729
+ if (feature.assignee) payload.assignees = [feature.assignee.replace(/^@/, "")];
20730
+ const res = await this.http.request(
20731
+ `${this.http.apiBase}/repos/${this.owner}/${this.repo}/issues`,
20732
+ { method: "POST", body: JSON.stringify(payload) }
20733
+ );
20734
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20735
+ const data = await res.json();
20736
+ this.cache.invalidatePrefix("list:");
20737
+ return Ok(this.mapIssue(data, /* @__PURE__ */ new Map()));
20738
+ } catch (err) {
20739
+ return Err(err instanceof Error ? err : new Error(String(err)));
20740
+ }
20741
+ }
20742
+ buildCreateLabels(feature) {
20743
+ const labels = [this.selectorLabel];
20744
+ if (feature.status && feature.status !== "backlog") labels.push(feature.status);
20745
+ return labels;
20746
+ }
20747
+ /**
20748
+ * Patches a feature.
20749
+ *
20750
+ * Confused-deputy guard: the externalId's owner/repo MUST match the
20751
+ * adapter's configured owner/repo. If they differ we return Err rather
20752
+ * than silently writing to the externalId's repo. The adapter's
20753
+ * configured owner/repo always wins.
20754
+ */
20755
+ async update(externalId, patch, ifMatch) {
20756
+ const r = await this.updateInternal(externalId, patch, ifMatch);
20757
+ if (!r.ok) return r;
20758
+ return Ok(r.value.feature);
20759
+ }
20760
+ /**
20761
+ * Internal update that also reports whether a real PATCH was issued.
20762
+ * Returns wrote=false on the idempotent path so callers (claim/release/complete)
20763
+ * can skip best-effort history logging when no state change occurred.
20764
+ *
20765
+ * When ifMatch is supplied, updateInternal performs a refetch GET; the
20766
+ * refetched feature is returned as `priorFeature` so callers (release/complete)
20767
+ * can extract the pre-PATCH assignee for history attribution without a second GET.
20768
+ */
20769
+ async updateInternal(externalId, patch, ifMatch) {
20770
+ try {
20771
+ const parsed = parseExternalId2(externalId);
20772
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20773
+ if (parsed.owner !== this.owner || parsed.repo !== this.repo) {
20774
+ return Err(new Error("externalId repo does not match adapter repo"));
20775
+ }
20776
+ let priorFeature;
20777
+ if (ifMatch) {
20778
+ const cur = await this.fetchById(externalId);
20779
+ if (!cur.ok) return cur;
20780
+ if (!cur.value) return Err(new Error(`Not found: ${externalId}`));
20781
+ priorFeature = cur.value.feature;
20782
+ const cmp = refetchAndCompare(cur.value.feature, patch);
20783
+ if (!cmp.ok) {
20784
+ return Err(new ConflictError(externalId, cmp.diff, cur.value.feature.updatedAt));
20785
+ }
20786
+ if (cmp.idempotent) return Ok({ feature: cur.value.feature, wrote: false, priorFeature });
20787
+ }
20788
+ const reqBody = await this.buildIssuePatchBody(externalId, patch, priorFeature);
20789
+ const res = await this.http.request(
20790
+ `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
20791
+ { method: "PATCH", body: JSON.stringify(reqBody) }
20792
+ );
20793
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20794
+ const data = await res.json();
20795
+ this.cache.invalidate(`feature:${externalId}`);
20796
+ this.cache.invalidatePrefix("list:");
20797
+ return Ok({
20798
+ feature: this.mapIssue(data, /* @__PURE__ */ new Map()),
20799
+ wrote: true,
20800
+ ...priorFeature !== void 0 ? { priorFeature } : {}
20801
+ });
20802
+ } catch (err) {
20803
+ return Err(err instanceof Error ? err : new Error(String(err)));
20804
+ }
20805
+ }
20806
+ /** Status labels recognized by mapStatus; 'backlog' and 'done' are state-driven. */
20807
+ static STATUS_LABELS = /* @__PURE__ */ new Set([
20808
+ "planned",
20809
+ "in-progress",
20810
+ "blocked",
20811
+ "needs-human"
20812
+ ]);
20813
+ async buildIssuePatchBody(externalId, patch, priorFeature) {
20814
+ const out = {};
20815
+ if (patch.name !== void 0) out.title = patch.name;
20816
+ if (patch.assignee !== void 0) {
20817
+ out.assignees = patch.assignee ? [patch.assignee.replace(/^@/, "")] : [];
20818
+ }
20819
+ if (patch.status !== void 0) {
20820
+ if (patch.status === "done") out.state = "closed";
20821
+ else out.state = "open";
20822
+ const labelsR = await this.fetchRawLabels(externalId);
20823
+ if (labelsR.ok) {
20824
+ const filtered = labelsR.value.filter(
20825
+ (l) => !_GitHubIssuesTrackerAdapter.STATUS_LABELS.has(l)
20826
+ );
20827
+ if (patch.status !== "backlog" && patch.status !== "done" && _GitHubIssuesTrackerAdapter.STATUS_LABELS.has(patch.status)) {
20828
+ filtered.push(patch.status);
20829
+ }
20830
+ out.labels = filtered;
20831
+ } else {
20832
+ console.warn(
20833
+ `harness-tracker: skipping label sync on ${externalId} due to GET error: ${labelsR.error.message}`
20834
+ );
20835
+ }
20836
+ }
20837
+ const bodyTouches = [
20838
+ "summary",
20839
+ "spec",
20840
+ "plans",
20841
+ "blockedBy",
20842
+ "priority",
20843
+ "milestone"
20844
+ ];
20845
+ if (bodyTouches.some((k) => patch[k] !== void 0)) {
20846
+ let current;
20847
+ if (priorFeature) {
20848
+ current = priorFeature;
20849
+ } else {
20850
+ const cur = await this.fetchById(externalId);
20851
+ if (!cur.ok || !cur.value) throw new Error("Cannot rebuild body without current state");
20852
+ current = cur.value.feature;
20853
+ }
20854
+ const meta = metaFromFeatureFields({
20855
+ spec: patch.spec !== void 0 ? patch.spec : current.spec,
20856
+ plans: patch.plans !== void 0 ? patch.plans : current.plans,
20857
+ blockedBy: patch.blockedBy !== void 0 ? patch.blockedBy : current.blockedBy,
20858
+ priority: patch.priority !== void 0 ? patch.priority : current.priority,
20859
+ milestone: patch.milestone !== void 0 ? patch.milestone : current.milestone
20860
+ });
20861
+ const summaryText = patch.summary !== void 0 ? patch.summary : current.summary;
20862
+ out.body = serializeBodyBlock(summaryText, meta);
20863
+ }
20864
+ return out;
20865
+ }
20866
+ /**
20867
+ * Lightweight GET that returns the raw label name list.
20868
+ *
20869
+ * P2-RR2-IMP-1: returns Result so callers can distinguish a real Err (network
20870
+ * failure / non-200 response) from an empty label set. The previous shape
20871
+ * silently swallowed errors and returned [], which combined with the
20872
+ * unconditional `out.labels = filtered` assignment in buildIssuePatchBody
20873
+ * caused PATCH requests to wipe ALL labels (including the selectorLabel and
20874
+ * user-added labels) on a transient GET blip.
20875
+ */
20876
+ async fetchRawLabels(externalId) {
20877
+ try {
20878
+ const parsed = parseExternalId2(externalId);
20879
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20880
+ const res = await this.http.request(
20881
+ `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
20882
+ { method: "GET" }
20883
+ );
20884
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20885
+ const data = await res.json();
20886
+ return Ok(data.labels.map((l) => l.name));
20887
+ } catch (err) {
20888
+ return Err(err instanceof Error ? err : new Error(String(err)));
20889
+ }
20890
+ }
20891
+ async claim(externalId, assignee, ifMatch) {
20892
+ const r = await this.updateInternal(externalId, { assignee, status: "in-progress" }, ifMatch);
20893
+ if (!r.ok) return r;
20894
+ if (r.value.wrote) await this.logEvent(externalId, "claimed", assignee);
20895
+ return Ok(r.value.feature);
20896
+ }
20897
+ async release(externalId, ifMatch) {
20898
+ const priorAssignee = await this.fetchPriorAssigneeIfNeeded(externalId, ifMatch);
20899
+ const r = await this.updateInternal(externalId, { assignee: null, status: "backlog" }, ifMatch);
20900
+ if (!r.ok) return r;
20901
+ if (r.value.wrote) {
20902
+ const actor = r.value.priorFeature?.assignee ?? priorAssignee ?? null;
20903
+ await this.logEvent(externalId, "released", actor ?? "unknown");
20904
+ }
20905
+ return Ok(r.value.feature);
20906
+ }
20907
+ async complete(externalId, ifMatch) {
20908
+ const priorAssignee = await this.fetchPriorAssigneeIfNeeded(externalId, ifMatch);
20909
+ const r = await this.updateInternal(externalId, { status: "done" }, ifMatch);
20910
+ if (!r.ok) return r;
20911
+ if (r.value.wrote) {
20912
+ const actor = r.value.priorFeature?.assignee ?? priorAssignee ?? null;
20913
+ await this.logEvent(externalId, "completed", actor ?? "unknown");
20914
+ }
20915
+ return Ok(r.value.feature);
20916
+ }
20917
+ /**
20918
+ * Best-effort GET to capture the assignee BEFORE a PATCH that may clear it.
20919
+ * Skipped when ifMatch is supplied because updateInternal will refetch and
20920
+ * thread the prior feature through.
20921
+ */
20922
+ async fetchPriorAssigneeIfNeeded(externalId, ifMatch) {
20923
+ if (ifMatch) return null;
20924
+ const cur = await this.fetchById(externalId);
20925
+ if (cur.ok && cur.value) return cur.value.feature.assignee;
20926
+ return null;
20927
+ }
20928
+ /**
20929
+ * Trust model for history events.
20930
+ *
20931
+ * `HISTORY_PREFIX` (`<!-- harness-history -->`) marks issue comments as
20932
+ * authentic harness events. fetchHistory reads only comments that begin
20933
+ * with this prefix; appendHistory writes the prefix unconditionally.
20934
+ *
20935
+ * Trust is per-repo: only repository contributors authorized to post
20936
+ * comments on a given issue can synthesize harness history events.
20937
+ * GitHub's per-repo permissions are the authorization boundary; the
20938
+ * prefix itself is not a cryptographic credential — a contributor with
20939
+ * comment access can forge events by hand-writing the prefix.
20940
+ *
20941
+ * HMAC-signing of event payloads is a future hardening (post-GA) for
20942
+ * deployments that need cross-actor tamper-evidence within the same
20943
+ * repo. The current shape is intentional: optimized for transparency
20944
+ * (events are human-readable, no key distribution required).
20945
+ */
20946
+ static HISTORY_PREFIX = "<!-- harness-history -->";
20947
+ async appendHistory(externalId, event) {
20948
+ try {
20949
+ const parsed = parseExternalId2(externalId);
20950
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20951
+ const body = `${_GitHubIssuesTrackerAdapter.HISTORY_PREFIX}
20952
+ ${JSON.stringify(event)}`;
20953
+ const res = await this.http.request(
20954
+ `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/comments`,
20955
+ { method: "POST", body: JSON.stringify({ body }) }
20956
+ );
20957
+ if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
20958
+ return Ok(void 0);
20959
+ } catch (err) {
20960
+ return Err(err instanceof Error ? err : new Error(String(err)));
20961
+ }
20962
+ }
20963
+ async fetchHistory(externalId, limit) {
20964
+ try {
20965
+ const parsed = parseExternalId2(externalId);
20966
+ if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
20967
+ const buildUrl = (page) => `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/comments?per_page=100&page=${page}`;
20968
+ const { items } = await this.http.paginate(buildUrl);
20969
+ const events = [];
20970
+ for (const c of items) {
20971
+ if (!c.body.startsWith(_GitHubIssuesTrackerAdapter.HISTORY_PREFIX)) continue;
20972
+ const json = c.body.slice(_GitHubIssuesTrackerAdapter.HISTORY_PREFIX.length).trim();
20973
+ try {
20974
+ events.push(JSON.parse(json));
20975
+ } catch (e) {
20976
+ console.warn(`harness-history: malformed JSON in ${externalId}: ${e.message}`);
20977
+ }
20978
+ }
20979
+ events.sort((a, b) => a.at.localeCompare(b.at));
20980
+ return Ok(limit ? events.slice(-limit) : events);
20981
+ } catch (err) {
20982
+ return Err(err instanceof Error ? err : new Error(String(err)));
20983
+ }
20984
+ }
20985
+ async logEvent(externalId, type, actor) {
20986
+ const event = {
20987
+ type,
20988
+ actor,
20989
+ at: (/* @__PURE__ */ new Date()).toISOString()
20990
+ };
20991
+ const r = await this.appendHistory(externalId, event);
20992
+ if (!r.ok) {
20993
+ console.warn(`harness-history: append failed for ${externalId}: ${r.error.message}`);
20994
+ }
20995
+ }
20996
+ // --- Helpers ---
20997
+ // The nameIndex parameter is retained for backwards-compatibility with the
20998
+ // fetchAll signature but is no longer consulted: blockedBy holds feature
20999
+ // names verbatim from the body-meta block (see client.ts TrackedFeature.blockedBy).
21000
+ mapIssue(issue, _nameIndex) {
21001
+ const { summary, meta } = parseBodyBlock(issue.body ?? "");
21002
+ const status = this.mapStatus(issue, meta);
21003
+ return {
21004
+ externalId: buildExternalId2(this.owner, this.repo, issue.number),
21005
+ name: issue.title,
21006
+ status,
21007
+ summary,
21008
+ spec: meta.spec ?? null,
21009
+ plans: meta.plan ? [meta.plan] : [],
21010
+ blockedBy: meta.blocked_by ?? [],
21011
+ assignee: issue.assignees[0]?.login ? `@${issue.assignees[0].login}` : null,
21012
+ priority: meta.priority ?? null,
21013
+ milestone: issue.milestone?.title ?? meta.milestone ?? null,
21014
+ createdAt: issue.created_at,
21015
+ updatedAt: issue.updated_at ?? null
21016
+ };
21017
+ }
21018
+ /**
21019
+ * Status precedence (highest wins):
21020
+ * 1. issue.state === 'closed' → 'done'
21021
+ * 2. 'blocked' label → 'blocked'
21022
+ * 3. 'needs-human' label → 'needs-human'
21023
+ * 4. 'in-progress' label OR any assignee → 'in-progress'
21024
+ * 5. 'planned' label → 'planned'
21025
+ * 6. otherwise → 'backlog'
21026
+ *
21027
+ * Note step 4: an assignment overrides a 'planned' label. This is
21028
+ * intentional — a feature that someone has picked up is "in-progress"
21029
+ * regardless of whether the planning label was cleared first. The reverse
21030
+ * (planned label + assignee) is the common case where the orchestrator
21031
+ * (or a human) starts work without scrubbing the planning label.
21032
+ */
21033
+ mapStatus(issue, _meta) {
21034
+ if (issue.state === "closed") return "done";
21035
+ const labels = issue.labels.map((l) => l.name);
21036
+ if (labels.includes("blocked")) return "blocked";
21037
+ if (labels.includes("needs-human")) return "needs-human";
21038
+ if (labels.includes("in-progress") || issue.assignees.length > 0) return "in-progress";
21039
+ if (labels.includes("planned")) return "planned";
21040
+ return "backlog";
21041
+ }
21042
+ };
21043
+ migrate_exports = {};
21044
+ __export2(migrate_exports, {
21045
+ bodyMetaMatches: () => bodyMetaMatches,
21046
+ buildHistoryCommentBody: () => buildHistoryCommentBody,
21047
+ buildMigrationPlan: () => buildMigrationPlan,
21048
+ hashHistoryEvent: () => hashHistoryEvent,
21049
+ parseHashFromCommentBody: () => parseHashFromCommentBody,
21050
+ runMigrationPlan: () => runMigrationPlan
21051
+ });
21052
+ HASH_RE = /<!--\s*harness-history\s+hash:([0-9a-f]{8})\s*-->/i;
19523
21053
  InteractionTypeSchema = z12.enum(["question", "confirmation", "transition"]);
19524
21054
  QuestionSchema = z12.object({
19525
21055
  text: z12.string(),
@@ -19552,10 +21082,10 @@ var init_dist3 = __esm({
19552
21082
  }
19553
21083
  rootDir;
19554
21084
  async scan() {
19555
- let projectName = path30.basename(this.rootDir);
21085
+ let projectName = path34.basename(this.rootDir);
19556
21086
  try {
19557
- const pkgPath = path30.join(this.rootDir, "package.json");
19558
- const pkgRaw = await fs31.readFile(pkgPath, "utf-8");
21087
+ const pkgPath = path34.join(this.rootDir, "package.json");
21088
+ const pkgRaw = await fs34.readFile(pkgPath, "utf-8");
19559
21089
  const pkg = JSON.parse(pkgRaw);
19560
21090
  if (pkg.name) projectName = pkg.name;
19561
21091
  } catch {
@@ -19668,8 +21198,8 @@ console.log('Blueprint player initialized.');
19668
21198
  styles: STYLES,
19669
21199
  scripts: SCRIPTS
19670
21200
  });
19671
- await fs32.mkdir(options.outputDir, { recursive: true });
19672
- await fs32.writeFile(path31.join(options.outputDir, "index.html"), html);
21201
+ await fs35.mkdir(options.outputDir, { recursive: true });
21202
+ await fs35.writeFile(path35.join(options.outputDir, "index.html"), html);
19673
21203
  }
19674
21204
  };
19675
21205
  TOKENS_PER_MILLION = 1e6;
@@ -20084,6 +21614,9 @@ export {
20084
21614
  architecture,
20085
21615
  archModule,
20086
21616
  archMatchers,
21617
+ WHATWG_BAD_PORTS,
21618
+ isBadPort,
21619
+ assertPortUsable,
20087
21620
  validateFileStructure,
20088
21621
  validateConfig,
20089
21622
  validateCommitMessage,
@@ -20102,6 +21635,8 @@ export {
20102
21635
  validateAgentsMap,
20103
21636
  runFallbackRules,
20104
21637
  validateAgentConfigs,
21638
+ getRoadmapMode,
21639
+ validateRoadmapMode,
20105
21640
  checkDocCoverage,
20106
21641
  validateKnowledgeMap,
20107
21642
  generateAgentsMap,
@@ -20391,8 +21926,17 @@ export {
20391
21926
  syncToExternal,
20392
21927
  syncFromExternal,
20393
21928
  fullSync,
21929
+ scoreRoadmapCandidatesFileLess,
20394
21930
  scoreRoadmapCandidates,
20395
21931
  assignFeature,
21932
+ scoreRoadmapCandidatesForMode,
21933
+ ConflictError,
21934
+ ETagStore,
21935
+ createTrackerClient,
21936
+ makeTrackerConflictBody,
21937
+ loadProjectRoadmapMode,
21938
+ loadTrackerClientConfigFromProject,
21939
+ migrate_exports,
20396
21940
  InteractionTypeSchema,
20397
21941
  QuestionSchema,
20398
21942
  ConfirmationSchema,