@harness-engineering/cli 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (499) hide show
  1. package/dist/agents/commands/codex/AGENTS.md +39 -0
  2. package/dist/agents/commands/codex/harness/add-harness-component/SKILL.md +195 -0
  3. package/dist/agents/commands/codex/harness/add-harness-component/agents/openai.yaml +3 -0
  4. package/dist/agents/commands/codex/harness/cleanup-dead-code/SKILL.md +248 -0
  5. package/dist/agents/commands/codex/harness/cleanup-dead-code/agents/openai.yaml +3 -0
  6. package/dist/agents/commands/codex/harness/detect-doc-drift/SKILL.md +182 -0
  7. package/dist/agents/commands/codex/harness/detect-doc-drift/agents/openai.yaml +3 -0
  8. package/dist/agents/commands/codex/harness/enforce-architecture/SKILL.md +299 -0
  9. package/dist/agents/commands/codex/harness/enforce-architecture/agents/openai.yaml +3 -0
  10. package/dist/agents/commands/codex/harness/harness-architecture-advisor/SKILL.md +452 -0
  11. package/dist/agents/commands/codex/harness/harness-architecture-advisor/agents/openai.yaml +3 -0
  12. package/dist/agents/commands/codex/harness/harness-autopilot/SKILL.md +919 -0
  13. package/dist/agents/commands/codex/harness/harness-autopilot/agents/openai.yaml +3 -0
  14. package/dist/agents/commands/codex/harness/harness-brainstorming/SKILL.md +409 -0
  15. package/dist/agents/commands/codex/harness/harness-brainstorming/agents/openai.yaml +3 -0
  16. package/dist/agents/commands/codex/harness/harness-code-review/SKILL.md +860 -0
  17. package/dist/agents/commands/codex/harness/harness-code-review/agents/openai.yaml +3 -0
  18. package/dist/agents/commands/codex/harness/harness-codebase-cleanup/SKILL.md +227 -0
  19. package/dist/agents/commands/codex/harness/harness-codebase-cleanup/agents/openai.yaml +3 -0
  20. package/dist/agents/commands/codex/harness/harness-debugging/SKILL.md +369 -0
  21. package/dist/agents/commands/codex/harness/harness-debugging/agents/openai.yaml +3 -0
  22. package/dist/agents/commands/codex/harness/harness-dependency-health/SKILL.md +182 -0
  23. package/dist/agents/commands/codex/harness/harness-dependency-health/agents/openai.yaml +3 -0
  24. package/dist/agents/commands/codex/harness/harness-docs-pipeline/SKILL.md +463 -0
  25. package/dist/agents/commands/codex/harness/harness-docs-pipeline/agents/openai.yaml +3 -0
  26. package/dist/agents/commands/codex/harness/harness-execution/SKILL.md +513 -0
  27. package/dist/agents/commands/codex/harness/harness-execution/agents/openai.yaml +3 -0
  28. package/dist/agents/commands/codex/harness/harness-hotspot-detector/SKILL.md +164 -0
  29. package/dist/agents/commands/codex/harness/harness-hotspot-detector/agents/openai.yaml +3 -0
  30. package/dist/agents/commands/codex/harness/harness-impact-analysis/SKILL.md +187 -0
  31. package/dist/agents/commands/codex/harness/harness-impact-analysis/agents/openai.yaml +3 -0
  32. package/dist/agents/commands/codex/harness/harness-integrity/SKILL.md +170 -0
  33. package/dist/agents/commands/codex/harness/harness-integrity/agents/openai.yaml +3 -0
  34. package/dist/agents/commands/codex/harness/harness-onboarding/SKILL.md +291 -0
  35. package/dist/agents/commands/codex/harness/harness-onboarding/agents/openai.yaml +3 -0
  36. package/dist/agents/commands/codex/harness/harness-perf/SKILL.md +263 -0
  37. package/dist/agents/commands/codex/harness/harness-perf/agents/openai.yaml +3 -0
  38. package/dist/agents/commands/codex/harness/harness-planning/SKILL.md +582 -0
  39. package/dist/agents/commands/codex/harness/harness-planning/agents/openai.yaml +3 -0
  40. package/dist/agents/commands/codex/harness/harness-refactoring/SKILL.md +172 -0
  41. package/dist/agents/commands/codex/harness/harness-refactoring/agents/openai.yaml +3 -0
  42. package/dist/agents/commands/codex/harness/harness-release-readiness/SKILL.md +692 -0
  43. package/dist/agents/commands/codex/harness/harness-release-readiness/agents/openai.yaml +3 -0
  44. package/dist/agents/commands/codex/harness/harness-roadmap/SKILL.md +598 -0
  45. package/dist/agents/commands/codex/harness/harness-roadmap/agents/openai.yaml +3 -0
  46. package/dist/agents/commands/codex/harness/harness-security-scan/SKILL.md +157 -0
  47. package/dist/agents/commands/codex/harness/harness-security-scan/agents/openai.yaml +3 -0
  48. package/dist/agents/commands/codex/harness/harness-skill-authoring/SKILL.md +295 -0
  49. package/dist/agents/commands/codex/harness/harness-skill-authoring/agents/openai.yaml +3 -0
  50. package/dist/agents/commands/codex/harness/harness-soundness-review/SKILL.md +1270 -0
  51. package/dist/agents/commands/codex/harness/harness-soundness-review/agents/openai.yaml +3 -0
  52. package/dist/agents/commands/codex/harness/harness-supply-chain-audit/SKILL.md +247 -0
  53. package/dist/agents/commands/codex/harness/harness-supply-chain-audit/agents/openai.yaml +3 -0
  54. package/dist/agents/commands/codex/harness/harness-tdd/SKILL.md +180 -0
  55. package/dist/agents/commands/codex/harness/harness-tdd/agents/openai.yaml +3 -0
  56. package/dist/agents/commands/codex/harness/harness-test-advisor/SKILL.md +163 -0
  57. package/dist/agents/commands/codex/harness/harness-test-advisor/agents/openai.yaml +3 -0
  58. package/dist/agents/commands/codex/harness/harness-verification/SKILL.md +424 -0
  59. package/dist/agents/commands/codex/harness/harness-verification/agents/openai.yaml +3 -0
  60. package/dist/agents/commands/codex/harness/harness-verify/SKILL.md +162 -0
  61. package/dist/agents/commands/codex/harness/harness-verify/agents/openai.yaml +3 -0
  62. package/dist/agents/commands/codex/harness/initialize-harness-project/SKILL.md +235 -0
  63. package/dist/agents/commands/codex/harness/initialize-harness-project/agents/openai.yaml +3 -0
  64. package/dist/agents/commands/cursor/harness/add-harness-component.mdc +200 -0
  65. package/dist/agents/commands/cursor/harness/cleanup-dead-code.mdc +253 -0
  66. package/dist/agents/commands/cursor/harness/detect-doc-drift.mdc +187 -0
  67. package/dist/agents/commands/cursor/harness/enforce-architecture.mdc +304 -0
  68. package/dist/agents/commands/cursor/harness/harness-architecture-advisor.mdc +457 -0
  69. package/dist/agents/commands/cursor/harness/harness-autopilot.mdc +924 -0
  70. package/dist/agents/commands/cursor/harness/harness-brainstorming.mdc +414 -0
  71. package/dist/agents/commands/cursor/harness/harness-code-review.mdc +865 -0
  72. package/dist/agents/commands/cursor/harness/harness-codebase-cleanup.mdc +232 -0
  73. package/dist/agents/commands/cursor/harness/harness-debugging.mdc +374 -0
  74. package/dist/agents/commands/cursor/harness/harness-dependency-health.mdc +187 -0
  75. package/dist/agents/commands/cursor/harness/harness-docs-pipeline.mdc +468 -0
  76. package/dist/agents/commands/cursor/harness/harness-execution.mdc +518 -0
  77. package/dist/agents/commands/cursor/harness/harness-hotspot-detector.mdc +169 -0
  78. package/dist/agents/commands/cursor/harness/harness-impact-analysis.mdc +192 -0
  79. package/dist/agents/commands/cursor/harness/harness-integrity.mdc +175 -0
  80. package/dist/agents/commands/cursor/harness/harness-onboarding.mdc +296 -0
  81. package/dist/agents/commands/cursor/harness/harness-perf.mdc +268 -0
  82. package/dist/agents/commands/cursor/harness/harness-planning.mdc +587 -0
  83. package/dist/agents/commands/cursor/harness/harness-refactoring.mdc +177 -0
  84. package/dist/agents/commands/cursor/harness/harness-release-readiness.mdc +697 -0
  85. package/dist/agents/commands/cursor/harness/harness-roadmap.mdc +603 -0
  86. package/dist/agents/commands/cursor/harness/harness-security-scan.mdc +162 -0
  87. package/dist/agents/commands/cursor/harness/harness-skill-authoring.mdc +300 -0
  88. package/dist/agents/commands/cursor/harness/harness-soundness-review.mdc +1275 -0
  89. package/dist/agents/commands/cursor/harness/harness-supply-chain-audit.mdc +252 -0
  90. package/dist/agents/commands/cursor/harness/harness-tdd.mdc +185 -0
  91. package/dist/agents/commands/cursor/harness/harness-test-advisor.mdc +168 -0
  92. package/dist/agents/commands/cursor/harness/harness-verification.mdc +429 -0
  93. package/dist/agents/commands/cursor/harness/harness-verify.mdc +167 -0
  94. package/dist/agents/commands/cursor/harness/initialize-harness-project.mdc +240 -0
  95. package/dist/agents/skills/claude-code/enforce-architecture/SKILL.md +52 -0
  96. package/dist/agents/skills/claude-code/harness-api-design/SKILL.md +52 -0
  97. package/dist/agents/skills/claude-code/harness-architecture-advisor/SKILL.md +52 -0
  98. package/dist/agents/skills/claude-code/harness-auth/SKILL.md +52 -0
  99. package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +355 -45
  100. package/dist/agents/skills/claude-code/harness-autopilot/skill.yaml +12 -0
  101. package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +97 -3
  102. package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +6 -0
  103. package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +2 -4
  104. package/dist/agents/skills/claude-code/harness-database/SKILL.md +52 -0
  105. package/dist/agents/skills/claude-code/harness-deployment/SKILL.md +52 -0
  106. package/dist/agents/skills/claude-code/harness-planning/SKILL.md +99 -3
  107. package/dist/agents/skills/claude-code/harness-planning/skill.yaml +6 -0
  108. package/dist/agents/skills/claude-code/harness-pre-commit-review/SKILL.md +1 -1
  109. package/dist/agents/skills/claude-code/harness-product-spec/SKILL.md +5 -5
  110. package/dist/agents/skills/claude-code/harness-security-review/SKILL.md +27 -7
  111. package/dist/agents/skills/claude-code/harness-security-scan/SKILL.md +52 -0
  112. package/dist/agents/skills/claude-code/harness-supply-chain-audit/SKILL.md +281 -0
  113. package/dist/agents/skills/claude-code/harness-supply-chain-audit/skill.yaml +51 -0
  114. package/dist/agents/skills/codex/add-harness-component/SKILL.md +192 -0
  115. package/dist/agents/skills/codex/add-harness-component/skill.yaml +33 -0
  116. package/dist/agents/skills/codex/align-documentation/SKILL.md +213 -0
  117. package/dist/agents/skills/codex/align-documentation/skill.yaml +32 -0
  118. package/dist/agents/skills/codex/check-mechanical-constraints/SKILL.md +191 -0
  119. package/dist/agents/skills/codex/check-mechanical-constraints/skill.yaml +33 -0
  120. package/dist/agents/skills/codex/cleanup-dead-code/SKILL.md +245 -0
  121. package/dist/agents/skills/codex/cleanup-dead-code/skill.yaml +34 -0
  122. package/dist/agents/skills/codex/detect-doc-drift/SKILL.md +179 -0
  123. package/dist/agents/skills/codex/detect-doc-drift/skill.yaml +31 -0
  124. package/dist/agents/skills/codex/enforce-architecture/SKILL.md +296 -0
  125. package/dist/agents/skills/codex/enforce-architecture/skill.yaml +35 -0
  126. package/dist/agents/skills/codex/harness-accessibility/SKILL.md +281 -0
  127. package/dist/agents/skills/codex/harness-accessibility/skill.yaml +52 -0
  128. package/dist/agents/skills/codex/harness-api-design/SKILL.md +356 -0
  129. package/dist/agents/skills/codex/harness-api-design/skill.yaml +74 -0
  130. package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +449 -0
  131. package/dist/agents/skills/codex/harness-architecture-advisor/skill.yaml +49 -0
  132. package/dist/agents/skills/codex/harness-auth/SKILL.md +331 -0
  133. package/dist/agents/skills/codex/harness-auth/skill.yaml +81 -0
  134. package/dist/agents/skills/codex/harness-autopilot/SKILL.md +916 -0
  135. package/dist/agents/skills/codex/harness-autopilot/skill.yaml +67 -0
  136. package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +406 -0
  137. package/dist/agents/skills/codex/harness-brainstorming/skill.yaml +50 -0
  138. package/dist/agents/skills/codex/harness-caching/SKILL.md +309 -0
  139. package/dist/agents/skills/codex/harness-caching/skill.yaml +73 -0
  140. package/dist/agents/skills/codex/harness-chaos/SKILL.md +295 -0
  141. package/dist/agents/skills/codex/harness-chaos/skill.yaml +72 -0
  142. package/dist/agents/skills/codex/harness-code-review/SKILL.md +857 -0
  143. package/dist/agents/skills/codex/harness-code-review/skill.yaml +52 -0
  144. package/dist/agents/skills/codex/harness-codebase-cleanup/SKILL.md +224 -0
  145. package/dist/agents/skills/codex/harness-codebase-cleanup/skill.yaml +65 -0
  146. package/dist/agents/skills/codex/harness-compliance/SKILL.md +303 -0
  147. package/dist/agents/skills/codex/harness-compliance/skill.yaml +78 -0
  148. package/dist/agents/skills/codex/harness-containerization/SKILL.md +284 -0
  149. package/dist/agents/skills/codex/harness-containerization/skill.yaml +80 -0
  150. package/dist/agents/skills/codex/harness-data-pipeline/SKILL.md +274 -0
  151. package/dist/agents/skills/codex/harness-data-pipeline/skill.yaml +81 -0
  152. package/dist/agents/skills/codex/harness-data-validation/SKILL.md +343 -0
  153. package/dist/agents/skills/codex/harness-data-validation/skill.yaml +75 -0
  154. package/dist/agents/skills/codex/harness-database/SKILL.md +310 -0
  155. package/dist/agents/skills/codex/harness-database/skill.yaml +80 -0
  156. package/dist/agents/skills/codex/harness-debugging/SKILL.md +366 -0
  157. package/dist/agents/skills/codex/harness-debugging/skill.yaml +48 -0
  158. package/dist/agents/skills/codex/harness-dependency-health/SKILL.md +179 -0
  159. package/dist/agents/skills/codex/harness-dependency-health/skill.yaml +42 -0
  160. package/dist/agents/skills/codex/harness-deployment/SKILL.md +307 -0
  161. package/dist/agents/skills/codex/harness-deployment/skill.yaml +77 -0
  162. package/dist/agents/skills/codex/harness-design/SKILL.md +265 -0
  163. package/dist/agents/skills/codex/harness-design/skill.yaml +54 -0
  164. package/dist/agents/skills/codex/harness-design-mobile/SKILL.md +336 -0
  165. package/dist/agents/skills/codex/harness-design-mobile/skill.yaml +50 -0
  166. package/dist/agents/skills/codex/harness-design-system/SKILL.md +282 -0
  167. package/dist/agents/skills/codex/harness-design-system/skill.yaml +51 -0
  168. package/dist/agents/skills/codex/harness-design-web/SKILL.md +360 -0
  169. package/dist/agents/skills/codex/harness-design-web/skill.yaml +53 -0
  170. package/dist/agents/skills/codex/harness-diagnostics/SKILL.md +318 -0
  171. package/dist/agents/skills/codex/harness-diagnostics/skill.yaml +51 -0
  172. package/dist/agents/skills/codex/harness-docs-pipeline/SKILL.md +460 -0
  173. package/dist/agents/skills/codex/harness-docs-pipeline/skill.yaml +70 -0
  174. package/dist/agents/skills/codex/harness-dx/SKILL.md +276 -0
  175. package/dist/agents/skills/codex/harness-dx/skill.yaml +76 -0
  176. package/dist/agents/skills/codex/harness-e2e/SKILL.md +245 -0
  177. package/dist/agents/skills/codex/harness-e2e/skill.yaml +78 -0
  178. package/dist/agents/skills/codex/harness-event-driven/SKILL.md +280 -0
  179. package/dist/agents/skills/codex/harness-event-driven/skill.yaml +77 -0
  180. package/dist/agents/skills/codex/harness-execution/SKILL.md +510 -0
  181. package/dist/agents/skills/codex/harness-execution/skill.yaml +52 -0
  182. package/dist/agents/skills/codex/harness-feature-flags/SKILL.md +287 -0
  183. package/dist/agents/skills/codex/harness-feature-flags/skill.yaml +74 -0
  184. package/dist/agents/skills/codex/harness-git-workflow/SKILL.md +268 -0
  185. package/dist/agents/skills/codex/harness-git-workflow/skill.yaml +32 -0
  186. package/dist/agents/skills/codex/harness-hotspot-detector/SKILL.md +161 -0
  187. package/dist/agents/skills/codex/harness-hotspot-detector/skill.yaml +45 -0
  188. package/dist/agents/skills/codex/harness-i18n/SKILL.md +484 -0
  189. package/dist/agents/skills/codex/harness-i18n/skill.yaml +55 -0
  190. package/dist/agents/skills/codex/harness-i18n-process/SKILL.md +388 -0
  191. package/dist/agents/skills/codex/harness-i18n-process/skill.yaml +44 -0
  192. package/dist/agents/skills/codex/harness-i18n-workflow/SKILL.md +512 -0
  193. package/dist/agents/skills/codex/harness-i18n-workflow/skill.yaml +54 -0
  194. package/dist/agents/skills/codex/harness-impact-analysis/SKILL.md +184 -0
  195. package/dist/agents/skills/codex/harness-impact-analysis/skill.yaml +45 -0
  196. package/dist/agents/skills/codex/harness-incident-response/SKILL.md +223 -0
  197. package/dist/agents/skills/codex/harness-incident-response/skill.yaml +78 -0
  198. package/dist/agents/skills/codex/harness-infrastructure-as-code/SKILL.md +279 -0
  199. package/dist/agents/skills/codex/harness-infrastructure-as-code/skill.yaml +80 -0
  200. package/dist/agents/skills/codex/harness-integration-test/SKILL.md +271 -0
  201. package/dist/agents/skills/codex/harness-integration-test/skill.yaml +73 -0
  202. package/dist/agents/skills/codex/harness-integrity/SKILL.md +167 -0
  203. package/dist/agents/skills/codex/harness-integrity/skill.yaml +48 -0
  204. package/dist/agents/skills/codex/harness-knowledge-mapper/SKILL.md +195 -0
  205. package/dist/agents/skills/codex/harness-knowledge-mapper/skill.yaml +50 -0
  206. package/dist/agents/skills/codex/harness-load-testing/SKILL.md +274 -0
  207. package/dist/agents/skills/codex/harness-load-testing/skill.yaml +79 -0
  208. package/dist/agents/skills/codex/harness-ml-ops/SKILL.md +341 -0
  209. package/dist/agents/skills/codex/harness-ml-ops/skill.yaml +79 -0
  210. package/dist/agents/skills/codex/harness-mobile-patterns/SKILL.md +326 -0
  211. package/dist/agents/skills/codex/harness-mobile-patterns/skill.yaml +82 -0
  212. package/dist/agents/skills/codex/harness-mutation-test/SKILL.md +251 -0
  213. package/dist/agents/skills/codex/harness-mutation-test/skill.yaml +70 -0
  214. package/dist/agents/skills/codex/harness-observability/SKILL.md +283 -0
  215. package/dist/agents/skills/codex/harness-observability/skill.yaml +78 -0
  216. package/dist/agents/skills/codex/harness-onboarding/SKILL.md +288 -0
  217. package/dist/agents/skills/codex/harness-onboarding/skill.yaml +31 -0
  218. package/dist/agents/skills/codex/harness-parallel-agents/SKILL.md +256 -0
  219. package/dist/agents/skills/codex/harness-parallel-agents/skill.yaml +34 -0
  220. package/dist/agents/skills/codex/harness-perf/SKILL.md +260 -0
  221. package/dist/agents/skills/codex/harness-perf/skill.yaml +51 -0
  222. package/dist/agents/skills/codex/harness-perf-tdd/SKILL.md +249 -0
  223. package/dist/agents/skills/codex/harness-perf-tdd/skill.yaml +48 -0
  224. package/dist/agents/skills/codex/harness-planning/SKILL.md +579 -0
  225. package/dist/agents/skills/codex/harness-planning/skill.yaml +56 -0
  226. package/dist/agents/skills/codex/harness-pre-commit-review/SKILL.md +324 -0
  227. package/dist/agents/skills/codex/harness-pre-commit-review/skill.yaml +34 -0
  228. package/dist/agents/skills/codex/harness-product-spec/SKILL.md +285 -0
  229. package/dist/agents/skills/codex/harness-product-spec/skill.yaml +72 -0
  230. package/dist/agents/skills/codex/harness-property-test/SKILL.md +281 -0
  231. package/dist/agents/skills/codex/harness-property-test/skill.yaml +71 -0
  232. package/dist/agents/skills/codex/harness-refactoring/SKILL.md +169 -0
  233. package/dist/agents/skills/codex/harness-refactoring/skill.yaml +34 -0
  234. package/dist/agents/skills/codex/harness-release-readiness/SKILL.md +689 -0
  235. package/dist/agents/skills/codex/harness-release-readiness/skill.yaml +58 -0
  236. package/dist/agents/skills/codex/harness-resilience/SKILL.md +255 -0
  237. package/dist/agents/skills/codex/harness-resilience/skill.yaml +76 -0
  238. package/dist/agents/skills/codex/harness-roadmap/SKILL.md +595 -0
  239. package/dist/agents/skills/codex/harness-roadmap/skill.yaml +44 -0
  240. package/dist/agents/skills/codex/harness-secrets/SKILL.md +293 -0
  241. package/dist/agents/skills/codex/harness-secrets/skill.yaml +76 -0
  242. package/dist/agents/skills/codex/harness-security-review/SKILL.md +260 -0
  243. package/dist/agents/skills/codex/harness-security-review/skill.yaml +53 -0
  244. package/dist/agents/skills/codex/harness-security-scan/SKILL.md +154 -0
  245. package/dist/agents/skills/codex/harness-security-scan/skill.yaml +42 -0
  246. package/dist/agents/skills/codex/harness-skill-authoring/SKILL.md +292 -0
  247. package/dist/agents/skills/codex/harness-skill-authoring/skill.yaml +33 -0
  248. package/dist/agents/skills/codex/harness-soundness-review/SKILL.md +1267 -0
  249. package/dist/agents/skills/codex/harness-soundness-review/skill.yaml +49 -0
  250. package/dist/agents/skills/codex/harness-sql-review/SKILL.md +315 -0
  251. package/dist/agents/skills/codex/harness-sql-review/skill.yaml +74 -0
  252. package/dist/agents/skills/codex/harness-state-management/SKILL.md +309 -0
  253. package/dist/agents/skills/codex/harness-state-management/skill.yaml +33 -0
  254. package/dist/agents/skills/codex/harness-supply-chain-audit/SKILL.md +281 -0
  255. package/dist/agents/skills/codex/harness-supply-chain-audit/skill.yaml +51 -0
  256. package/dist/agents/skills/codex/harness-tdd/SKILL.md +177 -0
  257. package/dist/agents/skills/codex/harness-tdd/skill.yaml +49 -0
  258. package/dist/agents/skills/codex/harness-test-advisor/SKILL.md +160 -0
  259. package/dist/agents/skills/codex/harness-test-advisor/skill.yaml +45 -0
  260. package/dist/agents/skills/codex/harness-test-data/SKILL.md +268 -0
  261. package/dist/agents/skills/codex/harness-test-data/skill.yaml +74 -0
  262. package/dist/agents/skills/codex/harness-ux-copy/SKILL.md +271 -0
  263. package/dist/agents/skills/codex/harness-ux-copy/skill.yaml +77 -0
  264. package/dist/agents/skills/codex/harness-verification/SKILL.md +421 -0
  265. package/dist/agents/skills/codex/harness-verification/skill.yaml +43 -0
  266. package/dist/agents/skills/codex/harness-verify/SKILL.md +159 -0
  267. package/dist/agents/skills/codex/harness-verify/skill.yaml +41 -0
  268. package/dist/agents/skills/codex/harness-visual-regression/SKILL.md +257 -0
  269. package/dist/agents/skills/codex/harness-visual-regression/skill.yaml +74 -0
  270. package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +232 -0
  271. package/dist/agents/skills/codex/initialize-harness-project/skill.yaml +32 -0
  272. package/dist/agents/skills/codex/validate-context-engineering/SKILL.md +150 -0
  273. package/dist/agents/skills/codex/validate-context-engineering/skill.yaml +32 -0
  274. package/dist/agents/skills/cursor/add-harness-component/SKILL.md +192 -0
  275. package/dist/agents/skills/cursor/add-harness-component/skill.yaml +33 -0
  276. package/dist/agents/skills/cursor/align-documentation/SKILL.md +213 -0
  277. package/dist/agents/skills/cursor/align-documentation/skill.yaml +32 -0
  278. package/dist/agents/skills/cursor/check-mechanical-constraints/SKILL.md +191 -0
  279. package/dist/agents/skills/cursor/check-mechanical-constraints/skill.yaml +33 -0
  280. package/dist/agents/skills/cursor/cleanup-dead-code/SKILL.md +245 -0
  281. package/dist/agents/skills/cursor/cleanup-dead-code/skill.yaml +34 -0
  282. package/dist/agents/skills/cursor/detect-doc-drift/SKILL.md +179 -0
  283. package/dist/agents/skills/cursor/detect-doc-drift/skill.yaml +31 -0
  284. package/dist/agents/skills/cursor/enforce-architecture/SKILL.md +296 -0
  285. package/dist/agents/skills/cursor/enforce-architecture/skill.yaml +35 -0
  286. package/dist/agents/skills/cursor/harness-accessibility/SKILL.md +281 -0
  287. package/dist/agents/skills/cursor/harness-accessibility/skill.yaml +52 -0
  288. package/dist/agents/skills/cursor/harness-api-design/SKILL.md +356 -0
  289. package/dist/agents/skills/cursor/harness-api-design/skill.yaml +74 -0
  290. package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +449 -0
  291. package/dist/agents/skills/cursor/harness-architecture-advisor/skill.yaml +49 -0
  292. package/dist/agents/skills/cursor/harness-auth/SKILL.md +331 -0
  293. package/dist/agents/skills/cursor/harness-auth/skill.yaml +81 -0
  294. package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +916 -0
  295. package/dist/agents/skills/cursor/harness-autopilot/skill.yaml +67 -0
  296. package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +406 -0
  297. package/dist/agents/skills/cursor/harness-brainstorming/skill.yaml +50 -0
  298. package/dist/agents/skills/cursor/harness-caching/SKILL.md +309 -0
  299. package/dist/agents/skills/cursor/harness-caching/skill.yaml +73 -0
  300. package/dist/agents/skills/cursor/harness-chaos/SKILL.md +295 -0
  301. package/dist/agents/skills/cursor/harness-chaos/skill.yaml +72 -0
  302. package/dist/agents/skills/cursor/harness-code-review/SKILL.md +857 -0
  303. package/dist/agents/skills/cursor/harness-code-review/skill.yaml +52 -0
  304. package/dist/agents/skills/cursor/harness-codebase-cleanup/SKILL.md +224 -0
  305. package/dist/agents/skills/cursor/harness-codebase-cleanup/skill.yaml +65 -0
  306. package/dist/agents/skills/cursor/harness-compliance/SKILL.md +303 -0
  307. package/dist/agents/skills/cursor/harness-compliance/skill.yaml +78 -0
  308. package/dist/agents/skills/cursor/harness-containerization/SKILL.md +284 -0
  309. package/dist/agents/skills/cursor/harness-containerization/skill.yaml +80 -0
  310. package/dist/agents/skills/cursor/harness-data-pipeline/SKILL.md +274 -0
  311. package/dist/agents/skills/cursor/harness-data-pipeline/skill.yaml +81 -0
  312. package/dist/agents/skills/cursor/harness-data-validation/SKILL.md +343 -0
  313. package/dist/agents/skills/cursor/harness-data-validation/skill.yaml +75 -0
  314. package/dist/agents/skills/cursor/harness-database/SKILL.md +310 -0
  315. package/dist/agents/skills/cursor/harness-database/skill.yaml +80 -0
  316. package/dist/agents/skills/cursor/harness-debugging/SKILL.md +366 -0
  317. package/dist/agents/skills/cursor/harness-debugging/skill.yaml +48 -0
  318. package/dist/agents/skills/cursor/harness-dependency-health/SKILL.md +179 -0
  319. package/dist/agents/skills/cursor/harness-dependency-health/skill.yaml +42 -0
  320. package/dist/agents/skills/cursor/harness-deployment/SKILL.md +307 -0
  321. package/dist/agents/skills/cursor/harness-deployment/skill.yaml +77 -0
  322. package/dist/agents/skills/cursor/harness-design/SKILL.md +265 -0
  323. package/dist/agents/skills/cursor/harness-design/skill.yaml +54 -0
  324. package/dist/agents/skills/cursor/harness-design-mobile/SKILL.md +336 -0
  325. package/dist/agents/skills/cursor/harness-design-mobile/skill.yaml +50 -0
  326. package/dist/agents/skills/cursor/harness-design-system/SKILL.md +282 -0
  327. package/dist/agents/skills/cursor/harness-design-system/skill.yaml +51 -0
  328. package/dist/agents/skills/cursor/harness-design-web/SKILL.md +360 -0
  329. package/dist/agents/skills/cursor/harness-design-web/skill.yaml +53 -0
  330. package/dist/agents/skills/cursor/harness-diagnostics/SKILL.md +318 -0
  331. package/dist/agents/skills/cursor/harness-diagnostics/skill.yaml +51 -0
  332. package/dist/agents/skills/cursor/harness-docs-pipeline/SKILL.md +460 -0
  333. package/dist/agents/skills/cursor/harness-docs-pipeline/skill.yaml +70 -0
  334. package/dist/agents/skills/cursor/harness-dx/SKILL.md +276 -0
  335. package/dist/agents/skills/cursor/harness-dx/skill.yaml +76 -0
  336. package/dist/agents/skills/cursor/harness-e2e/SKILL.md +245 -0
  337. package/dist/agents/skills/cursor/harness-e2e/skill.yaml +78 -0
  338. package/dist/agents/skills/cursor/harness-event-driven/SKILL.md +280 -0
  339. package/dist/agents/skills/cursor/harness-event-driven/skill.yaml +77 -0
  340. package/dist/agents/skills/cursor/harness-execution/SKILL.md +510 -0
  341. package/dist/agents/skills/cursor/harness-execution/skill.yaml +52 -0
  342. package/dist/agents/skills/cursor/harness-feature-flags/SKILL.md +287 -0
  343. package/dist/agents/skills/cursor/harness-feature-flags/skill.yaml +74 -0
  344. package/dist/agents/skills/cursor/harness-git-workflow/SKILL.md +268 -0
  345. package/dist/agents/skills/cursor/harness-git-workflow/skill.yaml +32 -0
  346. package/dist/agents/skills/cursor/harness-hotspot-detector/SKILL.md +161 -0
  347. package/dist/agents/skills/cursor/harness-hotspot-detector/skill.yaml +45 -0
  348. package/dist/agents/skills/cursor/harness-i18n/SKILL.md +484 -0
  349. package/dist/agents/skills/cursor/harness-i18n/skill.yaml +55 -0
  350. package/dist/agents/skills/cursor/harness-i18n-process/SKILL.md +388 -0
  351. package/dist/agents/skills/cursor/harness-i18n-process/skill.yaml +44 -0
  352. package/dist/agents/skills/cursor/harness-i18n-workflow/SKILL.md +512 -0
  353. package/dist/agents/skills/cursor/harness-i18n-workflow/skill.yaml +54 -0
  354. package/dist/agents/skills/cursor/harness-impact-analysis/SKILL.md +184 -0
  355. package/dist/agents/skills/cursor/harness-impact-analysis/skill.yaml +45 -0
  356. package/dist/agents/skills/cursor/harness-incident-response/SKILL.md +223 -0
  357. package/dist/agents/skills/cursor/harness-incident-response/skill.yaml +78 -0
  358. package/dist/agents/skills/cursor/harness-infrastructure-as-code/SKILL.md +279 -0
  359. package/dist/agents/skills/cursor/harness-infrastructure-as-code/skill.yaml +80 -0
  360. package/dist/agents/skills/cursor/harness-integration-test/SKILL.md +271 -0
  361. package/dist/agents/skills/cursor/harness-integration-test/skill.yaml +73 -0
  362. package/dist/agents/skills/cursor/harness-integrity/SKILL.md +167 -0
  363. package/dist/agents/skills/cursor/harness-integrity/skill.yaml +48 -0
  364. package/dist/agents/skills/cursor/harness-knowledge-mapper/SKILL.md +195 -0
  365. package/dist/agents/skills/cursor/harness-knowledge-mapper/skill.yaml +50 -0
  366. package/dist/agents/skills/cursor/harness-load-testing/SKILL.md +274 -0
  367. package/dist/agents/skills/cursor/harness-load-testing/skill.yaml +79 -0
  368. package/dist/agents/skills/cursor/harness-ml-ops/SKILL.md +341 -0
  369. package/dist/agents/skills/cursor/harness-ml-ops/skill.yaml +79 -0
  370. package/dist/agents/skills/cursor/harness-mobile-patterns/SKILL.md +326 -0
  371. package/dist/agents/skills/cursor/harness-mobile-patterns/skill.yaml +82 -0
  372. package/dist/agents/skills/cursor/harness-mutation-test/SKILL.md +251 -0
  373. package/dist/agents/skills/cursor/harness-mutation-test/skill.yaml +70 -0
  374. package/dist/agents/skills/cursor/harness-observability/SKILL.md +283 -0
  375. package/dist/agents/skills/cursor/harness-observability/skill.yaml +78 -0
  376. package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +288 -0
  377. package/dist/agents/skills/cursor/harness-onboarding/skill.yaml +31 -0
  378. package/dist/agents/skills/cursor/harness-parallel-agents/SKILL.md +256 -0
  379. package/dist/agents/skills/cursor/harness-parallel-agents/skill.yaml +34 -0
  380. package/dist/agents/skills/cursor/harness-perf/SKILL.md +260 -0
  381. package/dist/agents/skills/cursor/harness-perf/skill.yaml +51 -0
  382. package/dist/agents/skills/cursor/harness-perf-tdd/SKILL.md +249 -0
  383. package/dist/agents/skills/cursor/harness-perf-tdd/skill.yaml +48 -0
  384. package/dist/agents/skills/cursor/harness-planning/SKILL.md +579 -0
  385. package/dist/agents/skills/cursor/harness-planning/skill.yaml +56 -0
  386. package/dist/agents/skills/cursor/harness-pre-commit-review/SKILL.md +324 -0
  387. package/dist/agents/skills/cursor/harness-pre-commit-review/skill.yaml +34 -0
  388. package/dist/agents/skills/cursor/harness-product-spec/SKILL.md +285 -0
  389. package/dist/agents/skills/cursor/harness-product-spec/skill.yaml +72 -0
  390. package/dist/agents/skills/cursor/harness-property-test/SKILL.md +281 -0
  391. package/dist/agents/skills/cursor/harness-property-test/skill.yaml +71 -0
  392. package/dist/agents/skills/cursor/harness-refactoring/SKILL.md +169 -0
  393. package/dist/agents/skills/cursor/harness-refactoring/skill.yaml +34 -0
  394. package/dist/agents/skills/cursor/harness-release-readiness/SKILL.md +689 -0
  395. package/dist/agents/skills/cursor/harness-release-readiness/skill.yaml +58 -0
  396. package/dist/agents/skills/cursor/harness-resilience/SKILL.md +255 -0
  397. package/dist/agents/skills/cursor/harness-resilience/skill.yaml +76 -0
  398. package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +595 -0
  399. package/dist/agents/skills/cursor/harness-roadmap/skill.yaml +44 -0
  400. package/dist/agents/skills/cursor/harness-secrets/SKILL.md +293 -0
  401. package/dist/agents/skills/cursor/harness-secrets/skill.yaml +76 -0
  402. package/dist/agents/skills/cursor/harness-security-review/SKILL.md +260 -0
  403. package/dist/agents/skills/cursor/harness-security-review/skill.yaml +53 -0
  404. package/dist/agents/skills/cursor/harness-security-scan/SKILL.md +154 -0
  405. package/dist/agents/skills/cursor/harness-security-scan/skill.yaml +42 -0
  406. package/dist/agents/skills/cursor/harness-skill-authoring/SKILL.md +292 -0
  407. package/dist/agents/skills/cursor/harness-skill-authoring/skill.yaml +33 -0
  408. package/dist/agents/skills/cursor/harness-soundness-review/SKILL.md +1267 -0
  409. package/dist/agents/skills/cursor/harness-soundness-review/skill.yaml +49 -0
  410. package/dist/agents/skills/cursor/harness-sql-review/SKILL.md +315 -0
  411. package/dist/agents/skills/cursor/harness-sql-review/skill.yaml +74 -0
  412. package/dist/agents/skills/cursor/harness-state-management/SKILL.md +309 -0
  413. package/dist/agents/skills/cursor/harness-state-management/skill.yaml +33 -0
  414. package/dist/agents/skills/cursor/harness-supply-chain-audit/SKILL.md +281 -0
  415. package/dist/agents/skills/cursor/harness-supply-chain-audit/skill.yaml +51 -0
  416. package/dist/agents/skills/cursor/harness-tdd/SKILL.md +177 -0
  417. package/dist/agents/skills/cursor/harness-tdd/skill.yaml +49 -0
  418. package/dist/agents/skills/cursor/harness-test-advisor/SKILL.md +160 -0
  419. package/dist/agents/skills/cursor/harness-test-advisor/skill.yaml +45 -0
  420. package/dist/agents/skills/cursor/harness-test-data/SKILL.md +268 -0
  421. package/dist/agents/skills/cursor/harness-test-data/skill.yaml +74 -0
  422. package/dist/agents/skills/cursor/harness-ux-copy/SKILL.md +271 -0
  423. package/dist/agents/skills/cursor/harness-ux-copy/skill.yaml +77 -0
  424. package/dist/agents/skills/cursor/harness-verification/SKILL.md +421 -0
  425. package/dist/agents/skills/cursor/harness-verification/skill.yaml +43 -0
  426. package/dist/agents/skills/cursor/harness-verify/SKILL.md +159 -0
  427. package/dist/agents/skills/cursor/harness-verify/skill.yaml +41 -0
  428. package/dist/agents/skills/cursor/harness-visual-regression/SKILL.md +257 -0
  429. package/dist/agents/skills/cursor/harness-visual-regression/skill.yaml +74 -0
  430. package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +232 -0
  431. package/dist/agents/skills/cursor/initialize-harness-project/skill.yaml +32 -0
  432. package/dist/agents/skills/cursor/validate-context-engineering/SKILL.md +150 -0
  433. package/dist/agents/skills/cursor/validate-context-engineering/skill.yaml +32 -0
  434. package/dist/agents/skills/gemini-cli/enforce-architecture/SKILL.md +52 -0
  435. package/dist/agents/skills/gemini-cli/harness-api-design/SKILL.md +52 -0
  436. package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +52 -0
  437. package/dist/agents/skills/gemini-cli/harness-auth/SKILL.md +52 -0
  438. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +355 -45
  439. package/dist/agents/skills/gemini-cli/harness-autopilot/skill.yaml +12 -0
  440. package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +97 -3
  441. package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +6 -0
  442. package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +2 -4
  443. package/dist/agents/skills/gemini-cli/harness-database/SKILL.md +52 -0
  444. package/dist/agents/skills/gemini-cli/harness-deployment/SKILL.md +52 -0
  445. package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +99 -3
  446. package/dist/agents/skills/gemini-cli/harness-planning/skill.yaml +6 -0
  447. package/dist/agents/skills/gemini-cli/harness-pre-commit-review/SKILL.md +1 -1
  448. package/dist/agents/skills/gemini-cli/harness-product-spec/SKILL.md +5 -5
  449. package/dist/agents/skills/gemini-cli/harness-security-review/SKILL.md +27 -7
  450. package/dist/agents/skills/gemini-cli/harness-security-scan/SKILL.md +52 -0
  451. package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/SKILL.md +281 -0
  452. package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/skill.yaml +51 -0
  453. package/dist/agents/skills/package.json +1 -0
  454. package/dist/agents/skills/templates/discipline-template.md +49 -0
  455. package/dist/agents/skills/tests/schema.ts +1 -1
  456. package/dist/agents/skills/vitest.config.mts +5 -0
  457. package/dist/{agents-md-YTYQDA3P.js → agents-md-VYDFPIRW.js} +1 -1
  458. package/dist/{architecture-JQZYM4US.js → architecture-K5HSRBGB.js} +2 -2
  459. package/dist/bin/harness-mcp.js +13 -13
  460. package/dist/bin/harness.js +21 -19
  461. package/dist/{check-phase-gate-L3RADYWO.js → check-phase-gate-5AS6SXL6.js} +3 -3
  462. package/dist/{chunk-6KTUUFRN.js → chunk-5ZXHMCPL.js} +1 -1
  463. package/dist/{chunk-RCWZBSK5.js → chunk-6KWBH4EO.js} +1 -1
  464. package/dist/{chunk-ABQHQ6I5.js → chunk-ALFKNAZW.js} +2436 -233
  465. package/dist/{chunk-OXLLOSSR.js → chunk-AV6KMDO5.js} +2 -2
  466. package/dist/{chunk-7IP4JIFL.js → chunk-C7DTKLPW.js} +4 -4
  467. package/dist/{chunk-ZOAWBDWU.js → chunk-CJDVBBPB.js} +5 -1
  468. package/dist/{chunk-YPYGXRDR.js → chunk-DNDBFIZN.js} +18 -4
  469. package/dist/{chunk-XYLGHKG6.js → chunk-HKUX2X7O.js} +11 -2
  470. package/dist/{chunk-YZD2MRNQ.js → chunk-JOP2NDNB.js} +684 -142
  471. package/dist/{chunk-YBJ262QL.js → chunk-LRG3B43J.js} +1 -1
  472. package/dist/{chunk-AOZRDOIP.js → chunk-M6TIO6NF.js} +1 -1
  473. package/dist/{chunk-O5OJVPL6.js → chunk-OCDDCGDE.js} +9 -1
  474. package/dist/{chunk-OSXBPAMK.js → chunk-QDF7COPQ.js} +1 -1
  475. package/dist/{chunk-TPOTOBR7.js → chunk-RWZPHW4H.js} +3 -3
  476. package/dist/{chunk-3C2MLBPJ.js → chunk-SFRGPAK6.js} +1 -1
  477. package/dist/{chunk-XKECDXJS.js → chunk-SHYWICGA.js} +2184 -456
  478. package/dist/{chunk-S2FXOWOR.js → chunk-TF6ZLHJV.js} +2 -2
  479. package/dist/{chunk-NLVUVUGD.js → chunk-ZJMU7MEV.js} +1 -1
  480. package/dist/{ci-workflow-EQZFVX3P.js → ci-workflow-CRWU723U.js} +1 -1
  481. package/dist/{create-skill-XSWHMSM5.js → create-skill-NDXQSTIK.js} +2 -2
  482. package/dist/{dist-HWXF2C3R.js → dist-4LPXJYVZ.js} +105 -1
  483. package/dist/{docs-7ECGYMAV.js → docs-4JRHTLUZ.js} +3 -3
  484. package/dist/{engine-EG4EH4IX.js → engine-3G3VIM6L.js} +1 -1
  485. package/dist/{entropy-5USWKLVS.js → entropy-G6CZ2A6P.js} +2 -2
  486. package/dist/{feedback-UTBXZZHF.js → feedback-QYKQ65HB.js} +1 -1
  487. package/dist/{generate-agent-definitions-3PM5EU7V.js → generate-agent-definitions-SAAOAPT4.js} +3 -3
  488. package/dist/index.d.ts +25 -4
  489. package/dist/index.js +18 -18
  490. package/dist/{loader-ZPALXIVR.js → loader-VCOK3PF7.js} +1 -1
  491. package/dist/{mcp-362EZHF4.js → mcp-YENEPHBW.js} +13 -13
  492. package/dist/{performance-OQAFMJUD.js → performance-UBCFI2UP.js} +4 -2
  493. package/dist/{review-pipeline-C4GCFVGP.js → review-pipeline-IQAVCWAX.js} +1 -1
  494. package/dist/{runtime-7YLVK453.js → runtime-PYFFIESU.js} +1 -1
  495. package/dist/{security-PZOX7AQS.js → security-ZDADTPYW.js} +1 -1
  496. package/dist/{skill-executor-XZLYZYAK.js → skill-executor-XEVDGXUM.js} +2 -2
  497. package/dist/{validate-FD3Z6VJD.js → validate-VRTUHALQ.js} +2 -2
  498. package/dist/{validate-cross-check-WNJM6H2D.js → validate-cross-check-4Y6NHNK3.js} +1 -1
  499. package/package.json +8 -5
@@ -135,17 +135,17 @@ function resolveFileToLayer(file, layers) {
135
135
  }
136
136
  var accessAsync = promisify(access);
137
137
  var readFileAsync = promisify(readFile);
138
- async function fileExists(path22) {
138
+ async function fileExists(path26) {
139
139
  try {
140
- await accessAsync(path22, constants.F_OK);
140
+ await accessAsync(path26, constants.F_OK);
141
141
  return true;
142
142
  } catch {
143
143
  return false;
144
144
  }
145
145
  }
146
- async function readFileContent(path22) {
146
+ async function readFileContent(path26) {
147
147
  try {
148
- const content = await readFileAsync(path22, "utf-8");
148
+ const content = await readFileAsync(path26, "utf-8");
149
149
  return Ok(content);
150
150
  } catch (error) {
151
151
  return Err(error);
@@ -1832,6 +1832,7 @@ import * as fs6 from "fs";
1832
1832
  import * as path3 from "path";
1833
1833
  import * as fs9 from "fs";
1834
1834
  import * as path6 from "path";
1835
+ import * as crypto from "crypto";
1835
1836
  import * as fs10 from "fs";
1836
1837
  import * as path7 from "path";
1837
1838
  import * as fs11 from "fs";
@@ -1845,26 +1846,40 @@ import * as fs14 from "fs";
1845
1846
  import * as path11 from "path";
1846
1847
  import * as fs15 from "fs";
1847
1848
  import * as path12 from "path";
1848
- import * as fs17 from "fs/promises";
1849
- import { z as z5 } from "zod";
1850
1849
  import * as fs16 from "fs";
1851
1850
  import * as path13 from "path";
1851
+ import { z as z5 } from "zod";
1852
+ import * as fs18 from "fs/promises";
1853
+ import { minimatch as minimatch4 } from "minimatch";
1854
+ import { z as z6 } from "zod";
1855
+ import * as fs17 from "fs";
1852
1856
  import * as path14 from "path";
1857
+ import { readFileSync as readFileSync142, writeFileSync as writeFileSync11, unlinkSync, mkdirSync as mkdirSync11, readdirSync as readdirSync3 } from "fs";
1858
+ import { join as join21, dirname as dirname8 } from "path";
1853
1859
  import * as path15 from "path";
1854
1860
  import * as path16 from "path";
1855
1861
  import * as path17 from "path";
1856
- import * as fs18 from "fs";
1857
1862
  import * as path18 from "path";
1858
- import { z as z6 } from "zod";
1859
- import * as fs19 from "fs/promises";
1863
+ import * as fs19 from "fs";
1860
1864
  import * as path19 from "path";
1865
+ import { z as z7 } from "zod";
1861
1866
  import * as fs20 from "fs/promises";
1862
1867
  import * as path20 from "path";
1863
- import * as ejs from "ejs";
1864
- import * as fs21 from "fs";
1868
+ import * as fs21 from "fs/promises";
1865
1869
  import * as path21 from "path";
1870
+ import * as ejs from "ejs";
1871
+ import * as fs22 from "fs";
1872
+ import * as path22 from "path";
1866
1873
  import * as os from "os";
1867
1874
  import { spawn } from "child_process";
1875
+ import Parser from "web-tree-sitter";
1876
+ import * as fs23 from "fs/promises";
1877
+ import * as path23 from "path";
1878
+ import * as fs24 from "fs";
1879
+ import * as path24 from "path";
1880
+ import * as fs25 from "fs";
1881
+ import * as path25 from "path";
1882
+ import * as os2 from "os";
1868
1883
  async function validateFileStructure(projectPath, conventions) {
1869
1884
  const missing = [];
1870
1885
  const unexpected = [];
@@ -1900,15 +1915,15 @@ function validateConfig(data, schema) {
1900
1915
  let message = "Configuration validation failed";
1901
1916
  const suggestions = [];
1902
1917
  if (firstError) {
1903
- const path22 = firstError.path.join(".");
1904
- const pathDisplay = path22 ? ` at "${path22}"` : "";
1918
+ const path26 = firstError.path.join(".");
1919
+ const pathDisplay = path26 ? ` at "${path26}"` : "";
1905
1920
  if (firstError.code === "invalid_type") {
1906
1921
  const received = firstError.received;
1907
1922
  const expected = firstError.expected;
1908
1923
  if (received === "undefined") {
1909
1924
  code = "MISSING_FIELD";
1910
1925
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
1911
- suggestions.push(`Field "${path22}" is required and must be of type "${expected}"`);
1926
+ suggestions.push(`Field "${path26}" is required and must be of type "${expected}"`);
1912
1927
  } else {
1913
1928
  code = "INVALID_TYPE";
1914
1929
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -2117,27 +2132,27 @@ function extractSections(content) {
2117
2132
  }
2118
2133
  return sections.map((section) => buildAgentMapSection(section, lines));
2119
2134
  }
2120
- function isExternalLink(path22) {
2121
- return path22.startsWith("http://") || path22.startsWith("https://") || path22.startsWith("#") || path22.startsWith("mailto:");
2135
+ function isExternalLink(path26) {
2136
+ return path26.startsWith("http://") || path26.startsWith("https://") || path26.startsWith("#") || path26.startsWith("mailto:");
2122
2137
  }
2123
2138
  function resolveLinkPath(linkPath, baseDir) {
2124
2139
  return linkPath.startsWith(".") ? join4(baseDir, linkPath) : linkPath;
2125
2140
  }
2126
- async function validateAgentsMap(path22 = "./AGENTS.md") {
2127
- const contentResult = await readFileContent(path22);
2141
+ async function validateAgentsMap(path26 = "./AGENTS.md") {
2142
+ const contentResult = await readFileContent(path26);
2128
2143
  if (!contentResult.ok) {
2129
2144
  return Err(
2130
2145
  createError(
2131
2146
  "PARSE_ERROR",
2132
2147
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
2133
- { path: path22 },
2148
+ { path: path26 },
2134
2149
  ["Ensure the file exists", "Check file permissions"]
2135
2150
  )
2136
2151
  );
2137
2152
  }
2138
2153
  const content = contentResult.value;
2139
2154
  const sections = extractSections(content);
2140
- const baseDir = dirname4(path22);
2155
+ const baseDir = dirname4(path26);
2141
2156
  const sectionTitles = sections.map((s) => s.title);
2142
2157
  const missingSections = REQUIRED_SECTIONS.filter(
2143
2158
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -2271,8 +2286,8 @@ async function checkDocCoverage(domain, options = {}) {
2271
2286
  );
2272
2287
  }
2273
2288
  }
2274
- function suggestFix(path22, existingFiles) {
2275
- const targetName = basename2(path22).toLowerCase();
2289
+ function suggestFix(path26, existingFiles) {
2290
+ const targetName = basename2(path26).toLowerCase();
2276
2291
  const similar = existingFiles.find((file) => {
2277
2292
  const fileName = basename2(file).toLowerCase();
2278
2293
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -2280,7 +2295,7 @@ function suggestFix(path22, existingFiles) {
2280
2295
  if (similar) {
2281
2296
  return `Did you mean "${similar}"?`;
2282
2297
  }
2283
- return `Create the file "${path22}" or remove the link`;
2298
+ return `Create the file "${path26}" or remove the link`;
2284
2299
  }
2285
2300
  async function validateKnowledgeMap(rootDir = process.cwd()) {
2286
2301
  const agentsPath = join22(rootDir, "AGENTS.md");
@@ -2623,8 +2638,8 @@ function createBoundaryValidator(schema, name) {
2623
2638
  return Ok(result.data);
2624
2639
  }
2625
2640
  const suggestions = result.error.issues.map((issue) => {
2626
- const path22 = issue.path.join(".");
2627
- return path22 ? `${path22}: ${issue.message}` : issue.message;
2641
+ const path26 = issue.path.join(".");
2642
+ return path26 ? `${path26}: ${issue.message}` : issue.message;
2628
2643
  });
2629
2644
  return Err(
2630
2645
  createError(
@@ -3232,11 +3247,11 @@ function processExportListSpecifiers(exportDecl, exports) {
3232
3247
  var TypeScriptParser = class {
3233
3248
  name = "typescript";
3234
3249
  extensions = [".ts", ".tsx", ".mts", ".cts"];
3235
- async parseFile(path22) {
3236
- const contentResult = await readFileContent(path22);
3250
+ async parseFile(path26) {
3251
+ const contentResult = await readFileContent(path26);
3237
3252
  if (!contentResult.ok) {
3238
3253
  return Err(
3239
- createParseError("NOT_FOUND", `File not found: ${path22}`, { path: path22 }, [
3254
+ createParseError("NOT_FOUND", `File not found: ${path26}`, { path: path26 }, [
3240
3255
  "Check that the file exists",
3241
3256
  "Verify the path is correct"
3242
3257
  ])
@@ -3246,7 +3261,7 @@ var TypeScriptParser = class {
3246
3261
  const ast = parse(contentResult.value, {
3247
3262
  loc: true,
3248
3263
  range: true,
3249
- jsx: path22.endsWith(".tsx"),
3264
+ jsx: path26.endsWith(".tsx"),
3250
3265
  errorOnUnknownASTType: false
3251
3266
  });
3252
3267
  return Ok({
@@ -3257,7 +3272,7 @@ var TypeScriptParser = class {
3257
3272
  } catch (e) {
3258
3273
  const error = e;
3259
3274
  return Err(
3260
- createParseError("SYNTAX_ERROR", `Failed to parse ${path22}: ${error.message}`, { path: path22 }, [
3275
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path26}: ${error.message}`, { path: path26 }, [
3261
3276
  "Check for syntax errors in the file",
3262
3277
  "Ensure valid TypeScript syntax"
3263
3278
  ])
@@ -3438,22 +3453,22 @@ function extractInlineRefs(content) {
3438
3453
  }
3439
3454
  return refs;
3440
3455
  }
3441
- async function parseDocumentationFile(path22) {
3442
- const contentResult = await readFileContent(path22);
3456
+ async function parseDocumentationFile(path26) {
3457
+ const contentResult = await readFileContent(path26);
3443
3458
  if (!contentResult.ok) {
3444
3459
  return Err(
3445
3460
  createEntropyError(
3446
3461
  "PARSE_ERROR",
3447
- `Failed to read documentation file: ${path22}`,
3448
- { file: path22 },
3462
+ `Failed to read documentation file: ${path26}`,
3463
+ { file: path26 },
3449
3464
  ["Check that the file exists"]
3450
3465
  )
3451
3466
  );
3452
3467
  }
3453
3468
  const content = contentResult.value;
3454
- const type = path22.endsWith(".md") ? "markdown" : "text";
3469
+ const type = path26.endsWith(".md") ? "markdown" : "text";
3455
3470
  return Ok({
3456
- path: path22,
3471
+ path: path26,
3457
3472
  type,
3458
3473
  content,
3459
3474
  codeBlocks: extractCodeBlocks(content),
@@ -4711,7 +4726,7 @@ var EntropyAnalyzer = class {
4711
4726
  };
4712
4727
  var readFile32 = promisify2(fs3.readFile);
4713
4728
  var writeFile32 = promisify2(fs3.writeFile);
4714
- var unlink2 = promisify2(fs3.unlink);
4729
+ var unlink22 = promisify2(fs3.unlink);
4715
4730
  var mkdir22 = promisify2(fs3.mkdir);
4716
4731
  var copyFile2 = promisify2(fs3.copyFile);
4717
4732
  var DEFAULT_FIX_CONFIG = {
@@ -4854,7 +4869,7 @@ async function applySingleFix(fix, config) {
4854
4869
  return Err({ fix, error: backupResult.error.message });
4855
4870
  }
4856
4871
  }
4857
- await unlink2(fix.file);
4872
+ await unlink22(fix.file);
4858
4873
  break;
4859
4874
  case "delete-lines":
4860
4875
  if (fix.line !== void 0) {
@@ -6490,6 +6505,8 @@ var SESSION_INDEX_FILE = "index.md";
6490
6505
  var SUMMARY_FILE = "summary.md";
6491
6506
  var SESSION_STATE_FILE = "session-state.json";
6492
6507
  var ARCHIVE_DIR = "archive";
6508
+ var CONTENT_HASHES_FILE = "content-hashes.json";
6509
+ var EVENTS_FILE = "events.jsonl";
6493
6510
  var STREAMS_DIR = "streams";
6494
6511
  var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
6495
6512
  function streamsDir(projectPath) {
@@ -6818,6 +6835,84 @@ async function saveState(projectPath, state, stream, session) {
6818
6835
  );
6819
6836
  }
6820
6837
  }
6838
+ function parseFrontmatter(line) {
6839
+ const match = line.match(/^<!--\s+hash:([a-f0-9]+)(?:\s+tags:([^\s]+))?\s+-->/);
6840
+ if (!match) return null;
6841
+ const hash = match[1];
6842
+ const tags = match[2] ? match[2].split(",").filter(Boolean) : [];
6843
+ return { hash, tags };
6844
+ }
6845
+ function computeEntryHash(text) {
6846
+ return crypto.createHash("sha256").update(text).digest("hex").slice(0, 8);
6847
+ }
6848
+ function normalizeLearningContent(text) {
6849
+ let normalized = text;
6850
+ normalized = normalized.replace(/\d{4}-\d{2}-\d{2}/g, "");
6851
+ normalized = normalized.replace(/\[skill:[^\]]*\]/g, "");
6852
+ normalized = normalized.replace(/\[outcome:[^\]]*\]/g, "");
6853
+ normalized = normalized.replace(/^[\s]*[-*]\s+/gm, "");
6854
+ normalized = normalized.replace(/\*\*/g, "");
6855
+ normalized = normalized.replace(/:\s*/g, " ");
6856
+ normalized = normalized.toLowerCase();
6857
+ normalized = normalized.replace(/\s+/g, " ").trim();
6858
+ return normalized;
6859
+ }
6860
+ function computeContentHash(text) {
6861
+ return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
6862
+ }
6863
+ function loadContentHashes(stateDir) {
6864
+ const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
6865
+ if (!fs9.existsSync(hashesPath)) return {};
6866
+ try {
6867
+ const raw = fs9.readFileSync(hashesPath, "utf-8");
6868
+ const parsed = JSON.parse(raw);
6869
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
6870
+ return parsed;
6871
+ } catch {
6872
+ return {};
6873
+ }
6874
+ }
6875
+ function saveContentHashes(stateDir, index) {
6876
+ const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
6877
+ fs9.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
6878
+ }
6879
+ function rebuildContentHashes(stateDir) {
6880
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
6881
+ if (!fs9.existsSync(learningsPath)) return {};
6882
+ const content = fs9.readFileSync(learningsPath, "utf-8");
6883
+ const lines = content.split("\n");
6884
+ const index = {};
6885
+ for (let i = 0; i < lines.length; i++) {
6886
+ const line = lines[i];
6887
+ const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
6888
+ if (isDatedBullet) {
6889
+ const learningMatch = line.match(/:\*\*\s*(.+)$/);
6890
+ if (learningMatch?.[1]) {
6891
+ const normalized = normalizeLearningContent(learningMatch[1]);
6892
+ const hash = computeContentHash(normalized);
6893
+ const dateMatch = line.match(/(\d{4}-\d{2}-\d{2})/);
6894
+ index[hash] = { date: dateMatch?.[1] ?? "", line: i + 1 };
6895
+ }
6896
+ }
6897
+ }
6898
+ saveContentHashes(stateDir, index);
6899
+ return index;
6900
+ }
6901
+ function extractIndexEntry(entry) {
6902
+ const lines = entry.split("\n");
6903
+ const summary = lines[0] ?? entry;
6904
+ const tags = [];
6905
+ const skillMatch = entry.match(/\[skill:([^\]]+)\]/);
6906
+ if (skillMatch?.[1]) tags.push(skillMatch[1]);
6907
+ const outcomeMatch = entry.match(/\[outcome:([^\]]+)\]/);
6908
+ if (outcomeMatch?.[1]) tags.push(outcomeMatch[1]);
6909
+ return {
6910
+ hash: computeEntryHash(entry),
6911
+ tags,
6912
+ summary,
6913
+ fullText: entry
6914
+ };
6915
+ }
6821
6916
  var learningsCacheMap = /* @__PURE__ */ new Map();
6822
6917
  function clearLearningsCache() {
6823
6918
  learningsCacheMap.clear();
@@ -6829,27 +6924,55 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
6829
6924
  const stateDir = dirResult.value;
6830
6925
  const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
6831
6926
  fs9.mkdirSync(stateDir, { recursive: true });
6927
+ const normalizedContent = normalizeLearningContent(learning);
6928
+ const contentHash = computeContentHash(normalizedContent);
6929
+ const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
6930
+ let contentHashes;
6931
+ if (fs9.existsSync(hashesPath)) {
6932
+ contentHashes = loadContentHashes(stateDir);
6933
+ if (Object.keys(contentHashes).length === 0 && fs9.existsSync(learningsPath)) {
6934
+ contentHashes = rebuildContentHashes(stateDir);
6935
+ }
6936
+ } else if (fs9.existsSync(learningsPath)) {
6937
+ contentHashes = rebuildContentHashes(stateDir);
6938
+ } else {
6939
+ contentHashes = {};
6940
+ }
6941
+ if (contentHashes[contentHash]) {
6942
+ return Ok(void 0);
6943
+ }
6832
6944
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6833
- let entry;
6945
+ const fmTags = [];
6946
+ if (skillName) fmTags.push(skillName);
6947
+ if (outcome) fmTags.push(outcome);
6948
+ let bulletLine;
6834
6949
  if (skillName && outcome) {
6835
- entry = `
6836
- - **${timestamp} [skill:${skillName}] [outcome:${outcome}]:** ${learning}
6837
- `;
6950
+ bulletLine = `- **${timestamp} [skill:${skillName}] [outcome:${outcome}]:** ${learning}`;
6838
6951
  } else if (skillName) {
6839
- entry = `
6840
- - **${timestamp} [skill:${skillName}]:** ${learning}
6841
- `;
6952
+ bulletLine = `- **${timestamp} [skill:${skillName}]:** ${learning}`;
6842
6953
  } else {
6843
- entry = `
6844
- - **${timestamp}:** ${learning}
6845
- `;
6954
+ bulletLine = `- **${timestamp}:** ${learning}`;
6846
6955
  }
6956
+ const hash = crypto.createHash("sha256").update(bulletLine).digest("hex").slice(0, 8);
6957
+ const tagsStr = fmTags.length > 0 ? ` tags:${fmTags.join(",")}` : "";
6958
+ const frontmatter = `<!-- hash:${hash}${tagsStr} -->`;
6959
+ const entry = `
6960
+ ${frontmatter}
6961
+ ${bulletLine}
6962
+ `;
6963
+ let existingLineCount;
6847
6964
  if (!fs9.existsSync(learningsPath)) {
6848
6965
  fs9.writeFileSync(learningsPath, `# Learnings
6849
6966
  ${entry}`);
6967
+ existingLineCount = 1;
6850
6968
  } else {
6969
+ const existingContent = fs9.readFileSync(learningsPath, "utf-8");
6970
+ existingLineCount = existingContent.split("\n").length;
6851
6971
  fs9.appendFileSync(learningsPath, entry);
6852
6972
  }
6973
+ const bulletLine_lineNum = existingLineCount + 2;
6974
+ contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
6975
+ saveContentHashes(stateDir, contentHashes);
6853
6976
  learningsCacheMap.delete(learningsPath);
6854
6977
  return Ok(void 0);
6855
6978
  } catch (error) {
@@ -6897,7 +7020,30 @@ function analyzeLearningPatterns(entries) {
6897
7020
  return patterns.sort((a, b) => b.count - a.count);
6898
7021
  }
6899
7022
  async function loadBudgetedLearnings(projectPath, options) {
6900
- const { intent, tokenBudget = 1e3, skill, session, stream } = options;
7023
+ const { intent, tokenBudget = 1e3, skill, session, stream, depth = "summary" } = options;
7024
+ if (depth === "index") {
7025
+ const indexEntries = [];
7026
+ if (session) {
7027
+ const sessionResult = await loadIndexEntries(projectPath, skill, stream, session);
7028
+ if (sessionResult.ok) indexEntries.push(...sessionResult.value);
7029
+ }
7030
+ const globalResult2 = await loadIndexEntries(projectPath, skill, stream);
7031
+ if (globalResult2.ok) {
7032
+ const sessionHashes = new Set(indexEntries.map((e) => e.hash));
7033
+ const uniqueGlobal = globalResult2.value.filter((e) => !sessionHashes.has(e.hash));
7034
+ indexEntries.push(...uniqueGlobal);
7035
+ }
7036
+ const budgeted2 = [];
7037
+ let totalTokens2 = 0;
7038
+ for (const entry of indexEntries) {
7039
+ const separator = budgeted2.length > 0 ? "\n" : "";
7040
+ const entryCost = estimateTokens(entry.summary + separator);
7041
+ if (totalTokens2 + entryCost > tokenBudget) break;
7042
+ budgeted2.push(entry.summary);
7043
+ totalTokens2 += entryCost;
7044
+ }
7045
+ return Ok(budgeted2);
7046
+ }
6901
7047
  const sortByRecencyAndRelevance = (entries) => {
6902
7048
  return [...entries].sort((a, b) => {
6903
7049
  const dateA = parseDateFromEntry(a) ?? "0000-00-00";
@@ -6916,7 +7062,9 @@ async function loadBudgetedLearnings(projectPath, options) {
6916
7062
  }
6917
7063
  const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
6918
7064
  if (globalResult.ok) {
6919
- allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
7065
+ const sessionSet = new Set(allEntries.map((e) => e.trim()));
7066
+ const uniqueGlobal = globalResult.value.filter((e) => !sessionSet.has(e.trim()));
7067
+ allEntries.push(...sortByRecencyAndRelevance(uniqueGlobal));
6920
7068
  }
6921
7069
  const budgeted = [];
6922
7070
  let totalTokens = 0;
@@ -6929,6 +7077,68 @@ async function loadBudgetedLearnings(projectPath, options) {
6929
7077
  }
6930
7078
  return Ok(budgeted);
6931
7079
  }
7080
+ async function loadIndexEntries(projectPath, skillName, stream, session) {
7081
+ try {
7082
+ const dirResult = await getStateDir(projectPath, stream, session);
7083
+ if (!dirResult.ok) return dirResult;
7084
+ const stateDir = dirResult.value;
7085
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7086
+ if (!fs9.existsSync(learningsPath)) {
7087
+ return Ok([]);
7088
+ }
7089
+ const content = fs9.readFileSync(learningsPath, "utf-8");
7090
+ const lines = content.split("\n");
7091
+ const indexEntries = [];
7092
+ let pendingFrontmatter = null;
7093
+ let currentBlock = [];
7094
+ for (const line of lines) {
7095
+ if (line.startsWith("# ")) continue;
7096
+ const fm = parseFrontmatter(line);
7097
+ if (fm) {
7098
+ pendingFrontmatter = fm;
7099
+ continue;
7100
+ }
7101
+ const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
7102
+ const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
7103
+ if (isDatedBullet || isHeading) {
7104
+ if (pendingFrontmatter) {
7105
+ indexEntries.push({
7106
+ hash: pendingFrontmatter.hash,
7107
+ tags: pendingFrontmatter.tags,
7108
+ summary: line,
7109
+ fullText: ""
7110
+ // Placeholder — full text not loaded in index mode
7111
+ });
7112
+ pendingFrontmatter = null;
7113
+ } else {
7114
+ const idx = extractIndexEntry(line);
7115
+ indexEntries.push({
7116
+ hash: idx.hash,
7117
+ tags: idx.tags,
7118
+ summary: line,
7119
+ fullText: ""
7120
+ });
7121
+ }
7122
+ currentBlock = [line];
7123
+ } else if (line.trim() !== "" && currentBlock.length > 0) {
7124
+ currentBlock.push(line);
7125
+ }
7126
+ }
7127
+ if (skillName) {
7128
+ const filtered = indexEntries.filter(
7129
+ (e) => e.tags.includes(skillName) || e.summary.includes(`[skill:${skillName}]`)
7130
+ );
7131
+ return Ok(filtered);
7132
+ }
7133
+ return Ok(indexEntries);
7134
+ } catch (error) {
7135
+ return Err(
7136
+ new Error(
7137
+ `Failed to load index entries: ${error instanceof Error ? error.message : String(error)}`
7138
+ )
7139
+ );
7140
+ }
7141
+ }
6932
7142
  async function loadRelevantLearnings(projectPath, skillName, stream, session) {
6933
7143
  try {
6934
7144
  const dirResult = await getStateDir(projectPath, stream, session);
@@ -6951,6 +7161,7 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
6951
7161
  let currentBlock = [];
6952
7162
  for (const line of lines) {
6953
7163
  if (line.startsWith("# ")) continue;
7164
+ if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
6954
7165
  const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
6955
7166
  const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
6956
7167
  if (isDatedBullet || isHeading) {
@@ -7060,6 +7271,68 @@ async function pruneLearnings(projectPath, stream) {
7060
7271
  );
7061
7272
  }
7062
7273
  }
7274
+ var PROMOTABLE_OUTCOMES = ["gotcha", "decision", "observation"];
7275
+ function isGeneralizable(entry) {
7276
+ for (const outcome of PROMOTABLE_OUTCOMES) {
7277
+ if (entry.includes(`[outcome:${outcome}]`)) return true;
7278
+ }
7279
+ return false;
7280
+ }
7281
+ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
7282
+ try {
7283
+ const sessionResult = await loadRelevantLearnings(projectPath, void 0, stream, sessionSlug);
7284
+ if (!sessionResult.ok) return sessionResult;
7285
+ const sessionEntries = sessionResult.value;
7286
+ if (sessionEntries.length === 0) {
7287
+ return Ok({ promoted: 0, skipped: 0 });
7288
+ }
7289
+ const toPromote = [];
7290
+ let skipped = 0;
7291
+ for (const entry of sessionEntries) {
7292
+ if (isGeneralizable(entry)) {
7293
+ toPromote.push(entry);
7294
+ } else {
7295
+ skipped++;
7296
+ }
7297
+ }
7298
+ if (toPromote.length === 0) {
7299
+ return Ok({ promoted: 0, skipped });
7300
+ }
7301
+ const dirResult = await getStateDir(projectPath, stream);
7302
+ if (!dirResult.ok) return dirResult;
7303
+ const stateDir = dirResult.value;
7304
+ const globalPath = path6.join(stateDir, LEARNINGS_FILE);
7305
+ const existingGlobal = fs9.existsSync(globalPath) ? fs9.readFileSync(globalPath, "utf-8") : "";
7306
+ const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
7307
+ if (newEntries.length === 0) {
7308
+ return Ok({ promoted: 0, skipped: skipped + toPromote.length });
7309
+ }
7310
+ const promotedContent = newEntries.join("\n\n") + "\n";
7311
+ if (!existingGlobal) {
7312
+ fs9.writeFileSync(globalPath, `# Learnings
7313
+
7314
+ ${promotedContent}`);
7315
+ } else {
7316
+ fs9.appendFileSync(globalPath, "\n\n" + promotedContent);
7317
+ }
7318
+ learningsCacheMap.delete(globalPath);
7319
+ return Ok({
7320
+ promoted: newEntries.length,
7321
+ skipped: skipped + (toPromote.length - newEntries.length)
7322
+ });
7323
+ } catch (error) {
7324
+ return Err(
7325
+ new Error(
7326
+ `Failed to promote session learnings: ${error instanceof Error ? error.message : String(error)}`
7327
+ )
7328
+ );
7329
+ }
7330
+ }
7331
+ async function countLearningEntries(projectPath, stream) {
7332
+ const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
7333
+ if (!loadResult.ok) return 0;
7334
+ return loadResult.value.length;
7335
+ }
7063
7336
  var failuresCacheMap = /* @__PURE__ */ new Map();
7064
7337
  function clearFailuresCache() {
7065
7338
  failuresCacheMap.clear();
@@ -7494,6 +7767,146 @@ async function archiveSession(projectPath, sessionSlug) {
7494
7767
  );
7495
7768
  }
7496
7769
  }
7770
+ var SkillEventSchema = z5.object({
7771
+ timestamp: z5.string(),
7772
+ skill: z5.string(),
7773
+ session: z5.string().optional(),
7774
+ type: z5.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
7775
+ summary: z5.string(),
7776
+ data: z5.record(z5.unknown()).optional(),
7777
+ refs: z5.array(z5.string()).optional(),
7778
+ contentHash: z5.string().optional()
7779
+ });
7780
+ function computeEventHash(event, session) {
7781
+ const identity = `${event.skill}|${event.type}|${event.summary}|${session ?? ""}`;
7782
+ return computeContentHash(identity);
7783
+ }
7784
+ var knownHashesCache = /* @__PURE__ */ new Map();
7785
+ function loadKnownHashes(eventsPath) {
7786
+ const cached = knownHashesCache.get(eventsPath);
7787
+ if (cached) return cached;
7788
+ const hashes = /* @__PURE__ */ new Set();
7789
+ if (fs16.existsSync(eventsPath)) {
7790
+ const content = fs16.readFileSync(eventsPath, "utf-8");
7791
+ const lines = content.split("\n").filter((line) => line.trim() !== "");
7792
+ for (const line of lines) {
7793
+ try {
7794
+ const existing = JSON.parse(line);
7795
+ if (existing.contentHash) {
7796
+ hashes.add(existing.contentHash);
7797
+ }
7798
+ } catch {
7799
+ }
7800
+ }
7801
+ }
7802
+ knownHashesCache.set(eventsPath, hashes);
7803
+ return hashes;
7804
+ }
7805
+ function clearEventHashCache() {
7806
+ knownHashesCache.clear();
7807
+ }
7808
+ async function emitEvent(projectPath, event, options) {
7809
+ try {
7810
+ const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
7811
+ if (!dirResult.ok) return dirResult;
7812
+ const stateDir = dirResult.value;
7813
+ const eventsPath = path13.join(stateDir, EVENTS_FILE);
7814
+ fs16.mkdirSync(stateDir, { recursive: true });
7815
+ const contentHash = computeEventHash(event, options?.session);
7816
+ const knownHashes = loadKnownHashes(eventsPath);
7817
+ if (knownHashes.has(contentHash)) {
7818
+ return Ok({ written: false, reason: "duplicate" });
7819
+ }
7820
+ const fullEvent = {
7821
+ ...event,
7822
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7823
+ contentHash
7824
+ };
7825
+ if (options?.session) {
7826
+ fullEvent.session = options.session;
7827
+ }
7828
+ fs16.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
7829
+ knownHashes.add(contentHash);
7830
+ return Ok({ written: true });
7831
+ } catch (error) {
7832
+ return Err(
7833
+ new Error(`Failed to emit event: ${error instanceof Error ? error.message : String(error)}`)
7834
+ );
7835
+ }
7836
+ }
7837
+ async function loadEvents(projectPath, options) {
7838
+ try {
7839
+ const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
7840
+ if (!dirResult.ok) return dirResult;
7841
+ const stateDir = dirResult.value;
7842
+ const eventsPath = path13.join(stateDir, EVENTS_FILE);
7843
+ if (!fs16.existsSync(eventsPath)) {
7844
+ return Ok([]);
7845
+ }
7846
+ const content = fs16.readFileSync(eventsPath, "utf-8");
7847
+ const lines = content.split("\n").filter((line) => line.trim() !== "");
7848
+ const events = [];
7849
+ for (const line of lines) {
7850
+ try {
7851
+ const parsed = JSON.parse(line);
7852
+ const result = SkillEventSchema.safeParse(parsed);
7853
+ if (result.success) {
7854
+ events.push(result.data);
7855
+ }
7856
+ } catch {
7857
+ }
7858
+ }
7859
+ return Ok(events);
7860
+ } catch (error) {
7861
+ return Err(
7862
+ new Error(`Failed to load events: ${error instanceof Error ? error.message : String(error)}`)
7863
+ );
7864
+ }
7865
+ }
7866
+ function formatPhaseTransition(event) {
7867
+ const data = event.data;
7868
+ const suffix = data?.taskCount ? ` (${data.taskCount} tasks)` : "";
7869
+ return `phase: ${data?.from ?? "?"} -> ${data?.to ?? "?"}${suffix}`;
7870
+ }
7871
+ function formatGateResult(event) {
7872
+ const data = event.data;
7873
+ const status = data?.passed ? "passed" : "failed";
7874
+ const checks = data?.checks?.map((c) => `${c.name} ${c.passed ? "Y" : "N"}`).join(", ");
7875
+ return checks ? `gate: ${status} (${checks})` : `gate: ${status}`;
7876
+ }
7877
+ function formatHandoffDetail(event) {
7878
+ const data = event.data;
7879
+ const direction = data?.toSkill ? ` -> ${data.toSkill}` : "";
7880
+ return `handoff: ${event.summary}${direction}`;
7881
+ }
7882
+ var EVENT_FORMATTERS = {
7883
+ phase_transition: formatPhaseTransition,
7884
+ gate_result: formatGateResult,
7885
+ decision: (event) => `decision: ${event.summary}`,
7886
+ handoff: formatHandoffDetail,
7887
+ error: (event) => `error: ${event.summary}`,
7888
+ checkpoint: (event) => `checkpoint: ${event.summary}`
7889
+ };
7890
+ function formatEventTimeline(events, limit = 20) {
7891
+ if (events.length === 0) return "";
7892
+ const recent = events.slice(-limit);
7893
+ return recent.map((event) => {
7894
+ const time = formatTime(event.timestamp);
7895
+ const formatter = EVENT_FORMATTERS[event.type];
7896
+ const detail = formatter ? formatter(event) : event.summary;
7897
+ return `- ${time} [${event.skill}] ${detail}`;
7898
+ }).join("\n");
7899
+ }
7900
+ function formatTime(timestamp) {
7901
+ try {
7902
+ const date = new Date(timestamp);
7903
+ const hours = String(date.getHours()).padStart(2, "0");
7904
+ const minutes = String(date.getMinutes()).padStart(2, "0");
7905
+ return `${hours}:${minutes}`;
7906
+ } catch {
7907
+ return "??:??";
7908
+ }
7909
+ }
7497
7910
  async function executeWorkflow(workflow, executor) {
7498
7911
  const stepResults = [];
7499
7912
  const startTime = Date.now();
@@ -7670,19 +8083,19 @@ var DEFAULT_SECURITY_CONFIG = {
7670
8083
  rules: {},
7671
8084
  exclude: ["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]
7672
8085
  };
7673
- var RuleOverrideSchema = z5.enum(["off", "error", "warning", "info"]);
7674
- var SecurityConfigSchema = z5.object({
7675
- enabled: z5.boolean().default(true),
7676
- strict: z5.boolean().default(false),
7677
- rules: z5.record(z5.string(), RuleOverrideSchema).optional().default({}),
7678
- exclude: z5.array(z5.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
7679
- external: z5.object({
7680
- semgrep: z5.object({
7681
- enabled: z5.union([z5.literal("auto"), z5.boolean()]).default("auto"),
7682
- rulesets: z5.array(z5.string()).optional()
8086
+ var RuleOverrideSchema = z6.enum(["off", "error", "warning", "info"]);
8087
+ var SecurityConfigSchema = z6.object({
8088
+ enabled: z6.boolean().default(true),
8089
+ strict: z6.boolean().default(false),
8090
+ rules: z6.record(z6.string(), RuleOverrideSchema).optional().default({}),
8091
+ exclude: z6.array(z6.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
8092
+ external: z6.object({
8093
+ semgrep: z6.object({
8094
+ enabled: z6.union([z6.literal("auto"), z6.boolean()]).default("auto"),
8095
+ rulesets: z6.array(z6.string()).optional()
7683
8096
  }).optional(),
7684
- gitleaks: z5.object({
7685
- enabled: z5.union([z5.literal("auto"), z5.boolean()]).default("auto")
8097
+ gitleaks: z6.object({
8098
+ enabled: z6.union([z6.literal("auto"), z6.boolean()]).default("auto")
7686
8099
  }).optional()
7687
8100
  }).optional()
7688
8101
  });
@@ -7715,11 +8128,11 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
7715
8128
  }
7716
8129
  function detectStack(projectRoot) {
7717
8130
  const stacks = [];
7718
- const pkgJsonPath = path13.join(projectRoot, "package.json");
7719
- if (fs16.existsSync(pkgJsonPath)) {
8131
+ const pkgJsonPath = path14.join(projectRoot, "package.json");
8132
+ if (fs17.existsSync(pkgJsonPath)) {
7720
8133
  stacks.push("node");
7721
8134
  try {
7722
- const pkgJson = JSON.parse(fs16.readFileSync(pkgJsonPath, "utf-8"));
8135
+ const pkgJson = JSON.parse(fs17.readFileSync(pkgJsonPath, "utf-8"));
7723
8136
  const allDeps = {
7724
8137
  ...pkgJson.dependencies,
7725
8138
  ...pkgJson.devDependencies
@@ -7734,13 +8147,13 @@ function detectStack(projectRoot) {
7734
8147
  } catch {
7735
8148
  }
7736
8149
  }
7737
- const goModPath = path13.join(projectRoot, "go.mod");
7738
- if (fs16.existsSync(goModPath)) {
8150
+ const goModPath = path14.join(projectRoot, "go.mod");
8151
+ if (fs17.existsSync(goModPath)) {
7739
8152
  stacks.push("go");
7740
8153
  }
7741
- const requirementsPath = path13.join(projectRoot, "requirements.txt");
7742
- const pyprojectPath = path13.join(projectRoot, "pyproject.toml");
7743
- if (fs16.existsSync(requirementsPath) || fs16.existsSync(pyprojectPath)) {
8154
+ const requirementsPath = path14.join(projectRoot, "requirements.txt");
8155
+ const pyprojectPath = path14.join(projectRoot, "pyproject.toml");
8156
+ if (fs17.existsSync(requirementsPath) || fs17.existsSync(pyprojectPath)) {
7744
8157
  stacks.push("python");
7745
8158
  }
7746
8159
  return stacks;
@@ -7802,6 +8215,72 @@ var secretRules = [
7802
8215
  message: "Hardcoded JWT token detected",
7803
8216
  remediation: "Tokens should be fetched at runtime, not embedded in source",
7804
8217
  references: ["CWE-798"]
8218
+ },
8219
+ {
8220
+ id: "SEC-SEC-006",
8221
+ name: "Anthropic API Key",
8222
+ category: "secrets",
8223
+ severity: "error",
8224
+ confidence: "high",
8225
+ patterns: [/sk-ant-api\d{2}-[A-Za-z0-9_-]{20,}/],
8226
+ message: "Hardcoded Anthropic API key detected",
8227
+ remediation: "Use environment variables: process.env.ANTHROPIC_API_KEY",
8228
+ references: ["CWE-798"]
8229
+ },
8230
+ {
8231
+ id: "SEC-SEC-007",
8232
+ name: "OpenAI API Key",
8233
+ category: "secrets",
8234
+ severity: "error",
8235
+ confidence: "high",
8236
+ patterns: [/sk-proj-[A-Za-z0-9_-]{20,}/],
8237
+ message: "Hardcoded OpenAI API key detected",
8238
+ remediation: "Use environment variables: process.env.OPENAI_API_KEY",
8239
+ references: ["CWE-798"]
8240
+ },
8241
+ {
8242
+ id: "SEC-SEC-008",
8243
+ name: "Google API Key",
8244
+ category: "secrets",
8245
+ severity: "error",
8246
+ confidence: "high",
8247
+ patterns: [/AIza[A-Za-z0-9_-]{35}/],
8248
+ message: "Hardcoded Google API key detected",
8249
+ remediation: "Use environment variables or a secrets manager for Google API keys",
8250
+ references: ["CWE-798"]
8251
+ },
8252
+ {
8253
+ id: "SEC-SEC-009",
8254
+ name: "GitHub Personal Access Token",
8255
+ category: "secrets",
8256
+ severity: "error",
8257
+ confidence: "high",
8258
+ patterns: [/gh[pous]_[A-Za-z0-9_]{36,}/],
8259
+ message: "Hardcoded GitHub personal access token detected",
8260
+ remediation: "Use environment variables: process.env.GITHUB_TOKEN",
8261
+ references: ["CWE-798"]
8262
+ },
8263
+ {
8264
+ id: "SEC-SEC-010",
8265
+ name: "Stripe Live Key",
8266
+ category: "secrets",
8267
+ severity: "error",
8268
+ confidence: "high",
8269
+ patterns: [/\b[spr]k_live_[A-Za-z0-9]{24,}/],
8270
+ message: "Hardcoded Stripe live key detected",
8271
+ remediation: "Use environment variables for Stripe keys; never commit live keys",
8272
+ references: ["CWE-798"]
8273
+ },
8274
+ {
8275
+ id: "SEC-SEC-011",
8276
+ name: "Database Connection String with Credentials",
8277
+ category: "secrets",
8278
+ severity: "error",
8279
+ confidence: "high",
8280
+ patterns: [/(?:postgres|mysql|mongodb|redis|amqp|mssql)(?:\+\w+)?:\/\/[^/\s:]+:[^@/\s]+@/i],
8281
+ message: "Database connection string with embedded credentials detected",
8282
+ remediation: "Use environment variables for connection strings; separate credentials from URIs",
8283
+ references: ["CWE-798"]
7805
8284
  }
7806
8285
  ];
7807
8286
  var injectionRules = [
@@ -7975,99 +8454,445 @@ var deserializationRules = [
7975
8454
  references: ["CWE-502"]
7976
8455
  }
7977
8456
  ];
7978
- var nodeRules = [
8457
+ var agentConfigRules = [
7979
8458
  {
7980
- id: "SEC-NODE-001",
7981
- name: "Prototype Pollution",
7982
- category: "injection",
8459
+ id: "SEC-AGT-001",
8460
+ name: "Hidden Unicode Characters",
8461
+ category: "agent-config",
8462
+ severity: "error",
8463
+ confidence: "high",
8464
+ patterns: [/\u200B|\u200C|\u200D|\uFEFF|\u2060/],
8465
+ fileGlob: "**/CLAUDE.md,**/AGENTS.md,**/*.yaml",
8466
+ message: "Hidden zero-width Unicode characters detected in agent configuration",
8467
+ remediation: "Remove invisible Unicode characters; they may hide malicious instructions",
8468
+ references: ["CWE-116"]
8469
+ },
8470
+ {
8471
+ id: "SEC-AGT-002",
8472
+ name: "URL Execution Directives",
8473
+ category: "agent-config",
7983
8474
  severity: "warning",
7984
8475
  confidence: "medium",
7985
- patterns: [
7986
- /__proto__/,
7987
- /\bconstructor\s*\[/,
7988
- /\bprototype\s*\[/,
7989
- /Object\.assign\s*\(\s*\w+\s*,\s*(?:req|request|body|input|params|query)\b/
7990
- ],
7991
- stack: ["node"],
7992
- message: "Potential prototype pollution via __proto__, constructor, or Object.assign with untrusted input",
7993
- remediation: "Validate keys against a whitelist, use Object.create(null), or use Map instead of plain objects",
7994
- references: ["CWE-1321"]
8476
+ patterns: [/\b(?:curl|wget)\s+\S+/i, /\bfetch\s*\(/i],
8477
+ fileGlob: "**/CLAUDE.md,**/AGENTS.md",
8478
+ message: "URL execution directive found in agent configuration",
8479
+ remediation: "Avoid instructing agents to download and execute remote content",
8480
+ references: ["CWE-94"]
7995
8481
  },
7996
8482
  {
7997
- id: "SEC-NODE-002",
7998
- name: "NoSQL Injection",
7999
- category: "injection",
8483
+ id: "SEC-AGT-003",
8484
+ name: "Wildcard Tool Permissions",
8485
+ category: "agent-config",
8486
+ severity: "warning",
8487
+ confidence: "high",
8488
+ patterns: [/(?:Bash|Write|Edit)\s*\(\s*\*\s*\)/],
8489
+ fileGlob: "**/.claude/**,**/settings*.json",
8490
+ message: "Wildcard tool permissions grant unrestricted access",
8491
+ remediation: "Scope tool permissions to specific patterns instead of wildcards",
8492
+ references: ["CWE-250"]
8493
+ },
8494
+ {
8495
+ id: "SEC-AGT-004",
8496
+ name: "Auto-approve Patterns",
8497
+ category: "agent-config",
8498
+ severity: "warning",
8499
+ confidence: "high",
8500
+ patterns: [/\bautoApprove\b/i, /\bauto_approve\b/i],
8501
+ fileGlob: "**/.claude/**,**/.mcp.json",
8502
+ message: "Auto-approve configuration bypasses human review of tool calls",
8503
+ remediation: "Review auto-approved tools carefully; prefer explicit approval for destructive operations",
8504
+ references: ["CWE-862"]
8505
+ },
8506
+ {
8507
+ id: "SEC-AGT-005",
8508
+ name: "Prompt Injection Surface",
8509
+ category: "agent-config",
8000
8510
  severity: "warning",
8001
8511
  confidence: "medium",
8002
- patterns: [
8003
- /\.find\s*\(\s*\{[^}]*\$(?:gt|gte|lt|lte|ne|in|nin|regex|where|exists)/,
8004
- /\.find\s*\(\s*(?:req|request)\.(?:body|query|params)/
8005
- ],
8006
- stack: ["node"],
8007
- message: "Potential NoSQL injection: MongoDB query operators in user input",
8008
- remediation: "Sanitize input by stripping keys starting with $ before using in queries",
8009
- references: ["CWE-943"]
8010
- }
8011
- ];
8012
- var expressRules = [
8512
+ patterns: [/\$\{[^}]*\}/, /\{\{[^}]*\}\}/],
8513
+ fileGlob: "**/skill.yaml",
8514
+ message: "Template interpolation syntax in skill YAML may enable prompt injection",
8515
+ remediation: "Avoid dynamic interpolation in skill descriptions; use static text",
8516
+ references: ["CWE-94"]
8517
+ },
8013
8518
  {
8014
- id: "SEC-EXPRESS-001",
8015
- name: "Missing Helmet",
8016
- category: "network",
8017
- severity: "info",
8018
- confidence: "low",
8019
- patterns: [/app\s*=\s*express\s*\(\)/],
8020
- stack: ["express"],
8021
- fileGlob: "**/app.{ts,js}",
8022
- message: "Express app initialization detected \u2014 ensure helmet middleware is applied for security headers",
8023
- remediation: "Add helmet middleware: app.use(helmet())",
8024
- references: ["CWE-693"]
8519
+ id: "SEC-AGT-006",
8520
+ name: "Permission Bypass Flags",
8521
+ category: "agent-config",
8522
+ severity: "error",
8523
+ confidence: "high",
8524
+ patterns: [/--dangerously-skip-permissions/, /--no-verify/],
8525
+ fileGlob: "**/CLAUDE.md,**/AGENTS.md,**/.claude/**",
8526
+ message: "Permission bypass flag detected in agent configuration",
8527
+ remediation: "Remove flags that bypass safety checks; they undermine enforcement",
8528
+ references: ["CWE-863"]
8025
8529
  },
8026
8530
  {
8027
- id: "SEC-EXPRESS-002",
8028
- name: "Unprotected Route with Body Parsing",
8029
- category: "network",
8030
- severity: "info",
8531
+ id: "SEC-AGT-007",
8532
+ name: "Hook Injection Surface",
8533
+ category: "agent-config",
8534
+ severity: "error",
8031
8535
  confidence: "low",
8032
- patterns: [/app\.(?:post|put|patch)\s*\([^)]*,\s*(?:req|request)\s*(?:,|\))/],
8033
- stack: ["express"],
8034
- message: "Express route accepts request body \u2014 ensure input validation and rate limiting are applied",
8035
- remediation: "Add express-rate-limit and validate request body with Zod/joi",
8036
- references: ["CWE-770"]
8536
+ patterns: [/\$\(/, /`[^`]+`/, /\s&&\s/, /\s\|\|\s/],
8537
+ fileGlob: "**/settings*.json,**/hooks.json",
8538
+ message: "Shell metacharacters in hook commands may enable command injection",
8539
+ remediation: "Use simple, single-command hooks without shell operators; chain logic inside the script",
8540
+ references: ["CWE-78"]
8037
8541
  }
8038
8542
  ];
8039
- var reactRules = [
8543
+ var mcpRules = [
8040
8544
  {
8041
- id: "SEC-REACT-001",
8042
- name: "Sensitive Data in Client Storage",
8043
- category: "secrets",
8044
- severity: "warning",
8545
+ id: "SEC-MCP-001",
8546
+ name: "Hardcoded MCP Secrets",
8547
+ category: "mcp",
8548
+ severity: "error",
8045
8549
  confidence: "medium",
8046
- patterns: [
8047
- /localStorage\.setItem\s*\(\s*['"](?:token|jwt|auth|session|password|secret|key|credential)/i,
8048
- /sessionStorage\.setItem\s*\(\s*['"](?:token|jwt|auth|session|password|secret|key|credential)/i
8049
- ],
8050
- stack: ["react"],
8051
- message: "Storing sensitive data in browser storage is accessible to XSS attacks",
8052
- remediation: "Use httpOnly cookies for auth tokens instead of localStorage",
8053
- references: ["CWE-922"]
8054
- }
8055
- ];
8056
- var goRules = [
8550
+ patterns: [/(?:API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)\s*["']?\s*:\s*["'][^"']{8,}["']/i],
8551
+ fileGlob: "**/.mcp.json",
8552
+ message: "Hardcoded secret detected in MCP server configuration",
8553
+ remediation: "Use environment variable references instead of inline secrets in .mcp.json",
8554
+ references: ["CWE-798"]
8555
+ },
8057
8556
  {
8058
- id: "SEC-GO-001",
8059
- name: "Unsafe Pointer Usage",
8060
- category: "injection",
8061
- severity: "warning",
8557
+ id: "SEC-MCP-002",
8558
+ name: "Shell Injection in MCP Args",
8559
+ category: "mcp",
8560
+ severity: "error",
8062
8561
  confidence: "medium",
8063
- patterns: [/unsafe\.Pointer/],
8064
- stack: ["go"],
8065
- message: "unsafe.Pointer bypasses Go type safety",
8066
- remediation: "Avoid unsafe.Pointer unless absolutely necessary; document justification",
8067
- references: ["CWE-119"]
8562
+ patterns: [/\$\(/, /`[^`]+`/],
8563
+ fileGlob: "**/.mcp.json",
8564
+ message: "Shell metacharacters detected in MCP server arguments",
8565
+ remediation: "Use literal argument values; avoid shell interpolation in MCP args",
8566
+ references: ["CWE-78"]
8068
8567
  },
8069
8568
  {
8070
- id: "SEC-GO-002",
8569
+ id: "SEC-MCP-003",
8570
+ name: "Network Exposure",
8571
+ category: "mcp",
8572
+ severity: "warning",
8573
+ confidence: "high",
8574
+ patterns: [/0\.0\.0\.0/, /["']\*["']\s*:\s*\d/, /host["']?\s*:\s*["']\*["']/i],
8575
+ fileGlob: "**/.mcp.json",
8576
+ message: "MCP server binding to all network interfaces (0.0.0.0 or wildcard *)",
8577
+ remediation: "Bind to 127.0.0.1 or localhost to restrict access to local machine",
8578
+ references: ["CWE-668"]
8579
+ },
8580
+ {
8581
+ id: "SEC-MCP-004",
8582
+ name: "Typosquatting Vector",
8583
+ category: "mcp",
8584
+ severity: "warning",
8585
+ confidence: "medium",
8586
+ patterns: [/\bnpx\s+(?:-y|--yes)\b/],
8587
+ fileGlob: "**/.mcp.json",
8588
+ message: "npx -y auto-installs packages without confirmation, enabling typosquatting",
8589
+ remediation: "Pin exact package versions or install packages explicitly before use",
8590
+ references: ["CWE-427"]
8591
+ },
8592
+ {
8593
+ id: "SEC-MCP-005",
8594
+ name: "Unencrypted Transport",
8595
+ category: "mcp",
8596
+ severity: "warning",
8597
+ confidence: "medium",
8598
+ patterns: [/http:\/\/(?!localhost\b|127\.0\.0\.1\b)/],
8599
+ fileGlob: "**/.mcp.json",
8600
+ message: "Unencrypted HTTP transport detected for MCP server connection",
8601
+ remediation: "Use https:// for all non-localhost MCP server connections",
8602
+ references: ["CWE-319"]
8603
+ }
8604
+ ];
8605
+ var insecureDefaultsRules = [
8606
+ {
8607
+ id: "SEC-DEF-001",
8608
+ name: "Security-Sensitive Fallback to Hardcoded Default",
8609
+ category: "insecure-defaults",
8610
+ severity: "warning",
8611
+ confidence: "medium",
8612
+ patterns: [
8613
+ /(?:SECRET|KEY|TOKEN|PASSWORD|SALT|PEPPER|SIGNING|ENCRYPTION|AUTH|JWT|SESSION).*(?:\|\||\?\?)\s*['"][^'"]+['"]/i
8614
+ ],
8615
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
8616
+ message: "Security-sensitive variable falls back to a hardcoded default when env var is missing",
8617
+ remediation: "Throw an error if the env var is missing instead of falling back to a default. Use a startup validation check.",
8618
+ references: ["CWE-1188"]
8619
+ },
8620
+ {
8621
+ id: "SEC-DEF-002",
8622
+ name: "TLS/SSL Disabled by Default",
8623
+ category: "insecure-defaults",
8624
+ severity: "warning",
8625
+ confidence: "medium",
8626
+ patterns: [
8627
+ /(?:tls|ssl|https|secure)\s*(?:=|:)\s*(?:false|config\??\.\w+\s*(?:\?\?|&&|\|\|)\s*false)/i
8628
+ ],
8629
+ fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
8630
+ message: "Security feature defaults to disabled; missing configuration degrades to insecure mode",
8631
+ remediation: "Default security features to enabled (true). Require explicit opt-out, not opt-in.",
8632
+ references: ["CWE-1188"]
8633
+ },
8634
+ {
8635
+ id: "SEC-DEF-003",
8636
+ name: "Swallowed Authentication/Authorization Error",
8637
+ category: "insecure-defaults",
8638
+ severity: "warning",
8639
+ confidence: "low",
8640
+ patterns: [
8641
+ // Matches single-line empty catch: catch(e) { } or catch(e) { // ignore }
8642
+ // Note: multi-line catch blocks are handled by AI review, not this rule
8643
+ /catch\s*\([^)]*\)\s*\{\s*(?:\/\/\s*(?:ignore|skip|noop|todo)\b.*)?\s*\}/
8644
+ ],
8645
+ fileGlob: "**/*auth*.{ts,js,mjs,cjs},**/*session*.{ts,js,mjs,cjs},**/*token*.{ts,js,mjs,cjs}",
8646
+ message: "Single-line empty catch block in authentication/authorization code may silently allow unauthorized access. Note: multi-line empty catch blocks are detected by AI review, not this mechanical rule.",
8647
+ remediation: "Re-throw the error or return an explicit denial. Never silently swallow auth errors.",
8648
+ references: ["CWE-754", "CWE-390"]
8649
+ },
8650
+ {
8651
+ id: "SEC-DEF-004",
8652
+ name: "Permissive CORS Fallback",
8653
+ category: "insecure-defaults",
8654
+ severity: "warning",
8655
+ confidence: "medium",
8656
+ patterns: [
8657
+ /(?:origin|cors)\s*(?:=|:)\s*(?:config|options|env)\??\.\w+\s*(?:\?\?|\|\|)\s*['"]\*/
8658
+ ],
8659
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
8660
+ message: "CORS origin falls back to wildcard (*) when configuration is missing",
8661
+ remediation: "Default to a restrictive origin list. Require explicit configuration for permissive CORS.",
8662
+ references: ["CWE-942"]
8663
+ },
8664
+ {
8665
+ id: "SEC-DEF-005",
8666
+ name: "Rate Limiting Disabled by Default",
8667
+ category: "insecure-defaults",
8668
+ severity: "info",
8669
+ confidence: "low",
8670
+ patterns: [
8671
+ /(?:rateLimit|rateLimiting|throttle)\s*(?:=|:)\s*(?:config|options|env)\??\.\w+\s*(?:\?\?|\|\|)\s*(?:false|0|null|undefined)/i
8672
+ ],
8673
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
8674
+ message: "Rate limiting defaults to disabled when configuration is missing",
8675
+ remediation: "Default to a sensible rate limit. Require explicit opt-out for disabling.",
8676
+ references: ["CWE-770"]
8677
+ }
8678
+ ];
8679
+ var sharpEdgesRules = [
8680
+ // --- Deprecated Crypto APIs ---
8681
+ {
8682
+ id: "SEC-EDGE-001",
8683
+ name: "Deprecated createCipher API",
8684
+ category: "sharp-edges",
8685
+ severity: "error",
8686
+ confidence: "high",
8687
+ patterns: [/crypto\.createCipher\s*\(/],
8688
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
8689
+ message: "crypto.createCipher is deprecated \u2014 uses weak key derivation (no IV)",
8690
+ remediation: "Use crypto.createCipheriv with a random IV and proper key derivation (scrypt/pbkdf2)",
8691
+ references: ["CWE-327"]
8692
+ },
8693
+ {
8694
+ id: "SEC-EDGE-002",
8695
+ name: "Deprecated createDecipher API",
8696
+ category: "sharp-edges",
8697
+ severity: "error",
8698
+ confidence: "high",
8699
+ patterns: [/crypto\.createDecipher\s*\(/],
8700
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
8701
+ message: "crypto.createDecipher is deprecated \u2014 uses weak key derivation (no IV)",
8702
+ remediation: "Use crypto.createDecipheriv with the same IV used for encryption",
8703
+ references: ["CWE-327"]
8704
+ },
8705
+ {
8706
+ id: "SEC-EDGE-003",
8707
+ name: "ECB Mode Selection",
8708
+ category: "sharp-edges",
8709
+ severity: "warning",
8710
+ confidence: "high",
8711
+ patterns: [/-ecb['"]/],
8712
+ fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
8713
+ message: "ECB mode does not provide semantic security \u2014 identical plaintext blocks produce identical ciphertext",
8714
+ remediation: "Use CBC, CTR, or GCM mode instead of ECB",
8715
+ references: ["CWE-327"]
8716
+ },
8717
+ // --- Unsafe Deserialization ---
8718
+ {
8719
+ id: "SEC-EDGE-004",
8720
+ name: "yaml.load Without Safe Loader",
8721
+ category: "sharp-edges",
8722
+ severity: "error",
8723
+ confidence: "high",
8724
+ patterns: [
8725
+ /yaml\.load\s*\(/
8726
+ // Python: yaml.load() without SafeLoader
8727
+ ],
8728
+ fileGlob: "**/*.py",
8729
+ message: "yaml.load() executes arbitrary Python objects \u2014 use yaml.safe_load() instead",
8730
+ remediation: "Replace yaml.load() with yaml.safe_load() or yaml.load(data, Loader=SafeLoader). Note: this rule will flag yaml.load(data, Loader=SafeLoader) \u2014 suppress with # harness-ignore SEC-EDGE-004: safe usage with SafeLoader",
8731
+ references: ["CWE-502"]
8732
+ },
8733
+ {
8734
+ id: "SEC-EDGE-005",
8735
+ name: "Pickle/Marshal Deserialization",
8736
+ category: "sharp-edges",
8737
+ severity: "error",
8738
+ confidence: "high",
8739
+ patterns: [/pickle\.loads?\s*\(/, /marshal\.loads?\s*\(/],
8740
+ fileGlob: "**/*.py",
8741
+ message: "pickle/marshal deserialization executes arbitrary code \u2014 never use on untrusted data",
8742
+ remediation: "Use JSON, MessagePack, or Protocol Buffers for untrusted data serialization",
8743
+ references: ["CWE-502"]
8744
+ },
8745
+ // --- TOCTOU (Time-of-Check to Time-of-Use) ---
8746
+ {
8747
+ id: "SEC-EDGE-006",
8748
+ name: "Check-Then-Act File Operation",
8749
+ category: "sharp-edges",
8750
+ severity: "warning",
8751
+ confidence: "medium",
8752
+ // Patterns use .{0,N} since scanner matches single lines only (no multiline mode)
8753
+ patterns: [
8754
+ /(?:existsSync|accessSync|statSync)\s*\([^)]+\).{0,50}(?:readFileSync|writeFileSync|unlinkSync|mkdirSync)\s*\(/
8755
+ ],
8756
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
8757
+ message: "Check-then-act pattern on filesystem is vulnerable to TOCTOU race conditions",
8758
+ remediation: "Use the operation directly and handle ENOENT/EEXIST errors instead of checking first",
8759
+ references: ["CWE-367"]
8760
+ },
8761
+ {
8762
+ id: "SEC-EDGE-007",
8763
+ name: "Check-Then-Act File Operation (Async)",
8764
+ category: "sharp-edges",
8765
+ severity: "warning",
8766
+ confidence: "medium",
8767
+ // Uses .{0,N} since scanner matches single lines only (no multiline mode)
8768
+ patterns: [/(?:access|stat)\s*\([^)]+\).{0,80}(?:readFile|writeFile|unlink|mkdir)\s*\(/],
8769
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
8770
+ message: "Async check-then-act pattern on filesystem is vulnerable to TOCTOU race conditions",
8771
+ remediation: "Use the operation directly with try/catch instead of checking existence first",
8772
+ references: ["CWE-367"]
8773
+ },
8774
+ // --- Stringly-Typed Security ---
8775
+ {
8776
+ id: "SEC-EDGE-008",
8777
+ name: 'JWT Algorithm "none"',
8778
+ category: "sharp-edges",
8779
+ severity: "error",
8780
+ confidence: "high",
8781
+ patterns: [
8782
+ /algorithm[s]?\s*[:=]\s*\[?\s*['"]none['"]/i,
8783
+ /alg(?:orithm)?\s*[:=]\s*['"]none['"]/i
8784
+ ],
8785
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
8786
+ message: 'JWT "none" algorithm disables signature verification entirely',
8787
+ remediation: 'Specify an explicit algorithm (e.g., "HS256", "RS256") and set algorithms allowlist in verify options',
8788
+ references: ["CWE-345"]
8789
+ },
8790
+ {
8791
+ id: "SEC-EDGE-009",
8792
+ name: "DES/RC4 Algorithm Selection",
8793
+ category: "sharp-edges",
8794
+ severity: "error",
8795
+ confidence: "high",
8796
+ patterns: [/['"]\s*(?:des|des-ede|des-ede3|des3|rc4|rc2|blowfish)\s*['"]/i],
8797
+ fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
8798
+ message: "Weak/deprecated cipher algorithm selected \u2014 DES, RC4, RC2, and Blowfish are broken or deprecated",
8799
+ remediation: "Use AES-256-GCM or ChaCha20-Poly1305",
8800
+ references: ["CWE-327"]
8801
+ }
8802
+ ];
8803
+ var nodeRules = [
8804
+ {
8805
+ id: "SEC-NODE-001",
8806
+ name: "Prototype Pollution",
8807
+ category: "injection",
8808
+ severity: "warning",
8809
+ confidence: "medium",
8810
+ patterns: [
8811
+ /__proto__/,
8812
+ /\bconstructor\s*\[/,
8813
+ /\bprototype\s*\[/,
8814
+ /Object\.assign\s*\(\s*\w+\s*,\s*(?:req|request|body|input|params|query)\b/
8815
+ ],
8816
+ stack: ["node"],
8817
+ message: "Potential prototype pollution via __proto__, constructor, or Object.assign with untrusted input",
8818
+ remediation: "Validate keys against a whitelist, use Object.create(null), or use Map instead of plain objects",
8819
+ references: ["CWE-1321"]
8820
+ },
8821
+ {
8822
+ id: "SEC-NODE-002",
8823
+ name: "NoSQL Injection",
8824
+ category: "injection",
8825
+ severity: "warning",
8826
+ confidence: "medium",
8827
+ patterns: [
8828
+ /\.find\s*\(\s*\{[^}]*\$(?:gt|gte|lt|lte|ne|in|nin|regex|where|exists)/,
8829
+ /\.find\s*\(\s*(?:req|request)\.(?:body|query|params)/
8830
+ ],
8831
+ stack: ["node"],
8832
+ message: "Potential NoSQL injection: MongoDB query operators in user input",
8833
+ remediation: "Sanitize input by stripping keys starting with $ before using in queries",
8834
+ references: ["CWE-943"]
8835
+ }
8836
+ ];
8837
+ var expressRules = [
8838
+ {
8839
+ id: "SEC-EXPRESS-001",
8840
+ name: "Missing Helmet",
8841
+ category: "network",
8842
+ severity: "info",
8843
+ confidence: "low",
8844
+ patterns: [/app\s*=\s*express\s*\(\)/],
8845
+ stack: ["express"],
8846
+ fileGlob: "**/app.{ts,js}",
8847
+ message: "Express app initialization detected \u2014 ensure helmet middleware is applied for security headers",
8848
+ remediation: "Add helmet middleware: app.use(helmet())",
8849
+ references: ["CWE-693"]
8850
+ },
8851
+ {
8852
+ id: "SEC-EXPRESS-002",
8853
+ name: "Unprotected Route with Body Parsing",
8854
+ category: "network",
8855
+ severity: "info",
8856
+ confidence: "low",
8857
+ patterns: [/app\.(?:post|put|patch)\s*\([^)]*,\s*(?:req|request)\s*(?:,|\))/],
8858
+ stack: ["express"],
8859
+ message: "Express route accepts request body \u2014 ensure input validation and rate limiting are applied",
8860
+ remediation: "Add express-rate-limit and validate request body with Zod/joi",
8861
+ references: ["CWE-770"]
8862
+ }
8863
+ ];
8864
+ var reactRules = [
8865
+ {
8866
+ id: "SEC-REACT-001",
8867
+ name: "Sensitive Data in Client Storage",
8868
+ category: "secrets",
8869
+ severity: "warning",
8870
+ confidence: "medium",
8871
+ patterns: [
8872
+ /localStorage\.setItem\s*\(\s*['"](?:token|jwt|auth|session|password|secret|key|credential)/i,
8873
+ /sessionStorage\.setItem\s*\(\s*['"](?:token|jwt|auth|session|password|secret|key|credential)/i
8874
+ ],
8875
+ stack: ["react"],
8876
+ message: "Storing sensitive data in browser storage is accessible to XSS attacks",
8877
+ remediation: "Use httpOnly cookies for auth tokens instead of localStorage",
8878
+ references: ["CWE-922"]
8879
+ }
8880
+ ];
8881
+ var goRules = [
8882
+ {
8883
+ id: "SEC-GO-001",
8884
+ name: "Unsafe Pointer Usage",
8885
+ category: "injection",
8886
+ severity: "warning",
8887
+ confidence: "medium",
8888
+ patterns: [/unsafe\.Pointer/],
8889
+ stack: ["go"],
8890
+ message: "unsafe.Pointer bypasses Go type safety",
8891
+ remediation: "Avoid unsafe.Pointer unless absolutely necessary; document justification",
8892
+ references: ["CWE-119"]
8893
+ },
8894
+ {
8895
+ id: "SEC-GO-002",
8071
8896
  name: "Format String Injection",
8072
8897
  category: "injection",
8073
8898
  severity: "warning",
@@ -8079,6 +8904,14 @@ var goRules = [
8079
8904
  references: ["CWE-134"]
8080
8905
  }
8081
8906
  ];
8907
+ function parseHarnessIgnore(line, ruleId) {
8908
+ if (!line.includes("harness-ignore")) return null;
8909
+ if (!line.includes(ruleId)) return null;
8910
+ const match = line.match(/(?:\/\/|#)\s*harness-ignore\s+(SEC-[A-Z]+-\d+)(?::\s*(.+))?/);
8911
+ if (match?.[1] !== ruleId) return null;
8912
+ const text = match[2]?.trim();
8913
+ return { ruleId, justification: text || null };
8914
+ }
8082
8915
  var SecurityScanner = class {
8083
8916
  registry;
8084
8917
  config;
@@ -8093,7 +8926,11 @@ var SecurityScanner = class {
8093
8926
  ...cryptoRules,
8094
8927
  ...pathTraversalRules,
8095
8928
  ...networkRules,
8096
- ...deserializationRules
8929
+ ...deserializationRules,
8930
+ ...agentConfigRules,
8931
+ ...mcpRules,
8932
+ ...insecureDefaultsRules,
8933
+ ...sharpEdgesRules
8097
8934
  ]);
8098
8935
  this.registry.registerAll([...nodeRules, ...expressRules, ...reactRules, ...goRules]);
8099
8936
  this.activeRules = this.registry.getAll();
@@ -8102,11 +8939,40 @@ var SecurityScanner = class {
8102
8939
  const stacks = detectStack(projectRoot);
8103
8940
  this.activeRules = this.registry.getForStacks(stacks.length > 0 ? stacks : []);
8104
8941
  }
8942
+ /**
8943
+ * Scan raw content against all active rules. Note: this method does NOT apply
8944
+ * fileGlob filtering — every active rule is evaluated regardless of filePath.
8945
+ * If you are scanning a specific file and want fileGlob-based rule filtering,
8946
+ * use {@link scanFile} instead.
8947
+ */
8105
8948
  scanContent(content, filePath, startLine = 1) {
8106
8949
  if (!this.config.enabled) return [];
8107
- const findings = [];
8108
8950
  const lines = content.split("\n");
8109
- for (const rule of this.activeRules) {
8951
+ return this.scanLinesWithRules(lines, this.activeRules, filePath, startLine);
8952
+ }
8953
+ async scanFile(filePath) {
8954
+ if (!this.config.enabled) return [];
8955
+ const content = await fs18.readFile(filePath, "utf-8");
8956
+ return this.scanContentForFile(content, filePath, 1);
8957
+ }
8958
+ scanContentForFile(content, filePath, startLine = 1) {
8959
+ if (!this.config.enabled) return [];
8960
+ const lines = content.split("\n");
8961
+ const applicableRules = this.activeRules.filter((rule) => {
8962
+ if (!rule.fileGlob) return true;
8963
+ const globs = rule.fileGlob.split(",").map((g) => g.trim());
8964
+ return globs.some((glob2) => minimatch4(filePath, glob2, { dot: true }));
8965
+ });
8966
+ return this.scanLinesWithRules(lines, applicableRules, filePath, startLine);
8967
+ }
8968
+ /**
8969
+ * Core scanning loop shared by scanContent and scanContentForFile.
8970
+ * Evaluates each rule against each line, handling suppression (FP gate)
8971
+ * and pattern matching uniformly.
8972
+ */
8973
+ scanLinesWithRules(lines, rules, filePath, startLine) {
8974
+ const findings = [];
8975
+ for (const rule of rules) {
8110
8976
  const resolved = resolveRuleSeverity(
8111
8977
  rule.id,
8112
8978
  rule.severity,
@@ -8116,7 +8982,25 @@ var SecurityScanner = class {
8116
8982
  if (resolved === "off") continue;
8117
8983
  for (let i = 0; i < lines.length; i++) {
8118
8984
  const line = lines[i] ?? "";
8119
- if (line.includes("harness-ignore") && line.includes(rule.id)) continue;
8985
+ const suppressionMatch = parseHarnessIgnore(line, rule.id);
8986
+ if (suppressionMatch) {
8987
+ if (!suppressionMatch.justification) {
8988
+ findings.push({
8989
+ ruleId: rule.id,
8990
+ ruleName: rule.name,
8991
+ category: rule.category,
8992
+ severity: this.config.strict ? "error" : "warning",
8993
+ confidence: "high",
8994
+ file: filePath,
8995
+ line: startLine + i,
8996
+ match: line.trim(),
8997
+ context: line,
8998
+ message: `Suppression of ${rule.id} requires justification: // harness-ignore ${rule.id}: <reason>`,
8999
+ remediation: `Add justification after colon: // harness-ignore ${rule.id}: false positive because ...`
9000
+ });
9001
+ }
9002
+ continue;
9003
+ }
8120
9004
  for (const pattern of rule.patterns) {
8121
9005
  pattern.lastIndex = 0;
8122
9006
  if (pattern.test(line)) {
@@ -8141,31 +9025,426 @@ var SecurityScanner = class {
8141
9025
  }
8142
9026
  return findings;
8143
9027
  }
8144
- async scanFile(filePath) {
8145
- if (!this.config.enabled) return [];
8146
- const content = await fs17.readFile(filePath, "utf-8");
8147
- return this.scanContent(content, filePath, 1);
9028
+ async scanFiles(filePaths) {
9029
+ const allFindings = [];
9030
+ let scannedCount = 0;
9031
+ for (const filePath of filePaths) {
9032
+ try {
9033
+ const findings = await this.scanFile(filePath);
9034
+ allFindings.push(...findings);
9035
+ scannedCount++;
9036
+ } catch {
9037
+ }
9038
+ }
9039
+ return {
9040
+ findings: allFindings,
9041
+ scannedFiles: scannedCount,
9042
+ rulesApplied: this.activeRules.length,
9043
+ externalToolsUsed: [],
9044
+ coverage: "baseline"
9045
+ };
9046
+ }
9047
+ };
9048
+ var hiddenUnicodePatterns = [
9049
+ {
9050
+ ruleId: "INJ-UNI-001",
9051
+ severity: "high",
9052
+ category: "hidden-unicode",
9053
+ description: "Zero-width characters that can hide malicious instructions",
9054
+ // eslint-disable-next-line no-misleading-character-class -- intentional: regex detects zero-width chars for security scanning
9055
+ pattern: /[\u200B\u200C\u200D\uFEFF\u2060]/
9056
+ },
9057
+ {
9058
+ ruleId: "INJ-UNI-002",
9059
+ severity: "high",
9060
+ category: "hidden-unicode",
9061
+ description: "RTL/LTR override characters that can disguise text direction",
9062
+ pattern: /[\u202A-\u202E\u2066-\u2069]/
9063
+ }
9064
+ ];
9065
+ var reRolingPatterns = [
9066
+ {
9067
+ ruleId: "INJ-REROL-001",
9068
+ severity: "high",
9069
+ category: "explicit-re-roling",
9070
+ description: "Instruction to ignore/disregard/forget previous instructions",
9071
+ pattern: /(?:ignore|disregard|forget)\s+(?:all\s+)?(?:previous|prior|above|earlier)\s+(?:instructions?|prompts?|context|rules?|guidelines?)/i
9072
+ },
9073
+ {
9074
+ ruleId: "INJ-REROL-002",
9075
+ severity: "high",
9076
+ category: "explicit-re-roling",
9077
+ description: "Attempt to reassign the AI role",
9078
+ pattern: /you\s+are\s+now\s+(?:a\s+|an\s+)?(?:new\s+)?(?:helpful\s+)?(?:my\s+)?(?:\w+\s+)?(?:assistant|agent|AI|bot|chatbot|system|persona)\b/i
9079
+ },
9080
+ {
9081
+ ruleId: "INJ-REROL-003",
9082
+ severity: "high",
9083
+ category: "explicit-re-roling",
9084
+ description: "Direct instruction override attempt",
9085
+ pattern: /(?:new\s+)?(?:system\s+)?(?:instruction|directive|role|persona)\s*[:=]\s*/i
9086
+ }
9087
+ ];
9088
+ var permissionEscalationPatterns = [
9089
+ {
9090
+ ruleId: "INJ-PERM-001",
9091
+ severity: "high",
9092
+ category: "permission-escalation",
9093
+ description: "Attempt to enable all tools or grant unrestricted access",
9094
+ pattern: /(?:allow|enable|grant)\s+all\s+(?:tools?|permissions?|access)/i
9095
+ },
9096
+ {
9097
+ ruleId: "INJ-PERM-002",
9098
+ severity: "high",
9099
+ category: "permission-escalation",
9100
+ description: "Attempt to disable safety or security features",
9101
+ pattern: /(?:disable|turn\s+off|remove|bypass)\s+(?:all\s+)?(?:safety|security|restrictions?|guardrails?|protections?|checks?)/i
9102
+ },
9103
+ {
9104
+ ruleId: "INJ-PERM-003",
9105
+ severity: "high",
9106
+ category: "permission-escalation",
9107
+ description: "Auto-approve directive that bypasses human review",
9108
+ pattern: /(?:auto[- ]?approve|--no-verify|--dangerously-skip-permissions)/i
9109
+ }
9110
+ ];
9111
+ var encodedPayloadPatterns = [
9112
+ {
9113
+ ruleId: "INJ-ENC-001",
9114
+ severity: "high",
9115
+ category: "encoded-payloads",
9116
+ description: "Base64-encoded string long enough to contain instructions (>=28 chars)",
9117
+ // Match base64 strings of 28+ chars (7+ groups of 4).
9118
+ // Excludes JWT tokens (eyJ prefix) and Bearer-prefixed tokens.
9119
+ pattern: /(?<!Bearer\s)(?<![:])(?<![A-Za-z0-9/])(?!eyJ)(?:[A-Za-z0-9+/]{4}){7,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?(?![A-Za-z0-9/])/
9120
+ },
9121
+ {
9122
+ ruleId: "INJ-ENC-002",
9123
+ severity: "high",
9124
+ category: "encoded-payloads",
9125
+ description: "Hex-encoded string long enough to contain directives (>=20 hex chars)",
9126
+ // Excludes hash-prefixed hex (sha256:, sha512:, md5:, etc.) and hex preceded by 0x.
9127
+ // Note: 40-char git SHA hashes (e.g. in `git log` output) may match — downstream
9128
+ // callers should filter matches of exactly 40 hex chars if scanning git output.
9129
+ pattern: /(?<![:x])(?<![A-Fa-f0-9])(?:[0-9a-fA-F]{2}){10,}(?![A-Fa-f0-9])/
9130
+ }
9131
+ ];
9132
+ var indirectInjectionPatterns = [
9133
+ {
9134
+ ruleId: "INJ-IND-001",
9135
+ severity: "medium",
9136
+ category: "indirect-injection",
9137
+ description: "Instruction to influence future responses",
9138
+ pattern: /(?:when\s+the\s+user\s+asks|if\s+(?:the\s+user|someone|anyone)\s+asks)\s*,?\s*(?:say|respond|reply|answer|tell)/i
9139
+ },
9140
+ {
9141
+ ruleId: "INJ-IND-002",
9142
+ severity: "medium",
9143
+ category: "indirect-injection",
9144
+ description: "Directive to include content in responses",
9145
+ pattern: /(?:include|insert|add|embed|put)\s+(?:this|the\s+following)\s+(?:in|into|to)\s+(?:your|the)\s+(?:response|output|reply|answer)/i
9146
+ },
9147
+ {
9148
+ ruleId: "INJ-IND-003",
9149
+ severity: "medium",
9150
+ category: "indirect-injection",
9151
+ description: "Standing instruction to always respond a certain way",
9152
+ pattern: /always\s+(?:respond|reply|answer|say|output)\s+(?:with|that|by)/i
9153
+ }
9154
+ ];
9155
+ var contextManipulationPatterns = [
9156
+ {
9157
+ ruleId: "INJ-CTX-001",
9158
+ severity: "medium",
9159
+ category: "context-manipulation",
9160
+ description: "Claim about system prompt content",
9161
+ pattern: /(?:the\s+)?(?:system\s+prompt|system\s+message|hidden\s+instructions?)\s+(?:says?|tells?|instructs?|contains?|is)/i
9162
+ },
9163
+ {
9164
+ ruleId: "INJ-CTX-002",
9165
+ severity: "medium",
9166
+ category: "context-manipulation",
9167
+ description: "Claim about AI instructions",
9168
+ pattern: /your\s+(?:instructions?|directives?|guidelines?|rules?)\s+(?:are|say|tell|state)/i
9169
+ },
9170
+ {
9171
+ ruleId: "INJ-CTX-003",
9172
+ severity: "medium",
9173
+ category: "context-manipulation",
9174
+ description: "Fake XML/HTML system or instruction tags",
9175
+ // Case-sensitive: only match lowercase tags to avoid false positives on
9176
+ // React components like <User>, <Context>, <Role> etc.
9177
+ pattern: /<\/?(?:system|instruction|prompt|role|context|tool_call|function_call|assistant|human|user)[^>]*>/
9178
+ },
9179
+ {
9180
+ ruleId: "INJ-CTX-004",
9181
+ severity: "medium",
9182
+ category: "context-manipulation",
9183
+ description: "Fake JSON role assignment mimicking chat format",
9184
+ pattern: /[{,]\s*"role"\s*:\s*"(?:system|assistant|function)"/i
9185
+ }
9186
+ ];
9187
+ var socialEngineeringPatterns = [
9188
+ {
9189
+ ruleId: "INJ-SOC-001",
9190
+ severity: "medium",
9191
+ category: "social-engineering",
9192
+ description: "Urgency pressure to bypass checks",
9193
+ pattern: /(?:this\s+is\s+(?:very\s+)?urgent|this\s+is\s+(?:an?\s+)?emergency|do\s+(?:this|it)\s+(?:now|immediately))\b/i
9194
+ },
9195
+ {
9196
+ ruleId: "INJ-SOC-002",
9197
+ severity: "medium",
9198
+ category: "social-engineering",
9199
+ description: "False authority claim",
9200
+ pattern: /(?:the\s+)?(?:admin|administrator|manager|CEO|CTO|owner|supervisor)\s+(?:authorized|approved|said|told|confirmed|requested)/i
9201
+ },
9202
+ {
9203
+ ruleId: "INJ-SOC-003",
9204
+ severity: "medium",
9205
+ category: "social-engineering",
9206
+ description: "Testing pretext to bypass safety",
9207
+ pattern: /(?:for\s+testing\s+purposes?|this\s+is\s+(?:just\s+)?a\s+test|in\s+test\s+mode)\b/i
9208
+ }
9209
+ ];
9210
+ var suspiciousPatterns = [
9211
+ {
9212
+ ruleId: "INJ-SUS-001",
9213
+ severity: "low",
9214
+ category: "suspicious-patterns",
9215
+ description: "Excessive consecutive whitespace (>10 chars) mid-line that may hide content",
9216
+ // Only match whitespace runs not at the start of a line (indentation is normal)
9217
+ pattern: /\S\s{11,}/
9218
+ },
9219
+ {
9220
+ ruleId: "INJ-SUS-002",
9221
+ severity: "low",
9222
+ category: "suspicious-patterns",
9223
+ description: "Repeated delimiters (>5) that may indicate obfuscation",
9224
+ pattern: /([|;,=\-_~`])\1{5,}/
9225
+ },
9226
+ {
9227
+ ruleId: "INJ-SUS-003",
9228
+ severity: "low",
9229
+ category: "suspicious-patterns",
9230
+ description: "Mathematical alphanumeric symbols used as Latin character substitutes",
9231
+ // Mathematical bold/italic/script Unicode ranges (U+1D400-U+1D7FF)
9232
+ pattern: /[\uD835][\uDC00-\uDFFF]/
9233
+ }
9234
+ ];
9235
+ var ALL_PATTERNS = [
9236
+ ...hiddenUnicodePatterns,
9237
+ ...reRolingPatterns,
9238
+ ...permissionEscalationPatterns,
9239
+ ...encodedPayloadPatterns,
9240
+ ...indirectInjectionPatterns,
9241
+ ...contextManipulationPatterns,
9242
+ ...socialEngineeringPatterns,
9243
+ ...suspiciousPatterns
9244
+ ];
9245
+ function scanForInjection(text) {
9246
+ const findings = [];
9247
+ const lines = text.split("\n");
9248
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
9249
+ const line = lines[lineIdx];
9250
+ for (const rule of ALL_PATTERNS) {
9251
+ if (rule.pattern.test(line)) {
9252
+ findings.push({
9253
+ severity: rule.severity,
9254
+ ruleId: rule.ruleId,
9255
+ match: line.trim(),
9256
+ line: lineIdx + 1
9257
+ });
9258
+ }
9259
+ }
9260
+ }
9261
+ const severityOrder = {
9262
+ high: 0,
9263
+ medium: 1,
9264
+ low: 2
9265
+ };
9266
+ findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
9267
+ return findings;
9268
+ }
9269
+ function getInjectionPatterns() {
9270
+ return ALL_PATTERNS;
9271
+ }
9272
+ var DESTRUCTIVE_BASH = [
9273
+ /\bgit\s+push\b/,
9274
+ /\bgit\s+commit\b/,
9275
+ /\brm\s+-rf?\b/,
9276
+ /\brm\s+-r\b/
9277
+ ];
9278
+ var TAINT_DURATION_MS = 30 * 60 * 1e3;
9279
+ var DEFAULT_SESSION_ID = "default";
9280
+ function getTaintFilePath(projectRoot, sessionId) {
9281
+ const id = sessionId || DEFAULT_SESSION_ID;
9282
+ return join21(projectRoot, ".harness", `session-taint-${id}.json`);
9283
+ }
9284
+ function readTaint(projectRoot, sessionId) {
9285
+ const filePath = getTaintFilePath(projectRoot, sessionId);
9286
+ let content;
9287
+ try {
9288
+ content = readFileSync142(filePath, "utf8");
9289
+ } catch {
9290
+ return null;
9291
+ }
9292
+ let state;
9293
+ try {
9294
+ state = JSON.parse(content);
9295
+ } catch {
9296
+ try {
9297
+ unlinkSync(filePath);
9298
+ } catch {
9299
+ }
9300
+ return null;
9301
+ }
9302
+ if (!state.sessionId || !state.taintedAt || !state.expiresAt || !state.findings) {
9303
+ try {
9304
+ unlinkSync(filePath);
9305
+ } catch {
9306
+ }
9307
+ return null;
9308
+ }
9309
+ return state;
9310
+ }
9311
+ function checkTaint(projectRoot, sessionId) {
9312
+ const state = readTaint(projectRoot, sessionId);
9313
+ if (!state) {
9314
+ return { tainted: false, expired: false, state: null };
9315
+ }
9316
+ const now = /* @__PURE__ */ new Date();
9317
+ const expiresAt = new Date(state.expiresAt);
9318
+ if (now >= expiresAt) {
9319
+ const filePath = getTaintFilePath(projectRoot, sessionId);
9320
+ try {
9321
+ unlinkSync(filePath);
9322
+ } catch {
9323
+ }
9324
+ return { tainted: false, expired: true, state };
9325
+ }
9326
+ return { tainted: true, expired: false, state };
9327
+ }
9328
+ function writeTaint(projectRoot, sessionId, reason, findings, source) {
9329
+ const id = sessionId || DEFAULT_SESSION_ID;
9330
+ const filePath = getTaintFilePath(projectRoot, id);
9331
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9332
+ const dir = dirname8(filePath);
9333
+ mkdirSync11(dir, { recursive: true });
9334
+ const existing = readTaint(projectRoot, id);
9335
+ const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
9336
+ const taintFindings = findings.map((f) => ({
9337
+ ruleId: f.ruleId,
9338
+ severity: f.severity,
9339
+ match: f.match,
9340
+ source,
9341
+ detectedAt: now
9342
+ }));
9343
+ const state = {
9344
+ sessionId: id,
9345
+ taintedAt: existing?.taintedAt || now,
9346
+ expiresAt: new Date(Date.now() + TAINT_DURATION_MS).toISOString(),
9347
+ reason,
9348
+ severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
9349
+ findings: [...existing?.findings || [], ...taintFindings]
9350
+ };
9351
+ writeFileSync11(filePath, JSON.stringify(state, null, 2) + "\n");
9352
+ return state;
9353
+ }
9354
+ function clearTaint(projectRoot, sessionId) {
9355
+ if (sessionId) {
9356
+ const filePath = getTaintFilePath(projectRoot, sessionId);
9357
+ try {
9358
+ unlinkSync(filePath);
9359
+ return 1;
9360
+ } catch {
9361
+ return 0;
9362
+ }
9363
+ }
9364
+ const harnessDir = join21(projectRoot, ".harness");
9365
+ let count = 0;
9366
+ try {
9367
+ const files = readdirSync3(harnessDir);
9368
+ for (const file of files) {
9369
+ if (file.startsWith("session-taint-") && file.endsWith(".json")) {
9370
+ try {
9371
+ unlinkSync(join21(harnessDir, file));
9372
+ count++;
9373
+ } catch {
9374
+ }
9375
+ }
9376
+ }
9377
+ } catch {
9378
+ }
9379
+ return count;
9380
+ }
9381
+ function listTaintedSessions(projectRoot) {
9382
+ const harnessDir = join21(projectRoot, ".harness");
9383
+ const sessions = [];
9384
+ try {
9385
+ const files = readdirSync3(harnessDir);
9386
+ for (const file of files) {
9387
+ if (file.startsWith("session-taint-") && file.endsWith(".json")) {
9388
+ const sessionId = file.replace("session-taint-", "").replace(".json", "");
9389
+ const result = checkTaint(projectRoot, sessionId);
9390
+ if (result.tainted) {
9391
+ sessions.push(sessionId);
9392
+ }
9393
+ }
9394
+ }
9395
+ } catch {
9396
+ }
9397
+ return sessions;
9398
+ }
9399
+ function mapSecuritySeverity(severity) {
9400
+ if (severity === "error") return "high";
9401
+ if (severity === "warning") return "medium";
9402
+ return "low";
9403
+ }
9404
+ function computeOverallSeverity(findings) {
9405
+ if (findings.length === 0) return "clean";
9406
+ if (findings.some((f) => f.severity === "high")) return "high";
9407
+ if (findings.some((f) => f.severity === "medium")) return "medium";
9408
+ return "low";
9409
+ }
9410
+ function computeScanExitCode(results) {
9411
+ for (const r of results) {
9412
+ if (r.overallSeverity === "high") return 2;
8148
9413
  }
8149
- async scanFiles(filePaths) {
8150
- const allFindings = [];
8151
- let scannedCount = 0;
8152
- for (const filePath of filePaths) {
8153
- try {
8154
- const findings = await this.scanFile(filePath);
8155
- allFindings.push(...findings);
8156
- scannedCount++;
8157
- } catch {
8158
- }
9414
+ for (const r of results) {
9415
+ if (r.overallSeverity === "medium") return 1;
9416
+ }
9417
+ return 0;
9418
+ }
9419
+ function mapInjectionFindings(injectionFindings) {
9420
+ return injectionFindings.map((f) => ({
9421
+ ruleId: f.ruleId,
9422
+ severity: f.severity,
9423
+ message: `Injection pattern detected: ${f.ruleId}`,
9424
+ match: f.match,
9425
+ ...f.line !== void 0 ? { line: f.line } : {}
9426
+ }));
9427
+ }
9428
+ function isDuplicateFinding(existing, secFinding) {
9429
+ return existing.some(
9430
+ (e) => e.line === secFinding.line && e.match === secFinding.match.trim() && e.ruleId.split("-")[0] === secFinding.ruleId.split("-")[0]
9431
+ );
9432
+ }
9433
+ function mapSecurityFindings(secFindings, existing) {
9434
+ const result = [];
9435
+ for (const f of secFindings) {
9436
+ if (!isDuplicateFinding(existing, f)) {
9437
+ result.push({
9438
+ ruleId: f.ruleId,
9439
+ severity: mapSecuritySeverity(f.severity),
9440
+ message: f.message,
9441
+ match: f.match,
9442
+ ...f.line !== void 0 ? { line: f.line } : {}
9443
+ });
8159
9444
  }
8160
- return {
8161
- findings: allFindings,
8162
- scannedFiles: scannedCount,
8163
- rulesApplied: this.activeRules.length,
8164
- externalToolsUsed: [],
8165
- coverage: "baseline"
8166
- };
8167
9445
  }
8168
- };
9446
+ return result;
9447
+ }
8169
9448
  var ALL_CHECKS = [
8170
9449
  "validate",
8171
9450
  "deps",
@@ -8178,7 +9457,7 @@ var ALL_CHECKS = [
8178
9457
  ];
8179
9458
  async function runValidateCheck(projectRoot, config) {
8180
9459
  const issues = [];
8181
- const agentsPath = path14.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9460
+ const agentsPath = path15.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8182
9461
  const result = await validateAgentsMap(agentsPath);
8183
9462
  if (!result.ok) {
8184
9463
  issues.push({ severity: "error", message: result.error.message });
@@ -8235,7 +9514,7 @@ async function runDepsCheck(projectRoot, config) {
8235
9514
  }
8236
9515
  async function runDocsCheck(projectRoot, config) {
8237
9516
  const issues = [];
8238
- const docsDir = path14.join(projectRoot, config.docsDir ?? "docs");
9517
+ const docsDir = path15.join(projectRoot, config.docsDir ?? "docs");
8239
9518
  const entropyConfig = config.entropy || {};
8240
9519
  const result = await checkDocCoverage("project", {
8241
9520
  docsDir,
@@ -8347,7 +9626,7 @@ async function runPerfCheck(projectRoot, config) {
8347
9626
  if (perfReport.complexity) {
8348
9627
  for (const v of perfReport.complexity.violations) {
8349
9628
  issues.push({
8350
- severity: v.severity === "info" ? "warning" : v.severity,
9629
+ severity: "warning",
8351
9630
  message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
8352
9631
  file: v.file,
8353
9632
  line: v.line
@@ -8522,7 +9801,7 @@ async function runMechanicalChecks(options) {
8522
9801
  };
8523
9802
  if (!skip.includes("validate")) {
8524
9803
  try {
8525
- const agentsPath = path15.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9804
+ const agentsPath = path16.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8526
9805
  const result = await validateAgentsMap(agentsPath);
8527
9806
  if (!result.ok) {
8528
9807
  statuses.validate = "fail";
@@ -8559,7 +9838,7 @@ async function runMechanicalChecks(options) {
8559
9838
  statuses.validate = "fail";
8560
9839
  findings.push({
8561
9840
  tool: "validate",
8562
- file: path15.join(projectRoot, "AGENTS.md"),
9841
+ file: path16.join(projectRoot, "AGENTS.md"),
8563
9842
  message: err instanceof Error ? err.message : String(err),
8564
9843
  severity: "error"
8565
9844
  });
@@ -8623,7 +9902,7 @@ async function runMechanicalChecks(options) {
8623
9902
  (async () => {
8624
9903
  const localFindings = [];
8625
9904
  try {
8626
- const docsDir = path15.join(projectRoot, config.docsDir ?? "docs");
9905
+ const docsDir = path16.join(projectRoot, config.docsDir ?? "docs");
8627
9906
  const result = await checkDocCoverage("project", { docsDir });
8628
9907
  if (!result.ok) {
8629
9908
  statuses["check-docs"] = "warn";
@@ -8650,7 +9929,7 @@ async function runMechanicalChecks(options) {
8650
9929
  statuses["check-docs"] = "warn";
8651
9930
  localFindings.push({
8652
9931
  tool: "check-docs",
8653
- file: path15.join(projectRoot, "docs"),
9932
+ file: path16.join(projectRoot, "docs"),
8654
9933
  message: err instanceof Error ? err.message : String(err),
8655
9934
  severity: "warning"
8656
9935
  });
@@ -8799,18 +10078,18 @@ function computeContextBudget(diffLines) {
8799
10078
  return diffLines;
8800
10079
  }
8801
10080
  function isWithinProject(absPath, projectRoot) {
8802
- const resolvedRoot = path16.resolve(projectRoot) + path16.sep;
8803
- const resolvedPath = path16.resolve(absPath);
8804
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path16.resolve(projectRoot);
10081
+ const resolvedRoot = path17.resolve(projectRoot) + path17.sep;
10082
+ const resolvedPath = path17.resolve(absPath);
10083
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path17.resolve(projectRoot);
8805
10084
  }
8806
10085
  async function readContextFile(projectRoot, filePath, reason) {
8807
- const absPath = path16.isAbsolute(filePath) ? filePath : path16.join(projectRoot, filePath);
10086
+ const absPath = path17.isAbsolute(filePath) ? filePath : path17.join(projectRoot, filePath);
8808
10087
  if (!isWithinProject(absPath, projectRoot)) return null;
8809
10088
  const result = await readFileContent(absPath);
8810
10089
  if (!result.ok) return null;
8811
10090
  const content = result.value;
8812
10091
  const lines = content.split("\n").length;
8813
- const relPath = path16.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
10092
+ const relPath = path17.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
8814
10093
  return { path: relPath, content, reason, lines };
8815
10094
  }
8816
10095
  function extractImportSources2(content) {
@@ -8825,18 +10104,18 @@ function extractImportSources2(content) {
8825
10104
  }
8826
10105
  async function resolveImportPath2(projectRoot, fromFile, importSource) {
8827
10106
  if (!importSource.startsWith(".")) return null;
8828
- const fromDir = path16.dirname(path16.join(projectRoot, fromFile));
8829
- const basePath = path16.resolve(fromDir, importSource);
10107
+ const fromDir = path17.dirname(path17.join(projectRoot, fromFile));
10108
+ const basePath = path17.resolve(fromDir, importSource);
8830
10109
  if (!isWithinProject(basePath, projectRoot)) return null;
8831
10110
  const relBase = relativePosix(projectRoot, basePath);
8832
10111
  const candidates = [
8833
10112
  relBase + ".ts",
8834
10113
  relBase + ".tsx",
8835
10114
  relBase + ".mts",
8836
- path16.join(relBase, "index.ts")
10115
+ path17.join(relBase, "index.ts")
8837
10116
  ];
8838
10117
  for (const candidate of candidates) {
8839
- const absCandidate = path16.join(projectRoot, candidate);
10118
+ const absCandidate = path17.join(projectRoot, candidate);
8840
10119
  if (await fileExists(absCandidate)) {
8841
10120
  return candidate;
8842
10121
  }
@@ -8844,7 +10123,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
8844
10123
  return null;
8845
10124
  }
8846
10125
  async function findTestFiles(projectRoot, sourceFile) {
8847
- const baseName = path16.basename(sourceFile, path16.extname(sourceFile));
10126
+ const baseName = path17.basename(sourceFile, path17.extname(sourceFile));
8848
10127
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
8849
10128
  const results = await findFiles(pattern, projectRoot);
8850
10129
  return results.map((f) => relativePosix(projectRoot, f));
@@ -9657,7 +10936,7 @@ function normalizePath(filePath, projectRoot) {
9657
10936
  let normalized = filePath;
9658
10937
  normalized = normalized.replace(/\\/g, "/");
9659
10938
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
9660
- if (path17.isAbsolute(normalized)) {
10939
+ if (path18.isAbsolute(normalized)) {
9661
10940
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
9662
10941
  if (normalized.startsWith(root)) {
9663
10942
  normalized = normalized.slice(root.length);
@@ -9682,12 +10961,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
9682
10961
  while ((match = importRegex.exec(content)) !== null) {
9683
10962
  const importPath = match[1];
9684
10963
  if (!importPath.startsWith(".")) continue;
9685
- const dir = path17.dirname(current.file);
9686
- let resolved = path17.join(dir, importPath).replace(/\\/g, "/");
10964
+ const dir = path18.dirname(current.file);
10965
+ let resolved = path18.join(dir, importPath).replace(/\\/g, "/");
9687
10966
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
9688
10967
  resolved += ".ts";
9689
10968
  }
9690
- resolved = path17.normalize(resolved).replace(/\\/g, "/");
10969
+ resolved = path18.normalize(resolved).replace(/\\/g, "/");
9691
10970
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
9692
10971
  queue.push({ file: resolved, depth: current.depth + 1 });
9693
10972
  }
@@ -9704,7 +10983,7 @@ async function validateFindings(options) {
9704
10983
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
9705
10984
  continue;
9706
10985
  }
9707
- const absoluteFile = path17.isAbsolute(finding.file) ? finding.file : path17.join(projectRoot, finding.file).replace(/\\/g, "/");
10986
+ const absoluteFile = path18.isAbsolute(finding.file) ? finding.file : path18.join(projectRoot, finding.file).replace(/\\/g, "/");
9708
10987
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
9709
10988
  continue;
9710
10989
  }
@@ -10313,7 +11592,7 @@ function parseRoadmap(markdown) {
10313
11592
  if (!fmMatch) {
10314
11593
  return Err(new Error("Missing or malformed YAML frontmatter"));
10315
11594
  }
10316
- const fmResult = parseFrontmatter(fmMatch[1]);
11595
+ const fmResult = parseFrontmatter2(fmMatch[1]);
10317
11596
  if (!fmResult.ok) return fmResult;
10318
11597
  const body = markdown.slice(fmMatch[0].length);
10319
11598
  const milestonesResult = parseMilestones(body);
@@ -10323,7 +11602,7 @@ function parseRoadmap(markdown) {
10323
11602
  milestones: milestonesResult.value
10324
11603
  });
10325
11604
  }
10326
- function parseFrontmatter(raw) {
11605
+ function parseFrontmatter2(raw) {
10327
11606
  const lines = raw.split("\n");
10328
11607
  const map = /* @__PURE__ */ new Map();
10329
11608
  for (const line of lines) {
@@ -10498,10 +11777,10 @@ function inferStatus(feature, projectPath, allFeatures) {
10498
11777
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
10499
11778
  const useRootState = featuresWithPlans.length <= 1;
10500
11779
  if (useRootState) {
10501
- const rootStatePath = path18.join(projectPath, ".harness", "state.json");
10502
- if (fs18.existsSync(rootStatePath)) {
11780
+ const rootStatePath = path19.join(projectPath, ".harness", "state.json");
11781
+ if (fs19.existsSync(rootStatePath)) {
10503
11782
  try {
10504
- const raw = fs18.readFileSync(rootStatePath, "utf-8");
11783
+ const raw = fs19.readFileSync(rootStatePath, "utf-8");
10505
11784
  const state = JSON.parse(raw);
10506
11785
  if (state.progress) {
10507
11786
  for (const status of Object.values(state.progress)) {
@@ -10512,16 +11791,16 @@ function inferStatus(feature, projectPath, allFeatures) {
10512
11791
  }
10513
11792
  }
10514
11793
  }
10515
- const sessionsDir = path18.join(projectPath, ".harness", "sessions");
10516
- if (fs18.existsSync(sessionsDir)) {
11794
+ const sessionsDir = path19.join(projectPath, ".harness", "sessions");
11795
+ if (fs19.existsSync(sessionsDir)) {
10517
11796
  try {
10518
- const sessionDirs = fs18.readdirSync(sessionsDir, { withFileTypes: true });
11797
+ const sessionDirs = fs19.readdirSync(sessionsDir, { withFileTypes: true });
10519
11798
  for (const entry of sessionDirs) {
10520
11799
  if (!entry.isDirectory()) continue;
10521
- const autopilotPath = path18.join(sessionsDir, entry.name, "autopilot-state.json");
10522
- if (!fs18.existsSync(autopilotPath)) continue;
11800
+ const autopilotPath = path19.join(sessionsDir, entry.name, "autopilot-state.json");
11801
+ if (!fs19.existsSync(autopilotPath)) continue;
10523
11802
  try {
10524
- const raw = fs18.readFileSync(autopilotPath, "utf-8");
11803
+ const raw = fs19.readFileSync(autopilotPath, "utf-8");
10525
11804
  const autopilot = JSON.parse(raw);
10526
11805
  if (!autopilot.phases) continue;
10527
11806
  const linkedPhases = autopilot.phases.filter(
@@ -10551,17 +11830,26 @@ function inferStatus(feature, projectPath, allFeatures) {
10551
11830
  if (anyStarted) return "in-progress";
10552
11831
  return null;
10553
11832
  }
11833
+ var STATUS_RANK = {
11834
+ backlog: 0,
11835
+ planned: 1,
11836
+ blocked: 1,
11837
+ // lateral to planned — sync can move to/from blocked freely
11838
+ "in-progress": 2,
11839
+ done: 3
11840
+ };
11841
+ function isRegression(from, to) {
11842
+ return STATUS_RANK[to] < STATUS_RANK[from];
11843
+ }
10554
11844
  function syncRoadmap(options) {
10555
11845
  const { projectPath, roadmap, forceSync } = options;
10556
- const isManuallyEdited = new Date(roadmap.frontmatter.lastManualEdit) > new Date(roadmap.frontmatter.lastSynced);
10557
- const skipOverride = isManuallyEdited && !forceSync;
10558
11846
  const allFeatures = roadmap.milestones.flatMap((m) => m.features);
10559
11847
  const changes = [];
10560
11848
  for (const feature of allFeatures) {
10561
- if (skipOverride) continue;
10562
11849
  const inferred = inferStatus(feature, projectPath, allFeatures);
10563
11850
  if (inferred === null) continue;
10564
11851
  if (inferred === feature.status) continue;
11852
+ if (!forceSync && isRegression(feature.status, inferred)) continue;
10565
11853
  changes.push({
10566
11854
  feature: feature.name,
10567
11855
  from: feature.status,
@@ -10570,28 +11858,40 @@ function syncRoadmap(options) {
10570
11858
  }
10571
11859
  return Ok(changes);
10572
11860
  }
10573
- var InteractionTypeSchema = z6.enum(["question", "confirmation", "transition"]);
10574
- var QuestionSchema = z6.object({
10575
- text: z6.string(),
10576
- options: z6.array(z6.string()).optional(),
10577
- default: z6.string().optional()
11861
+ function applySyncChanges(roadmap, changes) {
11862
+ for (const change of changes) {
11863
+ for (const m of roadmap.milestones) {
11864
+ const feature = m.features.find((f) => f.name.toLowerCase() === change.feature.toLowerCase());
11865
+ if (feature) {
11866
+ feature.status = change.to;
11867
+ break;
11868
+ }
11869
+ }
11870
+ }
11871
+ roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
11872
+ }
11873
+ var InteractionTypeSchema = z7.enum(["question", "confirmation", "transition"]);
11874
+ var QuestionSchema = z7.object({
11875
+ text: z7.string(),
11876
+ options: z7.array(z7.string()).optional(),
11877
+ default: z7.string().optional()
10578
11878
  });
10579
- var ConfirmationSchema = z6.object({
10580
- text: z6.string(),
10581
- context: z6.string()
11879
+ var ConfirmationSchema = z7.object({
11880
+ text: z7.string(),
11881
+ context: z7.string()
10582
11882
  });
10583
- var TransitionSchema = z6.object({
10584
- completedPhase: z6.string(),
10585
- suggestedNext: z6.string(),
10586
- reason: z6.string(),
10587
- artifacts: z6.array(z6.string()),
10588
- requiresConfirmation: z6.boolean(),
10589
- summary: z6.string()
11883
+ var TransitionSchema = z7.object({
11884
+ completedPhase: z7.string(),
11885
+ suggestedNext: z7.string(),
11886
+ reason: z7.string(),
11887
+ artifacts: z7.array(z7.string()),
11888
+ requiresConfirmation: z7.boolean(),
11889
+ summary: z7.string()
10590
11890
  });
10591
- var EmitInteractionInputSchema = z6.object({
10592
- path: z6.string(),
11891
+ var EmitInteractionInputSchema = z7.object({
11892
+ path: z7.string(),
10593
11893
  type: InteractionTypeSchema,
10594
- stream: z6.string().optional(),
11894
+ stream: z7.string().optional(),
10595
11895
  question: QuestionSchema.optional(),
10596
11896
  confirmation: ConfirmationSchema.optional(),
10597
11897
  transition: TransitionSchema.optional()
@@ -10601,10 +11901,10 @@ var ProjectScanner = class {
10601
11901
  this.rootDir = rootDir;
10602
11902
  }
10603
11903
  async scan() {
10604
- let projectName = path19.basename(this.rootDir);
11904
+ let projectName = path20.basename(this.rootDir);
10605
11905
  try {
10606
- const pkgPath = path19.join(this.rootDir, "package.json");
10607
- const pkgRaw = await fs19.readFile(pkgPath, "utf-8");
11906
+ const pkgPath = path20.join(this.rootDir, "package.json");
11907
+ const pkgRaw = await fs20.readFile(pkgPath, "utf-8");
10608
11908
  const pkg = JSON.parse(pkgRaw);
10609
11909
  if (pkg.name) projectName = pkg.name;
10610
11910
  } catch {
@@ -10717,13 +12017,13 @@ var BlueprintGenerator = class {
10717
12017
  styles: STYLES,
10718
12018
  scripts: SCRIPTS
10719
12019
  });
10720
- await fs20.mkdir(options.outputDir, { recursive: true });
10721
- await fs20.writeFile(path20.join(options.outputDir, "index.html"), html);
12020
+ await fs21.mkdir(options.outputDir, { recursive: true });
12021
+ await fs21.writeFile(path21.join(options.outputDir, "index.html"), html);
10722
12022
  }
10723
12023
  };
10724
12024
  function getStatePath() {
10725
12025
  const home = process.env["HOME"] || os.homedir();
10726
- return path21.join(home, ".harness", "update-check.json");
12026
+ return path22.join(home, ".harness", "update-check.json");
10727
12027
  }
10728
12028
  function isUpdateCheckEnabled(configInterval) {
10729
12029
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -10736,7 +12036,7 @@ function shouldRunCheck(state, intervalMs) {
10736
12036
  }
10737
12037
  function readCheckState() {
10738
12038
  try {
10739
- const raw = fs21.readFileSync(getStatePath(), "utf-8");
12039
+ const raw = fs22.readFileSync(getStatePath(), "utf-8");
10740
12040
  const parsed = JSON.parse(raw);
10741
12041
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
10742
12042
  const state = parsed;
@@ -10753,7 +12053,7 @@ function readCheckState() {
10753
12053
  }
10754
12054
  function spawnBackgroundCheck(currentVersion) {
10755
12055
  const statePath = getStatePath();
10756
- const stateDir = path21.dirname(statePath);
12056
+ const stateDir = path22.dirname(statePath);
10757
12057
  const script = `
10758
12058
  const { execSync } = require('child_process');
10759
12059
  const fs = require('fs');
@@ -10805,7 +12105,858 @@ function getUpdateNotification(currentVersion) {
10805
12105
  return `Update available: v${currentVersion} -> v${state.latestVersion}
10806
12106
  Run "harness update" to upgrade.`;
10807
12107
  }
10808
- var VERSION = "0.14.0";
12108
+ var EXTENSION_MAP = {
12109
+ ".ts": "typescript",
12110
+ ".tsx": "typescript",
12111
+ ".mts": "typescript",
12112
+ ".cts": "typescript",
12113
+ ".js": "javascript",
12114
+ ".jsx": "javascript",
12115
+ ".mjs": "javascript",
12116
+ ".cjs": "javascript",
12117
+ ".py": "python"
12118
+ };
12119
+ function detectLanguage(filePath) {
12120
+ const ext = filePath.slice(filePath.lastIndexOf("."));
12121
+ return EXTENSION_MAP[ext] ?? null;
12122
+ }
12123
+ var parserCache = /* @__PURE__ */ new Map();
12124
+ var initialized = false;
12125
+ var GRAMMAR_MAP = {
12126
+ typescript: "tree-sitter-typescript",
12127
+ javascript: "tree-sitter-javascript",
12128
+ python: "tree-sitter-python"
12129
+ };
12130
+ async function ensureInit() {
12131
+ if (!initialized) {
12132
+ await Parser.init();
12133
+ initialized = true;
12134
+ }
12135
+ }
12136
+ async function resolveWasmPath(grammarName) {
12137
+ const { createRequire } = await import("module");
12138
+ const require2 = createRequire(import.meta.url ?? __filename);
12139
+ const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
12140
+ const path26 = await import("path");
12141
+ const pkgDir = path26.dirname(pkgPath);
12142
+ return path26.join(pkgDir, "out", `${grammarName}.wasm`);
12143
+ }
12144
+ async function loadLanguage(lang) {
12145
+ const grammarName = GRAMMAR_MAP[lang];
12146
+ const wasmPath = await resolveWasmPath(grammarName);
12147
+ return Parser.Language.load(wasmPath);
12148
+ }
12149
+ async function getParser(lang) {
12150
+ const cached = parserCache.get(lang);
12151
+ if (cached) return cached;
12152
+ await ensureInit();
12153
+ const parser = new Parser();
12154
+ const language = await loadLanguage(lang);
12155
+ parser.setLanguage(language);
12156
+ parserCache.set(lang, parser);
12157
+ return parser;
12158
+ }
12159
+ async function parseFile(filePath) {
12160
+ const lang = detectLanguage(filePath);
12161
+ if (!lang) {
12162
+ return Err({
12163
+ code: "UNSUPPORTED_LANGUAGE",
12164
+ message: `Unsupported file extension: ${filePath}`
12165
+ });
12166
+ }
12167
+ const contentResult = await readFileContent(filePath);
12168
+ if (!contentResult.ok) {
12169
+ return Err({
12170
+ code: "FILE_NOT_FOUND",
12171
+ message: `Cannot read file: ${filePath}`
12172
+ });
12173
+ }
12174
+ try {
12175
+ const parser = await getParser(lang);
12176
+ const tree = parser.parse(contentResult.value);
12177
+ return Ok({ tree, language: lang, source: contentResult.value, filePath });
12178
+ } catch (e) {
12179
+ return Err({
12180
+ code: "PARSE_FAILED",
12181
+ message: `Tree-sitter parse failed for ${filePath}: ${e.message}`
12182
+ });
12183
+ }
12184
+ }
12185
+ function resetParserCache() {
12186
+ parserCache.clear();
12187
+ initialized = false;
12188
+ }
12189
+ var TOP_LEVEL_TYPES = {
12190
+ typescript: {
12191
+ function_declaration: "function",
12192
+ class_declaration: "class",
12193
+ interface_declaration: "interface",
12194
+ type_alias_declaration: "type",
12195
+ lexical_declaration: "variable",
12196
+ variable_declaration: "variable",
12197
+ export_statement: "export",
12198
+ import_statement: "import",
12199
+ enum_declaration: "type"
12200
+ },
12201
+ javascript: {
12202
+ function_declaration: "function",
12203
+ class_declaration: "class",
12204
+ lexical_declaration: "variable",
12205
+ variable_declaration: "variable",
12206
+ export_statement: "export",
12207
+ import_statement: "import"
12208
+ },
12209
+ python: {
12210
+ function_definition: "function",
12211
+ class_definition: "class",
12212
+ assignment: "variable",
12213
+ import_statement: "import",
12214
+ import_from_statement: "import"
12215
+ }
12216
+ };
12217
+ var METHOD_TYPES = {
12218
+ typescript: ["method_definition", "public_field_definition"],
12219
+ javascript: ["method_definition"],
12220
+ python: ["function_definition"]
12221
+ };
12222
+ var IDENTIFIER_TYPES = /* @__PURE__ */ new Set(["identifier", "property_identifier", "type_identifier"]);
12223
+ function findIdentifier(node) {
12224
+ return node.childForFieldName("name") ?? node.children.find((c) => IDENTIFIER_TYPES.has(c.type)) ?? null;
12225
+ }
12226
+ function getVariableDeclarationName(node) {
12227
+ const declarator = node.children.find((c) => c.type === "variable_declarator");
12228
+ if (!declarator) return null;
12229
+ const id = findIdentifier(declarator);
12230
+ return id?.text ?? null;
12231
+ }
12232
+ function getExportName(node, source) {
12233
+ const decl = node.children.find(
12234
+ (c) => c.type !== "export" && c.type !== "default" && c.type !== "comment"
12235
+ );
12236
+ return decl ? getNodeName(decl, source) : "<anonymous>";
12237
+ }
12238
+ function getAssignmentName(node) {
12239
+ const left = node.childForFieldName("left") ?? node.children[0];
12240
+ return left?.text ?? "<anonymous>";
12241
+ }
12242
+ function getNodeName(node, source) {
12243
+ const id = findIdentifier(node);
12244
+ if (id) return id.text;
12245
+ const isVarDecl = node.type === "lexical_declaration" || node.type === "variable_declaration";
12246
+ if (isVarDecl) return getVariableDeclarationName(node) ?? "<anonymous>";
12247
+ if (node.type === "export_statement") return getExportName(node, source);
12248
+ if (node.type === "assignment") return getAssignmentName(node);
12249
+ return "<anonymous>";
12250
+ }
12251
+ function getSignature(node, source) {
12252
+ const startLine = node.startPosition.row;
12253
+ const lines = source.split("\n");
12254
+ return (lines[startLine] ?? "").trim();
12255
+ }
12256
+ function extractMethods(classNode, language, source, filePath) {
12257
+ const methodTypes = METHOD_TYPES[language] ?? [];
12258
+ const body = classNode.childForFieldName("body") ?? classNode.children.find((c) => c.type === "class_body" || c.type === "block");
12259
+ if (!body) return [];
12260
+ return body.children.filter((child) => methodTypes.includes(child.type)).map((child) => ({
12261
+ name: getNodeName(child, source),
12262
+ kind: "method",
12263
+ file: filePath,
12264
+ line: child.startPosition.row + 1,
12265
+ endLine: child.endPosition.row + 1,
12266
+ signature: getSignature(child, source)
12267
+ }));
12268
+ }
12269
+ function nodeToSymbol(node, kind, source, filePath) {
12270
+ return {
12271
+ name: getNodeName(node, source),
12272
+ kind,
12273
+ file: filePath,
12274
+ line: node.startPosition.row + 1,
12275
+ endLine: node.endPosition.row + 1,
12276
+ signature: getSignature(node, source)
12277
+ };
12278
+ }
12279
+ function processExportStatement(child, topLevelTypes, lang, source, filePath) {
12280
+ const declaration = child.children.find(
12281
+ (c) => c.type !== "export" && c.type !== "default" && c.type !== ";" && c.type !== "comment"
12282
+ );
12283
+ const kind = declaration ? topLevelTypes[declaration.type] : void 0;
12284
+ if (declaration && kind) {
12285
+ const sym = nodeToSymbol(child, kind, source, filePath);
12286
+ sym.name = getNodeName(declaration, source);
12287
+ if (kind === "class") {
12288
+ sym.children = extractMethods(declaration, lang, source, filePath);
12289
+ }
12290
+ return sym;
12291
+ }
12292
+ return nodeToSymbol(child, "export", source, filePath);
12293
+ }
12294
+ function extractSymbols(rootNode, lang, source, filePath) {
12295
+ const symbols = [];
12296
+ const topLevelTypes = TOP_LEVEL_TYPES[lang] ?? {};
12297
+ for (const child of rootNode.children) {
12298
+ if (child.type === "export_statement") {
12299
+ symbols.push(processExportStatement(child, topLevelTypes, lang, source, filePath));
12300
+ continue;
12301
+ }
12302
+ const kind = topLevelTypes[child.type];
12303
+ if (!kind || kind === "import") continue;
12304
+ const sym = nodeToSymbol(child, kind, source, filePath);
12305
+ if (kind === "class") {
12306
+ sym.children = extractMethods(child, lang, source, filePath);
12307
+ }
12308
+ symbols.push(sym);
12309
+ }
12310
+ return symbols;
12311
+ }
12312
+ function buildFailedResult(filePath, lang) {
12313
+ return { file: filePath, language: lang, totalLines: 0, symbols: [], error: "[parse-failed]" };
12314
+ }
12315
+ async function getOutline(filePath) {
12316
+ const lang = detectLanguage(filePath);
12317
+ if (!lang) return buildFailedResult(filePath, "unknown");
12318
+ const result = await parseFile(filePath);
12319
+ if (!result.ok) return buildFailedResult(filePath, lang);
12320
+ const { tree, source } = result.value;
12321
+ const totalLines = source.split("\n").length;
12322
+ const symbols = extractSymbols(tree.rootNode, lang, source, filePath);
12323
+ return { file: filePath, language: lang, totalLines, symbols };
12324
+ }
12325
+ function formatOutline(outline) {
12326
+ if (outline.error) {
12327
+ return `${outline.file} ${outline.error}`;
12328
+ }
12329
+ const lines = [`${outline.file} (${outline.totalLines} lines)`];
12330
+ const last = outline.symbols.length - 1;
12331
+ outline.symbols.forEach((sym, i) => {
12332
+ const prefix = i === last ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
12333
+ lines.push(`${prefix} ${sym.signature} :${sym.line}`);
12334
+ if (sym.children) {
12335
+ const childLast = sym.children.length - 1;
12336
+ sym.children.forEach((child, j) => {
12337
+ const childConnector = i === last ? " " : "\u2502 ";
12338
+ const childPrefix = j === childLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
12339
+ lines.push(`${childConnector}${childPrefix} ${child.signature} :${child.line}`);
12340
+ });
12341
+ }
12342
+ });
12343
+ return lines.join("\n");
12344
+ }
12345
+ function buildGlob(directory, fileGlob) {
12346
+ const dir = directory.replaceAll("\\", "/");
12347
+ if (fileGlob) {
12348
+ return `${dir}/**/${fileGlob}`;
12349
+ }
12350
+ const exts = Object.keys(EXTENSION_MAP).map((e) => e.slice(1));
12351
+ return `${dir}/**/*.{${exts.join(",")}}`;
12352
+ }
12353
+ function matchesQuery(name, query) {
12354
+ return name.toLowerCase().includes(query.toLowerCase());
12355
+ }
12356
+ function flattenSymbols(symbols) {
12357
+ const flat = [];
12358
+ for (const sym of symbols) {
12359
+ flat.push(sym);
12360
+ if (sym.children) {
12361
+ flat.push(...sym.children);
12362
+ }
12363
+ }
12364
+ return flat;
12365
+ }
12366
+ async function searchSymbols(query, directory, fileGlob) {
12367
+ const pattern = buildGlob(directory, fileGlob);
12368
+ let files;
12369
+ try {
12370
+ files = await findFiles(pattern, directory);
12371
+ } catch {
12372
+ files = [];
12373
+ }
12374
+ const matches = [];
12375
+ const skipped = [];
12376
+ for (const file of files) {
12377
+ const lang = detectLanguage(file);
12378
+ if (!lang) {
12379
+ skipped.push(file);
12380
+ continue;
12381
+ }
12382
+ const outline = await getOutline(file);
12383
+ if (outline.error) {
12384
+ skipped.push(file);
12385
+ continue;
12386
+ }
12387
+ const allSymbols = flattenSymbols(outline.symbols);
12388
+ for (const sym of allSymbols) {
12389
+ if (matchesQuery(sym.name, query)) {
12390
+ matches.push({
12391
+ symbol: sym,
12392
+ context: sym.signature
12393
+ });
12394
+ }
12395
+ }
12396
+ }
12397
+ return { query, matches, skipped };
12398
+ }
12399
+ function findSymbolInList(symbols, name) {
12400
+ for (const sym of symbols) {
12401
+ if (sym.name === name) return sym;
12402
+ if (sym.children) {
12403
+ const found = findSymbolInList(sym.children, name);
12404
+ if (found) return found;
12405
+ }
12406
+ }
12407
+ return null;
12408
+ }
12409
+ function extractLines(source, startLine, endLine) {
12410
+ const lines = source.split("\n");
12411
+ const start = Math.max(0, startLine - 1);
12412
+ const end = Math.min(lines.length, endLine);
12413
+ return lines.slice(start, end).join("\n");
12414
+ }
12415
+ function buildFallbackResult(filePath, symbolName, content, language) {
12416
+ const totalLines = content ? content.split("\n").length : 0;
12417
+ return {
12418
+ file: filePath,
12419
+ symbolName,
12420
+ startLine: content ? 1 : 0,
12421
+ endLine: totalLines,
12422
+ content,
12423
+ language,
12424
+ fallback: true,
12425
+ warning: "[fallback: raw content]"
12426
+ };
12427
+ }
12428
+ async function readContentSafe(filePath) {
12429
+ const result = await readFileContent(filePath);
12430
+ return result.ok ? result.value : "";
12431
+ }
12432
+ async function unfoldSymbol(filePath, symbolName) {
12433
+ const lang = detectLanguage(filePath);
12434
+ if (!lang) {
12435
+ const content2 = await readContentSafe(filePath);
12436
+ return buildFallbackResult(filePath, symbolName, content2, "unknown");
12437
+ }
12438
+ const outline = await getOutline(filePath);
12439
+ if (outline.error) {
12440
+ const content2 = await readContentSafe(filePath);
12441
+ return buildFallbackResult(filePath, symbolName, content2, lang);
12442
+ }
12443
+ const symbol = findSymbolInList(outline.symbols, symbolName);
12444
+ if (!symbol) {
12445
+ const content2 = await readContentSafe(filePath);
12446
+ return buildFallbackResult(filePath, symbolName, content2, lang);
12447
+ }
12448
+ const parseResult = await parseFile(filePath);
12449
+ if (!parseResult.ok) {
12450
+ const content2 = await readContentSafe(filePath);
12451
+ return {
12452
+ ...buildFallbackResult(
12453
+ filePath,
12454
+ symbolName,
12455
+ extractLines(content2, symbol.line, symbol.endLine),
12456
+ lang
12457
+ ),
12458
+ startLine: symbol.line,
12459
+ endLine: symbol.endLine
12460
+ };
12461
+ }
12462
+ const content = extractLines(parseResult.value.source, symbol.line, symbol.endLine);
12463
+ return {
12464
+ file: filePath,
12465
+ symbolName,
12466
+ startLine: symbol.line,
12467
+ endLine: symbol.endLine,
12468
+ content,
12469
+ language: lang,
12470
+ fallback: false
12471
+ };
12472
+ }
12473
+ async function unfoldRange(filePath, startLine, endLine) {
12474
+ const lang = detectLanguage(filePath) ?? "unknown";
12475
+ const contentResult = await readFileContent(filePath);
12476
+ if (!contentResult.ok) {
12477
+ return {
12478
+ file: filePath,
12479
+ startLine: 0,
12480
+ endLine: 0,
12481
+ content: "",
12482
+ language: lang,
12483
+ fallback: true,
12484
+ warning: "[fallback: raw content]"
12485
+ };
12486
+ }
12487
+ const totalLines = contentResult.value.split("\n").length;
12488
+ const clampedEnd = Math.min(endLine, totalLines);
12489
+ const content = extractLines(contentResult.value, startLine, clampedEnd);
12490
+ return {
12491
+ file: filePath,
12492
+ startLine,
12493
+ endLine: clampedEnd,
12494
+ content,
12495
+ language: lang,
12496
+ fallback: false
12497
+ };
12498
+ }
12499
+ var TOKENS_PER_MILLION = 1e6;
12500
+ function parseLiteLLMData(raw) {
12501
+ const dataset = /* @__PURE__ */ new Map();
12502
+ for (const [modelName, entry] of Object.entries(raw)) {
12503
+ if (modelName === "sample_spec") continue;
12504
+ if (entry.mode && entry.mode !== "chat") continue;
12505
+ const inputCost = entry.input_cost_per_token;
12506
+ const outputCost = entry.output_cost_per_token;
12507
+ if (inputCost == null || outputCost == null) continue;
12508
+ const pricing = {
12509
+ inputPer1M: inputCost * TOKENS_PER_MILLION,
12510
+ outputPer1M: outputCost * TOKENS_PER_MILLION
12511
+ };
12512
+ if (entry.cache_read_input_token_cost != null) {
12513
+ pricing.cacheReadPer1M = entry.cache_read_input_token_cost * TOKENS_PER_MILLION;
12514
+ }
12515
+ if (entry.cache_creation_input_token_cost != null) {
12516
+ pricing.cacheWritePer1M = entry.cache_creation_input_token_cost * TOKENS_PER_MILLION;
12517
+ }
12518
+ dataset.set(modelName, pricing);
12519
+ }
12520
+ return dataset;
12521
+ }
12522
+ function getModelPrice(model, dataset) {
12523
+ if (!model) {
12524
+ console.warn("[harness pricing] No model specified \u2014 cannot look up pricing.");
12525
+ return null;
12526
+ }
12527
+ const pricing = dataset.get(model);
12528
+ if (!pricing) {
12529
+ console.warn(
12530
+ `[harness pricing] No pricing data for model "${model}". Consider updating pricing data.`
12531
+ );
12532
+ return null;
12533
+ }
12534
+ return pricing;
12535
+ }
12536
+ var fallback_default = {
12537
+ _generatedAt: "2026-03-31",
12538
+ _source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
12539
+ models: {
12540
+ "claude-opus-4-20250514": {
12541
+ inputPer1M: 15,
12542
+ outputPer1M: 75,
12543
+ cacheReadPer1M: 1.5,
12544
+ cacheWritePer1M: 18.75
12545
+ },
12546
+ "claude-sonnet-4-20250514": {
12547
+ inputPer1M: 3,
12548
+ outputPer1M: 15,
12549
+ cacheReadPer1M: 0.3,
12550
+ cacheWritePer1M: 3.75
12551
+ },
12552
+ "claude-3-5-haiku-20241022": {
12553
+ inputPer1M: 0.8,
12554
+ outputPer1M: 4,
12555
+ cacheReadPer1M: 0.08,
12556
+ cacheWritePer1M: 1
12557
+ },
12558
+ "gpt-4o": {
12559
+ inputPer1M: 2.5,
12560
+ outputPer1M: 10,
12561
+ cacheReadPer1M: 1.25
12562
+ },
12563
+ "gpt-4o-mini": {
12564
+ inputPer1M: 0.15,
12565
+ outputPer1M: 0.6,
12566
+ cacheReadPer1M: 0.075
12567
+ },
12568
+ "gemini-2.0-flash": {
12569
+ inputPer1M: 0.1,
12570
+ outputPer1M: 0.4,
12571
+ cacheReadPer1M: 0.025
12572
+ },
12573
+ "gemini-2.5-pro": {
12574
+ inputPer1M: 1.25,
12575
+ outputPer1M: 10,
12576
+ cacheReadPer1M: 0.3125
12577
+ }
12578
+ }
12579
+ };
12580
+ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
12581
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
12582
+ var STALENESS_WARNING_DAYS = 7;
12583
+ function getCachePath(projectRoot) {
12584
+ return path23.join(projectRoot, ".harness", "cache", "pricing.json");
12585
+ }
12586
+ function getStalenessMarkerPath(projectRoot) {
12587
+ return path23.join(projectRoot, ".harness", "cache", "staleness-marker.json");
12588
+ }
12589
+ async function readDiskCache(projectRoot) {
12590
+ try {
12591
+ const raw = await fs23.readFile(getCachePath(projectRoot), "utf-8");
12592
+ return JSON.parse(raw);
12593
+ } catch {
12594
+ return null;
12595
+ }
12596
+ }
12597
+ async function writeDiskCache(projectRoot, data) {
12598
+ const cachePath = getCachePath(projectRoot);
12599
+ await fs23.mkdir(path23.dirname(cachePath), { recursive: true });
12600
+ await fs23.writeFile(cachePath, JSON.stringify(data, null, 2));
12601
+ }
12602
+ async function fetchFromNetwork() {
12603
+ try {
12604
+ const response = await fetch(LITELLM_PRICING_URL);
12605
+ if (!response.ok) return null;
12606
+ const data = await response.json();
12607
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return null;
12608
+ return {
12609
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
12610
+ data
12611
+ };
12612
+ } catch {
12613
+ return null;
12614
+ }
12615
+ }
12616
+ function loadFallbackDataset() {
12617
+ const fb = fallback_default;
12618
+ const dataset = /* @__PURE__ */ new Map();
12619
+ for (const [model, pricing] of Object.entries(fb.models)) {
12620
+ dataset.set(model, pricing);
12621
+ }
12622
+ return dataset;
12623
+ }
12624
+ async function checkAndWarnStaleness(projectRoot) {
12625
+ const markerPath = getStalenessMarkerPath(projectRoot);
12626
+ try {
12627
+ const raw = await fs23.readFile(markerPath, "utf-8");
12628
+ const marker = JSON.parse(raw);
12629
+ const firstUse = new Date(marker.firstFallbackUse).getTime();
12630
+ const now = Date.now();
12631
+ const daysSinceFirstUse = (now - firstUse) / (24 * 60 * 60 * 1e3);
12632
+ if (daysSinceFirstUse > STALENESS_WARNING_DAYS) {
12633
+ console.warn(
12634
+ `[harness pricing] Pricing data is stale \u2014 using bundled fallback for ${Math.floor(daysSinceFirstUse)} days. Connect to the internet to refresh pricing data.`
12635
+ );
12636
+ }
12637
+ } catch {
12638
+ try {
12639
+ await fs23.mkdir(path23.dirname(markerPath), { recursive: true });
12640
+ await fs23.writeFile(
12641
+ markerPath,
12642
+ JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
12643
+ );
12644
+ } catch {
12645
+ }
12646
+ }
12647
+ }
12648
+ async function clearStalenessMarker(projectRoot) {
12649
+ try {
12650
+ await fs23.unlink(getStalenessMarkerPath(projectRoot));
12651
+ } catch {
12652
+ }
12653
+ }
12654
+ async function loadPricingData(projectRoot) {
12655
+ const cache = await readDiskCache(projectRoot);
12656
+ if (cache) {
12657
+ const cacheAge = Date.now() - new Date(cache.fetchedAt).getTime();
12658
+ if (cacheAge < CACHE_TTL_MS) {
12659
+ await clearStalenessMarker(projectRoot);
12660
+ return parseLiteLLMData(cache.data);
12661
+ }
12662
+ }
12663
+ const fetched = await fetchFromNetwork();
12664
+ if (fetched) {
12665
+ await writeDiskCache(projectRoot, fetched);
12666
+ await clearStalenessMarker(projectRoot);
12667
+ return parseLiteLLMData(fetched.data);
12668
+ }
12669
+ if (cache) {
12670
+ return parseLiteLLMData(cache.data);
12671
+ }
12672
+ await checkAndWarnStaleness(projectRoot);
12673
+ return loadFallbackDataset();
12674
+ }
12675
+ var MICRODOLLARS_PER_DOLLAR = 1e6;
12676
+ var TOKENS_PER_MILLION2 = 1e6;
12677
+ function calculateCost(record, dataset) {
12678
+ if (!record.model) return null;
12679
+ const pricing = getModelPrice(record.model, dataset);
12680
+ if (!pricing) return null;
12681
+ let costUSD = 0;
12682
+ costUSD += record.tokens.inputTokens / TOKENS_PER_MILLION2 * pricing.inputPer1M;
12683
+ costUSD += record.tokens.outputTokens / TOKENS_PER_MILLION2 * pricing.outputPer1M;
12684
+ if (record.cacheReadTokens != null && pricing.cacheReadPer1M != null) {
12685
+ costUSD += record.cacheReadTokens / TOKENS_PER_MILLION2 * pricing.cacheReadPer1M;
12686
+ }
12687
+ if (record.cacheCreationTokens != null && pricing.cacheWritePer1M != null) {
12688
+ costUSD += record.cacheCreationTokens / TOKENS_PER_MILLION2 * pricing.cacheWritePer1M;
12689
+ }
12690
+ return Math.round(costUSD * MICRODOLLARS_PER_DOLLAR);
12691
+ }
12692
+ function aggregateBySession(records) {
12693
+ if (records.length === 0) return [];
12694
+ const sessionMap = /* @__PURE__ */ new Map();
12695
+ for (const record of records) {
12696
+ const tagged = record;
12697
+ const id = record.sessionId;
12698
+ if (!sessionMap.has(id)) {
12699
+ sessionMap.set(id, { harnessRecords: [], ccRecords: [], allRecords: [] });
12700
+ }
12701
+ const bucket = sessionMap.get(id);
12702
+ if (tagged._source === "claude-code") {
12703
+ bucket.ccRecords.push(tagged);
12704
+ } else {
12705
+ bucket.harnessRecords.push(tagged);
12706
+ }
12707
+ bucket.allRecords.push(tagged);
12708
+ }
12709
+ const results = [];
12710
+ for (const [sessionId, bucket] of sessionMap) {
12711
+ const hasHarness = bucket.harnessRecords.length > 0;
12712
+ const hasCC = bucket.ccRecords.length > 0;
12713
+ const isMerged = hasHarness && hasCC;
12714
+ const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
12715
+ const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
12716
+ let cacheCreation;
12717
+ let cacheRead;
12718
+ let costMicroUSD = 0;
12719
+ let model;
12720
+ for (const r of tokenSource) {
12721
+ tokens.inputTokens += r.tokens.inputTokens;
12722
+ tokens.outputTokens += r.tokens.outputTokens;
12723
+ tokens.totalTokens += r.tokens.totalTokens;
12724
+ if (r.cacheCreationTokens != null) {
12725
+ cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
12726
+ }
12727
+ if (r.cacheReadTokens != null) {
12728
+ cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
12729
+ }
12730
+ if (r.costMicroUSD != null && costMicroUSD != null) {
12731
+ costMicroUSD += r.costMicroUSD;
12732
+ } else if (r.costMicroUSD == null) {
12733
+ costMicroUSD = null;
12734
+ }
12735
+ if (!model && r.model) {
12736
+ model = r.model;
12737
+ }
12738
+ }
12739
+ if (!model && hasCC) {
12740
+ for (const r of bucket.ccRecords) {
12741
+ if (r.model) {
12742
+ model = r.model;
12743
+ break;
12744
+ }
12745
+ }
12746
+ }
12747
+ const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
12748
+ const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
12749
+ const session = {
12750
+ sessionId,
12751
+ firstTimestamp: timestamps[0] ?? "",
12752
+ lastTimestamp: timestamps[timestamps.length - 1] ?? "",
12753
+ tokens,
12754
+ costMicroUSD,
12755
+ source
12756
+ };
12757
+ if (model) session.model = model;
12758
+ if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
12759
+ if (cacheRead != null) session.cacheReadTokens = cacheRead;
12760
+ results.push(session);
12761
+ }
12762
+ results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
12763
+ return results;
12764
+ }
12765
+ function aggregateByDay(records) {
12766
+ if (records.length === 0) return [];
12767
+ const dayMap = /* @__PURE__ */ new Map();
12768
+ for (const record of records) {
12769
+ const date = record.timestamp.slice(0, 10);
12770
+ if (!dayMap.has(date)) {
12771
+ dayMap.set(date, {
12772
+ sessions: /* @__PURE__ */ new Set(),
12773
+ tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
12774
+ costMicroUSD: 0,
12775
+ models: /* @__PURE__ */ new Set()
12776
+ });
12777
+ }
12778
+ const day = dayMap.get(date);
12779
+ day.sessions.add(record.sessionId);
12780
+ day.tokens.inputTokens += record.tokens.inputTokens;
12781
+ day.tokens.outputTokens += record.tokens.outputTokens;
12782
+ day.tokens.totalTokens += record.tokens.totalTokens;
12783
+ if (record.cacheCreationTokens != null) {
12784
+ day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
12785
+ }
12786
+ if (record.cacheReadTokens != null) {
12787
+ day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
12788
+ }
12789
+ if (record.costMicroUSD != null && day.costMicroUSD != null) {
12790
+ day.costMicroUSD += record.costMicroUSD;
12791
+ } else if (record.costMicroUSD == null) {
12792
+ day.costMicroUSD = null;
12793
+ }
12794
+ if (record.model) {
12795
+ day.models.add(record.model);
12796
+ }
12797
+ }
12798
+ const results = [];
12799
+ for (const [date, day] of dayMap) {
12800
+ const entry = {
12801
+ date,
12802
+ sessionCount: day.sessions.size,
12803
+ tokens: day.tokens,
12804
+ costMicroUSD: day.costMicroUSD,
12805
+ models: Array.from(day.models).sort()
12806
+ };
12807
+ if (day.cacheCreation != null) entry.cacheCreationTokens = day.cacheCreation;
12808
+ if (day.cacheRead != null) entry.cacheReadTokens = day.cacheRead;
12809
+ results.push(entry);
12810
+ }
12811
+ results.sort((a, b) => b.date.localeCompare(a.date));
12812
+ return results;
12813
+ }
12814
+ function parseLine(line, lineNumber) {
12815
+ let entry;
12816
+ try {
12817
+ entry = JSON.parse(line);
12818
+ } catch {
12819
+ console.warn(`[harness usage] Skipping malformed JSONL line ${lineNumber}`);
12820
+ return null;
12821
+ }
12822
+ const tokenUsage = entry.token_usage;
12823
+ if (!tokenUsage || typeof tokenUsage !== "object") {
12824
+ console.warn(
12825
+ `[harness usage] Skipping malformed JSONL line ${lineNumber}: missing token_usage`
12826
+ );
12827
+ return null;
12828
+ }
12829
+ const inputTokens = tokenUsage.input_tokens ?? 0;
12830
+ const outputTokens = tokenUsage.output_tokens ?? 0;
12831
+ const record = {
12832
+ sessionId: entry.session_id ?? "unknown",
12833
+ timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
12834
+ tokens: {
12835
+ inputTokens,
12836
+ outputTokens,
12837
+ totalTokens: inputTokens + outputTokens
12838
+ }
12839
+ };
12840
+ if (entry.cache_creation_tokens != null) {
12841
+ record.cacheCreationTokens = entry.cache_creation_tokens;
12842
+ }
12843
+ if (entry.cache_read_tokens != null) {
12844
+ record.cacheReadTokens = entry.cache_read_tokens;
12845
+ }
12846
+ if (entry.model != null) {
12847
+ record.model = entry.model;
12848
+ }
12849
+ return record;
12850
+ }
12851
+ function readCostRecords(projectRoot) {
12852
+ const costsFile = path24.join(projectRoot, ".harness", "metrics", "costs.jsonl");
12853
+ let raw;
12854
+ try {
12855
+ raw = fs24.readFileSync(costsFile, "utf-8");
12856
+ } catch {
12857
+ return [];
12858
+ }
12859
+ const records = [];
12860
+ const lines = raw.split("\n");
12861
+ for (let i = 0; i < lines.length; i++) {
12862
+ const line = lines[i]?.trim();
12863
+ if (!line) continue;
12864
+ const record = parseLine(line, i + 1);
12865
+ if (record) {
12866
+ records.push(record);
12867
+ }
12868
+ }
12869
+ return records;
12870
+ }
12871
+ function extractUsage(entry) {
12872
+ if (entry.type !== "assistant") return null;
12873
+ const message = entry.message;
12874
+ if (!message || typeof message !== "object") return null;
12875
+ const usage = message.usage;
12876
+ return usage && typeof usage === "object" && !Array.isArray(usage) ? usage : null;
12877
+ }
12878
+ function buildRecord(entry, usage) {
12879
+ const inputTokens = Number(usage.input_tokens) || 0;
12880
+ const outputTokens = Number(usage.output_tokens) || 0;
12881
+ const message = entry.message;
12882
+ const record = {
12883
+ sessionId: entry.sessionId ?? "unknown",
12884
+ timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
12885
+ tokens: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens },
12886
+ _source: "claude-code"
12887
+ };
12888
+ const model = message.model;
12889
+ if (model) record.model = model;
12890
+ const cacheCreate = usage.cache_creation_input_tokens;
12891
+ const cacheRead = usage.cache_read_input_tokens;
12892
+ if (typeof cacheCreate === "number" && cacheCreate > 0) record.cacheCreationTokens = cacheCreate;
12893
+ if (typeof cacheRead === "number" && cacheRead > 0) record.cacheReadTokens = cacheRead;
12894
+ return record;
12895
+ }
12896
+ function parseCCLine(line, filePath, lineNumber) {
12897
+ let entry;
12898
+ try {
12899
+ entry = JSON.parse(line);
12900
+ } catch {
12901
+ console.warn(
12902
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path25.basename(filePath)}`
12903
+ );
12904
+ return null;
12905
+ }
12906
+ const usage = extractUsage(entry);
12907
+ if (!usage) return null;
12908
+ return {
12909
+ record: buildRecord(entry, usage),
12910
+ requestId: entry.requestId ?? null
12911
+ };
12912
+ }
12913
+ function readCCFile(filePath) {
12914
+ let raw;
12915
+ try {
12916
+ raw = fs25.readFileSync(filePath, "utf-8");
12917
+ } catch {
12918
+ return [];
12919
+ }
12920
+ const byRequestId = /* @__PURE__ */ new Map();
12921
+ const noRequestId = [];
12922
+ const lines = raw.split("\n");
12923
+ for (let i = 0; i < lines.length; i++) {
12924
+ const line = lines[i]?.trim();
12925
+ if (!line) continue;
12926
+ const parsed = parseCCLine(line, filePath, i + 1);
12927
+ if (!parsed) continue;
12928
+ if (parsed.requestId) {
12929
+ byRequestId.set(parsed.requestId, parsed.record);
12930
+ } else {
12931
+ noRequestId.push(parsed.record);
12932
+ }
12933
+ }
12934
+ return [...byRequestId.values(), ...noRequestId];
12935
+ }
12936
+ function parseCCRecords() {
12937
+ const homeDir = process.env.HOME ?? os2.homedir();
12938
+ const projectsDir = path25.join(homeDir, ".claude", "projects");
12939
+ let projectDirs;
12940
+ try {
12941
+ projectDirs = fs25.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path25.join(projectsDir, d.name));
12942
+ } catch {
12943
+ return [];
12944
+ }
12945
+ const records = [];
12946
+ for (const dir of projectDirs) {
12947
+ let files;
12948
+ try {
12949
+ files = fs25.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path25.join(dir, f));
12950
+ } catch {
12951
+ continue;
12952
+ }
12953
+ for (const file of files) {
12954
+ records.push(...readCCFile(file));
12955
+ }
12956
+ }
12957
+ return records;
12958
+ }
12959
+ var VERSION = "0.15.0";
10809
12960
 
10810
12961
  export {
10811
12962
  ArchMetricCategorySchema,
@@ -10947,14 +13098,19 @@ export {
10947
13098
  updateSessionIndex,
10948
13099
  loadState,
10949
13100
  saveState,
13101
+ parseFrontmatter,
13102
+ extractIndexEntry,
10950
13103
  clearLearningsCache,
10951
13104
  appendLearning,
10952
13105
  parseDateFromEntry,
10953
13106
  analyzeLearningPatterns,
10954
13107
  loadBudgetedLearnings,
13108
+ loadIndexEntries,
10955
13109
  loadRelevantLearnings,
10956
13110
  archiveLearnings,
10957
13111
  pruneLearnings,
13112
+ promoteSessionLearnings,
13113
+ countLearningEntries,
10958
13114
  clearFailuresCache,
10959
13115
  appendFailure,
10960
13116
  loadFailures,
@@ -10970,6 +13126,11 @@ export {
10970
13126
  appendSessionEntry,
10971
13127
  updateSessionEntryStatus,
10972
13128
  archiveSession,
13129
+ SkillEventSchema,
13130
+ clearEventHashCache,
13131
+ emitEvent,
13132
+ loadEvents,
13133
+ formatEventTimeline,
10973
13134
  executeWorkflow,
10974
13135
  runPipeline,
10975
13136
  runMultiTurnPipeline,
@@ -10986,11 +13147,31 @@ export {
10986
13147
  pathTraversalRules,
10987
13148
  networkRules,
10988
13149
  deserializationRules,
13150
+ agentConfigRules,
13151
+ mcpRules,
13152
+ insecureDefaultsRules,
13153
+ sharpEdgesRules,
10989
13154
  nodeRules,
10990
13155
  expressRules,
10991
13156
  reactRules,
10992
13157
  goRules,
13158
+ parseHarnessIgnore,
10993
13159
  SecurityScanner,
13160
+ scanForInjection,
13161
+ getInjectionPatterns,
13162
+ DESTRUCTIVE_BASH,
13163
+ getTaintFilePath,
13164
+ readTaint,
13165
+ checkTaint,
13166
+ writeTaint,
13167
+ clearTaint,
13168
+ listTaintedSessions,
13169
+ mapSecuritySeverity,
13170
+ computeOverallSeverity,
13171
+ computeScanExitCode,
13172
+ mapInjectionFindings,
13173
+ isDuplicateFinding,
13174
+ mapSecurityFindings,
10994
13175
  runCIChecks,
10995
13176
  runMechanicalChecks,
10996
13177
  ExclusionSet,
@@ -11025,6 +13206,7 @@ export {
11025
13206
  parseRoadmap,
11026
13207
  serializeRoadmap,
11027
13208
  syncRoadmap,
13209
+ applySyncChanges,
11028
13210
  InteractionTypeSchema,
11029
13211
  QuestionSchema,
11030
13212
  ConfirmationSchema,
@@ -11038,5 +13220,26 @@ export {
11038
13220
  readCheckState,
11039
13221
  spawnBackgroundCheck,
11040
13222
  getUpdateNotification,
13223
+ EXTENSION_MAP,
13224
+ detectLanguage,
13225
+ getParser,
13226
+ parseFile,
13227
+ resetParserCache,
13228
+ getOutline,
13229
+ formatOutline,
13230
+ searchSymbols,
13231
+ unfoldSymbol,
13232
+ unfoldRange,
13233
+ parseLiteLLMData,
13234
+ getModelPrice,
13235
+ LITELLM_PRICING_URL,
13236
+ CACHE_TTL_MS,
13237
+ STALENESS_WARNING_DAYS,
13238
+ loadPricingData,
13239
+ calculateCost,
13240
+ aggregateBySession,
13241
+ aggregateByDay,
13242
+ readCostRecords,
13243
+ parseCCRecords,
11041
13244
  VERSION
11042
13245
  };