@harness-engineering/cli 2.8.0 → 3.0.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 (120) hide show
  1. package/dist/agents/skills/claude-code/harness-audit-harness-strength/SKILL.md +188 -0
  2. package/dist/agents/skills/claude-code/harness-audit-harness-strength/skill.yaml +54 -0
  3. package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +2 -0
  4. package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +28 -14
  5. package/dist/agents/skills/claude-code/harness-execution/SKILL.md +1 -1
  6. package/dist/agents/skills/claude-code/harness-git-workflow/SKILL.md +2 -0
  7. package/dist/agents/skills/claude-code/harness-ideate/SKILL.md +2 -0
  8. package/dist/agents/skills/claude-code/harness-knowledge-pipeline/SKILL.md +2 -0
  9. package/dist/agents/skills/claude-code/harness-roadmap/SKILL.md +62 -4
  10. package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +2 -0
  11. package/dist/agents/skills/claude-code/harness-test-advisor/SKILL.md +80 -2
  12. package/dist/agents/skills/claude-code/harness-test-advisor/skill.yaml +4 -1
  13. package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +27 -25
  14. package/dist/agents/skills/claude-code/initialize-harness-project/skill.yaml +1 -0
  15. package/dist/agents/skills/claude-code/outcome-eval/SKILL.md +97 -0
  16. package/dist/agents/skills/claude-code/outcome-eval/skill.yaml +51 -0
  17. package/dist/agents/skills/codex/harness-audit-harness-strength/SKILL.md +188 -0
  18. package/dist/agents/skills/codex/harness-audit-harness-strength/skill.yaml +54 -0
  19. package/dist/agents/skills/codex/harness-autopilot/SKILL.md +2 -0
  20. package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +28 -14
  21. package/dist/agents/skills/codex/harness-execution/SKILL.md +1 -1
  22. package/dist/agents/skills/codex/harness-git-workflow/SKILL.md +2 -0
  23. package/dist/agents/skills/codex/harness-ideate/SKILL.md +2 -0
  24. package/dist/agents/skills/codex/harness-knowledge-pipeline/SKILL.md +2 -0
  25. package/dist/agents/skills/codex/harness-roadmap/SKILL.md +62 -4
  26. package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +2 -0
  27. package/dist/agents/skills/codex/harness-test-advisor/SKILL.md +80 -2
  28. package/dist/agents/skills/codex/harness-test-advisor/skill.yaml +4 -1
  29. package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +27 -25
  30. package/dist/agents/skills/codex/initialize-harness-project/skill.yaml +1 -0
  31. package/dist/agents/skills/codex/outcome-eval/SKILL.md +97 -0
  32. package/dist/agents/skills/codex/outcome-eval/skill.yaml +51 -0
  33. package/dist/agents/skills/cursor/harness-audit-harness-strength/SKILL.md +188 -0
  34. package/dist/agents/skills/cursor/harness-audit-harness-strength/skill.yaml +54 -0
  35. package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +2 -0
  36. package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +28 -14
  37. package/dist/agents/skills/cursor/harness-execution/SKILL.md +1 -1
  38. package/dist/agents/skills/cursor/harness-git-workflow/SKILL.md +2 -0
  39. package/dist/agents/skills/cursor/harness-ideate/SKILL.md +2 -0
  40. package/dist/agents/skills/cursor/harness-knowledge-pipeline/SKILL.md +2 -0
  41. package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +62 -4
  42. package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +2 -0
  43. package/dist/agents/skills/cursor/harness-test-advisor/SKILL.md +80 -2
  44. package/dist/agents/skills/cursor/harness-test-advisor/skill.yaml +4 -1
  45. package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +27 -25
  46. package/dist/agents/skills/cursor/initialize-harness-project/skill.yaml +1 -0
  47. package/dist/agents/skills/cursor/outcome-eval/SKILL.md +97 -0
  48. package/dist/agents/skills/cursor/outcome-eval/skill.yaml +51 -0
  49. package/dist/agents/skills/gemini-cli/harness-audit-harness-strength/SKILL.md +188 -0
  50. package/dist/agents/skills/gemini-cli/harness-audit-harness-strength/skill.yaml +54 -0
  51. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +2 -0
  52. package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +28 -14
  53. package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +1 -1
  54. package/dist/agents/skills/gemini-cli/harness-git-workflow/SKILL.md +2 -0
  55. package/dist/agents/skills/gemini-cli/harness-ideate/SKILL.md +2 -0
  56. package/dist/agents/skills/gemini-cli/harness-knowledge-pipeline/SKILL.md +2 -0
  57. package/dist/agents/skills/gemini-cli/harness-roadmap/SKILL.md +62 -4
  58. package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +2 -0
  59. package/dist/agents/skills/gemini-cli/harness-test-advisor/SKILL.md +80 -2
  60. package/dist/agents/skills/gemini-cli/harness-test-advisor/skill.yaml +4 -1
  61. package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +27 -25
  62. package/dist/agents/skills/gemini-cli/initialize-harness-project/skill.yaml +1 -0
  63. package/dist/agents/skills/gemini-cli/outcome-eval/SKILL.md +97 -0
  64. package/dist/agents/skills/gemini-cli/outcome-eval/skill.yaml +51 -0
  65. package/dist/agents/skills/tests/harness-test-advisor.test.ts +94 -0
  66. package/dist/{agents-md-WMJO5HSM.js → agents-md-XOJE5ETA.js} +2 -2
  67. package/dist/{architecture-EVYVGPRZ.js → architecture-JEZPJ725.js} +3 -3
  68. package/dist/{assess-project-KZG563SO.js → assess-project-RHXOQNMM.js} +1 -1
  69. package/dist/bin/harness-mcp.js +13 -13
  70. package/dist/bin/harness.js +22 -22
  71. package/dist/{check-phase-gate-MK364VXS.js → check-phase-gate-GNDOMS35.js} +3 -3
  72. package/dist/{chunk-VBNJAKUH.js → chunk-3ISHDWO7.js} +1 -1
  73. package/dist/{chunk-UJFNIWC7.js → chunk-527G27VI.js} +2 -2
  74. package/dist/{chunk-5XL5QJ5B.js → chunk-6WQCLCBO.js} +1 -1
  75. package/dist/{chunk-UXPWSV3B.js → chunk-BYVT5LVO.js} +3 -3
  76. package/dist/{chunk-HRSE6IQD.js → chunk-DE5U6KOL.js} +1 -1
  77. package/dist/{chunk-B7KMTHN3.js → chunk-GGKRA7A7.js} +1 -1
  78. package/dist/{chunk-STESM73J.js → chunk-GSP2XIVS.js} +1 -1
  79. package/dist/{chunk-KBCPELBC.js → chunk-HEIMJJI4.js} +2 -2
  80. package/dist/{chunk-KI6PHH6Q.js → chunk-HWTUUQOW.js} +3 -3
  81. package/dist/{chunk-2SNRCAC3.js → chunk-KAJOS3BA.js} +2 -2
  82. package/dist/{chunk-C7J55C6E.js → chunk-MGUPSE6D.js} +5 -5
  83. package/dist/{chunk-GOHWCHCS.js → chunk-N24HCQA4.js} +2243 -776
  84. package/dist/{chunk-EEFRPEWT.js → chunk-NWLGL3IR.js} +1 -1
  85. package/dist/{chunk-UUO4K7NX.js → chunk-ONCPJGHY.js} +6 -6
  86. package/dist/{chunk-X4EFYVCZ.js → chunk-PC2R5RUJ.js} +1 -1
  87. package/dist/{chunk-7EQAZKJ5.js → chunk-QBC7DI4Q.js} +1 -1
  88. package/dist/{chunk-UBZQE3JN.js → chunk-UX3PQKVZ.js} +7 -2
  89. package/dist/{chunk-CAQPSFPT.js → chunk-WWEVS7RO.js} +453 -91
  90. package/dist/{chunk-4BDOSCNY.js → chunk-XFU6SB2Q.js} +1015 -648
  91. package/dist/{chunk-LADPYELQ.js → chunk-XX4OLNWQ.js} +2 -2
  92. package/dist/{chunk-FZ5MBXEG.js → chunk-YQSYCBAH.js} +6 -6
  93. package/dist/{chunk-ACOOG3UW.js → chunk-Z4AWEIBY.js} +3 -3
  94. package/dist/{ci-workflow-IW2H5DC4.js → ci-workflow-ORZ4F45F.js} +2 -2
  95. package/dist/{dist-JCFK263L.js → dist-LHINSVK4.js} +85 -1
  96. package/dist/{docs-7CP6VV42.js → docs-AU3DXFFO.js} +3 -3
  97. package/dist/{engine-MKJEBWU7.js → engine-6GPWOYQH.js} +2 -2
  98. package/dist/{entropy-6JOZJYG7.js → entropy-VLKDRMRT.js} +2 -2
  99. package/dist/{feedback-T65AEJEG.js → feedback-YXYY44ZC.js} +1 -1
  100. package/dist/{generate-agent-definitions-4VI4YTO2.js → generate-agent-definitions-XIHQPKG3.js} +3 -3
  101. package/dist/hooks/cost-tracker.js +29 -6
  102. package/dist/index.d.ts +8 -8
  103. package/dist/index.js +22 -22
  104. package/dist/{loader-AWTHHE3A.js → loader-YAN56MT3.js} +2 -2
  105. package/dist/{mcp-JBIBINO2.js → mcp-RK3SFVYB.js} +13 -13
  106. package/dist/{performance-NCOUDOMW.js → performance-64DPTLLQ.js} +3 -3
  107. package/dist/{review-pipeline-YIC6JPMY.js → review-pipeline-NENG6LW7.js} +2 -2
  108. package/dist/{runtime-5MHV4UEE.js → runtime-FFIIRT2T.js} +2 -2
  109. package/dist/{security-VZPJNIDP.js → security-3PIBN35B.js} +1 -1
  110. package/dist/templates/basic/harness.config.json.hbs +34 -0
  111. package/dist/templates/ci/README.md +86 -0
  112. package/dist/templates/ci/required-review.ruleset.json +20 -0
  113. package/dist/templates/ci/required-review.yml.hbs +57 -0
  114. package/dist/templates/ci/template.json +5 -0
  115. package/dist/templates/intermediate/harness.config.json.hbs +36 -0
  116. package/dist/templates/orchestrator/harness.orchestrator.md +9 -0
  117. package/dist/{tool-tiers-B7JC2XC3.js → tool-tiers-OKK5FE57.js} +2 -0
  118. package/dist/{validate-7BE4VTFI.js → validate-7IAF2ES3.js} +3 -3
  119. package/dist/{validate-cross-check-F7GEBRDH.js → validate-cross-check-QPSAOZKD.js} +2 -2
  120. package/package.json +7 -7
@@ -1250,6 +1250,7 @@ __export(dist_exports, {
1250
1250
  AGENT_DESCRIPTORS: () => AGENT_DESCRIPTORS,
1251
1251
  AGREEMENT_LINE_GAP: () => AGREEMENT_LINE_GAP,
1252
1252
  ALLOWED_FIELD_KEYS: () => ALLOWED_FIELD_KEYS,
1253
+ ALL_RULES: () => ALL_RULES,
1253
1254
  ALL_SOLUTION_CATEGORIES: () => ALL_SOLUTION_CATEGORIES,
1254
1255
  ARCHITECTURE_DESCRIPTOR: () => ARCHITECTURE_DESCRIPTOR,
1255
1256
  AdjustedForecastSchema: () => AdjustedForecastSchema,
@@ -1260,6 +1261,8 @@ __export(dist_exports, {
1260
1261
  ArchConfigSchema: () => ArchConfigSchema,
1261
1262
  ArchDiffResultSchema: () => ArchDiffResultSchema,
1262
1263
  ArchMetricCategorySchema: () => ArchMetricCategorySchema,
1264
+ AuditResultSchema: () => AuditResultSchema,
1265
+ AuditSummarySchema: () => AuditSummarySchema,
1263
1266
  AuthAuditEntrySchema: () => AuthAuditEntrySchema,
1264
1267
  AuthTokenPublicSchema: () => AuthTokenPublicSchema,
1265
1268
  AuthTokenSchema: () => AuthTokenSchema,
@@ -1273,6 +1276,10 @@ __export(dist_exports, {
1273
1276
  BundleSchema: () => BundleSchema,
1274
1277
  CACHE_TTL_MS: () => CACHE_TTL_MS,
1275
1278
  CINotifier: () => CINotifier,
1279
+ CI_ASSESSMENTS: () => CI_ASSESSMENTS,
1280
+ CI_REVIEW_DOMAINS: () => CI_REVIEW_DOMAINS,
1281
+ CI_REVIEW_VERDICT_SCHEMA_VERSION: () => CI_REVIEW_VERDICT_SCHEMA_VERSION,
1282
+ CI_RUNNERS: () => CI_RUNNERS,
1276
1283
  COMPLIANCE_DESCRIPTOR: () => COMPLIANCE_DESCRIPTOR,
1277
1284
  CONDITIONAL_SUBAGENT_DESCRIPTORS: () => CONDITIONAL_SUBAGENT_DESCRIPTORS,
1278
1285
  CORROBORATED_AGREEMENT: () => CORROBORATED_AGREEMENT,
@@ -1282,6 +1289,7 @@ __export(dist_exports, {
1282
1289
  CategoryRegressionSchema: () => CategoryRegressionSchema,
1283
1290
  CategorySnapshotSchema: () => CategorySnapshotSchema,
1284
1291
  ChecklistBuilder: () => ChecklistBuilder,
1292
+ CiReviewVerdictSchema: () => CiReviewVerdictSchema,
1285
1293
  CircularDepsCollector: () => CircularDepsCollector,
1286
1294
  CompactionPipeline: () => CompactionPipeline,
1287
1295
  ComplexityCollector: () => ComplexityCollector,
@@ -1295,6 +1303,8 @@ __export(dist_exports, {
1295
1303
  ContributionsSchema: () => ContributionsSchema,
1296
1304
  CouplingCollector: () => CouplingCollector,
1297
1305
  CriticalPathResolver: () => CriticalPathResolver,
1306
+ DEFAULT_EXEC_MAX_STDOUT_BYTES: () => DEFAULT_EXEC_MAX_STDOUT_BYTES,
1307
+ DEFAULT_EXEC_TIMEOUT_MS: () => DEFAULT_EXEC_TIMEOUT_MS,
1298
1308
  DEFAULT_LOADER_CONFIG: () => DEFAULT_LOADER_CONFIG,
1299
1309
  DEFAULT_PROVIDER_TIERS: () => DEFAULT_PROVIDER_TIERS,
1300
1310
  DEFAULT_SECURITY_CONFIG: () => DEFAULT_SECURITY_CONFIG,
@@ -1333,7 +1343,10 @@ __export(dist_exports, {
1333
1343
  GeminiCacheAdapter: () => GeminiCacheAdapter,
1334
1344
  GitHubIssuesSyncAdapter: () => GitHubIssuesSyncAdapter,
1335
1345
  HandoffSchema: () => HandoffSchema,
1346
+ HarnessConfigSubsetSchema: () => HarnessConfigSubsetSchema,
1336
1347
  HarnessStateSchema: () => HarnessStateSchema,
1348
+ HarnessStrengthAuditor: () => HarnessStrengthAuditor,
1349
+ HookFileSchema: () => HookFileSchema,
1337
1350
  INDEXED_FILE_KINDS: () => INDEXED_FILE_KINDS,
1338
1351
  INSIGHTS_KEYS: () => INSIGHTS_KEYS,
1339
1352
  InteractionTypeSchema: () => InteractionTypeSchema,
@@ -1345,6 +1358,7 @@ __export(dist_exports, {
1345
1358
  MOCK_ADAPTER_NAME: () => MOCK_ADAPTER_NAME,
1346
1359
  ManifestSchema: () => ManifestSchema,
1347
1360
  MetricResultSchema: () => MetricResultSchema,
1361
+ ModeSchema: () => ModeSchema,
1348
1362
  ModuleSizeCollector: () => ModuleSizeCollector,
1349
1363
  NoOpExecutor: () => NoOpExecutor,
1350
1364
  NoOpSink: () => NoOpSink,
@@ -1371,6 +1385,7 @@ __export(dist_exports, {
1371
1385
  PredictionRegressionResultSchema: () => RegressionResultSchema,
1372
1386
  PredictionResultSchema: () => PredictionResultSchema,
1373
1387
  PredictionWarningSchema: () => PredictionWarningSchema,
1388
+ ProjectContextSchema: () => ProjectContextSchema,
1374
1389
  ProjectScanner: () => ProjectScanner,
1375
1390
  PromptCacheStatsSchema: () => PromptCacheStatsSchema,
1376
1391
  ProposalConflictError: () => ProposalConflictError,
@@ -1390,11 +1405,13 @@ __export(dist_exports, {
1390
1405
  REQUIRED_SECTIONS: () => REQUIRED_SECTIONS,
1391
1406
  REQUIRED_STRATEGY_SECTIONS: () => REQUIRED_STRATEGY_SECTIONS,
1392
1407
  RISK_KEYWORDS: () => RISK_KEYWORDS,
1408
+ RUNNER_PRESETS: () => RUNNER_PRESETS,
1393
1409
  RegressionDetector: () => RegressionDetector,
1394
1410
  RuleRegistry: () => RuleRegistry,
1395
1411
  SECURITY_DESCRIPTOR: () => SECURITY_DESCRIPTOR,
1396
1412
  SESSIONS_DEFAULTS: () => SESSIONS_DEFAULTS,
1397
1413
  SESSION_SECTION_NAMES: () => SESSION_SECTION_NAMES,
1414
+ SEVERITY_WEIGHTS: () => SEVERITY_WEIGHTS,
1398
1415
  STALENESS_WARNING_DAYS: () => STALENESS_WARNING_DAYS,
1399
1416
  STANDALONE_AGREEMENT: () => STANDALONE_AGREEMENT,
1400
1417
  STANDARD_COGNITIVE_MODES: () => STANDARD_COGNITIVE_MODES,
@@ -1410,6 +1427,7 @@ __export(dist_exports, {
1410
1427
  SecurityTrendLineSchema: () => SecurityTrendLineSchema,
1411
1428
  SecurityTrendResultSchema: () => SecurityTrendResultSchema,
1412
1429
  SessionSummarySchema: () => SessionSummarySchema,
1430
+ SeveritySchema: () => SeveritySchema,
1413
1431
  SharableBoundaryConfigSchema: () => SharableBoundaryConfigSchema,
1414
1432
  SharableForbiddenImportSchema: () => SharableForbiddenImportSchema,
1415
1433
  SharableLayerSchema: () => SharableLayerSchema,
@@ -1427,10 +1445,12 @@ __export(dist_exports, {
1427
1445
  StrategyFrontmatterSchema: () => StrategyFrontmatterSchema,
1428
1446
  StreamIndexSchema: () => StreamIndexSchema,
1429
1447
  StreamInfoSchema: () => StreamInfoSchema,
1448
+ StrengthFindingSchema: () => StrengthFindingSchema,
1430
1449
  StructuralStrategy: () => StructuralStrategy,
1431
1450
  SupplyChainSnapshotSchema: () => SupplyChainSnapshotSchema,
1432
1451
  TYPESCRIPT_STRICT_DESCRIPTOR: () => TYPESCRIPT_STRICT_DESCRIPTOR,
1433
1452
  ThresholdConfigSchema: () => ThresholdConfigSchema,
1453
+ TierSchema: () => TierSchema,
1434
1454
  TimeToFixResultSchema: () => TimeToFixResultSchema,
1435
1455
  TimeToFixStatsSchema: () => TimeToFixStatsSchema,
1436
1456
  TimelineFileSchema: () => TimelineFileSchema,
@@ -1485,8 +1505,10 @@ __export(dist_exports, {
1485
1505
  assertPortUsable: () => assertPortUsable,
1486
1506
  assertSanitized: () => assertSanitized,
1487
1507
  assignFeature: () => assignFeature,
1508
+ buildCiReviewVerdict: () => buildCiReviewVerdict,
1488
1509
  buildDependencyGraph: () => buildDependencyGraph,
1489
1510
  buildExclusionSet: () => buildExclusionSet,
1511
+ buildProjectContext: () => buildProjectContext,
1490
1512
  buildSnapshot: () => buildSnapshot,
1491
1513
  calculateCacheSavings: () => calculateCacheSavings,
1492
1514
  calculateCost: () => calculateCost,
@@ -1495,6 +1517,7 @@ __export(dist_exports, {
1495
1517
  checkEligibility: () => checkEligibility,
1496
1518
  checkEvidenceCoverage: () => checkEvidenceCoverage,
1497
1519
  checkOverlap: () => checkOverlap,
1520
+ checkRoadmapHealth: () => checkRoadmapHealth,
1498
1521
  checkTaint: () => checkTaint,
1499
1522
  classifyConfidence: () => classifyConfidence,
1500
1523
  classifyFinding: () => classifyFinding,
@@ -1537,11 +1560,16 @@ __export(dist_exports, {
1537
1560
  createTrackerClient: () => createTrackerClient,
1538
1561
  crossReferenceUndocumentedFixes: () => crossReferenceUndocumentedFixes,
1539
1562
  cryptoRules: () => cryptoRules,
1563
+ decidePromotionForRow: () => decidePromotionForRow,
1540
1564
  deduplicateCleanupFindings: () => deduplicateCleanupFindings,
1541
1565
  deduplicateFindings: () => deduplicateFindings,
1542
1566
  deepMergeConstraints: () => deepMergeConstraints,
1543
1567
  defaultCollectors: () => defaultCollectors,
1568
+ defaultExecFile: () => defaultExecFile,
1569
+ defaultIsArchive: () => defaultIsArchive,
1544
1570
  defineLayer: () => defineLayer,
1571
+ deriveBlockingFindings: () => deriveBlockingFindings,
1572
+ deriveExitCode: () => deriveExitCode,
1545
1573
  deriveSkillUsage: () => deriveSkillUsage,
1546
1574
  deserializationRules: () => deserializationRules,
1547
1575
  detectChangeType: () => detectChangeType,
@@ -1606,6 +1634,7 @@ __export(dist_exports, {
1606
1634
  getUpdateNotification: () => getUpdateNotification,
1607
1635
  gitScan: () => gitScan,
1608
1636
  goRules: () => goRules,
1637
+ groomRoadmap: () => groomRoadmap,
1609
1638
  injectionRules: () => injectionRules,
1610
1639
  insecureDefaultsRules: () => insecureDefaultsRules,
1611
1640
  invalidateCheckState: () => invalidateCheckState,
@@ -1616,6 +1645,8 @@ __export(dist_exports, {
1616
1645
  isRegression: () => isRegression,
1617
1646
  isSanitizedResult: () => isSanitizedResult,
1618
1647
  isSmallSuggestion: () => isSmallSuggestion,
1648
+ isSupportedRunner: () => isSupportedRunner,
1649
+ isUnactionablePlanned: () => isUnactionablePlanned,
1619
1650
  isUpdateCheckEnabled: () => isUpdateCheckEnabled,
1620
1651
  isoWeek: () => isoWeek,
1621
1652
  listActiveSessions: () => listActiveSessions,
@@ -1651,14 +1682,20 @@ __export(dist_exports, {
1651
1682
  normalizeLearningContent: () => normalizeLearningContent,
1652
1683
  normalizeViolationPattern: () => normalizeViolationPattern,
1653
1684
  paginate: () => paginate,
1685
+ parseAntigravityVerdict: () => parseAntigravityVerdict,
1654
1686
  parseCCRecords: () => parseCCRecords,
1687
+ parseCiReviewVerdict: () => parseCiReviewVerdict,
1688
+ parseClaudeVerdict: () => parseClaudeVerdict,
1689
+ parseCodexVerdict: () => parseCodexVerdict,
1655
1690
  parseDateFromEntry: () => parseDateFromEntry,
1656
1691
  parseDiff: () => parseDiff,
1657
1692
  parseFile: () => parseFile,
1658
1693
  parseFileRegions: () => parseFileRegions,
1659
1694
  parseFrontmatter: () => parseFrontmatter2,
1695
+ parseGeminiVerdict: () => parseGeminiVerdict,
1660
1696
  parseHarnessIgnore: () => parseHarnessIgnore,
1661
1697
  parseLiteLLMData: () => parseLiteLLMData,
1698
+ parseLocalVerdict: () => parseLocalVerdict,
1662
1699
  parseLookback: () => parseLookback,
1663
1700
  parseManifest: () => parseManifest,
1664
1701
  parseProtectedRegions: () => parseProtectedRegions,
@@ -1668,8 +1705,10 @@ __export(dist_exports, {
1668
1705
  parseSize: () => parseSize,
1669
1706
  parseStrategyDoc: () => parseStrategyDoc,
1670
1707
  pathTraversalRules: () => pathTraversalRules,
1708
+ presetKind: () => presetKind,
1671
1709
  previewFix: () => previewFix,
1672
1710
  projectValue: () => projectValue,
1711
+ promoteFeature: () => promoteFeature,
1673
1712
  promoteSessionLearnings: () => promoteSessionLearnings,
1674
1713
  proposalsDir: () => proposalsDir,
1675
1714
  pruneLearnings: () => pruneLearnings,
@@ -1692,6 +1731,7 @@ __export(dist_exports, {
1692
1731
  resetParserCache: () => resetParserCache,
1693
1732
  resolveConsent: () => resolveConsent,
1694
1733
  resolveFileToLayer: () => resolveFileToLayer,
1734
+ resolveMode: () => resolveMode,
1695
1735
  resolveModelTier: () => resolveModelTier,
1696
1736
  resolveReverseStatus: () => resolveReverseStatus,
1697
1737
  resolveRuleSeverity: () => resolveRuleSeverity,
@@ -1699,12 +1739,14 @@ __export(dist_exports, {
1699
1739
  resolveStability: () => resolveStability,
1700
1740
  resolveStreamPath: () => resolveStreamPath,
1701
1741
  resolveThresholds: () => resolveThresholds,
1742
+ rollupScore: () => rollupScore,
1702
1743
  runAdversarialAgent: () => runAdversarialAgent,
1703
1744
  runAgentConfigFallbackRules: () => runFallbackRules,
1704
1745
  runAll: () => runAll,
1705
1746
  runArchitectureAgent: () => runArchitectureAgent,
1706
1747
  runBugDetectionAgent: () => runBugDetectionAgent,
1707
1748
  runCIChecks: () => runCIChecks,
1749
+ runCiReview: () => runCiReview,
1708
1750
  runComplianceAgent: () => runComplianceAgent,
1709
1751
  runFrontendRacesAgent: () => runFrontendRacesAgent,
1710
1752
  runMechanicalChecks: () => runMechanicalChecks,
@@ -1829,41 +1871,41 @@ import * as fs7 from "fs";
1829
1871
  import { promisify } from "util";
1830
1872
  import { dirname as dirname5, basename as basename5, join as join16 } from "path";
1831
1873
  import { z as z5 } from "zod";
1874
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
1875
+ import { join as join17, dirname as dirname6 } from "path";
1876
+ import { execFileSync } from "child_process";
1832
1877
  import * as fs8 from "fs";
1833
1878
  import * as path5 from "path";
1834
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync13 } from "fs";
1835
- import { join as join18, dirname as dirname6 } from "path";
1836
- import { execFileSync } from "child_process";
1837
- import * as fs9 from "fs";
1838
- import * as path6 from "path";
1839
- import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync14, mkdirSync as mkdirSync22 } from "fs";
1879
+ import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync13, mkdirSync as mkdirSync22 } from "fs";
1840
1880
  import { dirname as dirname7 } from "path";
1841
1881
  import { z as z6 } from "zod";
1882
+ import * as fs9 from "fs";
1883
+ import * as path6 from "path";
1842
1884
  import * as fs10 from "fs";
1843
1885
  import * as path7 from "path";
1844
1886
  import * as fs11 from "fs";
1845
1887
  import * as path8 from "path";
1846
- import * as fs12 from "fs";
1847
- import * as path9 from "path";
1848
1888
  import { z as z7 } from "zod";
1849
- import * as fs16 from "fs";
1850
- import * as path13 from "path";
1851
1889
  import * as fs15 from "fs";
1852
1890
  import * as path12 from "path";
1853
- import * as fs13 from "fs";
1854
- import * as path10 from "path";
1855
- import { execSync } from "child_process";
1856
- import { z as z8 } from "zod";
1857
1891
  import * as fs14 from "fs";
1858
1892
  import * as path11 from "path";
1893
+ import * as fs12 from "fs";
1894
+ import * as path9 from "path";
1895
+ import { execSync } from "child_process";
1896
+ import { z as z8 } from "zod";
1897
+ import * as fs13 from "fs";
1898
+ import * as path10 from "path";
1899
+ import * as fs16 from "fs";
1900
+ import * as path13 from "path";
1901
+ import * as crypto2 from "crypto";
1859
1902
  import * as fs17 from "fs";
1860
1903
  import * as path14 from "path";
1861
- import * as crypto2 from "crypto";
1862
1904
  import * as fs18 from "fs";
1863
1905
  import * as path15 from "path";
1906
+ import * as crypto3 from "crypto";
1864
1907
  import * as fs19 from "fs";
1865
1908
  import * as path16 from "path";
1866
- import * as crypto3 from "crypto";
1867
1909
  import * as fs20 from "fs";
1868
1910
  import * as path17 from "path";
1869
1911
  import * as fs21 from "fs";
@@ -1872,95 +1914,100 @@ import * as fs22 from "fs";
1872
1914
  import * as path19 from "path";
1873
1915
  import * as fs23 from "fs";
1874
1916
  import * as path20 from "path";
1917
+ import { execSync as execSync2 } from "child_process";
1875
1918
  import * as fs24 from "fs";
1876
1919
  import * as path21 from "path";
1877
- import { execSync as execSync2 } from "child_process";
1878
1920
  import * as fs25 from "fs";
1879
1921
  import * as path22 from "path";
1880
1922
  import * as fs26 from "fs";
1881
1923
  import * as path23 from "path";
1882
1924
  import * as fs27 from "fs";
1883
1925
  import * as path24 from "path";
1884
- import * as fs28 from "fs";
1885
- import * as path25 from "path";
1886
1926
  import { z as z9 } from "zod";
1887
- import * as fs30 from "fs/promises";
1927
+ import * as fs29 from "fs/promises";
1888
1928
  import { minimatch as minimatch3 } from "minimatch";
1889
1929
  import { z as z10 } from "zod";
1890
- import * as fs29 from "fs";
1930
+ import * as fs28 from "fs";
1931
+ import * as path25 from "path";
1932
+ import * as fs30 from "fs";
1891
1933
  import * as path26 from "path";
1892
- import * as fs31 from "fs";
1893
- import * as path27 from "path";
1894
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2, mkdirSync as mkdirSync13, readdirSync as readdirSync3 } from "fs";
1895
- import { join as join39, dirname as dirname10 } from "path";
1934
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2, mkdirSync as mkdirSync13, readdirSync as readdirSync2 } from "fs";
1935
+ import { join as join38, dirname as dirname10 } from "path";
1896
1936
  import { createHash as createHash32 } from "crypto";
1897
1937
  import { z as z11 } from "zod";
1898
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync15, renameSync as renameSync72, mkdirSync as mkdirSync14, existsSync as existsSync342 } from "fs";
1938
+ import { readFileSync as readFileSync222, writeFileSync as writeFileSync15, renameSync as renameSync72, mkdirSync as mkdirSync14, existsSync as existsSync332 } from "fs";
1899
1939
  import { execSync as execSync3 } from "child_process";
1900
1940
  import { randomBytes as randomBytes2 } from "crypto";
1901
- import { isAbsolute as isAbsolute4, join as join40, relative as relative3, dirname as dirname11 } from "path";
1941
+ import { isAbsolute as isAbsolute4, join as join39, relative as relative3, dirname as dirname11 } from "path";
1942
+ import * as path27 from "path";
1902
1943
  import * as path28 from "path";
1903
1944
  import * as path29 from "path";
1904
1945
  import * as path30 from "path";
1946
+ import { z as z12 } from "zod";
1947
+ import { execFile as nodeExecFile } from "child_process";
1948
+ import * as fs31 from "fs";
1905
1949
  import * as path31 from "path";
1906
1950
  import * as fs32 from "fs";
1907
1951
  import * as path32 from "path";
1908
1952
  import * as fs33 from "fs";
1909
- import * as path33 from "path";
1910
- import * as fs34 from "fs";
1911
1953
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
1954
+ import * as fs34 from "fs";
1955
+ import * as path33 from "path";
1912
1956
  import * as fs35 from "fs";
1913
1957
  import * as path34 from "path";
1914
- import * as fs36 from "fs";
1915
- import * as path35 from "path";
1916
1958
  import { createHash as createHash4 } from "crypto";
1959
+ import * as path35 from "path";
1960
+ import { z as z13 } from "zod";
1961
+ import * as fs36 from "fs/promises";
1917
1962
  import * as path36 from "path";
1918
- import { z as z12 } from "zod";
1919
1963
  import * as fs37 from "fs/promises";
1920
1964
  import * as path37 from "path";
1921
- import * as fs38 from "fs/promises";
1922
- import * as path38 from "path";
1923
1965
  import * as ejs from "ejs";
1924
- import * as fs39 from "fs";
1925
- import * as path39 from "path";
1966
+ import * as fs38 from "fs";
1967
+ import * as path38 from "path";
1926
1968
  import * as os from "os";
1927
1969
  import { spawn as spawn2 } from "child_process";
1928
- import * as fs40 from "fs/promises";
1970
+ import * as fs39 from "fs/promises";
1971
+ import * as path39 from "path";
1972
+ import * as fs40 from "fs";
1929
1973
  import * as path40 from "path";
1930
1974
  import * as fs41 from "fs";
1931
1975
  import * as path41 from "path";
1976
+ import * as os2 from "os";
1932
1977
  import * as fs42 from "fs";
1933
1978
  import * as path42 from "path";
1934
- import * as os2 from "os";
1935
- import * as fs43 from "fs";
1936
- import * as path43 from "path";
1937
1979
  import * as fs44 from "fs";
1938
1980
  import * as path44 from "path";
1939
- import * as fs46 from "fs";
1940
- import * as path46 from "path";
1941
1981
  import { execFileSync as execFileSync2 } from "child_process";
1982
+ import * as fs43 from "fs";
1983
+ import * as path43 from "path";
1984
+ import * as crypto4 from "crypto";
1985
+ import { z as z14 } from "zod";
1986
+ import { existsSync as existsSync38, readFileSync as readFileSync34, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
1987
+ import { join as join582, basename as basename10, resolve as resolve6, relative as relative4 } from "path";
1942
1988
  import * as fs45 from "fs";
1943
1989
  import * as path45 from "path";
1944
- import * as crypto4 from "crypto";
1990
+ import * as fs46 from "fs";
1991
+ import * as path46 from "path";
1945
1992
  import * as fs47 from "fs";
1946
1993
  import * as path47 from "path";
1947
1994
  import * as fs48 from "fs";
1948
- import * as fs49 from "fs";
1949
1995
  import * as path48 from "path";
1950
- import { readFileSync as readFileSync392 } from "fs";
1996
+ import { randomUUID as randomUUID2 } from "crypto";
1997
+ import * as fs49 from "fs";
1998
+ import * as fs50 from "fs";
1999
+ import * as path49 from "path";
2000
+ import { readFileSync as readFileSync41 } from "fs";
1951
2001
  import { fileURLToPath } from "url";
1952
- import { dirname as dirname16, join as join622 } from "path";
2002
+ import { dirname as dirname16, join as join642 } from "path";
1953
2003
  import { execFile } from "child_process";
1954
2004
  import { promisify as promisify2 } from "util";
1955
2005
  import { execFile as execFile2 } from "child_process";
1956
2006
  import { promisify as promisify3 } from "util";
1957
- import * as fs50 from "fs/promises";
1958
- import * as path49 from "path";
1959
- import * as fs51 from "fs";
2007
+ import * as fs51 from "fs/promises";
1960
2008
  import * as path50 from "path";
1961
2009
  import * as fs52 from "fs";
1962
2010
  import * as path51 from "path";
1963
- import { randomUUID as randomUUID2 } from "crypto";
1964
2011
  function isBadPort(port) {
1965
2012
  return BAD_PORT_SET.has(port);
1966
2013
  }
@@ -2334,7 +2381,7 @@ function isAgnixDisabled() {
2334
2381
  return v === "1" || v === "true";
2335
2382
  }
2336
2383
  function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, spawnFn = spawn) {
2337
- return new Promise((resolve6) => {
2384
+ return new Promise((resolve7) => {
2338
2385
  const args = ["--format", "json"];
2339
2386
  if (strict) args.push("--strict");
2340
2387
  args.push(cwd);
@@ -2348,7 +2395,7 @@ function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, sp
2348
2395
  if (settled) return;
2349
2396
  settled = true;
2350
2397
  clearTimeout(timer);
2351
- resolve6(outcome);
2398
+ resolve7(outcome);
2352
2399
  };
2353
2400
  const timer = setTimeout(() => {
2354
2401
  child.kill("SIGKILL");
@@ -4938,139 +4985,6 @@ function validatePatternConfig(config) {
4938
4985
  }
4939
4986
  return Ok(result.data);
4940
4987
  }
4941
- async function safeGather(key, fn, warnings) {
4942
- try {
4943
- return await fn();
4944
- } catch (e) {
4945
- warnings.push(`${key}: ${e instanceof Error ? e.message : String(e)}`);
4946
- return null;
4947
- }
4948
- }
4949
- function arrayLen(arr) {
4950
- return Array.isArray(arr) ? arr.length : 0;
4951
- }
4952
- async function gatherEntropyBlock(projectPath) {
4953
- const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-V633GUHY-MBCQWKDU.js");
4954
- const analyzer = new EntropyAnalyzer2({
4955
- rootDir: projectPath,
4956
- include: ["src/**/*.ts", "src/**/*.tsx", "packages/*/src/**/*.ts"],
4957
- exclude: ["node_modules/**", "dist/**", "**/*.test.ts", "**/*.spec.ts"],
4958
- analyze: { drift: false, deadCode: true, patterns: false, complexity: false }
4959
- });
4960
- const result = await analyzer.analyze();
4961
- const empty = { driftCount: 0, deadFiles: 0, deadExports: 0 };
4962
- if (!result.ok) return empty;
4963
- const report = result.value;
4964
- return {
4965
- driftCount: arrayLen(report.drift?.drifts),
4966
- deadFiles: arrayLen(report.deadCode?.deadFiles),
4967
- deadExports: arrayLen(report.deadCode?.deadExports)
4968
- };
4969
- }
4970
- async function gatherDecayBlock(projectPath) {
4971
- const { TimelineManager: TimelineManager2 } = await import("./timeline-manager-FPYKJRHR-VVX7CSGW.js");
4972
- const mgr = new TimelineManager2(projectPath);
4973
- const trends = mgr.trends();
4974
- return {
4975
- recentBumps: arrayLen(trends.recentSnapshots),
4976
- topAffected: collectTopAffectedLabels(trends.topAffected)
4977
- };
4978
- }
4979
- function collectTopAffectedLabels(affected) {
4980
- if (!Array.isArray(affected)) return [];
4981
- const out = [];
4982
- for (const node of affected) {
4983
- const label = node.id ?? node.name;
4984
- if (typeof label === "string" && label.length > 0) out.push(label);
4985
- if (out.length >= MAX_TOP_AFFECTED) break;
4986
- }
4987
- return out;
4988
- }
4989
- async function gatherAttentionBlock(projectPath) {
4990
- const sessionsDir = path5.join(projectPath, ".harness", "sessions");
4991
- if (!fs8.existsSync(sessionsDir)) return { activeThreadCount: 0, staleThreadCount: 0 };
4992
- const now = Date.now();
4993
- const STALE_MS = 7 * 24 * 60 * 60 * 1e3;
4994
- let active = 0;
4995
- let stale = 0;
4996
- const entries = fs8.readdirSync(sessionsDir, { withFileTypes: true });
4997
- for (const entry of entries) {
4998
- if (!entry.isDirectory()) continue;
4999
- const dirPath = path5.join(sessionsDir, entry.name);
5000
- try {
5001
- const stat2 = fs8.statSync(dirPath);
5002
- if (now - stat2.mtimeMs > STALE_MS) {
5003
- stale++;
5004
- } else {
5005
- active++;
5006
- }
5007
- } catch {
5008
- }
5009
- }
5010
- return { activeThreadCount: active, staleThreadCount: stale };
5011
- }
5012
- async function gatherImpactBlock(projectPath) {
5013
- const cachePath = path5.join(projectPath, ".harness", "cache", "impact.json");
5014
- if (!fs8.existsSync(cachePath)) return { recentBlastRadius: [] };
5015
- try {
5016
- const raw = JSON.parse(fs8.readFileSync(cachePath, "utf8"));
5017
- const radii = raw.blastRadius ?? [];
5018
- return {
5019
- recentBlastRadius: radii.slice(0, 5).map((r) => ({
5020
- node: r.node,
5021
- affected: r.affected
5022
- }))
5023
- };
5024
- } catch {
5025
- return { recentBlastRadius: [] };
5026
- }
5027
- }
5028
- async function gatherHealthBlock(projectPath) {
5029
- const entropy = await gatherEntropyBlock(projectPath);
5030
- const signals = [];
5031
- if (entropy.driftCount > 0) signals.push(`${entropy.driftCount} drift findings`);
5032
- if (entropy.deadFiles > 0) signals.push(`${entropy.deadFiles} dead files`);
5033
- if (entropy.deadExports > 0) signals.push(`${entropy.deadExports} dead exports`);
5034
- const passed = signals.length === 0;
5035
- return {
5036
- passed,
5037
- signals,
5038
- summary: passed ? "No structural health findings." : signals.join("; ")
5039
- };
5040
- }
5041
- async function composeInsights(projectPath, opts = {}) {
5042
- const skip = new Set(opts.skip ?? []);
5043
- const warnings = [];
5044
- const wantedKeys = INSIGHTS_KEYS.filter((k) => !skip.has(k));
5045
- const [healthRes, entropyRes, decayRes, attentionRes, impactRes] = await Promise.all([
5046
- wantedKeys.includes("health") ? safeGather("health", () => gatherHealthBlock(projectPath), warnings) : Promise.resolve(null),
5047
- wantedKeys.includes("entropy") ? safeGather("entropy", () => gatherEntropyBlock(projectPath), warnings) : Promise.resolve(null),
5048
- wantedKeys.includes("decay") ? safeGather("decay", () => gatherDecayBlock(projectPath), warnings) : Promise.resolve(null),
5049
- wantedKeys.includes("attention") ? safeGather("attention", () => gatherAttentionBlock(projectPath), warnings) : Promise.resolve(null),
5050
- wantedKeys.includes("impact") ? safeGather("impact", () => gatherImpactBlock(projectPath), warnings) : Promise.resolve(null)
5051
- ]);
5052
- const projectName = pickProjectName(projectPath);
5053
- return {
5054
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5055
- project: { root: projectPath, ...projectName && { name: projectName } },
5056
- health: healthRes,
5057
- entropy: entropyRes,
5058
- decay: decayRes,
5059
- attention: attentionRes,
5060
- impact: impactRes,
5061
- warnings
5062
- };
5063
- }
5064
- function pickProjectName(projectPath) {
5065
- try {
5066
- const pkgPath = path5.join(projectPath, "package.json");
5067
- if (!fs8.existsSync(pkgPath)) return void 0;
5068
- const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf8"));
5069
- return pkg.name;
5070
- } catch {
5071
- return void 0;
5072
- }
5073
- }
5074
4988
  function mergeGraphInferred(highFanInFunctions, seen) {
5075
4989
  let added = 0;
5076
4990
  for (const item of highFanInFunctions) {
@@ -5941,10 +5855,10 @@ function detectEmergentConstraints(history, options) {
5941
5855
  return { suggestions, totalViolationsAnalyzed, windowWeeks, minOccurrences };
5942
5856
  }
5943
5857
  function streamsDir(projectPath) {
5944
- return path10.join(projectPath, HARNESS_DIR, STREAMS_DIR);
5858
+ return path9.join(projectPath, HARNESS_DIR, STREAMS_DIR);
5945
5859
  }
5946
5860
  function indexPath(projectPath) {
5947
- return path10.join(streamsDir(projectPath), INDEX_FILE);
5861
+ return path9.join(streamsDir(projectPath), INDEX_FILE);
5948
5862
  }
5949
5863
  function validateStreamName(name) {
5950
5864
  if (!STREAM_NAME_REGEX.test(name)) {
@@ -5958,11 +5872,11 @@ function validateStreamName(name) {
5958
5872
  }
5959
5873
  async function loadStreamIndex(projectPath) {
5960
5874
  const idxPath = indexPath(projectPath);
5961
- if (!fs13.existsSync(idxPath)) {
5875
+ if (!fs12.existsSync(idxPath)) {
5962
5876
  return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
5963
5877
  }
5964
5878
  try {
5965
- const raw = fs13.readFileSync(idxPath, "utf-8");
5879
+ const raw = fs12.readFileSync(idxPath, "utf-8");
5966
5880
  const parsed = JSON.parse(raw);
5967
5881
  const result = StreamIndexSchema.safeParse(parsed);
5968
5882
  if (!result.success) {
@@ -5980,8 +5894,8 @@ async function loadStreamIndex(projectPath) {
5980
5894
  async function saveStreamIndex(projectPath, index) {
5981
5895
  const dir = streamsDir(projectPath);
5982
5896
  try {
5983
- fs13.mkdirSync(dir, { recursive: true });
5984
- fs13.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
5897
+ fs12.mkdirSync(dir, { recursive: true });
5898
+ fs12.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
5985
5899
  return Ok(void 0);
5986
5900
  } catch (error) {
5987
5901
  return Err(
@@ -6020,18 +5934,18 @@ async function resolveStreamPath(projectPath, options) {
6020
5934
  )
6021
5935
  );
6022
5936
  }
6023
- return Ok(path10.join(streamsDir(projectPath), options.stream));
5937
+ return Ok(path9.join(streamsDir(projectPath), options.stream));
6024
5938
  }
6025
5939
  const branch = getCurrentBranch(projectPath);
6026
5940
  if (branch && branch !== "main" && branch !== "master") {
6027
5941
  for (const [name, info] of Object.entries(index.streams)) {
6028
5942
  if (info.branch === branch) {
6029
- return Ok(path10.join(streamsDir(projectPath), name));
5943
+ return Ok(path9.join(streamsDir(projectPath), name));
6030
5944
  }
6031
5945
  }
6032
5946
  }
6033
5947
  if (index.activeStream && index.streams[index.activeStream]) {
6034
- return Ok(path10.join(streamsDir(projectPath), index.activeStream));
5948
+ return Ok(path9.join(streamsDir(projectPath), index.activeStream));
6035
5949
  }
6036
5950
  return Err(
6037
5951
  new Error(
@@ -6059,9 +5973,9 @@ async function createStream(projectPath, name, branch) {
6059
5973
  if (index.streams[name]) {
6060
5974
  return Err(new Error(`Stream '${name}' already exists`));
6061
5975
  }
6062
- const streamPath = path10.join(streamsDir(projectPath), name);
5976
+ const streamPath = path9.join(streamsDir(projectPath), name);
6063
5977
  try {
6064
- fs13.mkdirSync(streamPath, { recursive: true });
5978
+ fs12.mkdirSync(streamPath, { recursive: true });
6065
5979
  } catch (error) {
6066
5980
  return Err(
6067
5981
  new Error(
@@ -6102,12 +6016,12 @@ async function archiveStream(projectPath, name) {
6102
6016
  if (!index.streams[name]) {
6103
6017
  return Err(new Error(`Stream '${name}' not found`));
6104
6018
  }
6105
- const streamPath = path10.join(streamsDir(projectPath), name);
6106
- const archiveDir = path10.join(projectPath, HARNESS_DIR, "archive", "streams");
6019
+ const streamPath = path9.join(streamsDir(projectPath), name);
6020
+ const archiveDir = path9.join(projectPath, HARNESS_DIR, "archive", "streams");
6107
6021
  try {
6108
- fs13.mkdirSync(archiveDir, { recursive: true });
6022
+ fs12.mkdirSync(archiveDir, { recursive: true });
6109
6023
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6110
- fs13.renameSync(streamPath, path10.join(archiveDir, `${name}-${date}`));
6024
+ fs12.renameSync(streamPath, path9.join(archiveDir, `${name}-${date}`));
6111
6025
  } catch (error) {
6112
6026
  return Err(
6113
6027
  new Error(
@@ -6128,19 +6042,19 @@ function getStreamForBranch(index, branch) {
6128
6042
  return null;
6129
6043
  }
6130
6044
  async function migrateToStreams(projectPath) {
6131
- const harnessDir = path10.join(projectPath, HARNESS_DIR);
6132
- if (fs13.existsSync(indexPath(projectPath))) {
6045
+ const harnessDir = path9.join(projectPath, HARNESS_DIR);
6046
+ if (fs12.existsSync(indexPath(projectPath))) {
6133
6047
  return Ok(void 0);
6134
6048
  }
6135
- const filesToMove = STATE_FILES.filter((f) => fs13.existsSync(path10.join(harnessDir, f)));
6049
+ const filesToMove = STATE_FILES.filter((f) => fs12.existsSync(path9.join(harnessDir, f)));
6136
6050
  if (filesToMove.length === 0) {
6137
6051
  return Ok(void 0);
6138
6052
  }
6139
- const defaultDir = path10.join(streamsDir(projectPath), "default");
6053
+ const defaultDir = path9.join(streamsDir(projectPath), "default");
6140
6054
  try {
6141
- fs13.mkdirSync(defaultDir, { recursive: true });
6055
+ fs12.mkdirSync(defaultDir, { recursive: true });
6142
6056
  for (const file of filesToMove) {
6143
- fs13.renameSync(path10.join(harnessDir, file), path10.join(defaultDir, file));
6057
+ fs12.renameSync(path9.join(harnessDir, file), path9.join(defaultDir, file));
6144
6058
  }
6145
6059
  } catch (error) {
6146
6060
  return Err(
@@ -6170,26 +6084,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
6170
6084
  new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
6171
6085
  );
6172
6086
  }
6173
- const sessionDir = path11.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
6087
+ const sessionDir = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
6174
6088
  if (options?.create) {
6175
- fs14.mkdirSync(sessionDir, { recursive: true });
6089
+ fs13.mkdirSync(sessionDir, { recursive: true });
6176
6090
  }
6177
6091
  return Ok(sessionDir);
6178
6092
  }
6179
6093
  function updateSessionIndex(projectPath, sessionSlug, description) {
6180
- const sessionsDir = path11.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
6181
- fs14.mkdirSync(sessionsDir, { recursive: true });
6182
- const indexPath2 = path11.join(sessionsDir, SESSION_INDEX_FILE);
6094
+ const sessionsDir = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
6095
+ fs13.mkdirSync(sessionsDir, { recursive: true });
6096
+ const indexPath2 = path10.join(sessionsDir, SESSION_INDEX_FILE);
6183
6097
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6184
6098
  const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
6185
- if (!fs14.existsSync(indexPath2)) {
6186
- fs14.writeFileSync(indexPath2, `## Active Sessions
6099
+ if (!fs13.existsSync(indexPath2)) {
6100
+ fs13.writeFileSync(indexPath2, `## Active Sessions
6187
6101
 
6188
6102
  ${newLine}
6189
6103
  `);
6190
6104
  return;
6191
6105
  }
6192
- const content = fs14.readFileSync(indexPath2, "utf-8");
6106
+ const content = fs13.readFileSync(indexPath2, "utf-8");
6193
6107
  const lines = content.split("\n");
6194
6108
  const slugPattern = `- [${sessionSlug}]`;
6195
6109
  const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
@@ -6199,7 +6113,7 @@ ${newLine}
6199
6113
  const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
6200
6114
  lines.splice(lastNonEmpty + 1, 0, newLine);
6201
6115
  }
6202
- fs14.writeFileSync(indexPath2, lines.join("\n"));
6116
+ fs13.writeFileSync(indexPath2, lines.join("\n"));
6203
6117
  }
6204
6118
  function evictIfNeeded(map) {
6205
6119
  if (map.size > MAX_CACHE_ENTRIES) {
@@ -6212,8 +6126,8 @@ async function getStateDir(projectPath, stream, session) {
6212
6126
  const sessionResult = resolveSessionDir(projectPath, session, { create: true });
6213
6127
  return sessionResult;
6214
6128
  }
6215
- const streamsIndexPath = path12.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
6216
- const hasStreams = fs15.existsSync(streamsIndexPath);
6129
+ const streamsIndexPath = path11.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
6130
+ const hasStreams = fs14.existsSync(streamsIndexPath);
6217
6131
  if (stream || hasStreams) {
6218
6132
  const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
6219
6133
  if (result.ok) {
@@ -6223,18 +6137,18 @@ async function getStateDir(projectPath, stream, session) {
6223
6137
  return result;
6224
6138
  }
6225
6139
  }
6226
- return Ok(path12.join(projectPath, HARNESS_DIR));
6140
+ return Ok(path11.join(projectPath, HARNESS_DIR));
6227
6141
  }
6228
6142
  async function loadState(projectPath, stream, session) {
6229
6143
  try {
6230
6144
  const dirResult = await getStateDir(projectPath, stream, session);
6231
6145
  if (!dirResult.ok) return dirResult;
6232
6146
  const stateDir = dirResult.value;
6233
- const statePath = path13.join(stateDir, STATE_FILE);
6234
- if (!fs16.existsSync(statePath)) {
6147
+ const statePath = path12.join(stateDir, STATE_FILE);
6148
+ if (!fs15.existsSync(statePath)) {
6235
6149
  return Ok({ ...DEFAULT_STATE });
6236
6150
  }
6237
- const raw = fs16.readFileSync(statePath, "utf-8");
6151
+ const raw = fs15.readFileSync(statePath, "utf-8");
6238
6152
  const parsed = JSON.parse(raw);
6239
6153
  const result = HarnessStateSchema.safeParse(parsed);
6240
6154
  if (!result.success) {
@@ -6252,11 +6166,11 @@ async function saveState(projectPath, state, stream, session) {
6252
6166
  const dirResult = await getStateDir(projectPath, stream, session);
6253
6167
  if (!dirResult.ok) return dirResult;
6254
6168
  const stateDir = dirResult.value;
6255
- const statePath = path13.join(stateDir, STATE_FILE);
6256
- fs16.mkdirSync(stateDir, { recursive: true });
6169
+ const statePath = path12.join(stateDir, STATE_FILE);
6170
+ fs15.mkdirSync(stateDir, { recursive: true });
6257
6171
  const tmpPath = statePath + ".tmp";
6258
- fs16.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
6259
- fs16.renameSync(tmpPath, statePath);
6172
+ fs15.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
6173
+ fs15.renameSync(tmpPath, statePath);
6260
6174
  return Ok(void 0);
6261
6175
  } catch (error) {
6262
6176
  return Err(
@@ -6342,10 +6256,10 @@ function computeContentHash(text) {
6342
6256
  return crypto2.createHash("sha256").update(text).digest("hex").slice(0, 16);
6343
6257
  }
6344
6258
  function loadContentHashes(stateDir) {
6345
- const hashesPath = path14.join(stateDir, CONTENT_HASHES_FILE);
6346
- if (!fs17.existsSync(hashesPath)) return {};
6259
+ const hashesPath = path13.join(stateDir, CONTENT_HASHES_FILE);
6260
+ if (!fs16.existsSync(hashesPath)) return {};
6347
6261
  try {
6348
- const raw = fs17.readFileSync(hashesPath, "utf-8");
6262
+ const raw = fs16.readFileSync(hashesPath, "utf-8");
6349
6263
  const parsed = JSON.parse(raw);
6350
6264
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
6351
6265
  return parsed;
@@ -6354,15 +6268,15 @@ function loadContentHashes(stateDir) {
6354
6268
  }
6355
6269
  }
6356
6270
  function saveContentHashes(stateDir, index) {
6357
- const hashesPath = path14.join(stateDir, CONTENT_HASHES_FILE);
6271
+ const hashesPath = path13.join(stateDir, CONTENT_HASHES_FILE);
6358
6272
  const tmpPath = hashesPath + ".tmp";
6359
- fs17.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
6360
- fs17.renameSync(tmpPath, hashesPath);
6273
+ fs16.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
6274
+ fs16.renameSync(tmpPath, hashesPath);
6361
6275
  }
6362
6276
  function rebuildContentHashes(stateDir, learningsFile) {
6363
- const learningsPath = path14.join(stateDir, learningsFile);
6364
- if (!fs17.existsSync(learningsPath)) return {};
6365
- const content = fs17.readFileSync(learningsPath, "utf-8");
6277
+ const learningsPath = path13.join(stateDir, learningsFile);
6278
+ if (!fs16.existsSync(learningsPath)) return {};
6279
+ const content = fs16.readFileSync(learningsPath, "utf-8");
6366
6280
  const lines = content.split("\n");
6367
6281
  const index = {};
6368
6282
  for (let i = 0; i < lines.length; i++) {
@@ -6421,18 +6335,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
6421
6335
  const dirResult = await getStateDir(projectPath, stream, session);
6422
6336
  if (!dirResult.ok) return dirResult;
6423
6337
  const stateDir = dirResult.value;
6424
- const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
6425
- if (!fs18.existsSync(learningsPath)) {
6338
+ const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
6339
+ if (!fs17.existsSync(learningsPath)) {
6426
6340
  return Ok([]);
6427
6341
  }
6428
- const stats = fs18.statSync(learningsPath);
6342
+ const stats = fs17.statSync(learningsPath);
6429
6343
  const cacheKey = learningsPath;
6430
6344
  const cached = learningsCacheMap.get(cacheKey);
6431
6345
  let entries;
6432
6346
  if (cached && cached.mtimeMs === stats.mtimeMs) {
6433
6347
  entries = cached.entries;
6434
6348
  } else {
6435
- const content = fs18.readFileSync(learningsPath, "utf-8");
6349
+ const content = fs17.readFileSync(learningsPath, "utf-8");
6436
6350
  const lines = content.split("\n");
6437
6351
  entries = [];
6438
6352
  let currentBlock = [];
@@ -6568,16 +6482,16 @@ function checkOverlap(newEntry, existingEntries, options) {
6568
6482
  };
6569
6483
  }
6570
6484
  function sleep(ms) {
6571
- return new Promise((resolve6) => setTimeout(resolve6, ms));
6485
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
6572
6486
  }
6573
6487
  async function acquireFileLock(lockPath) {
6574
6488
  for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
6575
6489
  try {
6576
- const fd = fs19.openSync(
6490
+ const fd = fs18.openSync(
6577
6491
  lockPath,
6578
- fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
6492
+ fs18.constants.O_CREAT | fs18.constants.O_EXCL | fs18.constants.O_WRONLY
6579
6493
  );
6580
- fs19.closeSync(fd);
6494
+ fs18.closeSync(fd);
6581
6495
  return;
6582
6496
  } catch (err) {
6583
6497
  const isExist = err instanceof Error && "code" in err && err.code === "EEXIST";
@@ -6591,20 +6505,20 @@ async function acquireFileLock(lockPath) {
6591
6505
  }
6592
6506
  function releaseFileLock(lockPath) {
6593
6507
  try {
6594
- fs19.unlinkSync(lockPath);
6508
+ fs18.unlinkSync(lockPath);
6595
6509
  } catch {
6596
6510
  }
6597
6511
  }
6598
6512
  function loadOrRebuildHashes(stateDir, learningsPath) {
6599
- const hashesPath = path16.join(stateDir, CONTENT_HASHES_FILE);
6600
- if (fs19.existsSync(hashesPath)) {
6513
+ const hashesPath = path15.join(stateDir, CONTENT_HASHES_FILE);
6514
+ if (fs18.existsSync(hashesPath)) {
6601
6515
  const hashes = loadContentHashes(stateDir);
6602
- if (Object.keys(hashes).length === 0 && fs19.existsSync(learningsPath)) {
6516
+ if (Object.keys(hashes).length === 0 && fs18.existsSync(learningsPath)) {
6603
6517
  return rebuildContentHashes(stateDir, LEARNINGS_FILE);
6604
6518
  }
6605
6519
  return hashes;
6606
6520
  }
6607
- if (fs19.existsSync(learningsPath)) {
6521
+ if (fs18.existsSync(learningsPath)) {
6608
6522
  return rebuildContentHashes(stateDir, LEARNINGS_FILE);
6609
6523
  }
6610
6524
  return {};
@@ -6628,8 +6542,8 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
6628
6542
  const dirResult = await getStateDir(projectPath, stream, session);
6629
6543
  if (!dirResult.ok) return dirResult;
6630
6544
  const stateDir = dirResult.value;
6631
- const learningsPath = path16.join(stateDir, LEARNINGS_FILE);
6632
- fs19.mkdirSync(stateDir, { recursive: true });
6545
+ const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
6546
+ fs18.mkdirSync(stateDir, { recursive: true });
6633
6547
  const lockPath = learningsPath + ".lock";
6634
6548
  await acquireFileLock(lockPath);
6635
6549
  try {
@@ -6659,17 +6573,17 @@ ${bulletLine}
6659
6573
  `;
6660
6574
  let overlapResult;
6661
6575
  let existingLineCount;
6662
- if (!fs19.existsSync(learningsPath)) {
6663
- fs19.writeFileSync(learningsPath, `# Learnings
6576
+ if (!fs18.existsSync(learningsPath)) {
6577
+ fs18.writeFileSync(learningsPath, `# Learnings
6664
6578
  ${entry}`);
6665
6579
  existingLineCount = 1;
6666
6580
  } else {
6667
- const existingContent = fs19.readFileSync(learningsPath, "utf-8");
6581
+ const existingContent = fs18.readFileSync(learningsPath, "utf-8");
6668
6582
  const existingEntries = existingContent.split("\n").filter((line) => /^- \*\*\d{4}-\d{2}-\d{2}/.test(line));
6669
6583
  const overlap = checkOverlap(bulletLine, existingEntries);
6670
6584
  if (overlap.score >= 0.7) overlapResult = overlap;
6671
6585
  existingLineCount = existingContent.split("\n").length;
6672
- fs19.appendFileSync(learningsPath, entry);
6586
+ fs18.appendFileSync(learningsPath, entry);
6673
6587
  }
6674
6588
  const bulletLineNum = existingLineCount + 2;
6675
6589
  contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLineNum };
@@ -6774,11 +6688,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
6774
6688
  const dirResult = await getStateDir(projectPath, stream, session);
6775
6689
  if (!dirResult.ok) return dirResult;
6776
6690
  const stateDir = dirResult.value;
6777
- const learningsPath = path16.join(stateDir, LEARNINGS_FILE);
6778
- if (!fs19.existsSync(learningsPath)) {
6691
+ const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
6692
+ if (!fs18.existsSync(learningsPath)) {
6779
6693
  return Ok([]);
6780
6694
  }
6781
- const content = fs19.readFileSync(learningsPath, "utf-8");
6695
+ const content = fs18.readFileSync(learningsPath, "utf-8");
6782
6696
  const lines = content.split("\n");
6783
6697
  const indexEntries = [];
6784
6698
  let pendingFrontmatter = null;
@@ -6820,16 +6734,16 @@ async function archiveLearnings(projectPath, entries, stream) {
6820
6734
  const dirResult = await getStateDir(projectPath, stream);
6821
6735
  if (!dirResult.ok) return dirResult;
6822
6736
  const stateDir = dirResult.value;
6823
- const archiveDir = path17.join(stateDir, "learnings-archive");
6824
- fs20.mkdirSync(archiveDir, { recursive: true });
6737
+ const archiveDir = path16.join(stateDir, "learnings-archive");
6738
+ fs19.mkdirSync(archiveDir, { recursive: true });
6825
6739
  const now = /* @__PURE__ */ new Date();
6826
6740
  const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
6827
- const archivePath = path17.join(archiveDir, `${yearMonth}.md`);
6741
+ const archivePath = path16.join(archiveDir, `${yearMonth}.md`);
6828
6742
  const archiveContent = entries.join("\n\n") + "\n";
6829
- if (fs20.existsSync(archivePath)) {
6830
- fs20.appendFileSync(archivePath, "\n" + archiveContent);
6743
+ if (fs19.existsSync(archivePath)) {
6744
+ fs19.appendFileSync(archivePath, "\n" + archiveContent);
6831
6745
  } else {
6832
- fs20.writeFileSync(archivePath, `# Learnings Archive
6746
+ fs19.writeFileSync(archivePath, `# Learnings Archive
6833
6747
 
6834
6748
  ${archiveContent}`);
6835
6749
  }
@@ -6847,8 +6761,8 @@ async function pruneLearnings(projectPath, stream) {
6847
6761
  const dirResult = await getStateDir(projectPath, stream);
6848
6762
  if (!dirResult.ok) return dirResult;
6849
6763
  const stateDir = dirResult.value;
6850
- const learningsPath = path17.join(stateDir, LEARNINGS_FILE);
6851
- if (!fs20.existsSync(learningsPath)) {
6764
+ const learningsPath = path16.join(stateDir, LEARNINGS_FILE);
6765
+ if (!fs19.existsSync(learningsPath)) {
6852
6766
  return Ok({ kept: 0, archived: 0, patterns: [] });
6853
6767
  }
6854
6768
  const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
@@ -6879,7 +6793,7 @@ async function pruneLearnings(projectPath, stream) {
6879
6793
  if (!archiveResult.ok) return archiveResult;
6880
6794
  }
6881
6795
  const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
6882
- fs20.writeFileSync(learningsPath, newContent);
6796
+ fs19.writeFileSync(learningsPath, newContent);
6883
6797
  invalidateLearningsCacheEntry(learningsPath);
6884
6798
  return Ok({
6885
6799
  kept: toKeep.length,
@@ -6923,19 +6837,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
6923
6837
  const dirResult = await getStateDir(projectPath, stream);
6924
6838
  if (!dirResult.ok) return dirResult;
6925
6839
  const stateDir = dirResult.value;
6926
- const globalPath = path17.join(stateDir, LEARNINGS_FILE);
6927
- const existingGlobal = fs20.existsSync(globalPath) ? fs20.readFileSync(globalPath, "utf-8") : "";
6840
+ const globalPath = path16.join(stateDir, LEARNINGS_FILE);
6841
+ const existingGlobal = fs19.existsSync(globalPath) ? fs19.readFileSync(globalPath, "utf-8") : "";
6928
6842
  const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
6929
6843
  if (newEntries.length === 0) {
6930
6844
  return Ok({ promoted: 0, skipped: skipped + toPromote.length });
6931
6845
  }
6932
6846
  const promotedContent = newEntries.join("\n\n") + "\n";
6933
6847
  if (!existingGlobal) {
6934
- fs20.writeFileSync(globalPath, `# Learnings
6848
+ fs19.writeFileSync(globalPath, `# Learnings
6935
6849
 
6936
6850
  ${promotedContent}`);
6937
6851
  } else {
6938
- fs20.appendFileSync(globalPath, "\n\n" + promotedContent);
6852
+ fs19.appendFileSync(globalPath, "\n\n" + promotedContent);
6939
6853
  }
6940
6854
  invalidateLearningsCacheEntry(globalPath);
6941
6855
  return Ok({
@@ -6971,13 +6885,13 @@ async function detectStaleLearnings(projectPath, stream, session) {
6971
6885
  freshCount++;
6972
6886
  continue;
6973
6887
  }
6974
- const resolvedRoot = path18.resolve(projectPath);
6888
+ const resolvedRoot = path17.resolve(projectPath);
6975
6889
  const missing = refs.filter((ref) => {
6976
- const absPath = path18.resolve(projectPath, ref);
6977
- if (!absPath.startsWith(resolvedRoot + path18.sep) && absPath !== resolvedRoot) {
6890
+ const absPath = path17.resolve(projectPath, ref);
6891
+ if (!absPath.startsWith(resolvedRoot + path17.sep) && absPath !== resolvedRoot) {
6978
6892
  return false;
6979
6893
  }
6980
- return !fs21.existsSync(absPath);
6894
+ return !fs20.existsSync(absPath);
6981
6895
  });
6982
6896
  if (missing.length > 0) {
6983
6897
  const idx = extractIndexEntry(entry);
@@ -7012,17 +6926,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
7012
6926
  const dirResult = await getStateDir(projectPath, stream, session);
7013
6927
  if (!dirResult.ok) return dirResult;
7014
6928
  const stateDir = dirResult.value;
7015
- const failuresPath = path19.join(stateDir, FAILURES_FILE);
7016
- fs22.mkdirSync(stateDir, { recursive: true });
6929
+ const failuresPath = path18.join(stateDir, FAILURES_FILE);
6930
+ fs21.mkdirSync(stateDir, { recursive: true });
7017
6931
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7018
6932
  const entry = `
7019
6933
  - **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
7020
6934
  `;
7021
- if (!fs22.existsSync(failuresPath)) {
7022
- fs22.writeFileSync(failuresPath, `# Failures
6935
+ if (!fs21.existsSync(failuresPath)) {
6936
+ fs21.writeFileSync(failuresPath, `# Failures
7023
6937
  ${entry}`);
7024
6938
  } else {
7025
- fs22.appendFileSync(failuresPath, entry);
6939
+ fs21.appendFileSync(failuresPath, entry);
7026
6940
  }
7027
6941
  failuresCacheMap.delete(failuresPath);
7028
6942
  return Ok(void 0);
@@ -7039,17 +6953,17 @@ async function loadFailures(projectPath, stream, session) {
7039
6953
  const dirResult = await getStateDir(projectPath, stream, session);
7040
6954
  if (!dirResult.ok) return dirResult;
7041
6955
  const stateDir = dirResult.value;
7042
- const failuresPath = path19.join(stateDir, FAILURES_FILE);
7043
- if (!fs22.existsSync(failuresPath)) {
6956
+ const failuresPath = path18.join(stateDir, FAILURES_FILE);
6957
+ if (!fs21.existsSync(failuresPath)) {
7044
6958
  return Ok([]);
7045
6959
  }
7046
- const stats = fs22.statSync(failuresPath);
6960
+ const stats = fs21.statSync(failuresPath);
7047
6961
  const cacheKey = failuresPath;
7048
6962
  const cached = failuresCacheMap.get(cacheKey);
7049
6963
  if (cached && cached.mtimeMs === stats.mtimeMs) {
7050
6964
  return Ok(cached.entries);
7051
6965
  }
7052
- const content = fs22.readFileSync(failuresPath, "utf-8");
6966
+ const content = fs21.readFileSync(failuresPath, "utf-8");
7053
6967
  const entries = [];
7054
6968
  for (const line of content.split("\n")) {
7055
6969
  const match = line.match(FAILURE_LINE_REGEX);
@@ -7078,20 +6992,20 @@ async function archiveFailures(projectPath, stream, session) {
7078
6992
  const dirResult = await getStateDir(projectPath, stream, session);
7079
6993
  if (!dirResult.ok) return dirResult;
7080
6994
  const stateDir = dirResult.value;
7081
- const failuresPath = path19.join(stateDir, FAILURES_FILE);
7082
- if (!fs22.existsSync(failuresPath)) {
6995
+ const failuresPath = path18.join(stateDir, FAILURES_FILE);
6996
+ if (!fs21.existsSync(failuresPath)) {
7083
6997
  return Ok(void 0);
7084
6998
  }
7085
- const archiveDir = path19.join(stateDir, "archive");
7086
- fs22.mkdirSync(archiveDir, { recursive: true });
6999
+ const archiveDir = path18.join(stateDir, "archive");
7000
+ fs21.mkdirSync(archiveDir, { recursive: true });
7087
7001
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7088
7002
  let archiveName = `failures-${date}.md`;
7089
7003
  let counter = 2;
7090
- while (fs22.existsSync(path19.join(archiveDir, archiveName))) {
7004
+ while (fs21.existsSync(path18.join(archiveDir, archiveName))) {
7091
7005
  archiveName = `failures-${date}-${counter}.md`;
7092
7006
  counter++;
7093
7007
  }
7094
- fs22.renameSync(failuresPath, path19.join(archiveDir, archiveName));
7008
+ fs21.renameSync(failuresPath, path18.join(archiveDir, archiveName));
7095
7009
  failuresCacheMap.delete(failuresPath);
7096
7010
  return Ok(void 0);
7097
7011
  } catch (error) {
@@ -7107,11 +7021,11 @@ async function saveHandoff(projectPath, handoff, stream, session) {
7107
7021
  const dirResult = await getStateDir(projectPath, stream, session);
7108
7022
  if (!dirResult.ok) return dirResult;
7109
7023
  const stateDir = dirResult.value;
7110
- const handoffPath = path20.join(stateDir, HANDOFF_FILE);
7111
- fs23.mkdirSync(stateDir, { recursive: true });
7024
+ const handoffPath = path19.join(stateDir, HANDOFF_FILE);
7025
+ fs22.mkdirSync(stateDir, { recursive: true });
7112
7026
  const tmpPath = handoffPath + ".tmp";
7113
- fs23.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
7114
- fs23.renameSync(tmpPath, handoffPath);
7027
+ fs22.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
7028
+ fs22.renameSync(tmpPath, handoffPath);
7115
7029
  return Ok(void 0);
7116
7030
  } catch (error) {
7117
7031
  return Err(
@@ -7124,11 +7038,11 @@ async function loadHandoff(projectPath, stream, session) {
7124
7038
  const dirResult = await getStateDir(projectPath, stream, session);
7125
7039
  if (!dirResult.ok) return dirResult;
7126
7040
  const stateDir = dirResult.value;
7127
- const handoffPath = path20.join(stateDir, HANDOFF_FILE);
7128
- if (!fs23.existsSync(handoffPath)) {
7041
+ const handoffPath = path19.join(stateDir, HANDOFF_FILE);
7042
+ if (!fs22.existsSync(handoffPath)) {
7129
7043
  return Ok(null);
7130
7044
  }
7131
- const raw = fs23.readFileSync(handoffPath, "utf-8");
7045
+ const raw = fs22.readFileSync(handoffPath, "utf-8");
7132
7046
  const parsed = JSON.parse(raw);
7133
7047
  const result = HandoffSchema.safeParse(parsed);
7134
7048
  if (!result.success) {
@@ -7142,28 +7056,28 @@ async function loadHandoff(projectPath, stream, session) {
7142
7056
  }
7143
7057
  }
7144
7058
  function loadChecksFromConfig(gateConfigPath) {
7145
- if (!fs24.existsSync(gateConfigPath)) return [];
7146
- const raw = JSON.parse(fs24.readFileSync(gateConfigPath, "utf-8"));
7059
+ if (!fs23.existsSync(gateConfigPath)) return [];
7060
+ const raw = JSON.parse(fs23.readFileSync(gateConfigPath, "utf-8"));
7147
7061
  const config = GateConfigSchema.safeParse(raw);
7148
7062
  if (config.success && config.data.checks) return config.data.checks;
7149
7063
  return [];
7150
7064
  }
7151
7065
  function discoverChecksFromProject(projectPath) {
7152
7066
  const checks = [];
7153
- const packageJsonPath = path21.join(projectPath, "package.json");
7154
- if (fs24.existsSync(packageJsonPath)) {
7155
- const pkg = JSON.parse(fs24.readFileSync(packageJsonPath, "utf-8"));
7067
+ const packageJsonPath = path20.join(projectPath, "package.json");
7068
+ if (fs23.existsSync(packageJsonPath)) {
7069
+ const pkg = JSON.parse(fs23.readFileSync(packageJsonPath, "utf-8"));
7156
7070
  const scripts = pkg.scripts || {};
7157
7071
  if (scripts.test) checks.push({ name: "test", command: "npm test" });
7158
7072
  if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
7159
7073
  if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
7160
7074
  if (scripts.build) checks.push({ name: "build", command: "npm run build" });
7161
7075
  }
7162
- if (fs24.existsSync(path21.join(projectPath, "go.mod"))) {
7076
+ if (fs23.existsSync(path20.join(projectPath, "go.mod"))) {
7163
7077
  checks.push({ name: "test", command: "go test ./..." });
7164
7078
  checks.push({ name: "build", command: "go build ./..." });
7165
7079
  }
7166
- if (fs24.existsSync(path21.join(projectPath, "pyproject.toml")) || fs24.existsSync(path21.join(projectPath, "setup.py"))) {
7080
+ if (fs23.existsSync(path20.join(projectPath, "pyproject.toml")) || fs23.existsSync(path20.join(projectPath, "setup.py"))) {
7167
7081
  checks.push({ name: "test", command: "python -m pytest" });
7168
7082
  }
7169
7083
  return checks;
@@ -7203,8 +7117,8 @@ function executeCheck(check, projectPath) {
7203
7117
  }
7204
7118
  }
7205
7119
  async function runMechanicalGate(projectPath) {
7206
- const harnessDir = path21.join(projectPath, HARNESS_DIR);
7207
- const gateConfigPath = path21.join(harnessDir, GATE_CONFIG_FILE);
7120
+ const harnessDir = path20.join(projectPath, HARNESS_DIR);
7121
+ const gateConfigPath = path20.join(harnessDir, GATE_CONFIG_FILE);
7208
7122
  try {
7209
7123
  let checks = loadChecksFromConfig(gateConfigPath);
7210
7124
  if (checks.length === 0) {
@@ -7260,9 +7174,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
7260
7174
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
7261
7175
  if (!dirResult.ok) return dirResult;
7262
7176
  const sessionDir = dirResult.value;
7263
- const summaryPath = path22.join(sessionDir, SUMMARY_FILE);
7177
+ const summaryPath = path21.join(sessionDir, SUMMARY_FILE);
7264
7178
  const content = formatSummary(data);
7265
- fs25.writeFileSync(summaryPath, content);
7179
+ fs24.writeFileSync(summaryPath, content);
7266
7180
  const description = deriveIndexDescription(data);
7267
7181
  updateSessionIndex(projectPath, sessionSlug, description);
7268
7182
  return Ok(void 0);
@@ -7279,11 +7193,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
7279
7193
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7280
7194
  if (!dirResult.ok) return dirResult;
7281
7195
  const sessionDir = dirResult.value;
7282
- const summaryPath = path22.join(sessionDir, SUMMARY_FILE);
7283
- if (!fs25.existsSync(summaryPath)) {
7196
+ const summaryPath = path21.join(sessionDir, SUMMARY_FILE);
7197
+ if (!fs24.existsSync(summaryPath)) {
7284
7198
  return Ok(null);
7285
7199
  }
7286
- const content = fs25.readFileSync(summaryPath, "utf-8");
7200
+ const content = fs24.readFileSync(summaryPath, "utf-8");
7287
7201
  return Ok(content);
7288
7202
  } catch (error) {
7289
7203
  return Err(
@@ -7295,11 +7209,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
7295
7209
  }
7296
7210
  function listActiveSessions(projectPath) {
7297
7211
  try {
7298
- const indexPath2 = path22.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
7299
- if (!fs25.existsSync(indexPath2)) {
7212
+ const indexPath2 = path21.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
7213
+ if (!fs24.existsSync(indexPath2)) {
7300
7214
  return Ok(null);
7301
7215
  }
7302
- const content = fs25.readFileSync(indexPath2, "utf-8");
7216
+ const content = fs24.readFileSync(indexPath2, "utf-8");
7303
7217
  return Ok(content);
7304
7218
  } catch (error) {
7305
7219
  return Err(
@@ -7320,12 +7234,12 @@ async function loadSessionState(projectPath, sessionSlug) {
7320
7234
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7321
7235
  if (!dirResult.ok) return dirResult;
7322
7236
  const sessionDir = dirResult.value;
7323
- const filePath = path23.join(sessionDir, SESSION_STATE_FILE);
7324
- if (!fs26.existsSync(filePath)) {
7237
+ const filePath = path22.join(sessionDir, SESSION_STATE_FILE);
7238
+ if (!fs25.existsSync(filePath)) {
7325
7239
  return Ok(emptySections());
7326
7240
  }
7327
7241
  try {
7328
- const raw = fs26.readFileSync(filePath, "utf-8");
7242
+ const raw = fs25.readFileSync(filePath, "utf-8");
7329
7243
  const parsed = JSON.parse(raw);
7330
7244
  const sections = emptySections();
7331
7245
  for (const name of SESSION_SECTION_NAMES) {
@@ -7346,9 +7260,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
7346
7260
  const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
7347
7261
  if (!dirResult.ok) return dirResult;
7348
7262
  const sessionDir = dirResult.value;
7349
- const filePath = path23.join(sessionDir, SESSION_STATE_FILE);
7263
+ const filePath = path22.join(sessionDir, SESSION_STATE_FILE);
7350
7264
  try {
7351
- fs26.writeFileSync(filePath, JSON.stringify(sections, null, 2));
7265
+ fs25.writeFileSync(filePath, JSON.stringify(sections, null, 2));
7352
7266
  return Ok(void 0);
7353
7267
  } catch (error) {
7354
7268
  return Err(
@@ -7404,26 +7318,26 @@ async function archiveSession(projectPath, sessionSlug, options = {}) {
7404
7318
  const dirResult = resolveSessionDir(projectPath, sessionSlug);
7405
7319
  if (!dirResult.ok) return dirResult;
7406
7320
  const sessionDir = dirResult.value;
7407
- if (!fs27.existsSync(sessionDir)) {
7321
+ if (!fs26.existsSync(sessionDir)) {
7408
7322
  return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
7409
7323
  }
7410
- const archiveBase = path24.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
7324
+ const archiveBase = path23.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
7411
7325
  try {
7412
- fs27.mkdirSync(archiveBase, { recursive: true });
7326
+ fs26.mkdirSync(archiveBase, { recursive: true });
7413
7327
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7414
7328
  let archiveName = `${sessionSlug}-${date}`;
7415
7329
  let counter = 1;
7416
- while (fs27.existsSync(path24.join(archiveBase, archiveName))) {
7330
+ while (fs26.existsSync(path23.join(archiveBase, archiveName))) {
7417
7331
  archiveName = `${sessionSlug}-${date}-${counter}`;
7418
7332
  counter++;
7419
7333
  }
7420
- const dest = path24.join(archiveBase, archiveName);
7334
+ const dest = path23.join(archiveBase, archiveName);
7421
7335
  try {
7422
- fs27.renameSync(sessionDir, dest);
7336
+ fs26.renameSync(sessionDir, dest);
7423
7337
  } catch (renameErr) {
7424
7338
  if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
7425
- fs27.cpSync(sessionDir, dest, { recursive: true });
7426
- fs27.rmSync(sessionDir, { recursive: true });
7339
+ fs26.cpSync(sessionDir, dest, { recursive: true });
7340
+ fs26.rmSync(sessionDir, { recursive: true });
7427
7341
  } else {
7428
7342
  throw renameErr;
7429
7343
  }
@@ -7458,8 +7372,8 @@ function loadKnownHashes(eventsPath) {
7458
7372
  const cached = knownHashesCache.get(eventsPath);
7459
7373
  if (cached) return cached;
7460
7374
  const hashes = /* @__PURE__ */ new Set();
7461
- if (fs28.existsSync(eventsPath)) {
7462
- const content = fs28.readFileSync(eventsPath, "utf-8");
7375
+ if (fs27.existsSync(eventsPath)) {
7376
+ const content = fs27.readFileSync(eventsPath, "utf-8");
7463
7377
  const lines = content.split("\n").filter((line) => line.trim() !== "");
7464
7378
  for (const line of lines) {
7465
7379
  try {
@@ -7482,8 +7396,8 @@ async function emitEvent(projectPath, event, options) {
7482
7396
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
7483
7397
  if (!dirResult.ok) return dirResult;
7484
7398
  const stateDir = dirResult.value;
7485
- const eventsPath = path25.join(stateDir, EVENTS_FILE);
7486
- fs28.mkdirSync(stateDir, { recursive: true });
7399
+ const eventsPath = path24.join(stateDir, EVENTS_FILE);
7400
+ fs27.mkdirSync(stateDir, { recursive: true });
7487
7401
  const contentHash = computeEventHash(event, options?.session);
7488
7402
  const knownHashes = loadKnownHashes(eventsPath);
7489
7403
  if (knownHashes.has(contentHash)) {
@@ -7497,7 +7411,7 @@ async function emitEvent(projectPath, event, options) {
7497
7411
  if (options?.session) {
7498
7412
  fullEvent.session = options.session;
7499
7413
  }
7500
- fs28.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
7414
+ fs27.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
7501
7415
  knownHashes.add(contentHash);
7502
7416
  return Ok({ written: true });
7503
7417
  } catch (error) {
@@ -7511,11 +7425,11 @@ async function loadEvents(projectPath, options) {
7511
7425
  const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
7512
7426
  if (!dirResult.ok) return dirResult;
7513
7427
  const stateDir = dirResult.value;
7514
- const eventsPath = path25.join(stateDir, EVENTS_FILE);
7515
- if (!fs28.existsSync(eventsPath)) {
7428
+ const eventsPath = path24.join(stateDir, EVENTS_FILE);
7429
+ if (!fs27.existsSync(eventsPath)) {
7516
7430
  return Ok([]);
7517
7431
  }
7518
- const content = fs28.readFileSync(eventsPath, "utf-8");
7432
+ const content = fs27.readFileSync(eventsPath, "utf-8");
7519
7433
  const lines = content.split("\n").filter((line) => line.trim() !== "");
7520
7434
  const events = [];
7521
7435
  for (const line of lines) {
@@ -7764,11 +7678,11 @@ function nodeSubStacks(allDeps) {
7764
7678
  return found;
7765
7679
  }
7766
7680
  function detectNodeStacks(projectRoot) {
7767
- const pkgJsonPath = path26.join(projectRoot, "package.json");
7768
- if (!fs29.existsSync(pkgJsonPath)) return [];
7681
+ const pkgJsonPath = path25.join(projectRoot, "package.json");
7682
+ if (!fs28.existsSync(pkgJsonPath)) return [];
7769
7683
  const stacks = ["node"];
7770
7684
  try {
7771
- const pkgJson = JSON.parse(fs29.readFileSync(pkgJsonPath, "utf-8"));
7685
+ const pkgJson = JSON.parse(fs28.readFileSync(pkgJsonPath, "utf-8"));
7772
7686
  const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
7773
7687
  stacks.push(...nodeSubStacks(allDeps));
7774
7688
  } catch {
@@ -7777,10 +7691,10 @@ function detectNodeStacks(projectRoot) {
7777
7691
  }
7778
7692
  function detectStack(projectRoot) {
7779
7693
  const stacks = [...detectNodeStacks(projectRoot)];
7780
- if (fs29.existsSync(path26.join(projectRoot, "go.mod"))) {
7694
+ if (fs28.existsSync(path25.join(projectRoot, "go.mod"))) {
7781
7695
  stacks.push("go");
7782
7696
  }
7783
- const hasPython = fs29.existsSync(path26.join(projectRoot, "requirements.txt")) || fs29.existsSync(path26.join(projectRoot, "pyproject.toml"));
7697
+ const hasPython = fs28.existsSync(path25.join(projectRoot, "requirements.txt")) || fs28.existsSync(path25.join(projectRoot, "pyproject.toml"));
7784
7698
  if (hasPython) {
7785
7699
  stacks.push("python");
7786
7700
  }
@@ -7803,7 +7717,7 @@ function createOsvClient(options = {}) {
7803
7717
  return {
7804
7718
  async check(pkg) {
7805
7719
  const cacheKey = `${pkg.ecosystem}-${sanitizePkgName(pkg.name)}@${pkg.version ?? "latest"}.json`;
7806
- const cachePath = path27.join(cacheDir, cacheKey);
7720
+ const cachePath = path26.join(cacheDir, cacheKey);
7807
7721
  const cached = await readCache(cachePath, ttlMs);
7808
7722
  if (cached) {
7809
7723
  return { ...cached, source: "cache" };
@@ -7824,7 +7738,7 @@ function createOsvClient(options = {}) {
7824
7738
  },
7825
7739
  async clearCache() {
7826
7740
  try {
7827
- await fs31.promises.rm(cacheDir, { recursive: true, force: true });
7741
+ await fs30.promises.rm(cacheDir, { recursive: true, force: true });
7828
7742
  } catch {
7829
7743
  }
7830
7744
  }
@@ -7860,9 +7774,9 @@ function classify(advisories) {
7860
7774
  }
7861
7775
  async function readCache(cachePath, ttlMs) {
7862
7776
  try {
7863
- const stat2 = await fs31.promises.stat(cachePath);
7777
+ const stat2 = await fs30.promises.stat(cachePath);
7864
7778
  if (Date.now() - stat2.mtimeMs > ttlMs) return null;
7865
- const buf = await fs31.promises.readFile(cachePath, "utf-8");
7779
+ const buf = await fs30.promises.readFile(cachePath, "utf-8");
7866
7780
  const parsed = JSON.parse(buf);
7867
7781
  return { malicious: parsed.malicious ?? [], other: parsed.other ?? [] };
7868
7782
  } catch {
@@ -7871,8 +7785,8 @@ async function readCache(cachePath, ttlMs) {
7871
7785
  }
7872
7786
  async function writeCache(cachePath, payload) {
7873
7787
  try {
7874
- await fs31.promises.mkdir(path27.dirname(cachePath), { recursive: true });
7875
- await fs31.promises.writeFile(cachePath, JSON.stringify(payload, null, 2), "utf-8");
7788
+ await fs30.promises.mkdir(path26.dirname(cachePath), { recursive: true });
7789
+ await fs30.promises.writeFile(cachePath, JSON.stringify(payload, null, 2), "utf-8");
7876
7790
  } catch {
7877
7791
  }
7878
7792
  }
@@ -7908,13 +7822,13 @@ function getInjectionPatterns() {
7908
7822
  }
7909
7823
  function getTaintFilePath(projectRoot, sessionId) {
7910
7824
  const id = sessionId || DEFAULT_SESSION_ID;
7911
- return join39(projectRoot, ".harness", `session-taint-${id}.json`);
7825
+ return join38(projectRoot, ".harness", `session-taint-${id}.json`);
7912
7826
  }
7913
7827
  function readTaint(projectRoot, sessionId) {
7914
7828
  const filePath = getTaintFilePath(projectRoot, sessionId);
7915
7829
  let content;
7916
7830
  try {
7917
- content = readFileSync22(filePath, "utf8");
7831
+ content = readFileSync21(filePath, "utf8");
7918
7832
  } catch {
7919
7833
  return null;
7920
7834
  }
@@ -7990,14 +7904,14 @@ function clearTaint(projectRoot, sessionId) {
7990
7904
  return 0;
7991
7905
  }
7992
7906
  }
7993
- const harnessDir = join39(projectRoot, ".harness");
7907
+ const harnessDir = join38(projectRoot, ".harness");
7994
7908
  let count = 0;
7995
7909
  try {
7996
- const files = readdirSync3(harnessDir);
7910
+ const files = readdirSync2(harnessDir);
7997
7911
  for (const file of files) {
7998
7912
  if (file.startsWith("session-taint-") && file.endsWith(".json")) {
7999
7913
  try {
8000
- unlinkSync2(join39(harnessDir, file));
7914
+ unlinkSync2(join38(harnessDir, file));
8001
7915
  count++;
8002
7916
  } catch {
8003
7917
  }
@@ -8008,10 +7922,10 @@ function clearTaint(projectRoot, sessionId) {
8008
7922
  return count;
8009
7923
  }
8010
7924
  function listTaintedSessions(projectRoot) {
8011
- const harnessDir = join39(projectRoot, ".harness");
7925
+ const harnessDir = join38(projectRoot, ".harness");
8012
7926
  const sessions = [];
8013
7927
  try {
8014
- const files = readdirSync3(harnessDir);
7928
+ const files = readdirSync2(harnessDir);
8015
7929
  for (const file of files) {
8016
7930
  if (file.startsWith("session-taint-") && file.endsWith(".json")) {
8017
7931
  const sessionId = file.replace("session-taint-", "").replace(".json", "");
@@ -8080,7 +7994,7 @@ function securityFindingId(finding) {
8080
7994
  }
8081
7995
  async function runValidateCheck(projectRoot, config) {
8082
7996
  const issues = [];
8083
- const agentsPath = path28.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
7997
+ const agentsPath = path27.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8084
7998
  const result = await validateAgentsMap(agentsPath);
8085
7999
  if (!result.ok) {
8086
8000
  issues.push({ severity: "error", message: result.error.message });
@@ -8137,7 +8051,7 @@ async function runDepsCheck(projectRoot, config) {
8137
8051
  }
8138
8052
  async function runDocsCheck(projectRoot, config) {
8139
8053
  const issues = [];
8140
- const docsDir = path28.join(projectRoot, config.docsDir ?? "docs");
8054
+ const docsDir = path27.join(projectRoot, config.docsDir ?? "docs");
8141
8055
  const entropyConfig = config.entropy || {};
8142
8056
  const result = await checkDocCoverage("project", {
8143
8057
  docsDir,
@@ -8316,7 +8230,7 @@ async function runTraceabilityCheck(projectRoot, config) {
8316
8230
  const issues = [];
8317
8231
  const traceConfig = config.traceability || {};
8318
8232
  if (traceConfig.enabled === false) return issues;
8319
- const graphDir = path28.join(projectRoot, ".harness", "graph");
8233
+ const graphDir = path27.join(projectRoot, ".harness", "graph");
8320
8234
  const store = new GraphStore();
8321
8235
  const loaded = await store.load(graphDir);
8322
8236
  if (!loaded) {
@@ -8494,7 +8408,7 @@ async function runMechanicalChecks(options) {
8494
8408
  };
8495
8409
  if (!skip.includes("validate")) {
8496
8410
  try {
8497
- const agentsPath = path29.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8411
+ const agentsPath = path28.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8498
8412
  const result = await validateAgentsMap(agentsPath);
8499
8413
  if (!result.ok) {
8500
8414
  statuses.validate = "fail";
@@ -8531,7 +8445,7 @@ async function runMechanicalChecks(options) {
8531
8445
  statuses.validate = "fail";
8532
8446
  findings.push({
8533
8447
  tool: "validate",
8534
- file: path29.join(projectRoot, "AGENTS.md"),
8448
+ file: path28.join(projectRoot, "AGENTS.md"),
8535
8449
  message: err instanceof Error ? err.message : String(err),
8536
8450
  severity: "error"
8537
8451
  });
@@ -8595,7 +8509,7 @@ async function runMechanicalChecks(options) {
8595
8509
  (async () => {
8596
8510
  const localFindings = [];
8597
8511
  try {
8598
- const docsDir = path29.join(projectRoot, config.docsDir ?? "docs");
8512
+ const docsDir = path28.join(projectRoot, config.docsDir ?? "docs");
8599
8513
  const result = await checkDocCoverage("project", { docsDir });
8600
8514
  if (!result.ok) {
8601
8515
  statuses["check-docs"] = "warn";
@@ -8622,7 +8536,7 @@ async function runMechanicalChecks(options) {
8622
8536
  statuses["check-docs"] = "warn";
8623
8537
  localFindings.push({
8624
8538
  tool: "check-docs",
8625
- file: path29.join(projectRoot, "docs"),
8539
+ file: path28.join(projectRoot, "docs"),
8626
8540
  message: err instanceof Error ? err.message : String(err),
8627
8541
  severity: "warning"
8628
8542
  });
@@ -8719,18 +8633,18 @@ function computeContextBudget(diffLines) {
8719
8633
  return diffLines;
8720
8634
  }
8721
8635
  function isWithinProject(absPath, projectRoot) {
8722
- const resolvedRoot = path30.resolve(projectRoot) + path30.sep;
8723
- const resolvedPath = path30.resolve(absPath);
8724
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path30.resolve(projectRoot);
8636
+ const resolvedRoot = path29.resolve(projectRoot) + path29.sep;
8637
+ const resolvedPath = path29.resolve(absPath);
8638
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path29.resolve(projectRoot);
8725
8639
  }
8726
8640
  async function readContextFile(projectRoot, filePath, reason) {
8727
- const absPath = path30.isAbsolute(filePath) ? filePath : path30.join(projectRoot, filePath);
8641
+ const absPath = path29.isAbsolute(filePath) ? filePath : path29.join(projectRoot, filePath);
8728
8642
  if (!isWithinProject(absPath, projectRoot)) return null;
8729
8643
  const result = await readFileContent(absPath);
8730
8644
  if (!result.ok) return null;
8731
8645
  const content = result.value;
8732
8646
  const lines = content.split("\n").length;
8733
- const relPath2 = path30.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
8647
+ const relPath2 = path29.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
8734
8648
  return { path: relPath2, content, reason, lines };
8735
8649
  }
8736
8650
  function extractImportSources2(content) {
@@ -8745,24 +8659,24 @@ function extractImportSources2(content) {
8745
8659
  }
8746
8660
  async function resolveImportPath(projectRoot, fromFile, importSource) {
8747
8661
  if (!importSource.startsWith(".")) return null;
8748
- const fromDir = path30.dirname(path30.join(projectRoot, fromFile));
8749
- const basePath = path30.resolve(fromDir, importSource);
8662
+ const fromDir = path29.dirname(path29.join(projectRoot, fromFile));
8663
+ const basePath = path29.resolve(fromDir, importSource);
8750
8664
  if (!isWithinProject(basePath, projectRoot)) return null;
8751
8665
  const relBase = relativePosix(projectRoot, basePath);
8752
- const sourceExt = path30.extname(relBase);
8666
+ const sourceExt = path29.extname(relBase);
8753
8667
  const candidates = [];
8754
8668
  const fallbacks = JS_EXT_FALLBACKS[sourceExt];
8755
8669
  if (fallbacks) {
8756
8670
  const stripped = relBase.slice(0, -sourceExt.length);
8757
8671
  for (const ext of fallbacks) candidates.push(stripped + ext);
8758
- candidates.push(path30.join(stripped, "index.ts"));
8759
- candidates.push(path30.join(stripped, "index.tsx"));
8760
- candidates.push(path30.join(stripped, "index.jsx"));
8672
+ candidates.push(path29.join(stripped, "index.ts"));
8673
+ candidates.push(path29.join(stripped, "index.tsx"));
8674
+ candidates.push(path29.join(stripped, "index.jsx"));
8761
8675
  }
8762
8676
  candidates.push(relBase + ".ts", relBase + ".tsx", relBase + ".mts");
8763
- candidates.push(path30.join(relBase, "index.ts"));
8677
+ candidates.push(path29.join(relBase, "index.ts"));
8764
8678
  for (const candidate of candidates) {
8765
- const absCandidate = path30.join(projectRoot, candidate);
8679
+ const absCandidate = path29.join(projectRoot, candidate);
8766
8680
  if (await fileExists(absCandidate)) {
8767
8681
  return candidate;
8768
8682
  }
@@ -8770,7 +8684,7 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
8770
8684
  return null;
8771
8685
  }
8772
8686
  async function findTestFiles(projectRoot, sourceFile) {
8773
- const baseName = path30.basename(sourceFile, path30.extname(sourceFile));
8687
+ const baseName = path29.basename(sourceFile, path29.extname(sourceFile));
8774
8688
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
8775
8689
  const results = await findFiles(pattern, projectRoot);
8776
8690
  return results.map((f) => relativePosix(projectRoot, f));
@@ -10069,7 +9983,7 @@ function normalizePath(filePath, projectRoot) {
10069
9983
  let normalized = filePath;
10070
9984
  normalized = normalized.replace(/\\/g, "/");
10071
9985
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
10072
- if (path31.isAbsolute(normalized)) {
9986
+ if (path30.isAbsolute(normalized)) {
10073
9987
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
10074
9988
  if (normalized.startsWith(root)) {
10075
9989
  normalized = normalized.slice(root.length);
@@ -10081,12 +9995,12 @@ function normalizePath(filePath, projectRoot) {
10081
9995
  return normalized;
10082
9996
  }
10083
9997
  function resolveImportPath2(currentFile, importPath) {
10084
- const dir = path31.dirname(currentFile);
10085
- let resolved = path31.join(dir, importPath).replace(/\\/g, "/");
9998
+ const dir = path30.dirname(currentFile);
9999
+ let resolved = path30.join(dir, importPath).replace(/\\/g, "/");
10086
10000
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
10087
10001
  resolved += ".ts";
10088
10002
  }
10089
- return path31.normalize(resolved).replace(/\\/g, "/");
10003
+ return path30.normalize(resolved).replace(/\\/g, "/");
10090
10004
  }
10091
10005
  function enqueueImports(content, current, visited, queue, maxDepth) {
10092
10006
  const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
@@ -10118,7 +10032,7 @@ function isMechanicallyExcluded(finding, exclusionSet, projectRoot) {
10118
10032
  const normalizedFile = normalizePath(finding.file, projectRoot);
10119
10033
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange)) return true;
10120
10034
  if (exclusionSet.isExcluded(finding.file, finding.lineRange)) return true;
10121
- const absoluteFile = path31.isAbsolute(finding.file) ? finding.file : path31.join(projectRoot, finding.file).replace(/\\/g, "/");
10035
+ const absoluteFile = path30.isAbsolute(finding.file) ? finding.file : path30.join(projectRoot, finding.file).replace(/\\/g, "/");
10122
10036
  return exclusionSet.isExcluded(absoluteFile, finding.lineRange);
10123
10037
  }
10124
10038
  async function validateWithGraph(crossFileRefs, graph) {
@@ -11112,45 +11026,363 @@ function findParallelGroups(nodes) {
11112
11026
  orphaned: [...index.orphaned].sort()
11113
11027
  };
11114
11028
  }
11115
- function serializeRoadmap(roadmap) {
11116
- const lines = [];
11117
- lines.push("---");
11118
- lines.push(`project: ${roadmap.frontmatter.project}`);
11119
- lines.push(`version: ${roadmap.frontmatter.version}`);
11120
- if (roadmap.frontmatter.created) {
11121
- lines.push(`created: ${roadmap.frontmatter.created}`);
11029
+ function findingsMatch(a, b) {
11030
+ if (a.id && b.id) return a.id === b.id;
11031
+ return JSON.stringify(a) === JSON.stringify(b);
11032
+ }
11033
+ function deriveBlockingFindings(findings) {
11034
+ return findings.filter((f) => f.severity === "critical");
11035
+ }
11036
+ function deriveExitCode(assessment, blockingFindings) {
11037
+ return blockingFindings.length > 0 || assessment === "request-changes" ? 1 : 0;
11038
+ }
11039
+ function parseCiReviewVerdict(input) {
11040
+ return CiReviewVerdictSchema.parse(input);
11041
+ }
11042
+ function buildCiReviewVerdict(parts) {
11043
+ const findings = z12.array(ReviewFindingSchema).parse(parts.findings ?? []);
11044
+ const blockingFindings = deriveBlockingFindings(findings);
11045
+ const exitCode = deriveExitCode(parts.assessment, blockingFindings);
11046
+ return parseCiReviewVerdict({
11047
+ schemaVersion: CI_REVIEW_VERDICT_SCHEMA_VERSION,
11048
+ runner: parts.runner,
11049
+ ranLlmTier: parts.ranLlmTier,
11050
+ assessment: parts.assessment,
11051
+ findings,
11052
+ blockingFindings,
11053
+ exitCode,
11054
+ skipped: parts.skipped ?? false,
11055
+ skipReason: parts.skipReason
11056
+ });
11057
+ }
11058
+ function parseClaudeVerdict(raw) {
11059
+ const envelope = JSON.parse(raw);
11060
+ if (envelope.is_error === true) {
11061
+ throw new Error(
11062
+ `parseClaudeVerdict: claude transcript reported is_error=true (subtype=${envelope.subtype ?? "unknown"})`
11063
+ );
11122
11064
  }
11123
- if (roadmap.frontmatter.updated) {
11124
- lines.push(`updated: ${roadmap.frontmatter.updated}`);
11065
+ if (typeof envelope.result !== "string") {
11066
+ throw new Error(
11067
+ "parseClaudeVerdict: claude transcript envelope is missing a string `.result` field"
11068
+ );
11125
11069
  }
11126
- lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
11127
- lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
11128
- lines.push("---");
11129
- lines.push("");
11130
- lines.push("# Roadmap");
11131
- for (const milestone of roadmap.milestones) {
11132
- lines.push("");
11133
- lines.push(serializeMilestoneHeading(milestone));
11134
- for (const feature of milestone.features) {
11135
- lines.push("");
11136
- lines.push(...serializeFeature(feature));
11070
+ const inner = JSON.parse(envelope.result);
11071
+ return buildCiReviewVerdict({
11072
+ runner: "claude",
11073
+ ranLlmTier: true,
11074
+ assessment: inner.assessment,
11075
+ findings: inner.findings
11076
+ });
11077
+ }
11078
+ function parseCodexVerdict(raw) {
11079
+ let lastAgentMessageText;
11080
+ for (const line of raw.split("\n")) {
11081
+ const trimmed = line.trim();
11082
+ if (trimmed.length === 0) continue;
11083
+ let event;
11084
+ try {
11085
+ event = JSON.parse(trimmed);
11086
+ } catch {
11087
+ continue;
11088
+ }
11089
+ if (event.type === "item.completed" && event.item?.type === "agent_message" && typeof event.item.text === "string") {
11090
+ lastAgentMessageText = event.item.text;
11137
11091
  }
11138
11092
  }
11139
- if (roadmap.assignmentHistory && roadmap.assignmentHistory.length > 0) {
11140
- lines.push("");
11141
- lines.push(...serializeAssignmentHistory(roadmap.assignmentHistory));
11093
+ if (lastAgentMessageText === void 0) {
11094
+ throw new Error(
11095
+ "parseCodexVerdict: no `item.completed` agent_message event found in codex JSONL stream"
11096
+ );
11142
11097
  }
11143
- lines.push("");
11144
- return lines.join("\n");
11098
+ const inner = JSON.parse(lastAgentMessageText);
11099
+ return buildCiReviewVerdict({
11100
+ runner: "codex",
11101
+ ranLlmTier: true,
11102
+ assessment: inner.assessment,
11103
+ findings: inner.findings
11104
+ });
11145
11105
  }
11146
- function serializeMilestoneHeading(milestone) {
11147
- return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
11106
+ function stripJsonFence(text) {
11107
+ const fence = /^```(?:json)?\s*\n?([\s\S]*?)\n?```$/i.exec(text.trim());
11108
+ const inner = fence?.[1];
11109
+ return inner !== void 0 ? inner.trim() : text;
11148
11110
  }
11149
- function orDash(value) {
11150
- return value ?? EM_DASH2;
11111
+ function extractFirstJsonObject(text) {
11112
+ const start = text.indexOf("{");
11113
+ if (start === -1) return void 0;
11114
+ let depth = 0;
11115
+ for (let i = start; i < text.length; i++) {
11116
+ const ch = text[i];
11117
+ if (ch === "{") depth++;
11118
+ else if (ch === "}") {
11119
+ depth--;
11120
+ if (depth === 0) return text.slice(start, i + 1);
11121
+ }
11122
+ }
11123
+ return void 0;
11151
11124
  }
11152
- function listOrDash(items) {
11153
- return items.length > 0 ? items.join(", ") : EM_DASH2;
11125
+ function parseAntigravityVerdict(raw) {
11126
+ const unfenced = stripJsonFence(raw.trim());
11127
+ let inner;
11128
+ try {
11129
+ inner = JSON.parse(unfenced);
11130
+ } catch {
11131
+ const candidate = extractFirstJsonObject(unfenced);
11132
+ if (candidate === void 0) {
11133
+ throw new Error(
11134
+ "parseAntigravityVerdict: no parseable JSON object found in antigravity (`agy --print`) output"
11135
+ );
11136
+ }
11137
+ try {
11138
+ inner = JSON.parse(candidate);
11139
+ } catch {
11140
+ throw new Error(
11141
+ "parseAntigravityVerdict: extracted candidate from antigravity output was not valid JSON"
11142
+ );
11143
+ }
11144
+ }
11145
+ return buildCiReviewVerdict({
11146
+ runner: "antigravity",
11147
+ ranLlmTier: true,
11148
+ assessment: inner.assessment,
11149
+ findings: inner.findings
11150
+ });
11151
+ }
11152
+ function parseLocalVerdict(raw) {
11153
+ const parsed = JSON.parse(raw);
11154
+ const assessment = VERDICT_MAP[parsed.assessment ?? "comment"] ?? "comment";
11155
+ return buildCiReviewVerdict({
11156
+ runner: "local",
11157
+ ranLlmTier: true,
11158
+ assessment,
11159
+ findings: parsed.findings
11160
+ });
11161
+ }
11162
+ function presetKind(id) {
11163
+ return RUNNER_PRESETS[id].kind;
11164
+ }
11165
+ function isSupportedRunner(id) {
11166
+ return RUNNER_PRESETS[id].supported === true;
11167
+ }
11168
+ function parseGeminiVerdict(raw) {
11169
+ const parsed = JSON.parse(raw);
11170
+ const assessment = VERDICT_MAP2[parsed.review?.verdict ?? "comment"] ?? "comment";
11171
+ return buildCiReviewVerdict({
11172
+ runner: "gemini",
11173
+ ranLlmTier: true,
11174
+ assessment,
11175
+ findings: parsed.review?.issues
11176
+ });
11177
+ }
11178
+ function diffToStdin(diff2) {
11179
+ return Array.from(diff2.fileDiffs.values()).join("\n");
11180
+ }
11181
+ function deriveAssessment(findings) {
11182
+ if (findings.some((f) => f.severity === "critical")) return "request-changes";
11183
+ if (findings.some((f) => f.severity === "important")) return "comment";
11184
+ return "approve";
11185
+ }
11186
+ function maxAssessment(a, b) {
11187
+ return CI_ASSESSMENTS.indexOf(a) >= CI_ASSESSMENTS.indexOf(b) ? a : b;
11188
+ }
11189
+ function floorOnlyResult(args) {
11190
+ const verdict = buildCiReviewVerdict({
11191
+ runner: "floor-only",
11192
+ ranLlmTier: false,
11193
+ assessment: args.assessment,
11194
+ findings: args.findings,
11195
+ skipped: args.skipped,
11196
+ skipReason: args.skipReason
11197
+ });
11198
+ return {
11199
+ verdict,
11200
+ exitCode: applyThreshold(verdict, args.blockOn, args.requiredRunnerFailed),
11201
+ terminalOutput: summarize(verdict),
11202
+ llmSkipReason: args.skipReason,
11203
+ ranLlmTier: false
11204
+ };
11205
+ }
11206
+ async function runAgentCliRunner(preset, ctx) {
11207
+ const instruction = "Run the harness code-review skill on the unified diff piped via STDIN. Emit ONLY the CiReviewVerdict JSON ({assessment, findings}).";
11208
+ const { command, args } = preset.headlessInvocation({ instruction });
11209
+ const { stdout } = await ctx.execFile(command, args, {
11210
+ stdin: diffToStdin(ctx.diff),
11211
+ env: ctx.env,
11212
+ timeoutMs: ctx.options.execTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS,
11213
+ maxStdoutBytes: ctx.options.execMaxStdoutBytes ?? DEFAULT_EXEC_MAX_STDOUT_BYTES
11214
+ });
11215
+ return preset.verdictParser(stdout).findings;
11216
+ }
11217
+ async function runEndpointRunner(preset, endpoint, model, invoke, ctx) {
11218
+ const instruction = "Review the unified diff and emit ONLY the CiReviewVerdict JSON ({assessment, findings}).";
11219
+ const raw = await invoke({ endpoint, model, instruction, diff: diffToStdin(ctx.diff) });
11220
+ return preset.verdictParser(raw).findings;
11221
+ }
11222
+ async function runLlmTier(runner, ctx) {
11223
+ const preset = RUNNER_PRESETS[runner];
11224
+ if (preset.supported !== true) {
11225
+ return { ...EMPTY_TIER, skipReason: `LLM tier skipped \u2014 runner '${runner}' is unsupported` };
11226
+ }
11227
+ if (preset.kind === "agent-cli") {
11228
+ if (!ctx.env[preset.secretEnvVar]) {
11229
+ return {
11230
+ ...EMPTY_TIER,
11231
+ skipReason: `LLM tier skipped \u2014 secret ${preset.secretEnvVar} not set (floor-only)`
11232
+ };
11233
+ }
11234
+ try {
11235
+ return {
11236
+ findings: await runAgentCliRunner(preset, ctx),
11237
+ ranLlmTier: true,
11238
+ requiredRunnerFailed: false
11239
+ };
11240
+ } catch (err) {
11241
+ return {
11242
+ ...EMPTY_TIER,
11243
+ requiredRunnerFailed: true,
11244
+ skipReason: `LLM tier failed \u2014 ${err.message}`
11245
+ };
11246
+ }
11247
+ }
11248
+ const invoke = ctx.options.localInvoke ?? preset.invoke;
11249
+ const endpoint = ctx.env[preset.endpointEnvVar];
11250
+ const model = ctx.env[preset.modelEnvVar];
11251
+ if (!invoke || !endpoint || !model) {
11252
+ return {
11253
+ ...EMPTY_TIER,
11254
+ skipReason: "LLM tier skipped \u2014 local endpoint not configured (no invoke seam or missing endpoint/model env)"
11255
+ };
11256
+ }
11257
+ try {
11258
+ return {
11259
+ findings: await runEndpointRunner(preset, endpoint, model, invoke, ctx),
11260
+ ranLlmTier: true,
11261
+ requiredRunnerFailed: false
11262
+ };
11263
+ } catch (err) {
11264
+ return {
11265
+ ...EMPTY_TIER,
11266
+ requiredRunnerFailed: true,
11267
+ skipReason: `LLM tier failed \u2014 ${err.message}`
11268
+ };
11269
+ }
11270
+ }
11271
+ async function runCiReview(options) {
11272
+ const { projectRoot, diff: diff2, commitMessage = "", runner, blockOn = "request-changes" } = options;
11273
+ const floor = await runReviewPipeline({
11274
+ projectRoot,
11275
+ diff: diff2,
11276
+ commitMessage,
11277
+ flags: { ci: true, comment: false, deep: false, noMechanical: false }
11278
+ });
11279
+ if (floor.skipped) {
11280
+ return floorOnlyResult({
11281
+ assessment: "request-changes",
11282
+ findings: [],
11283
+ skipped: true,
11284
+ skipReason: `floor review skipped \u2014 ${floor.skipReason ?? "ineligible"} (CI fails closed)`,
11285
+ blockOn,
11286
+ requiredRunnerFailed: true
11287
+ });
11288
+ }
11289
+ const floorFindings = floor.findings;
11290
+ const floorAssessment = maxAssessment(
11291
+ floor.assessment ?? "approve",
11292
+ deriveAssessment(floorFindings)
11293
+ );
11294
+ if (floor.stoppedByMechanical) {
11295
+ return floorOnlyResult({
11296
+ assessment: floorAssessment,
11297
+ findings: floorFindings,
11298
+ skipped: false,
11299
+ skipReason: "LLM tier skipped \u2014 floor mechanical-stop (short-circuit)",
11300
+ blockOn,
11301
+ requiredRunnerFailed: false
11302
+ });
11303
+ }
11304
+ const env = options.env ?? process.env;
11305
+ const execFile3 = options.execFile ?? defaultExecFile;
11306
+ const tier = runner ? await runLlmTier(runner, { diff: diff2, env, execFile: execFile3, options }) : EMPTY_TIER;
11307
+ const {
11308
+ findings: llmFindings,
11309
+ ranLlmTier,
11310
+ requiredRunnerFailed,
11311
+ skipReason: llmSkipReason
11312
+ } = tier;
11313
+ const mergedFindings = [...floorFindings, ...llmFindings];
11314
+ const mergedAssessment = maxAssessment(floorAssessment, deriveAssessment(mergedFindings));
11315
+ const verdict = buildCiReviewVerdict({
11316
+ runner: ranLlmTier ? runner : "floor-only",
11317
+ ranLlmTier,
11318
+ assessment: mergedAssessment,
11319
+ findings: mergedFindings,
11320
+ ...llmSkipReason ? { skipReason: llmSkipReason } : {}
11321
+ });
11322
+ return {
11323
+ verdict,
11324
+ exitCode: applyThreshold(verdict, blockOn, requiredRunnerFailed),
11325
+ terminalOutput: summarize(verdict),
11326
+ ...llmSkipReason ? { llmSkipReason } : {},
11327
+ ranLlmTier
11328
+ };
11329
+ }
11330
+ function applyThreshold(v, blockOn, requiredRunnerFailed) {
11331
+ if (requiredRunnerFailed) return 1;
11332
+ if (blockOn === "none") return 0;
11333
+ const rank = (a) => CI_ASSESSMENTS.indexOf(a);
11334
+ return rank(v.assessment) >= rank(blockOn) ? 1 : 0;
11335
+ }
11336
+ function summarize(v) {
11337
+ const lines = [
11338
+ `runner: ${v.runner}`,
11339
+ `ranLlmTier: ${v.ranLlmTier}`,
11340
+ `assessment: ${v.assessment}`,
11341
+ `findings: ${v.findings.length} (blocking: ${v.blockingFindings.length})`,
11342
+ `exitCode: ${v.exitCode}`
11343
+ ];
11344
+ if (v.skipReason) lines.push(`note: ${v.skipReason}`);
11345
+ return lines.join("\n");
11346
+ }
11347
+ function serializeRoadmap(roadmap) {
11348
+ const lines = [];
11349
+ lines.push("---");
11350
+ lines.push(`project: ${roadmap.frontmatter.project}`);
11351
+ lines.push(`version: ${roadmap.frontmatter.version}`);
11352
+ if (roadmap.frontmatter.created) {
11353
+ lines.push(`created: ${roadmap.frontmatter.created}`);
11354
+ }
11355
+ if (roadmap.frontmatter.updated) {
11356
+ lines.push(`updated: ${roadmap.frontmatter.updated}`);
11357
+ }
11358
+ lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
11359
+ lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
11360
+ lines.push("---");
11361
+ lines.push("");
11362
+ lines.push("# Roadmap");
11363
+ for (const milestone of roadmap.milestones) {
11364
+ lines.push("");
11365
+ lines.push(serializeMilestoneHeading(milestone));
11366
+ for (const feature of milestone.features) {
11367
+ lines.push("");
11368
+ lines.push(...serializeFeature(feature));
11369
+ }
11370
+ }
11371
+ if (roadmap.assignmentHistory && roadmap.assignmentHistory.length > 0) {
11372
+ lines.push("");
11373
+ lines.push(...serializeAssignmentHistory(roadmap.assignmentHistory));
11374
+ }
11375
+ lines.push("");
11376
+ return lines.join("\n");
11377
+ }
11378
+ function serializeMilestoneHeading(milestone) {
11379
+ return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
11380
+ }
11381
+ function orDash(value) {
11382
+ return value ?? EM_DASH2;
11383
+ }
11384
+ function listOrDash(items) {
11385
+ return items.length > 0 ? items.join(", ") : EM_DASH2;
11154
11386
  }
11155
11387
  function serializeExtendedLines(feature) {
11156
11388
  const hasExtended = feature.assignee !== null || feature.priority !== null || feature.externalId !== null || feature.updatedAt !== null;
@@ -11194,7 +11426,7 @@ function isRegression(from, to) {
11194
11426
  }
11195
11427
  function collectAutopilotStatuses(autopilotPath, featurePlans, allTaskStatuses) {
11196
11428
  try {
11197
- const raw = fs32.readFileSync(autopilotPath, "utf-8");
11429
+ const raw = fs31.readFileSync(autopilotPath, "utf-8");
11198
11430
  const autopilot = JSON.parse(raw);
11199
11431
  if (!autopilot.phases) return;
11200
11432
  const linkedPhases = autopilot.phases.filter(
@@ -11226,10 +11458,10 @@ function inferStatus(feature, projectPath, allFeatures) {
11226
11458
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
11227
11459
  const useRootState = featuresWithPlans.length <= 1;
11228
11460
  if (useRootState) {
11229
- const rootStatePath = path32.join(projectPath, ".harness", "state.json");
11230
- if (fs32.existsSync(rootStatePath)) {
11461
+ const rootStatePath = path31.join(projectPath, ".harness", "state.json");
11462
+ if (fs31.existsSync(rootStatePath)) {
11231
11463
  try {
11232
- const raw = fs32.readFileSync(rootStatePath, "utf-8");
11464
+ const raw = fs31.readFileSync(rootStatePath, "utf-8");
11233
11465
  const state = JSON.parse(raw);
11234
11466
  if (state.progress) {
11235
11467
  for (const status of Object.values(state.progress)) {
@@ -11240,14 +11472,14 @@ function inferStatus(feature, projectPath, allFeatures) {
11240
11472
  }
11241
11473
  }
11242
11474
  }
11243
- const sessionsDir = path32.join(projectPath, ".harness", "sessions");
11244
- if (fs32.existsSync(sessionsDir)) {
11475
+ const sessionsDir = path31.join(projectPath, ".harness", "sessions");
11476
+ if (fs31.existsSync(sessionsDir)) {
11245
11477
  try {
11246
- const sessionDirs = fs32.readdirSync(sessionsDir, { withFileTypes: true });
11478
+ const sessionDirs = fs31.readdirSync(sessionsDir, { withFileTypes: true });
11247
11479
  for (const entry of sessionDirs) {
11248
11480
  if (!entry.isDirectory()) continue;
11249
- const autopilotPath = path32.join(sessionsDir, entry.name, "autopilot-state.json");
11250
- if (!fs32.existsSync(autopilotPath)) continue;
11481
+ const autopilotPath = path31.join(sessionsDir, entry.name, "autopilot-state.json");
11482
+ if (!fs31.existsSync(autopilotPath)) continue;
11251
11483
  collectAutopilotStatuses(autopilotPath, feature.plans, allTaskStatuses);
11252
11484
  }
11253
11485
  } catch {
@@ -11289,6 +11521,171 @@ function applySyncChanges(roadmap, changes) {
11289
11521
  }
11290
11522
  roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
11291
11523
  }
11524
+ function isEmptySummary(summary) {
11525
+ const trimmed = summary.trim();
11526
+ return trimmed === "" || trimmed === EM_DASH3;
11527
+ }
11528
+ function rowWouldChange(currentSpec, currentSummary, args) {
11529
+ const specChanges = currentSpec !== args.spec;
11530
+ const summaryChanges = args.summary !== void 0 && args.summary !== "" && isEmptySummary(currentSummary);
11531
+ return specChanges || summaryChanges;
11532
+ }
11533
+ function decidePromotionForRow(status, currentSpec, currentSummary, args) {
11534
+ switch (status) {
11535
+ case "in-progress":
11536
+ return { action: "refuse", reason: "in-progress" };
11537
+ case "done":
11538
+ return { action: "refuse", reason: "done" };
11539
+ case "backlog":
11540
+ return { action: "set-planned" };
11541
+ // planned, blocked, needs-human — lateral states (see status-rank.ts).
11542
+ // Refresh the spec link but preserve status; warn upstream.
11543
+ default:
11544
+ return rowWouldChange(currentSpec, currentSummary, args) ? { action: "update-spec" } : { action: "noop" };
11545
+ }
11546
+ }
11547
+ function applySpecAndSummary(feature, args) {
11548
+ feature.spec = args.spec;
11549
+ if (args.summary !== void 0 && args.summary !== "" && isEmptySummary(feature.summary)) {
11550
+ feature.summary = args.summary;
11551
+ }
11552
+ }
11553
+ function levenshtein(a, b) {
11554
+ const m = a.length;
11555
+ const n = b.length;
11556
+ if (m === 0) return n;
11557
+ if (n === 0) return m;
11558
+ const row = new Array(n + 1);
11559
+ for (let j = 0; j <= n; j++) row[j] = j;
11560
+ for (let i = 1; i <= m; i++) {
11561
+ let prevDiag = row[0];
11562
+ row[0] = i;
11563
+ for (let j = 1; j <= n; j++) {
11564
+ const above = row[j];
11565
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
11566
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prevDiag + cost);
11567
+ prevDiag = above;
11568
+ }
11569
+ }
11570
+ return row[n];
11571
+ }
11572
+ function isProbableTypo(distance, queryLen) {
11573
+ if (distance === 0) return false;
11574
+ return distance <= Math.max(2, Math.floor(queryLen / 4));
11575
+ }
11576
+ function locateRows(roadmap) {
11577
+ const rows = [];
11578
+ roadmap.milestones.forEach((milestone, milestoneIndex) => {
11579
+ milestone.features.forEach((feature, featureIndex) => {
11580
+ rows.push({ milestoneIndex, featureIndex, milestoneName: milestone.name, feature });
11581
+ });
11582
+ });
11583
+ return rows;
11584
+ }
11585
+ function cloneRoadmap(roadmap) {
11586
+ return structuredClone(roadmap);
11587
+ }
11588
+ function makeCreatedFeature(args) {
11589
+ return {
11590
+ name: args.feature.trim(),
11591
+ status: "planned",
11592
+ spec: args.spec,
11593
+ plans: [],
11594
+ blockedBy: [],
11595
+ summary: args.summary ?? "",
11596
+ assignee: null,
11597
+ priority: null,
11598
+ externalId: null,
11599
+ updatedAt: null
11600
+ };
11601
+ }
11602
+ function appendCreatedRow(roadmap, args) {
11603
+ const next = cloneRoadmap(roadmap);
11604
+ let milestone = next.milestones.find(
11605
+ (m) => m.name.toLowerCase() === DEFAULT_MILESTONE.toLowerCase()
11606
+ );
11607
+ if (!milestone) {
11608
+ milestone = { name: DEFAULT_MILESTONE, isBacklog: false, features: [] };
11609
+ next.milestones.push(milestone);
11610
+ }
11611
+ milestone.features.push(makeCreatedFeature(args));
11612
+ return next;
11613
+ }
11614
+ function promoteFeature(roadmap, args) {
11615
+ const query = args.feature.trim();
11616
+ const queryLower = query.toLowerCase();
11617
+ const rows = locateRows(roadmap);
11618
+ const exact = rows.filter((r) => r.feature.name.trim().toLowerCase() === queryLower);
11619
+ if (exact.length > 1) {
11620
+ const matches = exact.map((r) => `${r.milestoneName} > ${r.feature.name}`);
11621
+ return {
11622
+ result: {
11623
+ ok: false,
11624
+ reason: "ambiguous",
11625
+ feature: query,
11626
+ detail: `"${query}" matches ${matches.length} rows across milestones. Re-invoke milestone-qualified: ${matches.join(", ")}.`,
11627
+ matches
11628
+ },
11629
+ nextRoadmap: roadmap
11630
+ };
11631
+ }
11632
+ if (exact.length === 1) {
11633
+ const located = exact[0];
11634
+ const decision = decidePromotionForRow(
11635
+ located.feature.status,
11636
+ located.feature.spec,
11637
+ located.feature.summary,
11638
+ args
11639
+ );
11640
+ if (decision.action === "refuse") {
11641
+ const detail = decision.reason === "in-progress" ? `"${query}" is in-progress: an agent is dispatched against this row. Stop the agent or use a different feature name.` : `"${query}" is already 'done'. To revise a shipped feature, use a new name.`;
11642
+ return {
11643
+ result: { ok: false, reason: decision.reason, detail, feature: query },
11644
+ nextRoadmap: roadmap
11645
+ };
11646
+ }
11647
+ if (decision.action === "noop") {
11648
+ return { result: { ok: true, transitioned: "noop", feature: query }, nextRoadmap: roadmap };
11649
+ }
11650
+ const next = cloneRoadmap(roadmap);
11651
+ const target = next.milestones[located.milestoneIndex].features[located.featureIndex];
11652
+ if (decision.action === "set-planned") {
11653
+ target.status = "planned";
11654
+ applySpecAndSummary(target, args);
11655
+ return {
11656
+ result: { ok: true, transitioned: "backlog\u2192planned", feature: query },
11657
+ nextRoadmap: next
11658
+ };
11659
+ }
11660
+ applySpecAndSummary(target, args);
11661
+ return {
11662
+ result: { ok: true, transitioned: "spec-updated", feature: query },
11663
+ nextRoadmap: next
11664
+ };
11665
+ }
11666
+ const ranked = rows.map((r) => ({
11667
+ name: r.feature.name,
11668
+ distance: levenshtein(queryLower, r.feature.name.toLowerCase())
11669
+ })).sort((a, b) => a.distance - b.distance);
11670
+ const nearest = ranked[0];
11671
+ if (nearest && isProbableTypo(nearest.distance, queryLower.length)) {
11672
+ const closestMatches = ranked.slice(0, MAX_CLOSEST_MATCHES).map((r) => r.name);
11673
+ return {
11674
+ result: {
11675
+ ok: false,
11676
+ reason: "not-found",
11677
+ feature: query,
11678
+ detail: `"${query}" not found. Did you mean: ${closestMatches.join(", ")}?`,
11679
+ closestMatches
11680
+ },
11681
+ nextRoadmap: roadmap
11682
+ };
11683
+ }
11684
+ return {
11685
+ result: { ok: true, transitioned: "created", feature: query },
11686
+ nextRoadmap: appendCreatedRow(roadmap, args)
11687
+ };
11688
+ }
11292
11689
  function resolveReverseStatus(externalStatus, labels, config) {
11293
11690
  const reverseMap = config.reverseStatusMap;
11294
11691
  if (!reverseMap) return null;
@@ -11329,7 +11726,7 @@ function parseRepoParts(repo) {
11329
11726
  return { owner: parts[0], repo: parts[1] };
11330
11727
  }
11331
11728
  function sleep2(ms) {
11332
- return new Promise((resolve6) => setTimeout(resolve6, ms));
11729
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
11333
11730
  }
11334
11731
  async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
11335
11732
  let lastResponse;
@@ -11362,9 +11759,9 @@ function isValidTrackerShape(tracker) {
11362
11759
  }
11363
11760
  function loadTrackerSyncConfig(projectRoot) {
11364
11761
  try {
11365
- const configPath = path33.join(projectRoot, "harness.config.json");
11366
- if (!fs33.existsSync(configPath)) return null;
11367
- const raw = fs33.readFileSync(configPath, "utf-8");
11762
+ const configPath = path32.join(projectRoot, "harness.config.json");
11763
+ if (!fs32.existsSync(configPath)) return null;
11764
+ const raw = fs32.readFileSync(configPath, "utf-8");
11368
11765
  const config = JSON.parse(raw);
11369
11766
  const tracker = config.roadmap?.tracker;
11370
11767
  if (!isValidTrackerShape(tracker)) return null;
@@ -11479,12 +11876,12 @@ async function syncFromExternal(roadmap, adapter2, config, options, prefetchedTi
11479
11876
  async function fullSync(roadmapPath, adapter2, config, options) {
11480
11877
  const previousSync = syncMutex;
11481
11878
  let releaseMutex;
11482
- syncMutex = new Promise((resolve6) => {
11483
- releaseMutex = resolve6;
11879
+ syncMutex = new Promise((resolve7) => {
11880
+ releaseMutex = resolve7;
11484
11881
  });
11485
11882
  await previousSync;
11486
11883
  try {
11487
- const raw = fs34.readFileSync(roadmapPath, "utf-8");
11884
+ const raw = fs33.readFileSync(roadmapPath, "utf-8");
11488
11885
  const parseResult = parseRoadmap(raw);
11489
11886
  if (!parseResult.ok) {
11490
11887
  return {
@@ -11497,7 +11894,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
11497
11894
  const tickets = fetchResult.ok ? fetchResult.value : void 0;
11498
11895
  const pushResult = await syncToExternal(roadmap, adapter2, config, tickets);
11499
11896
  const pullResult = await syncFromExternal(roadmap, adapter2, config, options);
11500
- fs34.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
11897
+ fs33.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
11501
11898
  return {
11502
11899
  created: pushResult.created,
11503
11900
  updated: pushResult.updated,
@@ -11857,9 +12254,9 @@ function makeTrackerConflictBody(err, opts = {}) {
11857
12254
  }
11858
12255
  function loadProjectRoadmapMode(projectRoot) {
11859
12256
  try {
11860
- const configPath = path34.join(projectRoot, "harness.config.json");
11861
- if (!fs35.existsSync(configPath)) return getRoadmapMode(null);
11862
- const raw = fs35.readFileSync(configPath, "utf-8");
12257
+ const configPath = path33.join(projectRoot, "harness.config.json");
12258
+ if (!fs34.existsSync(configPath)) return getRoadmapMode(null);
12259
+ const raw = fs34.readFileSync(configPath, "utf-8");
11863
12260
  const parsed = JSON.parse(raw);
11864
12261
  return getRoadmapMode(parsed);
11865
12262
  } catch {
@@ -11868,11 +12265,11 @@ function loadProjectRoadmapMode(projectRoot) {
11868
12265
  }
11869
12266
  function loadTrackerClientConfigFromProject(projectRoot) {
11870
12267
  try {
11871
- const configPath = path35.join(projectRoot, "harness.config.json");
11872
- if (!fs36.existsSync(configPath)) {
12268
+ const configPath = path34.join(projectRoot, "harness.config.json");
12269
+ if (!fs35.existsSync(configPath)) {
11873
12270
  return Err(new Error("harness.config.json not found"));
11874
12271
  }
11875
- const cfg = JSON.parse(fs36.readFileSync(configPath, "utf-8"));
12272
+ const cfg = JSON.parse(fs35.readFileSync(configPath, "utf-8"));
11876
12273
  const tracker = cfg.roadmap?.tracker;
11877
12274
  if (!tracker) {
11878
12275
  return Err(
@@ -12087,10 +12484,10 @@ function mapAction(action) {
12087
12484
  }
12088
12485
  async function runMigrationPlan(plan, deps, opts) {
12089
12486
  const projectRoot = opts.projectRoot;
12090
- const roadmapPath = path36.join(projectRoot, "docs", "roadmap.md");
12091
- const archivedPath = path36.join(projectRoot, "docs", "roadmap.md.archived");
12092
- const configPath = path36.join(projectRoot, "harness.config.json");
12093
- const configBackupPath = path36.join(projectRoot, "harness.config.json.pre-migration");
12487
+ const roadmapPath = path35.join(projectRoot, "docs", "roadmap.md");
12488
+ const archivedPath = path35.join(projectRoot, "docs", "roadmap.md.archived");
12489
+ const configPath = path35.join(projectRoot, "harness.config.json");
12490
+ const configBackupPath = path35.join(projectRoot, "harness.config.json.pre-migration");
12094
12491
  const createdSoFar = [];
12095
12492
  const report = {
12096
12493
  created: 0,
@@ -12200,9 +12597,111 @@ async function runMigrationPlan(plan, deps, opts) {
12200
12597
  }
12201
12598
  return Ok(report);
12202
12599
  }
12600
+ function defaultIsArchive(name) {
12601
+ return /^v\d/.test(name) || name === "Shipped" || name === "Hermes Adoption";
12602
+ }
12603
+ function isUnactionablePlanned(feature) {
12604
+ return feature.status === "planned" && feature.spec === null && feature.plans.length === 0;
12605
+ }
12606
+ function resolveOptions(options) {
12607
+ return {
12608
+ isArchive: options?.isArchive ?? defaultIsArchive,
12609
+ maxActiveMilestoneSize: options?.maxActiveMilestoneSize ?? DEFAULT_MAX_ACTIVE,
12610
+ catchAllMilestones: options?.catchAllMilestones ?? DEFAULT_CATCH_ALL,
12611
+ intakeMilestone: options?.intakeMilestone ?? DEFAULT_INTAKE
12612
+ };
12613
+ }
12614
+ function checkRoadmapHealth(roadmap, options) {
12615
+ const opts = resolveOptions(options);
12616
+ const findings = [];
12617
+ for (const milestone of roadmap.milestones) {
12618
+ const archive = opts.isArchive(milestone.name);
12619
+ const isIntake = milestone.name === opts.intakeMilestone;
12620
+ if (opts.catchAllMilestones.some((n) => n.toLowerCase() === milestone.name.toLowerCase())) {
12621
+ findings.push({
12622
+ ruleId: "RMH003",
12623
+ severity: "error",
12624
+ milestone: milestone.name,
12625
+ message: `Lifecycle catch-all milestone "${milestone.name}" exists. Milestones should be themes, not stages.`,
12626
+ suggestion: `Rename to a theme or run the roadmap skill's --groom mode to drain it into themed milestones.`
12627
+ });
12628
+ }
12629
+ if (!archive && !isIntake && milestone.features.length > opts.maxActiveMilestoneSize) {
12630
+ findings.push({
12631
+ ruleId: "RMH004",
12632
+ severity: "warning",
12633
+ milestone: milestone.name,
12634
+ message: `Active milestone "${milestone.name}" has ${milestone.features.length} features (cap ${opts.maxActiveMilestoneSize}); it is becoming a dump.`,
12635
+ suggestion: `Split it into sub-themes.`
12636
+ });
12637
+ }
12638
+ for (const feature of milestone.features) {
12639
+ if (feature.status === "done" && !archive && !isIntake) {
12640
+ findings.push({
12641
+ ruleId: "RMH001",
12642
+ severity: "warning",
12643
+ milestone: milestone.name,
12644
+ feature: feature.name,
12645
+ message: `Completed feature "${feature.name}" is still in active milestone "${milestone.name}".`,
12646
+ suggestion: `Archive it (move to Shipped) \u2014 run the roadmap skill's --groom mode.`
12647
+ });
12648
+ }
12649
+ if (isUnactionablePlanned(feature)) {
12650
+ findings.push({
12651
+ ruleId: "RMH002",
12652
+ severity: "warning",
12653
+ milestone: milestone.name,
12654
+ feature: feature.name,
12655
+ message: `"${feature.name}" is "planned" with no spec and no plan; the orchestrator cannot auto-execute it (it escalates to a human).`,
12656
+ suggestion: `Link a spec/plan to make it actionable, or demote it to "backlog".`
12657
+ });
12658
+ }
12659
+ }
12660
+ }
12661
+ return findings;
12662
+ }
12663
+ function groomRoadmap(roadmap, options) {
12664
+ const opts = resolveOptions(options);
12665
+ const archiveDone = options?.archiveDone ?? true;
12666
+ const next = structuredClone(roadmap);
12667
+ const archived = [];
12668
+ const changes = [];
12669
+ for (const milestone of next.milestones) {
12670
+ const archive = opts.isArchive(milestone.name);
12671
+ const isIntake = milestone.name === opts.intakeMilestone;
12672
+ const kept = [];
12673
+ for (const feature of milestone.features) {
12674
+ if (isUnactionablePlanned(feature)) {
12675
+ feature.status = "backlog";
12676
+ changes.push({
12677
+ kind: "demoted",
12678
+ feature: feature.name,
12679
+ from: milestone.name,
12680
+ to: "backlog"
12681
+ });
12682
+ }
12683
+ if (archiveDone && feature.status === "done" && !archive && !isIntake) {
12684
+ archived.push(feature);
12685
+ changes.push({
12686
+ kind: "archived",
12687
+ feature: feature.name,
12688
+ from: milestone.name,
12689
+ to: "Shipped"
12690
+ });
12691
+ continue;
12692
+ }
12693
+ kept.push(feature);
12694
+ }
12695
+ milestone.features = kept;
12696
+ }
12697
+ next.milestones = next.milestones.filter(
12698
+ (m) => m.features.length > 0 || m.name === opts.intakeMilestone
12699
+ );
12700
+ return { roadmap: next, archived, changes };
12701
+ }
12203
12702
  function getStatePath() {
12204
12703
  const home = process.env["HOME"] || os.homedir();
12205
- return path39.join(home, ".harness", "update-check.json");
12704
+ return path38.join(home, ".harness", "update-check.json");
12206
12705
  }
12207
12706
  function isUpdateCheckEnabled(configInterval) {
12208
12707
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -12215,13 +12714,13 @@ function shouldRunCheck(state, intervalMs) {
12215
12714
  }
12216
12715
  function invalidateCheckState() {
12217
12716
  try {
12218
- fs39.unlinkSync(getStatePath());
12717
+ fs38.unlinkSync(getStatePath());
12219
12718
  } catch {
12220
12719
  }
12221
12720
  }
12222
12721
  function readCheckState() {
12223
12722
  try {
12224
- const raw = fs39.readFileSync(getStatePath(), "utf-8");
12723
+ const raw = fs38.readFileSync(getStatePath(), "utf-8");
12225
12724
  const parsed = JSON.parse(raw);
12226
12725
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
12227
12726
  const state = parsed;
@@ -12238,7 +12737,7 @@ function readCheckState() {
12238
12737
  }
12239
12738
  function spawnBackgroundCheck(currentVersion) {
12240
12739
  const statePath = getStatePath();
12241
- const stateDir = path39.dirname(statePath);
12740
+ const stateDir = path38.dirname(statePath);
12242
12741
  const script = `
12243
12742
  const { execSync } = require('child_process');
12244
12743
  const fs = require('fs');
@@ -12481,14 +12980,14 @@ function getModelPrice(model, dataset) {
12481
12980
  return pricing;
12482
12981
  }
12483
12982
  function getCachePath(projectRoot) {
12484
- return path40.join(projectRoot, ".harness", "cache", "pricing.json");
12983
+ return path39.join(projectRoot, ".harness", "cache", "pricing.json");
12485
12984
  }
12486
12985
  function getStalenessMarkerPath(projectRoot) {
12487
- return path40.join(projectRoot, ".harness", "cache", "staleness-marker.json");
12986
+ return path39.join(projectRoot, ".harness", "cache", "staleness-marker.json");
12488
12987
  }
12489
12988
  async function readDiskCache(projectRoot) {
12490
12989
  try {
12491
- const raw = await fs40.readFile(getCachePath(projectRoot), "utf-8");
12990
+ const raw = await fs39.readFile(getCachePath(projectRoot), "utf-8");
12492
12991
  return JSON.parse(raw);
12493
12992
  } catch {
12494
12993
  return null;
@@ -12496,8 +12995,8 @@ async function readDiskCache(projectRoot) {
12496
12995
  }
12497
12996
  async function writeDiskCache(projectRoot, data) {
12498
12997
  const cachePath = getCachePath(projectRoot);
12499
- await fs40.mkdir(path40.dirname(cachePath), { recursive: true });
12500
- await fs40.writeFile(cachePath, JSON.stringify(data, null, 2));
12998
+ await fs39.mkdir(path39.dirname(cachePath), { recursive: true });
12999
+ await fs39.writeFile(cachePath, JSON.stringify(data, null, 2));
12501
13000
  }
12502
13001
  async function fetchFromNetwork() {
12503
13002
  try {
@@ -12524,7 +13023,7 @@ function loadFallbackDataset() {
12524
13023
  async function checkAndWarnStaleness(projectRoot) {
12525
13024
  const markerPath = getStalenessMarkerPath(projectRoot);
12526
13025
  try {
12527
- const raw = await fs40.readFile(markerPath, "utf-8");
13026
+ const raw = await fs39.readFile(markerPath, "utf-8");
12528
13027
  const marker = JSON.parse(raw);
12529
13028
  const firstUse = new Date(marker.firstFallbackUse).getTime();
12530
13029
  const now = Date.now();
@@ -12536,8 +13035,8 @@ async function checkAndWarnStaleness(projectRoot) {
12536
13035
  }
12537
13036
  } catch {
12538
13037
  try {
12539
- await fs40.mkdir(path40.dirname(markerPath), { recursive: true });
12540
- await fs40.writeFile(
13038
+ await fs39.mkdir(path39.dirname(markerPath), { recursive: true });
13039
+ await fs39.writeFile(
12541
13040
  markerPath,
12542
13041
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
12543
13042
  );
@@ -12547,7 +13046,7 @@ async function checkAndWarnStaleness(projectRoot) {
12547
13046
  }
12548
13047
  async function clearStalenessMarker(projectRoot) {
12549
13048
  try {
12550
- await fs40.unlink(getStalenessMarkerPath(projectRoot));
13049
+ await fs39.unlink(getStalenessMarkerPath(projectRoot));
12551
13050
  } catch {
12552
13051
  }
12553
13052
  }
@@ -12784,10 +13283,10 @@ function parseLine(line, lineNumber) {
12784
13283
  return record;
12785
13284
  }
12786
13285
  function readCostRecords(projectRoot) {
12787
- const costsFile = path41.join(projectRoot, ".harness", "metrics", "costs.jsonl");
13286
+ const costsFile = path40.join(projectRoot, ".harness", "metrics", "costs.jsonl");
12788
13287
  let raw;
12789
13288
  try {
12790
- raw = fs41.readFileSync(costsFile, "utf-8");
13289
+ raw = fs40.readFileSync(costsFile, "utf-8");
12791
13290
  } catch {
12792
13291
  return [];
12793
13292
  }
@@ -12837,7 +13336,7 @@ function parseCCLine(line, filePath, lineNumber) {
12837
13336
  entry = JSON.parse(line);
12838
13337
  } catch {
12839
13338
  console.warn(
12840
- `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path42.basename(filePath)}`
13339
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path41.basename(filePath)}`
12841
13340
  );
12842
13341
  return null;
12843
13342
  }
@@ -12851,7 +13350,7 @@ function parseCCLine(line, filePath, lineNumber) {
12851
13350
  function readCCFile(filePath) {
12852
13351
  let raw;
12853
13352
  try {
12854
- raw = fs42.readFileSync(filePath, "utf-8");
13353
+ raw = fs41.readFileSync(filePath, "utf-8");
12855
13354
  } catch {
12856
13355
  return [];
12857
13356
  }
@@ -12873,10 +13372,10 @@ function readCCFile(filePath) {
12873
13372
  }
12874
13373
  function parseCCRecords() {
12875
13374
  const homeDir = process.env.HOME ?? os2.homedir();
12876
- const projectsDir = path42.join(homeDir, ".claude", "projects");
13375
+ const projectsDir = path41.join(homeDir, ".claude", "projects");
12877
13376
  let projectDirs;
12878
13377
  try {
12879
- projectDirs = fs42.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path42.join(projectsDir, d.name));
13378
+ projectDirs = fs41.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path41.join(projectsDir, d.name));
12880
13379
  } catch {
12881
13380
  return [];
12882
13381
  }
@@ -12884,7 +13383,7 @@ function parseCCRecords() {
12884
13383
  for (const dir of projectDirs) {
12885
13384
  let files;
12886
13385
  try {
12887
- files = fs42.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path42.join(dir, f));
13386
+ files = fs41.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path41.join(dir, f));
12888
13387
  } catch {
12889
13388
  continue;
12890
13389
  }
@@ -12912,10 +13411,10 @@ function parseLine2(line, lineNumber) {
12912
13411
  }
12913
13412
  }
12914
13413
  function readAdoptionRecords(projectRoot) {
12915
- const adoptionFile = path43.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
13414
+ const adoptionFile = path42.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
12916
13415
  let raw;
12917
13416
  try {
12918
- raw = fs43.readFileSync(adoptionFile, "utf-8");
13417
+ raw = fs42.readFileSync(adoptionFile, "utf-8");
12919
13418
  } catch {
12920
13419
  return [];
12921
13420
  }
@@ -12987,38 +13486,6 @@ function aggregateByDay2(records) {
12987
13486
  function topSkills(records, n) {
12988
13487
  return aggregateBySkill(records).slice(0, n);
12989
13488
  }
12990
- function loadNotificationsConfig(projectRoot) {
12991
- const configPath = path44.join(projectRoot, "harness.config.json");
12992
- if (!fs44.existsSync(configPath)) {
12993
- return Ok({ sinks: [] });
12994
- }
12995
- let raw;
12996
- try {
12997
- raw = fs44.readFileSync(configPath, "utf-8");
12998
- } catch (err) {
12999
- return Err(err instanceof Error ? err : new Error(String(err)));
13000
- }
13001
- let parsed;
13002
- try {
13003
- parsed = JSON.parse(raw);
13004
- } catch (err) {
13005
- return Err(
13006
- new Error(
13007
- `Failed to parse harness.config.json: ${err instanceof Error ? err.message : String(err)}`
13008
- )
13009
- );
13010
- }
13011
- if (parsed.notifications === void 0) {
13012
- return Ok({ sinks: [] });
13013
- }
13014
- const result = NotificationsConfigSchema.safeParse(parsed.notifications);
13015
- if (!result.success) {
13016
- const issues = result.error.issues.map((i) => ` - notifications.${i.path.join(".")}: ${i.message}`).join("\n");
13017
- return Err(new Error(`Invalid notifications config:
13018
- ${issues}`));
13019
- }
13020
- return Ok(result.data);
13021
- }
13022
13489
  function isEmptyObject(v) {
13023
13490
  return typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 0;
13024
13491
  }
@@ -13095,25 +13562,25 @@ function resolveStability(contentType) {
13095
13562
  return STABILITY_LOOKUP[contentType] ?? "ephemeral";
13096
13563
  }
13097
13564
  function getOrCreateInstallId(projectRoot) {
13098
- const harnessDir = path45.join(projectRoot, ".harness");
13099
- const installIdFile = path45.join(harnessDir, ".install-id");
13565
+ const harnessDir = path43.join(projectRoot, ".harness");
13566
+ const installIdFile = path43.join(harnessDir, ".install-id");
13100
13567
  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;
13101
13568
  try {
13102
- const existing = fs45.readFileSync(installIdFile, "utf-8").trim();
13569
+ const existing = fs43.readFileSync(installIdFile, "utf-8").trim();
13103
13570
  if (UUID_V4_RE.test(existing)) {
13104
13571
  return existing;
13105
13572
  }
13106
13573
  } catch {
13107
13574
  }
13108
13575
  const id = crypto4.randomUUID();
13109
- fs45.mkdirSync(harnessDir, { recursive: true });
13110
- fs45.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
13576
+ fs43.mkdirSync(harnessDir, { recursive: true });
13577
+ fs43.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
13111
13578
  return id;
13112
13579
  }
13113
13580
  function parseIdentityFromTelemetryFile(filePath) {
13114
13581
  const identity = {};
13115
13582
  try {
13116
- const raw = fs46.readFileSync(filePath, "utf-8");
13583
+ const raw = fs44.readFileSync(filePath, "utf-8");
13117
13584
  const parsed = JSON.parse(raw);
13118
13585
  const src = parsed?.identity;
13119
13586
  if (!src || typeof src !== "object") return identity;
@@ -13126,7 +13593,7 @@ function parseIdentityFromTelemetryFile(filePath) {
13126
13593
  }
13127
13594
  function readProjectNameFallback(configPath) {
13128
13595
  try {
13129
- const raw = fs46.readFileSync(configPath, "utf-8");
13596
+ const raw = fs44.readFileSync(configPath, "utf-8");
13130
13597
  const config = JSON.parse(raw);
13131
13598
  return typeof config?.name === "string" ? config.name : void 0;
13132
13599
  } catch {
@@ -13147,10 +13614,10 @@ function readGitAliasFallback(cwd) {
13147
13614
  }
13148
13615
  }
13149
13616
  function readIdentity(projectRoot) {
13150
- const filePath = path46.join(projectRoot, ".harness", "telemetry.json");
13617
+ const filePath = path44.join(projectRoot, ".harness", "telemetry.json");
13151
13618
  const identity = parseIdentityFromTelemetryFile(filePath);
13152
13619
  if (!identity.project) {
13153
- const fallbackProject = readProjectNameFallback(path46.join(projectRoot, "harness.config.json"));
13620
+ const fallbackProject = readProjectNameFallback(path44.join(projectRoot, "harness.config.json"));
13154
13621
  if (fallbackProject) identity.project = fallbackProject;
13155
13622
  }
13156
13623
  if (!identity.alias) {
@@ -13197,7 +13664,7 @@ function collectEvents(projectRoot, consent) {
13197
13664
  );
13198
13665
  }
13199
13666
  function sleep3(ms) {
13200
- return new Promise((resolve6) => setTimeout(resolve6, ms));
13667
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
13201
13668
  }
13202
13669
  async function send(events, apiKey) {
13203
13670
  if (events.length === 0) return;
@@ -13273,36 +13740,383 @@ function spanToOTLP(s) {
13273
13740
  if (s.statusCode !== void 0) span["status"] = { code: s.statusCode };
13274
13741
  return span;
13275
13742
  }
13276
- function acquireCompoundLock(category, opts = {}) {
13277
- if (!KNOWN_CATEGORIES.has(category)) {
13278
- throw new Error(
13279
- `Unknown category "${category}". Must be one of: ${[...KNOWN_CATEGORIES].join(", ")}`
13280
- );
13743
+ function tierFor(score) {
13744
+ if (score >= 85) return "solid";
13745
+ if (score >= 50) return "at-risk";
13746
+ return "theatre";
13747
+ }
13748
+ function rollupScore(findings) {
13749
+ const deduction = findings.reduce((sum, f) => sum + SEVERITY_WEIGHTS[f.severity], 0);
13750
+ const score = clamp(100 - deduction, 0, 100);
13751
+ return { score, tier: tierFor(score) };
13752
+ }
13753
+ function resolveMode(opts, root) {
13754
+ if (opts.mode) return opts.mode;
13755
+ const hasTemplates = existsSync38(join582(root, "templates"));
13756
+ const hasSkills = existsSync38(join582(root, "agents", "skills"));
13757
+ return hasTemplates && hasSkills ? "toolkit" : "adopter";
13758
+ }
13759
+ function readTextOrNull(path52) {
13760
+ try {
13761
+ return existsSync38(path52) ? readFileSync34(path52, "utf8") : null;
13762
+ } catch {
13763
+ return null;
13281
13764
  }
13282
- const cwd = opts.cwd ?? process.cwd();
13283
- const lockDir = path47.join(cwd, ".harness", "locks");
13284
- fs47.mkdirSync(lockDir, { recursive: true });
13285
- const lockPath = path47.join(lockDir, `compound-${category}.lock`);
13286
- let fd;
13765
+ }
13766
+ function readJsonOrNull(path52) {
13767
+ const text = readTextOrNull(path52);
13768
+ if (text === null) return null;
13287
13769
  try {
13288
- fd = fs47.openSync(lockPath, "wx");
13289
- } catch (e) {
13290
- const err = e;
13291
- if (err.code === "EEXIST") {
13292
- const holderPid = readHolderPid(lockPath);
13293
- throw new CompoundLockHeldError(category, holderPid, lockPath);
13294
- }
13295
- throw err;
13770
+ return JSON.parse(text);
13771
+ } catch {
13772
+ return null;
13296
13773
  }
13297
- fs47.writeSync(fd, String(process.pid));
13298
- fs47.closeSync(fd);
13299
- let released = false;
13300
- const release = () => {
13301
- if (released) return;
13302
- released = true;
13303
- try {
13304
- fs47.unlinkSync(lockPath);
13305
- } catch {
13774
+ }
13775
+ function toRootRelative(root, abs) {
13776
+ return relative4(root, abs).replaceAll("\\", "/");
13777
+ }
13778
+ function readConfig(root) {
13779
+ const raw = readJsonOrNull(join582(root, "harness.config.json"));
13780
+ if (raw === null) return null;
13781
+ const parsed = HarnessConfigSubsetSchema.safeParse(raw);
13782
+ return parsed.success ? parsed.data : null;
13783
+ }
13784
+ function readHookDir(root, dir) {
13785
+ if (!existsSync38(dir)) return [];
13786
+ try {
13787
+ return readdirSync5(dir).map((name) => ({ name, full: join582(dir, name) })).filter(({ full }) => {
13788
+ try {
13789
+ return statSync4(full).isFile();
13790
+ } catch {
13791
+ return false;
13792
+ }
13793
+ }).map(({ name, full }) => ({
13794
+ name,
13795
+ path: toRootRelative(root, full),
13796
+ text: readTextOrNull(full) ?? ""
13797
+ }));
13798
+ } catch {
13799
+ return [];
13800
+ }
13801
+ }
13802
+ function resolveHookFiles(root) {
13803
+ const collected = /* @__PURE__ */ new Map();
13804
+ for (const h of [
13805
+ ...readHookDir(root, join582(root, ".husky")),
13806
+ ...readHookDir(root, join582(root, ".claude", "hooks")),
13807
+ ...readHookDir(root, join582(root, ".harness", "hooks"))
13808
+ ]) {
13809
+ collected.set(resolve6(root, h.path), h);
13810
+ }
13811
+ const settings = readJsonOrNull(join582(root, ".claude", "settings.json"));
13812
+ for (const ref of extractSettingsHookScripts(settings, root)) {
13813
+ const abs = resolve6(root, ref);
13814
+ if (collected.has(abs)) continue;
13815
+ const text = readTextOrNull(abs);
13816
+ if (text !== null) {
13817
+ collected.set(abs, { name: basename10(abs), path: toRootRelative(root, abs), text });
13818
+ }
13819
+ }
13820
+ return [...collected.values()];
13821
+ }
13822
+ function extractSettingsHookScripts(settings, root) {
13823
+ const out = [];
13824
+ if (settings === null || typeof settings !== "object") return out;
13825
+ const hooks = settings.hooks;
13826
+ const walk3 = (v) => {
13827
+ if (typeof v === "string") {
13828
+ for (const m of v.matchAll(HOOK_SCRIPT_TOKEN)) {
13829
+ const token = m[1];
13830
+ if (token) out.push(normalizeHookRef(token, root));
13831
+ }
13832
+ } else if (Array.isArray(v)) {
13833
+ v.forEach(walk3);
13834
+ } else if (v && typeof v === "object") {
13835
+ Object.values(v).forEach(walk3);
13836
+ }
13837
+ };
13838
+ walk3(hooks);
13839
+ return out;
13840
+ }
13841
+ function normalizeHookRef(token, root) {
13842
+ let ref = token.replace(/^["']|["']$/g, "");
13843
+ ref = ref.replace(/^\$\(git rev-parse --show-toplevel\)\/?/, `${root}/`);
13844
+ return ref;
13845
+ }
13846
+ function readWorkflows(root) {
13847
+ const dir = join582(root, ".github", "workflows");
13848
+ if (!existsSync38(dir)) return [];
13849
+ try {
13850
+ return readdirSync5(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml")).map((n) => join582(dir, n)).filter((p) => {
13851
+ try {
13852
+ return statSync4(p).isFile();
13853
+ } catch {
13854
+ return false;
13855
+ }
13856
+ }).map((p) => ({ path: toRootRelative(root, p), text: readTextOrNull(p) ?? "" }));
13857
+ } catch {
13858
+ return [];
13859
+ }
13860
+ }
13861
+ function readTemplates(root) {
13862
+ const dir = join582(root, "templates");
13863
+ if (!existsSync38(dir)) return [];
13864
+ const out = [];
13865
+ const walk3 = (d) => {
13866
+ let entries;
13867
+ try {
13868
+ entries = readdirSync5(d);
13869
+ } catch {
13870
+ return;
13871
+ }
13872
+ for (const name of entries) {
13873
+ const full = join582(d, name);
13874
+ let st;
13875
+ try {
13876
+ st = statSync4(full);
13877
+ } catch {
13878
+ continue;
13879
+ }
13880
+ if (st.isDirectory()) walk3(full);
13881
+ else if (name.endsWith(".hbs"))
13882
+ out.push({ path: toRootRelative(root, full), text: readTextOrNull(full) ?? "" });
13883
+ }
13884
+ };
13885
+ walk3(dir);
13886
+ return out;
13887
+ }
13888
+ function readInitSkill(root) {
13889
+ return readTextOrNull(
13890
+ join582(root, "agents", "skills", "claude-code", "initialize-harness-project", "SKILL.md")
13891
+ );
13892
+ }
13893
+ function buildProjectContext(root, mode) {
13894
+ const ctx = {
13895
+ root,
13896
+ mode,
13897
+ config: readConfig(root),
13898
+ preCommit: readTextOrNull(join582(root, ".husky", "pre-commit")),
13899
+ hookFiles: resolveHookFiles(root),
13900
+ workflows: readWorkflows(root),
13901
+ healthSnapshot: readJsonOrNull(join582(root, ".harness", "health-snapshot.json"))
13902
+ };
13903
+ if (mode === "toolkit") {
13904
+ ctx.templates = readTemplates(root);
13905
+ ctx.initSkill = readInitSkill(root);
13906
+ }
13907
+ return ctx;
13908
+ }
13909
+ function stripComments(text) {
13910
+ return text.split("\n").map((l) => l.replace(/(^|\s)#.*$/, "$1")).join("\n");
13911
+ }
13912
+ function findRewriteInFailureBranch(lines) {
13913
+ const ifStack = [];
13914
+ for (let i = 0; i < lines.length; i++) {
13915
+ const l = lines[i];
13916
+ const ifCount = (l.match(/\bif\b/g) ?? []).length;
13917
+ const fiCount = (l.match(/\bfi\b/g) ?? []).length;
13918
+ const opensFailure = /\bif\s+!/.test(l);
13919
+ for (let k = 0; k < ifCount; k++) ifStack.push(opensFailure);
13920
+ const inFailureBranch = ifStack.includes(true);
13921
+ for (let k = 0; k < fiCount; k++) ifStack.pop();
13922
+ if (inFailureBranch && BASELINE_REWRITE.test(l)) return i;
13923
+ }
13924
+ return -1;
13925
+ }
13926
+ function thresholdsEmpty(cfg) {
13927
+ if (!cfg) return false;
13928
+ const hasLayers = (cfg.layers?.length ?? 0) > 0;
13929
+ if (!hasLayers) return false;
13930
+ const thresholds = cfg.architecture?.thresholds;
13931
+ return !thresholds || Object.keys(thresholds).length === 0;
13932
+ }
13933
+ function makeFinding3(file) {
13934
+ return {
13935
+ id: "STRENGTH-004",
13936
+ gearPiece: "architecture-thresholds",
13937
+ file,
13938
+ message: "Architecture layers are declared but no thresholds are set \u2014 the architecture check has nothing to enforce, so drift goes undetected.",
13939
+ remediation: "Add concrete `architecture.thresholds` (e.g. maxFanOut, maxDependencyDepth) so layer/coupling regressions actually fail the check."
13940
+ };
13941
+ }
13942
+ function isAutoLine(line) {
13943
+ return AUTO_PATTERN.test(line) && !COMMENT_LINE.test(line) && !NAME_VALUE_LINE.test(line);
13944
+ }
13945
+ function hasAutoStep(text) {
13946
+ return text.split("\n").some(isAutoLine);
13947
+ }
13948
+ function isSnapshot(value) {
13949
+ if (value === null || typeof value !== "object") return false;
13950
+ const checks = value.checks;
13951
+ return checks !== null && typeof checks === "object";
13952
+ }
13953
+ function severityFor(rule, ctx) {
13954
+ const override = ctx.config?.audit?.harnessStrength?.severities?.[rule.id];
13955
+ return override ?? rule.defaultSeverity;
13956
+ }
13957
+ async function safeGather(key, fn, warnings) {
13958
+ try {
13959
+ return await fn();
13960
+ } catch (e) {
13961
+ warnings.push(`${key}: ${e instanceof Error ? e.message : String(e)}`);
13962
+ return null;
13963
+ }
13964
+ }
13965
+ function arrayLen(arr) {
13966
+ return Array.isArray(arr) ? arr.length : 0;
13967
+ }
13968
+ async function gatherEntropyBlock(projectPath) {
13969
+ const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-V633GUHY-MBCQWKDU.js");
13970
+ const analyzer = new EntropyAnalyzer2({
13971
+ rootDir: projectPath,
13972
+ include: ["src/**/*.ts", "src/**/*.tsx", "packages/*/src/**/*.ts"],
13973
+ exclude: ["node_modules/**", "dist/**", "**/*.test.ts", "**/*.spec.ts"],
13974
+ analyze: { drift: false, deadCode: true, patterns: false, complexity: false }
13975
+ });
13976
+ const result = await analyzer.analyze();
13977
+ const empty = { driftCount: 0, deadFiles: 0, deadExports: 0 };
13978
+ if (!result.ok) return empty;
13979
+ const report = result.value;
13980
+ return {
13981
+ driftCount: arrayLen(report.drift?.drifts),
13982
+ deadFiles: arrayLen(report.deadCode?.deadFiles),
13983
+ deadExports: arrayLen(report.deadCode?.deadExports)
13984
+ };
13985
+ }
13986
+ async function gatherDecayBlock(projectPath) {
13987
+ const { TimelineManager: TimelineManager2 } = await import("./timeline-manager-FPYKJRHR-VVX7CSGW.js");
13988
+ const mgr = new TimelineManager2(projectPath);
13989
+ const trends = mgr.trends();
13990
+ return {
13991
+ recentBumps: arrayLen(trends.recentSnapshots),
13992
+ topAffected: collectTopAffectedLabels(trends.topAffected)
13993
+ };
13994
+ }
13995
+ function collectTopAffectedLabels(affected) {
13996
+ if (!Array.isArray(affected)) return [];
13997
+ const out = [];
13998
+ for (const node of affected) {
13999
+ const label = node.id ?? node.name;
14000
+ if (typeof label === "string" && label.length > 0) out.push(label);
14001
+ if (out.length >= MAX_TOP_AFFECTED) break;
14002
+ }
14003
+ return out;
14004
+ }
14005
+ async function gatherAttentionBlock(projectPath) {
14006
+ const sessionsDir = path45.join(projectPath, ".harness", "sessions");
14007
+ if (!fs45.existsSync(sessionsDir)) return { activeThreadCount: 0, staleThreadCount: 0 };
14008
+ const now = Date.now();
14009
+ const STALE_MS = 7 * 24 * 60 * 60 * 1e3;
14010
+ let active = 0;
14011
+ let stale = 0;
14012
+ const entries = fs45.readdirSync(sessionsDir, { withFileTypes: true });
14013
+ for (const entry of entries) {
14014
+ if (!entry.isDirectory()) continue;
14015
+ const dirPath = path45.join(sessionsDir, entry.name);
14016
+ try {
14017
+ const stat2 = fs45.statSync(dirPath);
14018
+ if (now - stat2.mtimeMs > STALE_MS) {
14019
+ stale++;
14020
+ } else {
14021
+ active++;
14022
+ }
14023
+ } catch {
14024
+ }
14025
+ }
14026
+ return { activeThreadCount: active, staleThreadCount: stale };
14027
+ }
14028
+ async function gatherImpactBlock(projectPath) {
14029
+ const cachePath = path45.join(projectPath, ".harness", "cache", "impact.json");
14030
+ if (!fs45.existsSync(cachePath)) return { recentBlastRadius: [] };
14031
+ try {
14032
+ const raw = JSON.parse(fs45.readFileSync(cachePath, "utf8"));
14033
+ const radii = raw.blastRadius ?? [];
14034
+ return {
14035
+ recentBlastRadius: radii.slice(0, 5).map((r) => ({
14036
+ node: r.node,
14037
+ affected: r.affected
14038
+ }))
14039
+ };
14040
+ } catch {
14041
+ return { recentBlastRadius: [] };
14042
+ }
14043
+ }
14044
+ async function gatherHealthBlock(projectPath) {
14045
+ const entropy = await gatherEntropyBlock(projectPath);
14046
+ const signals = [];
14047
+ if (entropy.driftCount > 0) signals.push(`${entropy.driftCount} drift findings`);
14048
+ if (entropy.deadFiles > 0) signals.push(`${entropy.deadFiles} dead files`);
14049
+ if (entropy.deadExports > 0) signals.push(`${entropy.deadExports} dead exports`);
14050
+ const passed = signals.length === 0;
14051
+ return {
14052
+ passed,
14053
+ signals,
14054
+ summary: passed ? "No structural health findings." : signals.join("; ")
14055
+ };
14056
+ }
14057
+ async function composeInsights(projectPath, opts = {}) {
14058
+ const skip = new Set(opts.skip ?? []);
14059
+ const warnings = [];
14060
+ const wantedKeys = INSIGHTS_KEYS.filter((k) => !skip.has(k));
14061
+ const [healthRes, entropyRes, decayRes, attentionRes, impactRes] = await Promise.all([
14062
+ wantedKeys.includes("health") ? safeGather("health", () => gatherHealthBlock(projectPath), warnings) : Promise.resolve(null),
14063
+ wantedKeys.includes("entropy") ? safeGather("entropy", () => gatherEntropyBlock(projectPath), warnings) : Promise.resolve(null),
14064
+ wantedKeys.includes("decay") ? safeGather("decay", () => gatherDecayBlock(projectPath), warnings) : Promise.resolve(null),
14065
+ wantedKeys.includes("attention") ? safeGather("attention", () => gatherAttentionBlock(projectPath), warnings) : Promise.resolve(null),
14066
+ wantedKeys.includes("impact") ? safeGather("impact", () => gatherImpactBlock(projectPath), warnings) : Promise.resolve(null)
14067
+ ]);
14068
+ const projectName = pickProjectName(projectPath);
14069
+ return {
14070
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14071
+ project: { root: projectPath, ...projectName && { name: projectName } },
14072
+ health: healthRes,
14073
+ entropy: entropyRes,
14074
+ decay: decayRes,
14075
+ attention: attentionRes,
14076
+ impact: impactRes,
14077
+ warnings
14078
+ };
14079
+ }
14080
+ function pickProjectName(projectPath) {
14081
+ try {
14082
+ const pkgPath = path45.join(projectPath, "package.json");
14083
+ if (!fs45.existsSync(pkgPath)) return void 0;
14084
+ const pkg = JSON.parse(fs45.readFileSync(pkgPath, "utf8"));
14085
+ return pkg.name;
14086
+ } catch {
14087
+ return void 0;
14088
+ }
14089
+ }
14090
+ function acquireCompoundLock(category, opts = {}) {
14091
+ if (!KNOWN_CATEGORIES.has(category)) {
14092
+ throw new Error(
14093
+ `Unknown category "${category}". Must be one of: ${[...KNOWN_CATEGORIES].join(", ")}`
14094
+ );
14095
+ }
14096
+ const cwd = opts.cwd ?? process.cwd();
14097
+ const lockDir = path46.join(cwd, ".harness", "locks");
14098
+ fs46.mkdirSync(lockDir, { recursive: true });
14099
+ const lockPath = path46.join(lockDir, `compound-${category}.lock`);
14100
+ let fd;
14101
+ try {
14102
+ fd = fs46.openSync(lockPath, "wx");
14103
+ } catch (e) {
14104
+ const err = e;
14105
+ if (err.code === "EEXIST") {
14106
+ const holderPid = readHolderPid(lockPath);
14107
+ throw new CompoundLockHeldError(category, holderPid, lockPath);
14108
+ }
14109
+ throw err;
14110
+ }
14111
+ fs46.writeSync(fd, String(process.pid));
14112
+ fs46.closeSync(fd);
14113
+ let released = false;
14114
+ const release = () => {
14115
+ if (released) return;
14116
+ released = true;
14117
+ try {
14118
+ fs46.unlinkSync(lockPath);
14119
+ } catch {
13306
14120
  }
13307
14121
  process.removeListener("exit", onExit);
13308
14122
  process.removeListener("SIGINT", onSignal);
@@ -13327,13 +14141,151 @@ function acquireCompoundLock(category, opts = {}) {
13327
14141
  }
13328
14142
  function readHolderPid(lockPath) {
13329
14143
  try {
13330
- const raw = fs47.readFileSync(lockPath, "utf-8").trim();
14144
+ const raw = fs46.readFileSync(lockPath, "utf-8").trim();
13331
14145
  const n = Number.parseInt(raw, 10);
13332
14146
  return Number.isFinite(n) ? n : -1;
13333
14147
  } catch {
13334
14148
  return -1;
13335
14149
  }
13336
14150
  }
14151
+ function loadNotificationsConfig(projectRoot) {
14152
+ const configPath = path47.join(projectRoot, "harness.config.json");
14153
+ if (!fs47.existsSync(configPath)) {
14154
+ return Ok({ sinks: [] });
14155
+ }
14156
+ let raw;
14157
+ try {
14158
+ raw = fs47.readFileSync(configPath, "utf-8");
14159
+ } catch (err) {
14160
+ return Err(err instanceof Error ? err : new Error(String(err)));
14161
+ }
14162
+ let parsed;
14163
+ try {
14164
+ parsed = JSON.parse(raw);
14165
+ } catch (err) {
14166
+ return Err(
14167
+ new Error(
14168
+ `Failed to parse harness.config.json: ${err instanceof Error ? err.message : String(err)}`
14169
+ )
14170
+ );
14171
+ }
14172
+ if (parsed.notifications === void 0) {
14173
+ return Ok({ sinks: [] });
14174
+ }
14175
+ const result = NotificationsConfigSchema.safeParse(parsed.notifications);
14176
+ if (!result.success) {
14177
+ const issues = result.error.issues.map((i) => ` - notifications.${i.path.join(".")}: ${i.message}`).join("\n");
14178
+ return Err(new Error(`Invalid notifications config:
14179
+ ${issues}`));
14180
+ }
14181
+ return Ok(result.data);
14182
+ }
14183
+ function proposalsDir(projectPath) {
14184
+ return path48.join(projectPath, ".harness", "proposals");
14185
+ }
14186
+ function proposalPath(projectPath, id) {
14187
+ return path48.join(proposalsDir(projectPath), `${id}.json`);
14188
+ }
14189
+ function ensureDir(dir) {
14190
+ fs48.mkdirSync(dir, { recursive: true });
14191
+ }
14192
+ function writeAtomic(filePath, content) {
14193
+ const tmpPath = `${filePath}.tmp`;
14194
+ fs48.writeFileSync(tmpPath, content);
14195
+ fs48.renameSync(tmpPath, filePath);
14196
+ }
14197
+ async function createProposal(projectPath, input) {
14198
+ const validated = EmitSkillProposalInputSchema.parse(input);
14199
+ const id = `proposal_${randomUUID2().replace(/-/g, "")}`;
14200
+ const proposal = SkillProposalSchema.parse({
14201
+ id,
14202
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
14203
+ kind: validated.kind,
14204
+ targetSkill: validated.targetSkill,
14205
+ proposedBy: validated.proposedBy,
14206
+ source: {
14207
+ sessionId: validated.sessionId,
14208
+ taskId: validated.taskId,
14209
+ justification: validated.justification
14210
+ },
14211
+ content: validated.content,
14212
+ status: "open"
14213
+ });
14214
+ if (proposal.kind === "refinement" && proposal.targetSkill) {
14215
+ const existing = await listProposals(projectPath, { status: "open" });
14216
+ const clash = existing.find(
14217
+ (p) => p.kind === "refinement" && p.targetSkill === proposal.targetSkill
14218
+ );
14219
+ if (clash) {
14220
+ throw new ProposalConflictError(
14221
+ `An open refinement proposal already exists for skill "${proposal.targetSkill}" (id: ${clash.id})`
14222
+ );
14223
+ }
14224
+ }
14225
+ const dir = proposalsDir(projectPath);
14226
+ ensureDir(dir);
14227
+ writeAtomic(proposalPath(projectPath, id), JSON.stringify(proposal, null, 2));
14228
+ return proposal;
14229
+ }
14230
+ async function getProposal(projectPath, id) {
14231
+ const file = proposalPath(projectPath, id);
14232
+ try {
14233
+ const raw = fs48.readFileSync(file, "utf-8");
14234
+ const parsed = SkillProposalSchema.safeParse(JSON.parse(raw));
14235
+ return parsed.success ? parsed.data : null;
14236
+ } catch {
14237
+ return null;
14238
+ }
14239
+ }
14240
+ async function listProposals(projectPath, opts = {}) {
14241
+ const dir = proposalsDir(projectPath);
14242
+ let files;
14243
+ try {
14244
+ files = fs48.readdirSync(dir);
14245
+ } catch {
14246
+ return [];
14247
+ }
14248
+ const out = [];
14249
+ for (const file of files) {
14250
+ if (!file.endsWith(".json")) continue;
14251
+ const id = file.slice(0, -".json".length);
14252
+ const proposal = await getProposal(projectPath, id);
14253
+ if (!proposal) continue;
14254
+ if (opts.status && opts.status !== "all" && proposal.status !== opts.status) continue;
14255
+ out.push(proposal);
14256
+ }
14257
+ out.sort((a, b) => a.createdAt > b.createdAt ? -1 : a.createdAt < b.createdAt ? 1 : 0);
14258
+ return out;
14259
+ }
14260
+ async function updateProposal(projectPath, id, patch) {
14261
+ const current = await getProposal(projectPath, id);
14262
+ if (!current) throw new ProposalNotFoundError(id);
14263
+ const next = SkillProposalSchema.parse({
14264
+ ...current,
14265
+ ...patch,
14266
+ id: current.id,
14267
+ createdAt: current.createdAt,
14268
+ kind: current.kind
14269
+ });
14270
+ writeAtomic(proposalPath(projectPath, id), JSON.stringify(next, null, 2));
14271
+ return next;
14272
+ }
14273
+ function deriveSkillUsage(projectRoot, skillName, windowDays = 30) {
14274
+ const records = readAdoptionRecords(projectRoot);
14275
+ const cutoffMs = Date.now() - windowDays * 24 * 60 * 60 * 1e3;
14276
+ let count = 0;
14277
+ let lastUsed;
14278
+ for (const rec of records) {
14279
+ if (rec.skill !== skillName) continue;
14280
+ const startedMs = Date.parse(rec.startedAt);
14281
+ if (Number.isFinite(startedMs) && startedMs < cutoffMs) continue;
14282
+ count += 1;
14283
+ if (!lastUsed || rec.startedAt > lastUsed) lastUsed = rec.startedAt;
14284
+ }
14285
+ const stats = { count, windowDays };
14286
+ if (lastUsed) stats.lastUsed = lastUsed;
14287
+ return stats;
14288
+ }
13337
14289
  function isObject(value) {
13338
14290
  return Boolean(value) && typeof value === "object";
13339
14291
  }
@@ -13359,10 +14311,10 @@ function assertSanitized(value) {
13359
14311
  }
13360
14312
  function writePulseConfig(config, opts) {
13361
14313
  PulseConfigSchema.parse(config);
13362
- if (!fs48.existsSync(opts.configPath)) {
14314
+ if (!fs49.existsSync(opts.configPath)) {
13363
14315
  throw new Error(`harness.config.json not found at ${opts.configPath}`);
13364
14316
  }
13365
- const raw = fs48.readFileSync(opts.configPath, "utf-8");
14317
+ const raw = fs49.readFileSync(opts.configPath, "utf-8");
13366
14318
  let parsed;
13367
14319
  try {
13368
14320
  parsed = JSON.parse(raw);
@@ -13370,18 +14322,18 @@ function writePulseConfig(config, opts) {
13370
14322
  throw new Error(`Invalid JSON in ${opts.configPath}: ${e.message}`, { cause: e });
13371
14323
  }
13372
14324
  const bakPath = `${opts.configPath}.bak`;
13373
- if (!opts.skipBackup && !fs48.existsSync(bakPath)) {
13374
- fs48.writeFileSync(bakPath, raw, "utf-8");
14325
+ if (!opts.skipBackup && !fs49.existsSync(bakPath)) {
14326
+ fs49.writeFileSync(bakPath, raw, "utf-8");
13375
14327
  }
13376
14328
  parsed.pulse = config;
13377
14329
  const serialized = JSON.stringify(parsed, null, 2) + "\n";
13378
14330
  const tmpPath = `${opts.configPath}.tmp-${process.pid}`;
13379
- fs48.writeFileSync(tmpPath, serialized, "utf-8");
14331
+ fs49.writeFileSync(tmpPath, serialized, "utf-8");
13380
14332
  try {
13381
- fs48.renameSync(tmpPath, opts.configPath);
14333
+ fs49.renameSync(tmpPath, opts.configPath);
13382
14334
  } catch (e) {
13383
14335
  try {
13384
- fs48.unlinkSync(tmpPath);
14336
+ fs49.unlinkSync(tmpPath);
13385
14337
  } catch {
13386
14338
  }
13387
14339
  throw e;
@@ -13389,12 +14341,12 @@ function writePulseConfig(config, opts) {
13389
14341
  }
13390
14342
  function seedFromStrategy(opts = {}) {
13391
14343
  const cwd = opts.cwd ?? process.cwd();
13392
- const strategyPath = path48.join(cwd, "STRATEGY.md");
14344
+ const strategyPath = path49.join(cwd, "STRATEGY.md");
13393
14345
  const warnings = [];
13394
- if (!fs49.existsSync(strategyPath)) {
14346
+ if (!fs50.existsSync(strategyPath)) {
13395
14347
  return { name: null, keyMetrics: [], warnings: ["STRATEGY.md not found"] };
13396
14348
  }
13397
- const raw = fs49.readFileSync(strategyPath, "utf-8");
14349
+ const raw = fs50.readFileSync(strategyPath, "utf-8");
13398
14350
  let name = null;
13399
14351
  const fmMatch = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
13400
14352
  const fm = fmMatch?.[1];
@@ -13546,7 +14498,7 @@ function loadTemplate() {
13546
14498
  const url = import.meta?.url;
13547
14499
  if (!url) return INLINE_TEMPLATE;
13548
14500
  const here = dirname16(fileURLToPath(url));
13549
- return readFileSync392(join622(here, "template.md"), "utf-8");
14501
+ return readFileSync41(join642(here, "template.md"), "utf-8");
13550
14502
  } catch {
13551
14503
  return INLINE_TEMPLATE;
13552
14504
  }
@@ -13749,12 +14701,12 @@ function tokenize(text) {
13749
14701
  async function* walk2(dir) {
13750
14702
  let entries;
13751
14703
  try {
13752
- entries = await fs50.readdir(dir, { withFileTypes: true });
14704
+ entries = await fs51.readdir(dir, { withFileTypes: true });
13753
14705
  } catch {
13754
14706
  return;
13755
14707
  }
13756
14708
  for (const e of entries) {
13757
- const p = path49.join(dir, e.name);
14709
+ const p = path50.join(dir, e.name);
13758
14710
  if (e.isDirectory()) yield* walk2(p);
13759
14711
  else if (e.isFile() && e.name.endsWith(".md")) yield p;
13760
14712
  }
@@ -13763,11 +14715,11 @@ async function readDocumentedTokens(solutionsDir) {
13763
14715
  const docs = [];
13764
14716
  for (const [track, categories] of TRACK_CATEGORIES2) {
13765
14717
  for (const category of categories) {
13766
- const dir = path49.join(solutionsDir, track, category);
14718
+ const dir = path50.join(solutionsDir, track, category);
13767
14719
  for await (const file of walk2(dir)) {
13768
- const raw = await fs50.readFile(file, "utf-8");
14720
+ const raw = await fs51.readFile(file, "utf-8");
13769
14721
  const m = /^#\s+(.+)$/m.exec(raw);
13770
- const title = m?.[1] ?? path49.basename(file, ".md");
14722
+ const title = m?.[1] ?? path50.basename(file, ".md");
13771
14723
  docs.push(tokenize(title));
13772
14724
  }
13773
14725
  }
@@ -13852,161 +14804,55 @@ function emitSection(name, body) {
13852
14804
  ` : `## ${name}
13853
14805
 
13854
14806
  ${trimmed}
13855
- `;
13856
- }
13857
- function serializeStrategyDoc(doc, opts = {}) {
13858
- const sectionByName = new Map(doc.sections.map((s) => [s.name, s.body]));
13859
- const sectionBlocks = [];
13860
- for (const name of SECTION_ORDER) {
13861
- const body = sectionByName.get(name);
13862
- if (body === void 0) continue;
13863
- sectionBlocks.push(emitSection(name, body));
13864
- }
13865
- const blocks = [emitFrontmatter(doc), "", emitH1(doc, opts), "", sectionBlocks.join("\n")];
13866
- const out = blocks.join("\n");
13867
- return out.endsWith("\n") ? out : out + "\n";
13868
- }
13869
- function readExistingH1(filePath) {
13870
- if (!fs51.existsSync(filePath)) return void 0;
13871
- const raw = fs51.readFileSync(filePath, "utf-8");
13872
- const lines = raw.split("\n").slice(0, 50);
13873
- for (const line of lines) {
13874
- if (line.startsWith("# ")) return line.trimEnd();
13875
- }
13876
- return void 0;
13877
- }
13878
- function writeStrategyDoc(doc, opts) {
13879
- StrategyDocSchema.parse(doc);
13880
- const targetPath = path50.join(opts.cwd, FILENAME);
13881
- const bakPath = `${targetPath}.bak`;
13882
- const tmpPath = `${targetPath}.tmp-${process.pid}`;
13883
- const preservedH1 = readExistingH1(targetPath);
13884
- if (fs51.existsSync(targetPath) && !opts.skipBackup && !fs51.existsSync(bakPath)) {
13885
- const raw = fs51.readFileSync(targetPath, "utf-8");
13886
- fs51.writeFileSync(bakPath, raw, "utf-8");
13887
- }
13888
- const serialized = serializeStrategyDoc(
13889
- doc,
13890
- preservedH1 !== void 0 ? { h1: preservedH1 } : {}
13891
- );
13892
- fs51.writeFileSync(tmpPath, serialized, "utf-8");
13893
- try {
13894
- fs51.renameSync(tmpPath, targetPath);
13895
- } catch (e) {
13896
- try {
13897
- fs51.unlinkSync(tmpPath);
13898
- } catch {
13899
- }
13900
- throw e;
13901
- }
13902
- }
13903
- function proposalsDir(projectPath) {
13904
- return path51.join(projectPath, ".harness", "proposals");
13905
- }
13906
- function proposalPath(projectPath, id) {
13907
- return path51.join(proposalsDir(projectPath), `${id}.json`);
13908
- }
13909
- function ensureDir(dir) {
13910
- fs52.mkdirSync(dir, { recursive: true });
13911
- }
13912
- function writeAtomic(filePath, content) {
13913
- const tmpPath = `${filePath}.tmp`;
13914
- fs52.writeFileSync(tmpPath, content);
13915
- fs52.renameSync(tmpPath, filePath);
13916
- }
13917
- async function createProposal(projectPath, input) {
13918
- const validated = EmitSkillProposalInputSchema.parse(input);
13919
- const id = `proposal_${randomUUID2().replace(/-/g, "")}`;
13920
- const proposal = SkillProposalSchema.parse({
13921
- id,
13922
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13923
- kind: validated.kind,
13924
- targetSkill: validated.targetSkill,
13925
- proposedBy: validated.proposedBy,
13926
- source: {
13927
- sessionId: validated.sessionId,
13928
- taskId: validated.taskId,
13929
- justification: validated.justification
13930
- },
13931
- content: validated.content,
13932
- status: "open"
13933
- });
13934
- if (proposal.kind === "refinement" && proposal.targetSkill) {
13935
- const existing = await listProposals(projectPath, { status: "open" });
13936
- const clash = existing.find(
13937
- (p) => p.kind === "refinement" && p.targetSkill === proposal.targetSkill
13938
- );
13939
- if (clash) {
13940
- throw new ProposalConflictError(
13941
- `An open refinement proposal already exists for skill "${proposal.targetSkill}" (id: ${clash.id})`
13942
- );
13943
- }
13944
- }
13945
- const dir = proposalsDir(projectPath);
13946
- ensureDir(dir);
13947
- writeAtomic(proposalPath(projectPath, id), JSON.stringify(proposal, null, 2));
13948
- return proposal;
14807
+ `;
13949
14808
  }
13950
- async function getProposal(projectPath, id) {
13951
- const file = proposalPath(projectPath, id);
13952
- try {
13953
- const raw = fs52.readFileSync(file, "utf-8");
13954
- const parsed = SkillProposalSchema.safeParse(JSON.parse(raw));
13955
- return parsed.success ? parsed.data : null;
13956
- } catch {
13957
- return null;
14809
+ function serializeStrategyDoc(doc, opts = {}) {
14810
+ const sectionByName = new Map(doc.sections.map((s) => [s.name, s.body]));
14811
+ const sectionBlocks = [];
14812
+ for (const name of SECTION_ORDER) {
14813
+ const body = sectionByName.get(name);
14814
+ if (body === void 0) continue;
14815
+ sectionBlocks.push(emitSection(name, body));
13958
14816
  }
14817
+ const blocks = [emitFrontmatter(doc), "", emitH1(doc, opts), "", sectionBlocks.join("\n")];
14818
+ const out = blocks.join("\n");
14819
+ return out.endsWith("\n") ? out : out + "\n";
13959
14820
  }
13960
- async function listProposals(projectPath, opts = {}) {
13961
- const dir = proposalsDir(projectPath);
13962
- let files;
13963
- try {
13964
- files = fs52.readdirSync(dir);
13965
- } catch {
13966
- return [];
13967
- }
13968
- const out = [];
13969
- for (const file of files) {
13970
- if (!file.endsWith(".json")) continue;
13971
- const id = file.slice(0, -".json".length);
13972
- const proposal = await getProposal(projectPath, id);
13973
- if (!proposal) continue;
13974
- if (opts.status && opts.status !== "all" && proposal.status !== opts.status) continue;
13975
- out.push(proposal);
14821
+ function readExistingH1(filePath) {
14822
+ if (!fs52.existsSync(filePath)) return void 0;
14823
+ const raw = fs52.readFileSync(filePath, "utf-8");
14824
+ const lines = raw.split("\n").slice(0, 50);
14825
+ for (const line of lines) {
14826
+ if (line.startsWith("# ")) return line.trimEnd();
13976
14827
  }
13977
- out.sort((a, b) => a.createdAt > b.createdAt ? -1 : a.createdAt < b.createdAt ? 1 : 0);
13978
- return out;
13979
- }
13980
- async function updateProposal(projectPath, id, patch) {
13981
- const current = await getProposal(projectPath, id);
13982
- if (!current) throw new ProposalNotFoundError(id);
13983
- const next = SkillProposalSchema.parse({
13984
- ...current,
13985
- ...patch,
13986
- id: current.id,
13987
- createdAt: current.createdAt,
13988
- kind: current.kind
13989
- });
13990
- writeAtomic(proposalPath(projectPath, id), JSON.stringify(next, null, 2));
13991
- return next;
14828
+ return void 0;
13992
14829
  }
13993
- function deriveSkillUsage(projectRoot, skillName, windowDays = 30) {
13994
- const records = readAdoptionRecords(projectRoot);
13995
- const cutoffMs = Date.now() - windowDays * 24 * 60 * 60 * 1e3;
13996
- let count = 0;
13997
- let lastUsed;
13998
- for (const rec of records) {
13999
- if (rec.skill !== skillName) continue;
14000
- const startedMs = Date.parse(rec.startedAt);
14001
- if (Number.isFinite(startedMs) && startedMs < cutoffMs) continue;
14002
- count += 1;
14003
- if (!lastUsed || rec.startedAt > lastUsed) lastUsed = rec.startedAt;
14830
+ function writeStrategyDoc(doc, opts) {
14831
+ StrategyDocSchema.parse(doc);
14832
+ const targetPath = path51.join(opts.cwd, FILENAME);
14833
+ const bakPath = `${targetPath}.bak`;
14834
+ const tmpPath = `${targetPath}.tmp-${process.pid}`;
14835
+ const preservedH1 = readExistingH1(targetPath);
14836
+ if (fs52.existsSync(targetPath) && !opts.skipBackup && !fs52.existsSync(bakPath)) {
14837
+ const raw = fs52.readFileSync(targetPath, "utf-8");
14838
+ fs52.writeFileSync(bakPath, raw, "utf-8");
14839
+ }
14840
+ const serialized = serializeStrategyDoc(
14841
+ doc,
14842
+ preservedH1 !== void 0 ? { h1: preservedH1 } : {}
14843
+ );
14844
+ fs52.writeFileSync(tmpPath, serialized, "utf-8");
14845
+ try {
14846
+ fs52.renameSync(tmpPath, targetPath);
14847
+ } catch (e) {
14848
+ try {
14849
+ fs52.unlinkSync(tmpPath);
14850
+ } catch {
14851
+ }
14852
+ throw e;
14004
14853
  }
14005
- const stats = { count, windowDays };
14006
- if (lastUsed) stats.lastUsed = lastUsed;
14007
- return stats;
14008
14854
  }
14009
- var WHATWG_BAD_PORTS, BAD_PORT_SET, VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, KNOWN_SECTION_NAMES, ISO_DATE, PLACEHOLDER_INLINE, REQUIRED, ALLOWED, StrategyFrontmatterSchema, StrategySectionSchema, StrategyDocSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE2, 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, KEBAB_CASE, TICKET_ID, 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, readFile72, 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, MAX_TOP_AFFECTED, BaselineManager, BenchmarkRunner, RegressionDetector, SOURCE_EXTENSIONS, FUNCTION_DECL_RE, CONST_DECL_RE, CriticalPathResolver, NoOpTelemetryAdapter, NoOpExecutor, ConsoleSink, feedbackConfig, ChecklistBuilder, AgentActionEmitter, globalEmitter, FileSink, NoOpSink, ConfidenceTierSchema, RegressionResultSchema, DirectionSchema, CategoryForecastSchema, SpecImpactSignalsSchema, SpecImpactEstimateSchema, ContributingFeatureSchema, AdjustedForecastSchema, PredictionWarningSchema, StabilityForecastSchema, PredictionResultSchema, PredictionOptionsSchema, VALID_STATUSES, EM_DASH, VALID_PRIORITIES, ALL_CATEGORIES, 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, OSV_ENDPOINT, DEFAULT_CACHE_DIR, DEFAULT_TTL_HOURS, 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_FALLBACKS, 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, ADVERSARIAL_DESCRIPTOR, UNVERIFIED_INPUT_PATTERN, PROMISE_NO_REJECT_PATTERN, UNGUARDED_OPTIONAL_CALL, COMPOSE_NO_AWAIT, FETCH_NO_TIMEOUT, TYPESCRIPT_STRICT_DESCRIPTOR, EXPLICIT_ANY_PATTERN, TS_IGNORE_PATTERN, UNSAFE_AS_UNKNOWN, NON_NULL_ASSERTION, VAGUE_HELPER_NAME, FRONTEND_RACES_DESCRIPTOR, AGENT_DESCRIPTORS, CONDITIONAL_SUBAGENT_DESCRIPTORS, AGENT_RUNNERS, SUBAGENT_ORDER, RISK_KEYWORDS, EXCLUDED_PATH_PATTERNS, ASYNC_UI_SIGNALS, WORD_BOUNDARY, 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, DEFAULT_CAPACITY, CacheMetricsRecorder, RETRY_BACKOFFS_MS, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_BATCH_SIZE, defaultFetch, defaultWarn, OTLPExporter, SERVICE_NAME_ATTR, SpanKind, 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, SECTION_ORDER, FILENAME, ProposalNotFoundError, ProposalConflictError;
14855
+ var WHATWG_BAD_PORTS, BAD_PORT_SET, VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, KNOWN_SECTION_NAMES, ISO_DATE, PLACEHOLDER_INLINE, REQUIRED, ALLOWED, StrategyFrontmatterSchema, StrategySectionSchema, StrategyDocSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE2, 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, KEBAB_CASE, TICKET_ID, 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, readFile72, 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, ConfidenceTierSchema, RegressionResultSchema, DirectionSchema, CategoryForecastSchema, SpecImpactSignalsSchema, SpecImpactEstimateSchema, ContributingFeatureSchema, AdjustedForecastSchema, PredictionWarningSchema, StabilityForecastSchema, PredictionResultSchema, PredictionOptionsSchema, VALID_STATUSES, EM_DASH, VALID_PRIORITIES, ALL_CATEGORIES, 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, OSV_ENDPOINT, DEFAULT_CACHE_DIR, DEFAULT_TTL_HOURS, 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_FALLBACKS, 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, ADVERSARIAL_DESCRIPTOR, UNVERIFIED_INPUT_PATTERN, PROMISE_NO_REJECT_PATTERN, UNGUARDED_OPTIONAL_CALL, COMPOSE_NO_AWAIT, FETCH_NO_TIMEOUT, TYPESCRIPT_STRICT_DESCRIPTOR, EXPLICIT_ANY_PATTERN, TS_IGNORE_PATTERN, UNSAFE_AS_UNKNOWN, NON_NULL_ASSERTION, VAGUE_HELPER_NAME, FRONTEND_RACES_DESCRIPTOR, AGENT_DESCRIPTORS, CONDITIONAL_SUBAGENT_DESCRIPTORS, AGENT_RUNNERS, SUBAGENT_ORDER, RISK_KEYWORDS, EXCLUDED_PATH_PATTERNS, ASYNC_UI_SIGNALS, WORD_BOUNDARY, 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, CI_REVIEW_VERDICT_SCHEMA_VERSION, CI_RUNNERS, CI_ASSESSMENTS, CI_REVIEW_DOMAINS, ReviewFindingSchema, CiReviewVerdictSchema, VERDICT_MAP, RUNNER_PRESETS, VERDICT_MAP2, DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_EXEC_MAX_STDOUT_BYTES, STDERR_CAP_BYTES, STDERR_TAIL_BYTES, defaultExecFile, EMPTY_TIER, EM_DASH2, STATUS_RANK, EM_DASH3, DEFAULT_MILESTONE, MAX_CLOSEST_MATCHES, 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, DEFAULT_CATCH_ALL, DEFAULT_INTAKE, DEFAULT_MAX_ACTIVE, 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, DEFAULT_CAPACITY, CacheMetricsRecorder, RETRY_BACKOFFS_MS, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_BATCH_SIZE, defaultFetch, defaultWarn, OTLPExporter, SERVICE_NAME_ATTR, SpanKind, ModeSchema, SeveritySchema, TierSchema, StrengthFindingSchema, HarnessConfigSubsetSchema, HookFileSchema, ProjectContextSchema, AuditSummarySchema, AuditResultSchema, SEVERITY_WEIGHTS, clamp, HOOK_SCRIPT_TOKEN, NEVER_BLOCKS, ALWAYS_EXIT_ZERO, EXIT_ZERO_LINE, NONZERO_EXIT, GATE, ERREXIT, strength001NonblockingHooks, BASELINE_REWRITE, strength002Autobaseline, SKIP_RE, strength003SkipList, strength004EmptyThresholds, INIT_SKILL_PATH, DEFAULT_BASIC, strength005LowestTier, AUTO_PATTERN, COMMENT_LINE, NAME_VALUE_LINE, PAT_GATING, REVIEW_SIGNAL, strength006AutoapproveBaseline, CHECK_SIGNAL_MAP, strength007SnapshotSignalMismatch, ALL_RULES, HarnessStrengthAuditor, MAX_TOP_AFFECTED, CompoundLockHeldError, KNOWN_CATEGORIES, ProposalNotFoundError, ProposalConflictError, 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, SECTION_ORDER, FILENAME;
14010
14856
  var init_dist3 = __esm({
14011
14857
  "../core/dist/index.mjs"() {
14012
14858
  init_chunk_FYTJQ2ZY();
@@ -14018,7 +14864,6 @@ var init_dist3 = __esm({
14018
14864
  init_dist();
14019
14865
  init_dist();
14020
14866
  init_dist();
14021
- init_dist();
14022
14867
  init_dist2();
14023
14868
  init_dist();
14024
14869
  init_dist();
@@ -14032,11 +14877,12 @@ var init_dist3 = __esm({
14032
14877
  init_dist();
14033
14878
  init_dist();
14034
14879
  init_dist();
14035
- init_dist();
14036
14880
  init_dist2();
14037
14881
  init_dist();
14038
14882
  init_dist();
14039
14883
  init_dist();
14884
+ init_dist();
14885
+ init_dist();
14040
14886
  WHATWG_BAD_PORTS = Object.freeze([
14041
14887
  1,
14042
14888
  7,
@@ -14623,22 +15469,21 @@ var init_dist3 = __esm({
14623
15469
  exclude: z5.array(z5.string()).optional(),
14624
15470
  docPaths: z5.array(z5.string()).optional()
14625
15471
  });
14626
- MAX_TOP_AFFECTED = 5;
14627
15472
  BaselineManager = class {
14628
15473
  baselinesPath;
14629
15474
  constructor(projectRoot) {
14630
- this.baselinesPath = join18(projectRoot, ".harness", "perf", "baselines.json");
15475
+ this.baselinesPath = join17(projectRoot, ".harness", "perf", "baselines.json");
14631
15476
  }
14632
15477
  /**
14633
15478
  * Load the baselines file from disk.
14634
15479
  * Returns null if the file does not exist or contains invalid JSON.
14635
15480
  */
14636
15481
  load() {
14637
- if (!existsSync13(this.baselinesPath)) {
15482
+ if (!existsSync12(this.baselinesPath)) {
14638
15483
  return null;
14639
15484
  }
14640
15485
  try {
14641
- const raw = readFileSync32(this.baselinesPath, "utf-8");
15486
+ const raw = readFileSync22(this.baselinesPath, "utf-8");
14642
15487
  return JSON.parse(raw);
14643
15488
  } catch {
14644
15489
  return null;
@@ -14668,7 +15513,7 @@ var init_dist3 = __esm({
14668
15513
  benchmarks
14669
15514
  };
14670
15515
  const dir = dirname6(this.baselinesPath);
14671
- if (!existsSync13(dir)) {
15516
+ if (!existsSync12(dir)) {
14672
15517
  mkdirSync2(dir, { recursive: true });
14673
15518
  }
14674
15519
  writeFileSync2(this.baselinesPath, JSON.stringify(file, null, 2));
@@ -14901,16 +15746,16 @@ var init_dist3 = __esm({
14901
15746
  walkDir(dir, entries) {
14902
15747
  let items;
14903
15748
  try {
14904
- items = fs9.readdirSync(dir, { withFileTypes: true });
15749
+ items = fs8.readdirSync(dir, { withFileTypes: true });
14905
15750
  } catch {
14906
15751
  return;
14907
15752
  }
14908
15753
  for (const item of items) {
14909
15754
  if (item.isDirectory()) {
14910
15755
  if (DEFAULT_SKIP_DIRS.has(item.name)) continue;
14911
- this.walkDir(path6.join(dir, item.name), entries);
14912
- } else if (item.isFile() && SOURCE_EXTENSIONS.has(path6.extname(item.name))) {
14913
- this.scanFile(path6.join(dir, item.name), entries);
15756
+ this.walkDir(path5.join(dir, item.name), entries);
15757
+ } else if (item.isFile() && SOURCE_EXTENSIONS.has(path5.extname(item.name))) {
15758
+ this.scanFile(path5.join(dir, item.name), entries);
14914
15759
  }
14915
15760
  }
14916
15761
  }
@@ -14925,12 +15770,12 @@ var init_dist3 = __esm({
14925
15770
  scanFile(filePath, entries) {
14926
15771
  let content;
14927
15772
  try {
14928
- content = fs9.readFileSync(filePath, "utf-8");
15773
+ content = fs8.readFileSync(filePath, "utf-8");
14929
15774
  } catch {
14930
15775
  return;
14931
15776
  }
14932
15777
  const lines = content.split("\n");
14933
- const relativePath = path6.relative(this.projectRoot, filePath).replace(/\\/g, "/");
15778
+ const relativePath = path5.relative(this.projectRoot, filePath).replace(/\\/g, "/");
14934
15779
  for (let i = 0; i < lines.length; i++) {
14935
15780
  if (!lines[i].includes("@perf-critical")) continue;
14936
15781
  const fnName = this.resolveFunctionName(lines, i + 1);
@@ -15270,7 +16115,7 @@ var init_dist3 = __esm({
15270
16115
  ensureDirectory() {
15271
16116
  if (!this.initialized) {
15272
16117
  const dir = dirname7(this.filePath);
15273
- if (!existsSync14(dir)) {
16118
+ if (!existsSync13(dir)) {
15274
16119
  mkdirSync22(dir, { recursive: true });
15275
16120
  }
15276
16121
  this.initialized = true;
@@ -15301,7 +16146,7 @@ var init_dist3 = __esm({
15301
16146
  this.ensureDirectory();
15302
16147
  const content = this.buffer.join("");
15303
16148
  this.buffer = [];
15304
- if (this.options.mode === "overwrite" && !existsSync14(this.filePath)) {
16149
+ if (this.options.mode === "overwrite" && !existsSync13(this.filePath)) {
15305
16150
  writeFileSync22(this.filePath, content);
15306
16151
  } else {
15307
16152
  appendFileSync(this.filePath, content);
@@ -15735,8 +16580,8 @@ var init_dist3 = __esm({
15735
16580
  return null;
15736
16581
  }
15737
16582
  try {
15738
- const roadmapPath = path7.join(this.rootDir, "roadmap.md");
15739
- const raw = fs10.readFileSync(roadmapPath, "utf-8");
16583
+ const roadmapPath = path6.join(this.rootDir, "roadmap.md");
16584
+ const raw = fs9.readFileSync(roadmapPath, "utf-8");
15740
16585
  const parseResult = parseRoadmap(raw);
15741
16586
  if (!parseResult.ok) return null;
15742
16587
  const features = parseResult.value.milestones.flatMap(
@@ -15771,8 +16616,8 @@ var init_dist3 = __esm({
15771
16616
  * @param specPath - Relative path from rootDir to the spec file.
15772
16617
  */
15773
16618
  estimate(specPath) {
15774
- const absolutePath = path8.join(this.rootDir, specPath);
15775
- const content = fs11.readFileSync(absolutePath, "utf-8");
16619
+ const absolutePath = path7.join(this.rootDir, specPath);
16620
+ const content = fs10.readFileSync(absolutePath, "utf-8");
15776
16621
  const newFileCount = this.extractNewFileCount(content);
15777
16622
  const affectedLayers = this.extractAffectedLayers(content);
15778
16623
  const newDependencies = this.extractNewDependencies(content);
@@ -15784,7 +16629,7 @@ var init_dist3 = __esm({
15784
16629
  phaseCount
15785
16630
  );
15786
16631
  const h1Match = content.match(/^#\s+(.+)$/m);
15787
- const featureName = h1Match ? h1Match[1].trim() : path8.basename(specPath, ".md");
16632
+ const featureName = h1Match ? h1Match[1].trim() : path7.basename(specPath, ".md");
15788
16633
  return {
15789
16634
  specPath,
15790
16635
  featureName,
@@ -15805,8 +16650,8 @@ var init_dist3 = __esm({
15805
16650
  const results = [];
15806
16651
  for (const feature of features) {
15807
16652
  if (!feature.spec) continue;
15808
- const absolutePath = path8.join(this.rootDir, feature.spec);
15809
- if (!fs11.existsSync(absolutePath)) continue;
16653
+ const absolutePath = path7.join(this.rootDir, feature.spec);
16654
+ if (!fs10.existsSync(absolutePath)) continue;
15810
16655
  const estimate = this.estimate(feature.spec);
15811
16656
  results.push({ ...estimate, featureName: feature.name });
15812
16657
  }
@@ -15834,8 +16679,8 @@ var init_dist3 = __esm({
15834
16679
  }
15835
16680
  let count = 0;
15836
16681
  for (const fp of filePaths) {
15837
- const absolute = path8.join(this.rootDir, fp);
15838
- if (!fs11.existsSync(absolute)) {
16682
+ const absolute = path7.join(this.rootDir, fp);
16683
+ if (!fs10.existsSync(absolute)) {
15839
16684
  count++;
15840
16685
  }
15841
16686
  }
@@ -15905,8 +16750,8 @@ var init_dist3 = __esm({
15905
16750
  // --- Private: Config Loading ---
15906
16751
  loadLayerNames() {
15907
16752
  try {
15908
- const configPath = path8.join(this.rootDir, "harness.config.json");
15909
- const raw = fs11.readFileSync(configPath, "utf-8");
16753
+ const configPath = path7.join(this.rootDir, "harness.config.json");
16754
+ const raw = fs10.readFileSync(configPath, "utf-8");
15910
16755
  const config = JSON.parse(raw);
15911
16756
  return (config.layers ?? []).map((l) => l.name);
15912
16757
  } catch {
@@ -15925,8 +16770,8 @@ var init_dist3 = __esm({
15925
16770
  historyPath;
15926
16771
  /** Load history from disk, returning empty history if file doesn't exist. */
15927
16772
  load() {
15928
- if (!fs12.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
15929
- const raw = fs12.readFileSync(this.historyPath, "utf-8");
16773
+ if (!fs11.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
16774
+ const raw = fs11.readFileSync(this.historyPath, "utf-8");
15930
16775
  return JSON.parse(raw);
15931
16776
  }
15932
16777
  /** Append a timestamped snapshot of current violations. */
@@ -15940,18 +16785,18 @@ var init_dist3 = __esm({
15940
16785
  }
15941
16786
  /** Prune snapshots older than retentionDays. */
15942
16787
  prune(retentionDays) {
15943
- if (!fs12.existsSync(this.historyPath)) return;
16788
+ if (!fs11.existsSync(this.historyPath)) return;
15944
16789
  const history = this.load();
15945
16790
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
15946
16791
  history.snapshots = history.snapshots.filter((s) => new Date(s.timestamp).getTime() >= cutoff);
15947
16792
  this.write(history);
15948
16793
  }
15949
16794
  write(history) {
15950
- const dir = path9.dirname(this.historyPath);
15951
- if (!fs12.existsSync(dir)) {
15952
- fs12.mkdirSync(dir, { recursive: true });
16795
+ const dir = path8.dirname(this.historyPath);
16796
+ if (!fs11.existsSync(dir)) {
16797
+ fs11.mkdirSync(dir, { recursive: true });
15953
16798
  }
15954
- fs12.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
16799
+ fs11.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
15955
16800
  }
15956
16801
  };
15957
16802
  NORMALIZERS = {
@@ -16953,7 +17798,7 @@ var init_dist3 = __esm({
16953
17798
  }
16954
17799
  async scanFile(filePath) {
16955
17800
  if (!this.config.enabled) return [];
16956
- const content = await fs30.readFile(filePath, "utf-8");
17801
+ const content = await fs29.readFile(filePath, "utf-8");
16957
17802
  return this.scanContentForFile(content, filePath, 1);
16958
17803
  }
16959
17804
  scanContentForFile(content, filePath, startLine = 1) {
@@ -17075,7 +17920,7 @@ var init_dist3 = __esm({
17075
17920
  }
17076
17921
  };
17077
17922
  OSV_ENDPOINT = "https://api.osv.dev/v1/query";
17078
- DEFAULT_CACHE_DIR = path27.join(".harness", "cache", "osv");
17923
+ DEFAULT_CACHE_DIR = path26.join(".harness", "cache", "osv");
17079
17924
  DEFAULT_TTL_HOURS = 24;
17080
17925
  hiddenUnicodePatterns = [
17081
17926
  {
@@ -17382,7 +18227,7 @@ var init_dist3 = __esm({
17382
18227
  timelinePath;
17383
18228
  constructor(rootDir) {
17384
18229
  this.rootDir = rootDir;
17385
- this.timelinePath = join40(rootDir, ".harness", "security", "timeline.json");
18230
+ this.timelinePath = join39(rootDir, ".harness", "security", "timeline.json");
17386
18231
  }
17387
18232
  /**
17388
18233
  * Load timeline from disk.
@@ -17393,11 +18238,11 @@ var init_dist3 = __esm({
17393
18238
  * fixup is one-shot.
17394
18239
  */
17395
18240
  load() {
17396
- if (!existsSync342(this.timelinePath)) {
18241
+ if (!existsSync332(this.timelinePath)) {
17397
18242
  return { version: 1, snapshots: [], findingLifecycles: [] };
17398
18243
  }
17399
18244
  try {
17400
- const raw = readFileSync23(this.timelinePath, "utf-8");
18245
+ const raw = readFileSync222(this.timelinePath, "utf-8");
17401
18246
  const data = JSON.parse(raw);
17402
18247
  const parsed = SecurityTimelineFileSchema.safeParse(data);
17403
18248
  if (!parsed.success) {
@@ -17427,7 +18272,7 @@ var init_dist3 = __esm({
17427
18272
  */
17428
18273
  save(timeline) {
17429
18274
  const dir = dirname11(this.timelinePath);
17430
- if (!existsSync342(dir)) {
18275
+ if (!existsSync332(dir)) {
17431
18276
  mkdirSync14(dir, { recursive: true });
17432
18277
  }
17433
18278
  const tmp = this.timelinePath + "." + randomBytes2(4).toString("hex") + ".tmp";
@@ -18151,6 +18996,244 @@ var init_dist3 = __esm({
18151
18996
  "spec-compliance": ["compliance", "architecture"],
18152
18997
  "code-quality": ["bug", "security"]
18153
18998
  };
18999
+ CI_REVIEW_VERDICT_SCHEMA_VERSION = 1;
19000
+ CI_RUNNERS = [
19001
+ "claude",
19002
+ "gemini",
19003
+ "antigravity",
19004
+ "codex",
19005
+ "cursor",
19006
+ "local",
19007
+ "floor-only"
19008
+ ];
19009
+ CI_ASSESSMENTS = ["approve", "comment", "request-changes"];
19010
+ CI_REVIEW_DOMAINS = [
19011
+ "compliance",
19012
+ "bug",
19013
+ "security",
19014
+ "architecture",
19015
+ "learnings"
19016
+ ];
19017
+ ReviewFindingSchema = z12.object({
19018
+ id: z12.string().min(1),
19019
+ file: z12.string().min(1),
19020
+ lineRange: z12.tuple([z12.number(), z12.number()]),
19021
+ domain: z12.enum(CI_REVIEW_DOMAINS),
19022
+ severity: z12.enum(["critical", "important", "suggestion"]),
19023
+ title: z12.string().min(1),
19024
+ rationale: z12.string().min(1),
19025
+ suggestion: z12.string().optional(),
19026
+ evidence: z12.array(z12.string()),
19027
+ validatedBy: z12.enum(["mechanical", "graph", "heuristic"]),
19028
+ cweId: z12.string().optional(),
19029
+ owaspCategory: z12.string().optional(),
19030
+ confidence: z12.union([
19031
+ z12.enum(["high", "medium", "low"]),
19032
+ z12.literal(25),
19033
+ z12.literal(50),
19034
+ z12.literal(75),
19035
+ z12.literal(100)
19036
+ ]).optional(),
19037
+ remediation: z12.string().optional(),
19038
+ references: z12.array(z12.string()).optional(),
19039
+ trustScore: z12.number().optional(),
19040
+ rubricItemId: z12.string().optional(),
19041
+ subagent: z12.string().optional()
19042
+ }).passthrough();
19043
+ CiReviewVerdictSchema = z12.object({
19044
+ schemaVersion: z12.literal(CI_REVIEW_VERDICT_SCHEMA_VERSION),
19045
+ runner: z12.enum(CI_RUNNERS),
19046
+ ranLlmTier: z12.boolean(),
19047
+ assessment: z12.enum(CI_ASSESSMENTS),
19048
+ findings: z12.array(ReviewFindingSchema),
19049
+ blockingFindings: z12.array(ReviewFindingSchema),
19050
+ exitCode: z12.number().int(),
19051
+ skipped: z12.boolean(),
19052
+ skipReason: z12.string().optional()
19053
+ }).superRefine((v, ctx) => {
19054
+ for (const [i, f] of v.blockingFindings.entries()) {
19055
+ if (f.severity !== "critical") {
19056
+ ctx.addIssue({
19057
+ code: z12.ZodIssueCode.custom,
19058
+ path: ["blockingFindings", i, "severity"],
19059
+ message: `blockingFindings[${i}] has severity '${f.severity}'; only 'critical' findings may block`
19060
+ });
19061
+ }
19062
+ }
19063
+ for (const [i, bf] of v.blockingFindings.entries()) {
19064
+ const present = v.findings.some((f) => findingsMatch(f, bf));
19065
+ if (!present) {
19066
+ ctx.addIssue({
19067
+ code: z12.ZodIssueCode.custom,
19068
+ path: ["blockingFindings", i],
19069
+ message: `blockingFindings[${i}] (id='${bf.id}') is not present in findings`
19070
+ });
19071
+ }
19072
+ }
19073
+ const criticalCount = v.findings.filter((f) => f.severity === "critical").length;
19074
+ if (v.blockingFindings.length !== criticalCount) {
19075
+ ctx.addIssue({
19076
+ code: z12.ZodIssueCode.custom,
19077
+ path: ["blockingFindings"],
19078
+ message: `blockingFindings (${v.blockingFindings.length}) must equal the critical findings in findings (${criticalCount})`
19079
+ });
19080
+ }
19081
+ const blocking = v.blockingFindings.length > 0;
19082
+ if (blocking && v.assessment !== "request-changes") {
19083
+ ctx.addIssue({
19084
+ code: z12.ZodIssueCode.custom,
19085
+ path: ["assessment"],
19086
+ message: `assessment must be 'request-changes' when blockingFindings is non-empty (got '${v.assessment}')`
19087
+ });
19088
+ }
19089
+ const mustExitNonZero = blocking || v.assessment === "request-changes";
19090
+ if (mustExitNonZero && v.exitCode === 0) {
19091
+ ctx.addIssue({
19092
+ code: z12.ZodIssueCode.custom,
19093
+ path: ["exitCode"],
19094
+ message: `exitCode must be non-zero when blockingFindings is non-empty or assessment is 'request-changes'`
19095
+ });
19096
+ }
19097
+ if (!mustExitNonZero && v.exitCode !== 0) {
19098
+ ctx.addIssue({
19099
+ code: z12.ZodIssueCode.custom,
19100
+ path: ["exitCode"],
19101
+ message: `exitCode must be 0 when there are no blocking findings and assessment is not 'request-changes'`
19102
+ });
19103
+ }
19104
+ });
19105
+ VERDICT_MAP = {
19106
+ approve: "approve",
19107
+ comment: "comment",
19108
+ "request-changes": "request-changes"
19109
+ };
19110
+ RUNNER_PRESETS = {
19111
+ claude: {
19112
+ kind: "agent-cli",
19113
+ supported: true,
19114
+ secretEnvVar: "ANTHROPIC_API_KEY",
19115
+ // Verified (Task 10 smoke test): diff piped via STDIN, instruction as the
19116
+ // `-p` prompt, JSON transcript on stdout. There is NO `--input-file` flag.
19117
+ headlessInvocation: ({ instruction }) => ({
19118
+ command: "claude",
19119
+ args: ["-p", instruction, "--output-format", "json"]
19120
+ }),
19121
+ verdictParser: parseClaudeVerdict
19122
+ },
19123
+ gemini: {
19124
+ kind: "agent-cli",
19125
+ supported: false,
19126
+ // argv corrected from `gemini --help` (NOT `--json`; it is `-o json`, and there
19127
+ // is no `--file` flag — the diff goes on STDIN):
19128
+ // command: 'gemini'
19129
+ // args: ['-p', instruction, '-o', 'json'] // diff piped via STDIN
19130
+ // Re-enable by restoring `supported: true`, `secretEnvVar: 'GEMINI_API_KEY'`,
19131
+ // the headlessInvocation above, and `verdictParser: parseGeminiVerdict` once
19132
+ // the output envelope is captured in CI.
19133
+ unsupportedReason: "SUPERSEDED by the `antigravity` runner (use `--runner antigravity`): the Gemini agentic CLI was renamed to Antigravity (command `agy`), which authenticates locally and produces a capturable verdict where this `gemini` CLI did not. Retained as a valid runner id for back-compat. Original deferral note: gemini argv corrected from --help (`gemini -p <instruction> -o json`, diff on STDIN), but the JSON output envelope is UNVERIFIED: no GEMINI_API_KEY was available in the authoring environment, so the CLI fell through to interactive OAuth and produced no capturable result. Verification deferred to a real CI run with credentials."
19134
+ },
19135
+ antigravity: {
19136
+ kind: "agent-cli",
19137
+ supported: true,
19138
+ // agy is authenticated locally via OAuth here (no API key needed for local
19139
+ // runs). For CI, the gating secret is uncertain — this is a BEST-GUESS pending
19140
+ // CI verification: antigravity is Gemini-family (the renamed `gemini` CLI), so
19141
+ // GEMINI_API_KEY is the most likely credential. Local auth is OAuth-based and
19142
+ // needs no secret; confirm/correct this against a real CI run.
19143
+ secretEnvVar: "GEMINI_API_KEY",
19144
+ // Verified live against the real CLI: diff piped via STDIN, instruction as the
19145
+ // `--print` prompt. There is NO `--output-format`/`--json` flag (it errors
19146
+ // `flags provided but not defined: -output-format`); `--print` (alias
19147
+ // `--prompt`) runs a single non-interactive prompt and emits the model's
19148
+ // response as plain text. Consistent with the claude/codex STDIN diff model.
19149
+ headlessInvocation: ({ instruction }) => ({
19150
+ command: "agy",
19151
+ args: ["--print", instruction]
19152
+ }),
19153
+ verdictParser: parseAntigravityVerdict
19154
+ },
19155
+ codex: {
19156
+ kind: "agent-cli",
19157
+ supported: true,
19158
+ secretEnvVar: "OPENAI_API_KEY",
19159
+ // Verified (Task 10 smoke test): diff piped via STDIN, instruction as the
19160
+ // positional prompt, JSONL event stream on stdout. There is NO `--file` flag.
19161
+ headlessInvocation: ({ instruction }) => ({
19162
+ command: "codex",
19163
+ args: ["exec", "--json", instruction]
19164
+ }),
19165
+ verdictParser: parseCodexVerdict
19166
+ },
19167
+ cursor: {
19168
+ kind: "agent-cli",
19169
+ supported: false,
19170
+ unsupportedReason: "cursor headless CI invocation is unverified and the CLI is not present in this environment; deferred to a real CI run per the required-review-ci re-scope decision."
19171
+ },
19172
+ local: {
19173
+ kind: "endpoint",
19174
+ supported: true,
19175
+ endpointEnvVar: "HARNESS_LOCAL_ENDPOINT",
19176
+ modelEnvVar: "HARNESS_LOCAL_MODEL",
19177
+ // `invoke` is intentionally left unset here — it is the injected seam Phase 2
19178
+ // wires to the openai-compatible provider (see LocalEndpointInvoke). The
19179
+ // deterministic surface (env-var seams + verdictParser) is what Phase 1 ships
19180
+ // and unit-tests; live single-pass verification is DEFERRED (no endpoint here).
19181
+ verdictParser: parseLocalVerdict
19182
+ }
19183
+ };
19184
+ VERDICT_MAP2 = {
19185
+ approve: "approve",
19186
+ comment: "comment",
19187
+ "request-changes": "request-changes"
19188
+ };
19189
+ DEFAULT_EXEC_TIMEOUT_MS = 12e4;
19190
+ DEFAULT_EXEC_MAX_STDOUT_BYTES = 1024 * 1024 * 64;
19191
+ STDERR_CAP_BYTES = 1024 * 1024;
19192
+ STDERR_TAIL_BYTES = 2048;
19193
+ defaultExecFile = async (command, args, opts) => {
19194
+ const timeout = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
19195
+ const maxStdoutBytes = opts.maxStdoutBytes ?? DEFAULT_EXEC_MAX_STDOUT_BYTES;
19196
+ const child = nodeExecFile(command, args, {
19197
+ env: opts.env,
19198
+ timeout,
19199
+ killSignal: "SIGTERM"
19200
+ });
19201
+ child.stdin?.end(opts.stdin);
19202
+ return new Promise((resolve7, reject) => {
19203
+ let out = "";
19204
+ let outBytes = 0;
19205
+ let err = "";
19206
+ let errBytes = 0;
19207
+ let overflowed = false;
19208
+ child.stdout?.on("data", (d) => {
19209
+ const chunk = Buffer.isBuffer(d) ? d : Buffer.from(String(d));
19210
+ outBytes += chunk.length;
19211
+ out += chunk.toString();
19212
+ if (!overflowed && outBytes > maxStdoutBytes) {
19213
+ overflowed = true;
19214
+ child.kill("SIGTERM");
19215
+ reject(new Error(`runner output exceeded ${maxStdoutBytes} bytes`));
19216
+ }
19217
+ });
19218
+ child.stderr?.on("data", (d) => {
19219
+ const chunk = Buffer.isBuffer(d) ? d : Buffer.from(String(d));
19220
+ errBytes += chunk.length;
19221
+ err += chunk.toString();
19222
+ if (errBytes > STDERR_CAP_BYTES) err = err.slice(-STDERR_CAP_BYTES);
19223
+ });
19224
+ child.on("error", reject);
19225
+ child.on("close", (code) => {
19226
+ if (overflowed) return;
19227
+ if (code === 0) {
19228
+ resolve7({ stdout: out });
19229
+ return;
19230
+ }
19231
+ const tail = err.length > 0 ? ` \u2014 stderr: ${err.slice(-STDERR_TAIL_BYTES)}` : "";
19232
+ reject(new Error(`exited with code ${code}${tail}`));
19233
+ });
19234
+ });
19235
+ };
19236
+ EMPTY_TIER = { findings: [], ranLlmTier: false, requiredRunnerFailed: false };
18154
19237
  EM_DASH2 = "\u2014";
18155
19238
  STATUS_RANK = {
18156
19239
  backlog: 0,
@@ -18162,6 +19245,9 @@ var init_dist3 = __esm({
18162
19245
  "in-progress": 2,
18163
19246
  done: 3
18164
19247
  };
19248
+ EM_DASH3 = "\u2014";
19249
+ DEFAULT_MILESTONE = "Intake";
19250
+ MAX_CLOSEST_MATCHES = 3;
18165
19251
  RETRY_DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
18166
19252
  GitHubIssuesSyncAdapter = class {
18167
19253
  token;
@@ -19130,28 +20216,31 @@ ${JSON.stringify(event)}`;
19130
20216
  runMigrationPlan: () => runMigrationPlan
19131
20217
  });
19132
20218
  HASH_RE = /<!--\s*harness-history\s+hash:([0-9a-f]{8})\s*-->/i;
19133
- InteractionTypeSchema = z12.enum(["question", "confirmation", "transition"]);
19134
- QuestionSchema = z12.object({
19135
- text: z12.string(),
19136
- options: z12.array(z12.string()).optional(),
19137
- default: z12.string().optional()
20219
+ DEFAULT_CATCH_ALL = ["Backlog", "Current Work"];
20220
+ DEFAULT_INTAKE = "Intake";
20221
+ DEFAULT_MAX_ACTIVE = 25;
20222
+ InteractionTypeSchema = z13.enum(["question", "confirmation", "transition"]);
20223
+ QuestionSchema = z13.object({
20224
+ text: z13.string(),
20225
+ options: z13.array(z13.string()).optional(),
20226
+ default: z13.string().optional()
19138
20227
  });
19139
- ConfirmationSchema = z12.object({
19140
- text: z12.string(),
19141
- context: z12.string()
20228
+ ConfirmationSchema = z13.object({
20229
+ text: z13.string(),
20230
+ context: z13.string()
19142
20231
  });
19143
- TransitionSchema = z12.object({
19144
- completedPhase: z12.string(),
19145
- suggestedNext: z12.string(),
19146
- reason: z12.string(),
19147
- artifacts: z12.array(z12.string()),
19148
- requiresConfirmation: z12.boolean(),
19149
- summary: z12.string()
20232
+ TransitionSchema = z13.object({
20233
+ completedPhase: z13.string(),
20234
+ suggestedNext: z13.string(),
20235
+ reason: z13.string(),
20236
+ artifacts: z13.array(z13.string()),
20237
+ requiresConfirmation: z13.boolean(),
20238
+ summary: z13.string()
19150
20239
  });
19151
- EmitInteractionInputSchema = z12.object({
19152
- path: z12.string(),
20240
+ EmitInteractionInputSchema = z13.object({
20241
+ path: z13.string(),
19153
20242
  type: InteractionTypeSchema,
19154
- stream: z12.string().optional(),
20243
+ stream: z13.string().optional(),
19155
20244
  question: QuestionSchema.optional(),
19156
20245
  confirmation: ConfirmationSchema.optional(),
19157
20246
  transition: TransitionSchema.optional()
@@ -19162,10 +20251,10 @@ ${JSON.stringify(event)}`;
19162
20251
  }
19163
20252
  rootDir;
19164
20253
  async scan() {
19165
- let projectName = path37.basename(this.rootDir);
20254
+ let projectName = path36.basename(this.rootDir);
19166
20255
  try {
19167
- const pkgPath = path37.join(this.rootDir, "package.json");
19168
- const pkgRaw = await fs37.readFile(pkgPath, "utf-8");
20256
+ const pkgPath = path36.join(this.rootDir, "package.json");
20257
+ const pkgRaw = await fs36.readFile(pkgPath, "utf-8");
19169
20258
  const pkg = JSON.parse(pkgRaw);
19170
20259
  if (pkg.name) projectName = pkg.name;
19171
20260
  } catch {
@@ -19278,8 +20367,8 @@ console.log('Blueprint player initialized.');
19278
20367
  styles: STYLES,
19279
20368
  scripts: SCRIPTS
19280
20369
  });
19281
- await fs38.mkdir(options.outputDir, { recursive: true });
19282
- await fs38.writeFile(path38.join(options.outputDir, "index.html"), html);
20370
+ await fs37.mkdir(options.outputDir, { recursive: true });
20371
+ await fs37.writeFile(path37.join(options.outputDir, "index.html"), html);
19283
20372
  }
19284
20373
  };
19285
20374
  TOKENS_PER_MILLION = 1e6;
@@ -19641,7 +20730,7 @@ console.log('Blueprint player initialized.');
19641
20730
  }
19642
20731
  const backoff = RETRY_BACKOFFS_MS[attempt] ?? 0;
19643
20732
  if (attempt < RETRY_BACKOFFS_MS.length - 1) {
19644
- await new Promise((resolve6) => setTimeout(resolve6, backoff));
20733
+ await new Promise((resolve7) => setTimeout(resolve7, backoff));
19645
20734
  }
19646
20735
  }
19647
20736
  this.warn(
@@ -19678,6 +20767,342 @@ console.log('Blueprint player initialized.');
19678
20767
  SpanKind2[SpanKind2["CONSUMER"] = 5] = "CONSUMER";
19679
20768
  return SpanKind2;
19680
20769
  })(SpanKind || {});
20770
+ ModeSchema = z14.enum(["adopter", "toolkit"]);
20771
+ SeveritySchema = z14.enum(["error", "warning", "info"]);
20772
+ TierSchema = z14.enum(["solid", "at-risk", "theatre"]);
20773
+ StrengthFindingSchema = z14.object({
20774
+ id: z14.string(),
20775
+ // e.g. "STRENGTH-001"
20776
+ gearPiece: z14.string(),
20777
+ // label only (v1) — gear piece this defends
20778
+ severity: SeveritySchema,
20779
+ file: z14.string(),
20780
+ // relative to root
20781
+ line: z14.number().int().positive().optional(),
20782
+ // when locatable
20783
+ message: z14.string(),
20784
+ // what's wrong
20785
+ remediation: z14.string()
20786
+ // concrete fix
20787
+ });
20788
+ HarnessConfigSubsetSchema = z14.object({
20789
+ layers: z14.array(z14.unknown()).optional(),
20790
+ architecture: z14.object({ thresholds: z14.record(z14.unknown()).optional() }).passthrough().optional(),
20791
+ template: z14.object({ level: z14.string().optional() }).passthrough().optional(),
20792
+ audit: z14.object({
20793
+ harnessStrength: z14.object({ severities: z14.record(SeveritySchema).optional() }).passthrough().optional()
20794
+ }).passthrough().optional()
20795
+ }).passthrough();
20796
+ HookFileSchema = z14.object({
20797
+ name: z14.string(),
20798
+ path: z14.string(),
20799
+ text: z14.string()
20800
+ });
20801
+ ProjectContextSchema = z14.object({
20802
+ root: z14.string(),
20803
+ mode: ModeSchema,
20804
+ config: HarnessConfigSubsetSchema.nullable(),
20805
+ preCommit: z14.string().nullable(),
20806
+ hookFiles: z14.array(HookFileSchema),
20807
+ workflows: z14.array(z14.object({ path: z14.string(), text: z14.string() })),
20808
+ healthSnapshot: z14.unknown().nullable(),
20809
+ templates: z14.array(z14.object({ path: z14.string(), text: z14.string() })).optional(),
20810
+ initSkill: z14.string().nullable().optional()
20811
+ });
20812
+ AuditSummarySchema = z14.object({
20813
+ errors: z14.number().int().nonnegative(),
20814
+ warnings: z14.number().int().nonnegative(),
20815
+ info: z14.number().int().nonnegative(),
20816
+ rulesRun: z14.number().int().nonnegative(),
20817
+ rulesPassing: z14.number().int().nonnegative()
20818
+ });
20819
+ AuditResultSchema = z14.object({
20820
+ mode: ModeSchema,
20821
+ score: z14.number().min(0).max(100),
20822
+ tier: TierSchema,
20823
+ findings: z14.array(StrengthFindingSchema),
20824
+ summary: AuditSummarySchema
20825
+ });
20826
+ SEVERITY_WEIGHTS = {
20827
+ error: 14,
20828
+ warning: 6,
20829
+ info: 2
20830
+ };
20831
+ clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
20832
+ HOOK_SCRIPT_TOKEN = /(?:^|[\s"'])([^\s"']+\.(?:sh|mjs|cjs|js|ts))(?=$|[\s"'])/g;
20833
+ NEVER_BLOCKS = /never\s+blocks/i;
20834
+ ALWAYS_EXIT_ZERO = /always\s+exits?\s+0/i;
20835
+ EXIT_ZERO_LINE = /^\s*exit\s+0\s*$/;
20836
+ NONZERO_EXIT = /\bexit\s+[1-9]\b/;
20837
+ GATE = /\bif\b|\|\||&&|\bthen\b/;
20838
+ ERREXIT = /\bset\s+-(?:[a-df-z]*e[a-z]*|o\s+errexit)\b/;
20839
+ strength001NonblockingHooks = {
20840
+ id: "STRENGTH-001",
20841
+ gearPiece: "blocking-gate",
20842
+ defaultSeverity: "error",
20843
+ appliesIn: () => true,
20844
+ evaluable: (ctx) => ctx.hookFiles.length > 0,
20845
+ detect(ctx) {
20846
+ const findings = [];
20847
+ for (const hook of ctx.hookFiles) {
20848
+ const lines = hook.text.split("\n");
20849
+ let line;
20850
+ const markerIdx = lines.findIndex((l) => NEVER_BLOCKS.test(l) || ALWAYS_EXIT_ZERO.test(l));
20851
+ if (markerIdx >= 0) {
20852
+ line = markerIdx + 1;
20853
+ } else {
20854
+ const exitIdx = lines.findIndex((l) => EXIT_ZERO_LINE.test(l));
20855
+ const code = stripComments(hook.text);
20856
+ const hasGuard = NONZERO_EXIT.test(code) || GATE.test(code) || ERREXIT.test(code);
20857
+ if (exitIdx >= 0 && !hasGuard) {
20858
+ line = exitIdx + 1;
20859
+ }
20860
+ }
20861
+ if (line !== void 0) {
20862
+ findings.push({
20863
+ id: "STRENGTH-001",
20864
+ gearPiece: "blocking-gate",
20865
+ file: hook.path,
20866
+ line,
20867
+ message: `Hook "${hook.name}" cannot block the commit \u2014 it always succeeds, so a regression passes through.`,
20868
+ remediation: 'Remove the unconditional `exit 0` / "never blocks" escape hatch so the hook fails (non-zero exit) when a check fails.'
20869
+ });
20870
+ }
20871
+ }
20872
+ return findings;
20873
+ }
20874
+ };
20875
+ BASELINE_REWRITE = /--update-baseline|(?:check-arch|baselines?\.json).*(?:--update-baseline|>\s*\S*baselines?\.json)/i;
20876
+ strength002Autobaseline = {
20877
+ id: "STRENGTH-002",
20878
+ gearPiece: "regression-baseline",
20879
+ defaultSeverity: "error",
20880
+ appliesIn: () => true,
20881
+ evaluable: (ctx) => ctx.preCommit !== null,
20882
+ detect(ctx) {
20883
+ if (ctx.preCommit === null) return [];
20884
+ const idx = findRewriteInFailureBranch(ctx.preCommit.split("\n"));
20885
+ if (idx < 0) return [];
20886
+ return [
20887
+ {
20888
+ id: "STRENGTH-002",
20889
+ gearPiece: "regression-baseline",
20890
+ file: ".husky/pre-commit",
20891
+ line: idx + 1,
20892
+ message: "pre-commit auto-rewrites the baseline inside a failure branch \u2014 regressions are silently absorbed instead of blocking the commit.",
20893
+ remediation: "Remove the auto `--update-baseline` from the failure path; let the hook fail and update baselines deliberately in a separate, reviewed step."
20894
+ }
20895
+ ];
20896
+ }
20897
+ };
20898
+ SKIP_RE = /--skip[= ]+([\w,-]+)/;
20899
+ strength003SkipList = {
20900
+ id: "STRENGTH-003",
20901
+ gearPiece: "skip-discipline",
20902
+ defaultSeverity: "warning",
20903
+ appliesIn: () => true,
20904
+ evaluable: (ctx) => ctx.preCommit !== null,
20905
+ detect(ctx) {
20906
+ if (ctx.preCommit === null) return [];
20907
+ const lines = ctx.preCommit.split("\n");
20908
+ const findings = [];
20909
+ for (let i = 0; i < lines.length; i++) {
20910
+ const line = lines[i];
20911
+ const m = SKIP_RE.exec(line);
20912
+ if (!m) continue;
20913
+ const categories = m[1].split(",").filter(Boolean);
20914
+ if (categories.length <= 2) continue;
20915
+ const afterSkip = line.slice(m.index + m[0].length);
20916
+ if (/(^|\s)#/.test(afterSkip)) continue;
20917
+ findings.push({
20918
+ id: "STRENGTH-003",
20919
+ gearPiece: "skip-discipline",
20920
+ file: ".husky/pre-commit",
20921
+ line: i + 1,
20922
+ message: `pre-commit skips ${categories.length} check categories (${categories.join(", ")}) with no inline justification \u2014 the local gate barely guards anything.`,
20923
+ remediation: "Reduce the --skip list to at most 2 categories, or add an inline `# justified: ...` comment explaining why each is deferred (e.g. runs in CI)."
20924
+ });
20925
+ }
20926
+ return findings;
20927
+ }
20928
+ };
20929
+ strength004EmptyThresholds = {
20930
+ id: "STRENGTH-004",
20931
+ gearPiece: "architecture-thresholds",
20932
+ defaultSeverity: "error",
20933
+ appliesIn: () => true,
20934
+ evaluable: (ctx) => ctx.config !== null || ctx.mode === "toolkit" && (ctx.templates?.length ?? 0) > 0,
20935
+ detect(ctx) {
20936
+ const findings = [];
20937
+ if (thresholdsEmpty(ctx.config)) {
20938
+ findings.push(makeFinding3("harness.config.json"));
20939
+ }
20940
+ if (ctx.mode === "toolkit") {
20941
+ for (const tpl of ctx.templates ?? []) {
20942
+ if (!tpl.path.endsWith("harness.config.json.hbs")) continue;
20943
+ let parsed;
20944
+ try {
20945
+ parsed = JSON.parse(tpl.text);
20946
+ } catch {
20947
+ continue;
20948
+ }
20949
+ if (thresholdsEmpty(parsed)) findings.push(makeFinding3(tpl.path));
20950
+ }
20951
+ }
20952
+ return findings;
20953
+ }
20954
+ };
20955
+ INIT_SKILL_PATH = "agents/skills/claude-code/initialize-harness-project/SKILL.md";
20956
+ DEFAULT_BASIC = /default(?:s| recommendation)?[^\n]*\bbasic\b|\bbasic\b[^\n]*\b(?:default|recommend)/i;
20957
+ strength005LowestTier = {
20958
+ id: "STRENGTH-005",
20959
+ gearPiece: "tier-default",
20960
+ defaultSeverity: "warning",
20961
+ appliesIn: () => true,
20962
+ evaluable: (ctx) => ctx.mode === "adopter" ? ctx.config !== null : (ctx.initSkill ?? null) !== null,
20963
+ detect(ctx) {
20964
+ if (ctx.mode === "adopter") {
20965
+ if (ctx.config?.template?.level === "basic") {
20966
+ return [
20967
+ {
20968
+ id: "STRENGTH-005",
20969
+ gearPiece: "tier-default",
20970
+ file: "harness.config.json",
20971
+ message: "Project is pinned to the `basic` tier \u2014 the weakest gate set. Most projects warrant `standard` or higher.",
20972
+ remediation: "Raise `template.level` to `standard` (or higher) unless `basic` is a deliberate, documented choice."
20973
+ }
20974
+ ];
20975
+ }
20976
+ return [];
20977
+ }
20978
+ const text = ctx.initSkill ?? null;
20979
+ if (text === null) return [];
20980
+ const lines = text.split("\n");
20981
+ const idx = lines.findIndex((l) => DEFAULT_BASIC.test(l));
20982
+ if (idx < 0) return [];
20983
+ return [
20984
+ {
20985
+ id: "STRENGTH-005",
20986
+ gearPiece: "tier-default",
20987
+ file: INIT_SKILL_PATH,
20988
+ line: idx + 1,
20989
+ message: "The init skill recommends the `basic` tier by default \u2014 new projects ship with the weakest gate set.",
20990
+ remediation: "Change the default recommendation to `standard` so new projects start with a meaningful gate set."
20991
+ }
20992
+ ];
20993
+ }
20994
+ };
20995
+ AUTO_PATTERN = /auto-?approve|auto-?merge|--auto\b|enable-pull-request-automerge/i;
20996
+ COMMENT_LINE = /^\s*#/;
20997
+ NAME_VALUE_LINE = /^\s*(?:-\s*)?name\s*:/i;
20998
+ PAT_GATING = /secrets\.(?!GITHUB_TOKEN\b)\w*(?:PAT|TOKEN)\w*/;
20999
+ REVIEW_SIGNAL = /required_reviewers|approvals?\s*:\s*[1-9]|CODEOWNERS|github\.event\.review\.state\s*==\s*['"]approved['"]/i;
21000
+ strength006AutoapproveBaseline = {
21001
+ id: "STRENGTH-006",
21002
+ gearPiece: "review-gate",
21003
+ defaultSeverity: "error",
21004
+ appliesIn: () => true,
21005
+ evaluable: (ctx) => ctx.workflows.length > 0,
21006
+ detect(ctx) {
21007
+ const findings = [];
21008
+ for (const wf of ctx.workflows) {
21009
+ const hasAuto = hasAutoStep(wf.text);
21010
+ const hasPat = PAT_GATING.test(wf.text);
21011
+ const hasReview = REVIEW_SIGNAL.test(wf.text);
21012
+ if (hasAuto && hasPat && !hasReview) {
21013
+ const lines = wf.text.split("\n");
21014
+ const idx = lines.findIndex(isAutoLine);
21015
+ findings.push({
21016
+ id: "STRENGTH-006",
21017
+ gearPiece: "review-gate",
21018
+ file: wf.path,
21019
+ ...idx >= 0 ? { line: idx + 1 } : {},
21020
+ message: "Workflow auto-approves/auto-merges a PR using a token with no independent-review gate \u2014 the bot can rubber-stamp its own changes.",
21021
+ remediation: "Gate auto-merge behind a human review (e.g. require an approved review, required_reviewers, or CODEOWNERS) instead of a PAT alone."
21022
+ });
21023
+ }
21024
+ }
21025
+ return findings;
21026
+ }
21027
+ };
21028
+ CHECK_SIGNAL_MAP = {
21029
+ security: "security-findings",
21030
+ entropy: "entropy-drift",
21031
+ deps: "dependency-violations",
21032
+ perf: "perf-regression",
21033
+ docs: "doc-coverage",
21034
+ lint: "lint-issues"
21035
+ };
21036
+ strength007SnapshotSignalMismatch = {
21037
+ id: "STRENGTH-007",
21038
+ gearPiece: "snapshot-honesty",
21039
+ defaultSeverity: "error",
21040
+ appliesIn: () => true,
21041
+ evaluable: (ctx) => isSnapshot(ctx.healthSnapshot),
21042
+ detect(ctx) {
21043
+ if (!isSnapshot(ctx.healthSnapshot)) return [];
21044
+ const { checks, signals } = ctx.healthSnapshot;
21045
+ const present = new Set(Array.isArray(signals) ? signals : []);
21046
+ const findings = [];
21047
+ for (const [check, result] of Object.entries(checks)) {
21048
+ const signal = CHECK_SIGNAL_MAP[check];
21049
+ if (signal === void 0) continue;
21050
+ if (result?.passed === true && present.has(signal)) {
21051
+ findings.push({
21052
+ id: "STRENGTH-007",
21053
+ gearPiece: "snapshot-honesty",
21054
+ file: ".harness/health-snapshot.json",
21055
+ message: `Health snapshot reports the "${check}" check as passing while the contradicting signal "${signal}" is present \u2014 the snapshot is dishonest.`,
21056
+ remediation: `Reconcile the snapshot: either the "${check}" check did not actually pass, or the "${signal}" signal is stale. Regenerate the snapshot from a clean run.`
21057
+ });
21058
+ }
21059
+ }
21060
+ return findings;
21061
+ }
21062
+ };
21063
+ ALL_RULES = [
21064
+ strength001NonblockingHooks,
21065
+ strength002Autobaseline,
21066
+ strength003SkipList,
21067
+ strength004EmptyThresholds,
21068
+ strength005LowestTier,
21069
+ strength006AutoapproveBaseline,
21070
+ strength007SnapshotSignalMismatch
21071
+ ];
21072
+ HarnessStrengthAuditor = class {
21073
+ audit(root, opts = {}) {
21074
+ try {
21075
+ const mode = resolveMode(opts, root);
21076
+ const ctx = buildProjectContext(root, mode);
21077
+ const evaluable = ALL_RULES.filter(
21078
+ (r) => r.appliesIn(mode) && (r.evaluable ? r.evaluable(ctx) : true)
21079
+ );
21080
+ const findings = [];
21081
+ let rulesPassing = 0;
21082
+ for (const rule of evaluable) {
21083
+ const raw = rule.detect(ctx);
21084
+ if (raw.length === 0) {
21085
+ rulesPassing++;
21086
+ continue;
21087
+ }
21088
+ const severity = severityFor(rule, ctx);
21089
+ for (const f of raw) findings.push({ ...f, severity });
21090
+ }
21091
+ const { score, tier } = rollupScore(findings);
21092
+ const summary = {
21093
+ errors: findings.filter((f) => f.severity === "error").length,
21094
+ warnings: findings.filter((f) => f.severity === "warning").length,
21095
+ info: findings.filter((f) => f.severity === "info").length,
21096
+ rulesRun: evaluable.length,
21097
+ rulesPassing
21098
+ };
21099
+ return Ok({ mode, score, tier, findings, summary });
21100
+ } catch (err) {
21101
+ return Err(err instanceof Error ? err : new Error(String(err)));
21102
+ }
21103
+ }
21104
+ };
21105
+ MAX_TOP_AFFECTED = 5;
19681
21106
  CompoundLockHeldError = class extends Error {
19682
21107
  constructor(category, holderPid, lockPath) {
19683
21108
  super(
@@ -19693,6 +21118,18 @@ console.log('Blueprint player initialized.');
19693
21118
  lockPath;
19694
21119
  };
19695
21120
  KNOWN_CATEGORIES = new Set(ALL_SOLUTION_CATEGORIES);
21121
+ ProposalNotFoundError = class extends Error {
21122
+ constructor(id) {
21123
+ super(`proposal not found: ${id}`);
21124
+ this.name = "ProposalNotFoundError";
21125
+ }
21126
+ };
21127
+ ProposalConflictError = class extends Error {
21128
+ constructor(message) {
21129
+ super(message);
21130
+ this.name = "ProposalConflictError";
21131
+ }
21132
+ };
19696
21133
  ALLOWED_FIELD_KEYS = [
19697
21134
  "event_name",
19698
21135
  "count",
@@ -19822,18 +21259,6 @@ console.log('Blueprint player initialized.');
19822
21259
  ...OPTIONAL_STRATEGY_SECTIONS
19823
21260
  ];
19824
21261
  FILENAME = "STRATEGY.md";
19825
- ProposalNotFoundError = class extends Error {
19826
- constructor(id) {
19827
- super(`proposal not found: ${id}`);
19828
- this.name = "ProposalNotFoundError";
19829
- }
19830
- };
19831
- ProposalConflictError = class extends Error {
19832
- constructor(message) {
19833
- super(message);
19834
- this.name = "ProposalConflictError";
19835
- }
19836
- };
19837
21262
  }
19838
21263
  });
19839
21264
 
@@ -19933,7 +21358,6 @@ export {
19933
21358
  PatternConfigSchema,
19934
21359
  EntropyConfigSchema,
19935
21360
  validatePatternConfig,
19936
- composeInsights,
19937
21361
  BaselineManager,
19938
21362
  BenchmarkRunner,
19939
21363
  RegressionDetector,
@@ -20165,11 +21589,34 @@ export {
20165
21589
  splitBundlesByStage,
20166
21590
  runReviewPipeline,
20167
21591
  findParallelGroups,
21592
+ CI_REVIEW_VERDICT_SCHEMA_VERSION,
21593
+ CI_RUNNERS,
21594
+ CI_ASSESSMENTS,
21595
+ CI_REVIEW_DOMAINS,
21596
+ deriveBlockingFindings,
21597
+ deriveExitCode,
21598
+ CiReviewVerdictSchema,
21599
+ parseCiReviewVerdict,
21600
+ buildCiReviewVerdict,
21601
+ parseClaudeVerdict,
21602
+ parseCodexVerdict,
21603
+ parseAntigravityVerdict,
21604
+ parseLocalVerdict,
21605
+ RUNNER_PRESETS,
21606
+ presetKind,
21607
+ isSupportedRunner,
21608
+ parseGeminiVerdict,
21609
+ DEFAULT_EXEC_TIMEOUT_MS,
21610
+ DEFAULT_EXEC_MAX_STDOUT_BYTES,
21611
+ defaultExecFile,
21612
+ runCiReview,
20168
21613
  serializeRoadmap,
20169
21614
  STATUS_RANK,
20170
21615
  isRegression,
20171
21616
  syncRoadmap,
20172
21617
  applySyncChanges,
21618
+ decidePromotionForRow,
21619
+ promoteFeature,
20173
21620
  resolveReverseStatus,
20174
21621
  GitHubIssuesSyncAdapter,
20175
21622
  loadTrackerSyncConfig,
@@ -20187,6 +21634,10 @@ export {
20187
21634
  loadProjectRoadmapMode,
20188
21635
  loadTrackerClientConfigFromProject,
20189
21636
  migrate_exports,
21637
+ defaultIsArchive,
21638
+ isUnactionablePlanned,
21639
+ checkRoadmapHealth,
21640
+ groomRoadmap,
20190
21641
  InteractionTypeSchema,
20191
21642
  QuestionSchema,
20192
21643
  ConfirmationSchema,
@@ -20219,7 +21670,6 @@ export {
20219
21670
  aggregateBySkill,
20220
21671
  aggregateByDay2,
20221
21672
  topSkills,
20222
- loadNotificationsConfig,
20223
21673
  StructuralStrategy,
20224
21674
  DEFAULT_TOKEN_BUDGET,
20225
21675
  TruncationStrategy,
@@ -20238,8 +21688,33 @@ export {
20238
21688
  CacheMetricsRecorder,
20239
21689
  OTLPExporter,
20240
21690
  SpanKind,
21691
+ ModeSchema,
21692
+ SeveritySchema,
21693
+ TierSchema,
21694
+ StrengthFindingSchema,
21695
+ HarnessConfigSubsetSchema,
21696
+ HookFileSchema,
21697
+ ProjectContextSchema,
21698
+ AuditSummarySchema,
21699
+ AuditResultSchema,
21700
+ SEVERITY_WEIGHTS,
21701
+ rollupScore,
21702
+ resolveMode,
21703
+ buildProjectContext,
21704
+ ALL_RULES,
21705
+ HarnessStrengthAuditor,
21706
+ composeInsights,
20241
21707
  CompoundLockHeldError,
20242
21708
  acquireCompoundLock,
21709
+ loadNotificationsConfig,
21710
+ proposalsDir,
21711
+ ProposalNotFoundError,
21712
+ ProposalConflictError,
21713
+ createProposal,
21714
+ getProposal,
21715
+ listProposals,
21716
+ updateProposal,
21717
+ deriveSkillUsage,
20243
21718
  ALLOWED_FIELD_KEYS,
20244
21719
  PII_TOKENS,
20245
21720
  PII_FIELD_DENYLIST,
@@ -20269,14 +21744,6 @@ export {
20269
21744
  assembleCandidateReport,
20270
21745
  serializeStrategyDoc,
20271
21746
  writeStrategyDoc,
20272
- proposalsDir,
20273
- ProposalNotFoundError,
20274
- ProposalConflictError,
20275
- createProposal,
20276
- getProposal,
20277
- listProposals,
20278
- updateProposal,
20279
- deriveSkillUsage,
20280
21747
  dist_exports,
20281
21748
  init_dist3 as init_dist
20282
21749
  };