all-hands-cli 0.1.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 (305) hide show
  1. package/.allhands/README.md +75 -0
  2. package/.allhands/agents/compounder.yaml +15 -0
  3. package/.allhands/agents/coordinator.yaml +17 -0
  4. package/.allhands/agents/documentor.yaml +15 -0
  5. package/.allhands/agents/e2e-test-planner.yaml +17 -0
  6. package/.allhands/agents/emergent.yaml +22 -0
  7. package/.allhands/agents/executor.yaml +14 -0
  8. package/.allhands/agents/ideation.yaml +11 -0
  9. package/.allhands/agents/initiative-steering.yaml +19 -0
  10. package/.allhands/agents/judge.yaml +13 -0
  11. package/.allhands/agents/planner.yaml +19 -0
  12. package/.allhands/agents/pr-reviewer.yaml +15 -0
  13. package/.allhands/docs.json +5 -0
  14. package/.allhands/docs.local.json +26 -0
  15. package/.allhands/flows/COMPOUNDING.md +203 -0
  16. package/.allhands/flows/COORDINATION.md +89 -0
  17. package/.allhands/flows/CORE.md +87 -0
  18. package/.allhands/flows/DOCUMENTATION.md +218 -0
  19. package/.allhands/flows/E2E_TEST_PLAN_BUILDING.md +140 -0
  20. package/.allhands/flows/EMERGENT_PLANNING.md +57 -0
  21. package/.allhands/flows/IDEATION_SCOPING.md +154 -0
  22. package/.allhands/flows/INITIATIVE_STEERING.md +110 -0
  23. package/.allhands/flows/JUDGE_REVIEWING.md +79 -0
  24. package/.allhands/flows/PROMPT_TASK_EXECUTION.md +68 -0
  25. package/.allhands/flows/PR_REVIEWING.md +43 -0
  26. package/.allhands/flows/SPEC_PLANNING.md +216 -0
  27. package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +27 -0
  28. package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +27 -0
  29. package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +27 -0
  30. package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +27 -0
  31. package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +27 -0
  32. package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +27 -0
  33. package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +72 -0
  34. package/.allhands/flows/shared/CREATE_HARNESS_SPEC.md +48 -0
  35. package/.allhands/flows/shared/CREATE_SPEC.md +41 -0
  36. package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +70 -0
  37. package/.allhands/flows/shared/DOCUMENTATION_DISCOVERY.md +123 -0
  38. package/.allhands/flows/shared/DOCUMENTATION_WRITER.md +101 -0
  39. package/.allhands/flows/shared/EMERGENT_REFINEMENT_ANALYSIS.md +76 -0
  40. package/.allhands/flows/shared/EXTERNAL_TECH_GUIDANCE.md +97 -0
  41. package/.allhands/flows/shared/IDEATION_CODEBASE_GROUNDING.md +49 -0
  42. package/.allhands/flows/shared/PLAN_DEEPENING.md +152 -0
  43. package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +113 -0
  44. package/.allhands/flows/shared/PROMPT_VALIDATION_REVIEW.MD +99 -0
  45. package/.allhands/flows/shared/QUICK_PREMORTEM.md +70 -0
  46. package/.allhands/flows/shared/RESEARCH_GUIDANCE.md +38 -0
  47. package/.allhands/flows/shared/REVIEW_OPTIONS_BREAKDOWN.md +68 -0
  48. package/.allhands/flows/shared/SKILL_EXTRACTION.md +84 -0
  49. package/.allhands/flows/shared/SPEC_FLOW_ANALYSIS.md +119 -0
  50. package/.allhands/flows/shared/TDD_WORKFLOW.md +109 -0
  51. package/.allhands/flows/shared/UTILIZE_VALIDATION_TOOLING.md +84 -0
  52. package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +11 -0
  53. package/.allhands/flows/shared/WRITING_HARNESS_MCP_TOOLS.md +84 -0
  54. package/.allhands/flows/shared/jury/ARCHITECTURE_REVIEW.md +91 -0
  55. package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +80 -0
  56. package/.allhands/flows/shared/jury/CLAIM_VERIFICATION_REVIEW.md +101 -0
  57. package/.allhands/flows/shared/jury/EXPECTATIONS_FIT_REVIEW.md +78 -0
  58. package/.allhands/flows/shared/jury/MAINTAINABILITY_REVIEW.md +110 -0
  59. package/.allhands/flows/shared/jury/PROMPTS_EXPECTATIONS_FIT.md +74 -0
  60. package/.allhands/flows/shared/jury/PROMPTS_FLOW_ANALYSIS.md +92 -0
  61. package/.allhands/flows/shared/jury/PROMPTS_YAGNI.md +78 -0
  62. package/.allhands/flows/shared/jury/PROMPT_PREMORTEM.md +125 -0
  63. package/.allhands/flows/shared/jury/SECURITY_REVIEW.md +86 -0
  64. package/.allhands/flows/shared/jury/YAGNI_REVIEW.md +82 -0
  65. package/.allhands/flows/wip/DEBUG_INVESTIGATION.md +162 -0
  66. package/.allhands/flows/wip/MEMORY_RECALL.md +62 -0
  67. package/.allhands/harness/ah +131 -0
  68. package/.allhands/harness/package-lock.json +5292 -0
  69. package/.allhands/harness/package.json +52 -0
  70. package/.allhands/harness/src/__tests__/e2e/commands.test.ts +307 -0
  71. package/.allhands/harness/src/__tests__/e2e/event-loop.test.ts +539 -0
  72. package/.allhands/harness/src/__tests__/e2e/hooks.test.ts +427 -0
  73. package/.allhands/harness/src/__tests__/e2e/new-initiative-routing.test.ts +137 -0
  74. package/.allhands/harness/src/__tests__/e2e/run-e2e.ts +109 -0
  75. package/.allhands/harness/src/__tests__/e2e/specs-type.test.ts +210 -0
  76. package/.allhands/harness/src/__tests__/e2e/validation-hooks.test.ts +669 -0
  77. package/.allhands/harness/src/__tests__/e2e/validation-path-consistency.test.ts +354 -0
  78. package/.allhands/harness/src/__tests__/e2e/validation.test.ts +528 -0
  79. package/.allhands/harness/src/__tests__/harness/assertions.ts +318 -0
  80. package/.allhands/harness/src/__tests__/harness/cli-runner.ts +359 -0
  81. package/.allhands/harness/src/__tests__/harness/fixture.ts +384 -0
  82. package/.allhands/harness/src/__tests__/harness/hook-runner.ts +411 -0
  83. package/.allhands/harness/src/__tests__/harness/index.ts +122 -0
  84. package/.allhands/harness/src/cli.ts +36 -0
  85. package/.allhands/harness/src/commands/complexity.ts +177 -0
  86. package/.allhands/harness/src/commands/context7.ts +202 -0
  87. package/.allhands/harness/src/commands/docs.ts +557 -0
  88. package/.allhands/harness/src/commands/hooks.ts +24 -0
  89. package/.allhands/harness/src/commands/index.ts +51 -0
  90. package/.allhands/harness/src/commands/knowledge.ts +382 -0
  91. package/.allhands/harness/src/commands/memories.ts +302 -0
  92. package/.allhands/harness/src/commands/notify.ts +61 -0
  93. package/.allhands/harness/src/commands/oracle.ts +158 -0
  94. package/.allhands/harness/src/commands/perplexity.ts +220 -0
  95. package/.allhands/harness/src/commands/planning.ts +245 -0
  96. package/.allhands/harness/src/commands/schema.ts +73 -0
  97. package/.allhands/harness/src/commands/skills.ts +128 -0
  98. package/.allhands/harness/src/commands/solutions.ts +353 -0
  99. package/.allhands/harness/src/commands/spawn.ts +158 -0
  100. package/.allhands/harness/src/commands/specs.ts +532 -0
  101. package/.allhands/harness/src/commands/tavily.ts +226 -0
  102. package/.allhands/harness/src/commands/tools.ts +579 -0
  103. package/.allhands/harness/src/commands/trace.ts +327 -0
  104. package/.allhands/harness/src/commands/tui.ts +960 -0
  105. package/.allhands/harness/src/commands/validate.ts +143 -0
  106. package/.allhands/harness/src/commands/validation-tools.ts +108 -0
  107. package/.allhands/harness/src/hooks/context.ts +1442 -0
  108. package/.allhands/harness/src/hooks/enforcement.ts +170 -0
  109. package/.allhands/harness/src/hooks/index.ts +54 -0
  110. package/.allhands/harness/src/hooks/lifecycle.ts +229 -0
  111. package/.allhands/harness/src/hooks/notification.ts +104 -0
  112. package/.allhands/harness/src/hooks/observability.ts +551 -0
  113. package/.allhands/harness/src/hooks/session.ts +88 -0
  114. package/.allhands/harness/src/hooks/shared.ts +815 -0
  115. package/.allhands/harness/src/hooks/transcript-parser.ts +208 -0
  116. package/.allhands/harness/src/hooks/validation.ts +617 -0
  117. package/.allhands/harness/src/lib/__tests__/ctags.test.ts +244 -0
  118. package/.allhands/harness/src/lib/__tests__/docs-validation.test.ts +344 -0
  119. package/.allhands/harness/src/lib/__tests__/mcp-runtime.test.ts +190 -0
  120. package/.allhands/harness/src/lib/__tests__/schema.test.ts +861 -0
  121. package/.allhands/harness/src/lib/base-command.ts +198 -0
  122. package/.allhands/harness/src/lib/cli-daemon.ts +343 -0
  123. package/.allhands/harness/src/lib/compaction.ts +313 -0
  124. package/.allhands/harness/src/lib/ctags.ts +497 -0
  125. package/.allhands/harness/src/lib/docs-validation.ts +907 -0
  126. package/.allhands/harness/src/lib/event-loop.ts +662 -0
  127. package/.allhands/harness/src/lib/flows.ts +155 -0
  128. package/.allhands/harness/src/lib/git.ts +276 -0
  129. package/.allhands/harness/src/lib/knowledge-worker.ts +72 -0
  130. package/.allhands/harness/src/lib/knowledge.ts +810 -0
  131. package/.allhands/harness/src/lib/llm.ts +255 -0
  132. package/.allhands/harness/src/lib/mcp-client.ts +432 -0
  133. package/.allhands/harness/src/lib/mcp-daemon.ts +486 -0
  134. package/.allhands/harness/src/lib/mcp-runtime.ts +418 -0
  135. package/.allhands/harness/src/lib/notification.ts +115 -0
  136. package/.allhands/harness/src/lib/opencode/index.ts +70 -0
  137. package/.allhands/harness/src/lib/opencode/profiles.ts +300 -0
  138. package/.allhands/harness/src/lib/opencode/prompts/codesearch.md +98 -0
  139. package/.allhands/harness/src/lib/opencode/prompts/knowledge-aggregator.md +67 -0
  140. package/.allhands/harness/src/lib/opencode/runner.ts +281 -0
  141. package/.allhands/harness/src/lib/oracle.ts +926 -0
  142. package/.allhands/harness/src/lib/planning-utils.ts +150 -0
  143. package/.allhands/harness/src/lib/planning.ts +605 -0
  144. package/.allhands/harness/src/lib/pr-review.ts +225 -0
  145. package/.allhands/harness/src/lib/prompts.ts +522 -0
  146. package/.allhands/harness/src/lib/schema.ts +418 -0
  147. package/.allhands/harness/src/lib/schemas/agent-profile.ts +141 -0
  148. package/.allhands/harness/src/lib/schemas/template-vars.ts +138 -0
  149. package/.allhands/harness/src/lib/session.ts +164 -0
  150. package/.allhands/harness/src/lib/specs.ts +348 -0
  151. package/.allhands/harness/src/lib/tldr.ts +829 -0
  152. package/.allhands/harness/src/lib/tmux.ts +1051 -0
  153. package/.allhands/harness/src/lib/trace-store.ts +714 -0
  154. package/.allhands/harness/src/mcp/__tests__/index.test.ts +46 -0
  155. package/.allhands/harness/src/mcp/_template.ts +47 -0
  156. package/.allhands/harness/src/mcp/filesystem.ts +33 -0
  157. package/.allhands/harness/src/mcp/index.ts +69 -0
  158. package/.allhands/harness/src/mcp/playwright.ts +34 -0
  159. package/.allhands/harness/src/mcp/xcodebuild.ts +29 -0
  160. package/.allhands/harness/src/schemas/docs.schema.json +44 -0
  161. package/.allhands/harness/src/schemas/settings.schema.json +214 -0
  162. package/.allhands/harness/src/tui/actions.ts +227 -0
  163. package/.allhands/harness/src/tui/file-viewer-modal.ts +270 -0
  164. package/.allhands/harness/src/tui/index.ts +1574 -0
  165. package/.allhands/harness/src/tui/modal.ts +232 -0
  166. package/.allhands/harness/src/tui/prompts-pane.ts +186 -0
  167. package/.allhands/harness/src/tui/status-pane.ts +434 -0
  168. package/.allhands/harness/tsconfig.json +22 -0
  169. package/.allhands/harness/vitest.config.ts +13 -0
  170. package/.allhands/pillars.md +33 -0
  171. package/.allhands/principles.md +88 -0
  172. package/.allhands/schemas/alignment.yaml +51 -0
  173. package/.allhands/schemas/documentation.yaml +10 -0
  174. package/.allhands/schemas/prompt.yaml +92 -0
  175. package/.allhands/schemas/skill.yaml +34 -0
  176. package/.allhands/schemas/solution.yaml +131 -0
  177. package/.allhands/schemas/spec.yaml +67 -0
  178. package/.allhands/schemas/validation-suite.yaml +49 -0
  179. package/.allhands/schemas/workflow.yaml +51 -0
  180. package/.allhands/settings.json +57 -0
  181. package/.allhands/skills/claude-code-patterns/SKILL.md +60 -0
  182. package/.allhands/skills/claude-code-patterns/docs/context-hygiene.md +19 -0
  183. package/.allhands/skills/harness-maintenance/SKILL.md +449 -0
  184. package/.allhands/skills/harness-maintenance/references/core-architecture.md +187 -0
  185. package/.allhands/skills/harness-maintenance/references/harness-skills.md +87 -0
  186. package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +78 -0
  187. package/.allhands/skills/harness-maintenance/references/tools-commands-mcp-hooks.md +115 -0
  188. package/.allhands/skills/harness-maintenance/references/validation-tooling.md +77 -0
  189. package/.allhands/skills/harness-maintenance/references/writing-flows.md +84 -0
  190. package/.allhands/validation/browser-automation.md +109 -0
  191. package/.allhands/validation/xcode-automation.md +195 -0
  192. package/.allhands/workflows/documentation.md +86 -0
  193. package/.allhands/workflows/investigation.md +81 -0
  194. package/.allhands/workflows/milestone.md +91 -0
  195. package/.allhands/workflows/optimization.md +85 -0
  196. package/.allhands/workflows/refactor.md +99 -0
  197. package/.allhands/workflows/triage.md +81 -0
  198. package/.claude/README.md +1 -0
  199. package/.claude/agents/explorer.md +10 -0
  200. package/.claude/agents/researcher.md +11 -0
  201. package/.claude/agents/task-runner.md +8 -0
  202. package/.claude/settings.json +231 -0
  203. package/.env.ai.example +7 -0
  204. package/.github/workflows/npm-publish.yml +69 -0
  205. package/.internal.json +45 -0
  206. package/.tldr/config.json +11 -0
  207. package/.tldrignore +90 -0
  208. package/CLAUDE.md +6 -0
  209. package/README.md +98 -0
  210. package/bin/sync-cli.js +7552 -0
  211. package/concerns.md +7 -0
  212. package/docs/README.md +41 -0
  213. package/docs/agents/README.md +24 -0
  214. package/docs/agents/agent-configuration-system.md +86 -0
  215. package/docs/agents/execution-agents.md +50 -0
  216. package/docs/agents/knowledge-agents.md +61 -0
  217. package/docs/agents/orchestration-agent.md +57 -0
  218. package/docs/agents/planning-agents.md +84 -0
  219. package/docs/agents/quality-review-agents.md +67 -0
  220. package/docs/agents/workflow-agent-orchestration.md +69 -0
  221. package/docs/flows/README.md +44 -0
  222. package/docs/flows/compounding.md +126 -0
  223. package/docs/flows/coordination.md +72 -0
  224. package/docs/flows/core-harness-integration.md +63 -0
  225. package/docs/flows/documentation-orchestration.md +98 -0
  226. package/docs/flows/e2e-test-plan-building.md +83 -0
  227. package/docs/flows/emergent-refinement.md +104 -0
  228. package/docs/flows/flow-authoring-and-mcp-tools.md +89 -0
  229. package/docs/flows/judge-reviewing.md +112 -0
  230. package/docs/flows/plan-deepening-and-research.md +107 -0
  231. package/docs/flows/plan-review-jury.md +114 -0
  232. package/docs/flows/pr-reviewing.md +54 -0
  233. package/docs/flows/prompt-task-execution.md +119 -0
  234. package/docs/flows/spec-planning.md +162 -0
  235. package/docs/flows/type-specific-scoping-flows.md +49 -0
  236. package/docs/flows/validation-and-skills-integration.md +145 -0
  237. package/docs/flows/wip/wip-flows.md +102 -0
  238. package/docs/harness/README.md +23 -0
  239. package/docs/harness/agent-profiles.md +84 -0
  240. package/docs/harness/cli/README.md +24 -0
  241. package/docs/harness/cli/cli-entry-and-command-discovery.md +91 -0
  242. package/docs/harness/cli/docs-command.md +87 -0
  243. package/docs/harness/cli/knowledge-command.md +91 -0
  244. package/docs/harness/cli/minor-cli-commands.md +65 -0
  245. package/docs/harness/cli/oracle-command.md +113 -0
  246. package/docs/harness/cli/planning-command.md +95 -0
  247. package/docs/harness/cli/schema-and-validation-commands.md +154 -0
  248. package/docs/harness/cli/search-commands.md +97 -0
  249. package/docs/harness/cli/spawn-command.md +136 -0
  250. package/docs/harness/cli/specs-command.md +102 -0
  251. package/docs/harness/cli/tools-command.md +122 -0
  252. package/docs/harness/cli/trace-command.md +122 -0
  253. package/docs/harness/cli-daemon.md +92 -0
  254. package/docs/harness/event-loop.md +184 -0
  255. package/docs/harness/hooks/README.md +15 -0
  256. package/docs/harness/hooks/context-hooks.md +96 -0
  257. package/docs/harness/hooks/lifecycle-and-observability-hooks.md +135 -0
  258. package/docs/harness/hooks/validation-hooks.md +97 -0
  259. package/docs/harness/test-harness.md +149 -0
  260. package/docs/harness/tui.md +176 -0
  261. package/docs/memories.md +20 -0
  262. package/docs/solutions/agentic-issues/premature-agent-deletion-tui-action-dependency-20260130.md +49 -0
  263. package/docs/solutions/agentic-issues/ref-anchor-scope-mismatch-skill-references-20260131.md +55 -0
  264. package/docs/solutions/agentic-issues/tautological-tests-routing-20260131.md +52 -0
  265. package/docs/solutions/integration_issue/blocktool-output-format-mismatch-hook-runner-20260130.md +52 -0
  266. package/docs/solutions/integration_issue/dual-validation-path-divergence-schema-20260130.md +66 -0
  267. package/docs/solutions/security-issues/unsanitized-domain-path-join-20260131.md +52 -0
  268. package/docs/solutions/test-failures/event-loop-mock-ordering-checkAgentWindows-20260130.md +63 -0
  269. package/docs/sync-cli/README.md +19 -0
  270. package/docs/sync-cli/cli-entrypoint-and-commands.md +39 -0
  271. package/docs/sync-cli/commands/README.md +11 -0
  272. package/docs/sync-cli/commands/pull-manifest-command.md +36 -0
  273. package/docs/sync-cli/commands/push-command.md +84 -0
  274. package/docs/sync-cli/commands/sync-command.md +71 -0
  275. package/docs/sync-cli/systems/README.md +14 -0
  276. package/docs/sync-cli/systems/git-and-github-integration.md +49 -0
  277. package/docs/sync-cli/systems/interactive-ui.md +43 -0
  278. package/docs/sync-cli/systems/manifest-and-distribution.md +51 -0
  279. package/docs/sync-cli/systems/path-resolution.md +42 -0
  280. package/package.json +46 -0
  281. package/scripts/install-shim.sh +40 -0
  282. package/scripts/pre-pack.sh +25 -0
  283. package/specs/harness-maintenance-skill.spec.md +138 -0
  284. package/specs/roadmap/git-spec-lifecycle-management.spec.md +113 -0
  285. package/specs/sync-init-flag.spec.md +117 -0
  286. package/specs/unified-workflow-orchestration.spec.md +250 -0
  287. package/specs/validation-tooling-practice.spec.md +98 -0
  288. package/specs/workflow-domain-configuration.spec.md +265 -0
  289. package/src/commands/pull-manifest.ts +31 -0
  290. package/src/commands/push.ts +344 -0
  291. package/src/commands/sync.ts +289 -0
  292. package/src/lib/constants.ts +10 -0
  293. package/src/lib/dotfiles.ts +36 -0
  294. package/src/lib/fs-utils.ts +18 -0
  295. package/src/lib/gh.ts +40 -0
  296. package/src/lib/git.ts +63 -0
  297. package/src/lib/gitignore.ts +167 -0
  298. package/src/lib/manifest.ts +121 -0
  299. package/src/lib/marker-sync.ts +39 -0
  300. package/src/lib/paths.ts +38 -0
  301. package/src/lib/target-lines.ts +66 -0
  302. package/src/lib/ui.ts +78 -0
  303. package/src/sync-cli.ts +120 -0
  304. package/target-lines.json +23 -0
  305. package/tsconfig.json +20 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Enforcement Hooks
3
+ *
4
+ * PreToolUse hooks that enforce usage patterns:
5
+ * - Block GitHub URLs in WebFetch/Bash (suggest gh CLI)
6
+ * - Block WebFetch (suggest research tools)
7
+ * - Block WebSearch (suggest research delegation)
8
+ */
9
+
10
+ import type { Command } from 'commander';
11
+ import {
12
+ HookInput,
13
+ HookCategory,
14
+ RegisterFn,
15
+ allowTool,
16
+ denyTool,
17
+ registerCategory,
18
+ registerCategoryForDaemon,
19
+ } from './shared.js';
20
+
21
+ // ─────────────────────────────────────────────────────────────────────────────
22
+ // Constants
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+
25
+ const GITHUB_DOMAINS = ['github.com', 'raw.githubusercontent.com', 'gist.github.com'];
26
+
27
+ // ─────────────────────────────────────────────────────────────────────────────
28
+ // GitHub URL Enforcement
29
+ // ─────────────────────────────────────────────────────────────────────────────
30
+
31
+ const HOOK_GITHUB_URL = 'enforcement github-url';
32
+
33
+ /**
34
+ * Block GitHub URLs in WebFetch and Bash fetch commands.
35
+ * Suggests using the gh CLI instead.
36
+ *
37
+ * Triggered by: PreToolUse matcher "(WebFetch|Bash)"
38
+ */
39
+ export function enforceGitHubUrl(input: HookInput): void {
40
+ const toolName = input.tool_name || '';
41
+ const toolInput = input.tool_input || {};
42
+
43
+ // Check WebFetch URLs
44
+ if (toolName === 'WebFetch') {
45
+ const url = (toolInput.url as string) || '';
46
+ for (const domain of GITHUB_DOMAINS) {
47
+ if (url.includes(domain)) {
48
+ denyTool("GitHub URL detected. Use 'gh' CLI: gh api repos/OWNER/REPO/contents/PATH", HOOK_GITHUB_URL);
49
+ }
50
+ }
51
+ allowTool(HOOK_GITHUB_URL);
52
+ }
53
+
54
+ // Check Bash commands for curl, wget, tavily extract
55
+ if (toolName === 'Bash') {
56
+ const command = (toolInput.command as string) || '';
57
+
58
+ // Check for fetch-like commands
59
+ const isFetchCmd = ['curl', 'wget', 'tavily extract'].some((cmd) => command.includes(cmd));
60
+ if (!isFetchCmd) {
61
+ allowTool(HOOK_GITHUB_URL);
62
+ }
63
+
64
+ // Check for GitHub URLs
65
+ for (const domain of GITHUB_DOMAINS) {
66
+ if (command.includes(domain)) {
67
+ denyTool("GitHub URL detected. Use 'gh' CLI: gh api repos/OWNER/REPO/contents/PATH", HOOK_GITHUB_URL);
68
+ }
69
+ }
70
+ }
71
+
72
+ allowTool(HOOK_GITHUB_URL);
73
+ }
74
+
75
+ // ─────────────────────────────────────────────────────────────────────────────
76
+ // Research Fetch Enforcement
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+
79
+ const HOOK_RESEARCH_FETCH = 'enforcement research-fetch';
80
+
81
+ /**
82
+ * Block WebFetch and suggest research tools.
83
+ *
84
+ * Triggered by: PreToolUse matcher "WebFetch"
85
+ */
86
+ export function enforceResearchFetch(input: HookInput): void {
87
+ const url = (input.tool_input?.url as string) || '';
88
+
89
+ if (!url) {
90
+ allowTool(HOOK_RESEARCH_FETCH);
91
+ }
92
+
93
+ denyTool(
94
+ 'WebFetch blocked. Use `ah tavily extract "<url>"` instead.',
95
+ HOOK_RESEARCH_FETCH
96
+ );
97
+ }
98
+
99
+ // ─────────────────────────────────────────────────────────────────────────────
100
+ // Research Search Enforcement
101
+ // ─────────────────────────────────────────────────────────────────────────────
102
+
103
+ const HOOK_RESEARCH_SEARCH = 'enforcement research-search';
104
+
105
+ /**
106
+ * Block WebSearch and suggest research delegation.
107
+ *
108
+ * Triggered by: PreToolUse matcher "WebSearch"
109
+ */
110
+ export function enforceResearchSearch(_input: HookInput): void {
111
+ denyTool(
112
+ 'WebSearch blocked. Use `ah perplexity research "<query>"` instead.',
113
+ HOOK_RESEARCH_SEARCH
114
+ );
115
+ }
116
+
117
+ // ─────────────────────────────────────────────────────────────────────────────
118
+ // Hook Category Definition
119
+ // ─────────────────────────────────────────────────────────────────────────────
120
+
121
+ /** Enforcement hooks category */
122
+ export const category: HookCategory = {
123
+ name: 'enforcement',
124
+ description: 'Enforcement hooks (PreToolUse)',
125
+ hooks: [
126
+ {
127
+ name: 'github-url',
128
+ description: 'Block GitHub URLs in fetch commands',
129
+ handler: enforceGitHubUrl,
130
+ errorFallback: { type: 'allowTool' },
131
+ logPayload: (input) => ({ tool: input.tool_name }),
132
+ },
133
+ {
134
+ name: 'research-fetch',
135
+ description: 'Block WebFetch and suggest `ah tavily extract "<url>"` instead',
136
+ handler: enforceResearchFetch,
137
+ errorFallback: { type: 'allowTool' },
138
+ logPayload: (input) => ({ tool: input.tool_name }),
139
+ },
140
+ {
141
+ name: 'research-search',
142
+ description: 'Block WebSearch and suggest `ah perplexity research "<query>"` instead',
143
+ handler: enforceResearchSearch,
144
+ errorFallback: { type: 'allowTool' },
145
+ logPayload: (input) => ({ tool: input.tool_name }),
146
+ },
147
+ ],
148
+ };
149
+
150
+ // ─────────────────────────────────────────────────────────────────────────────
151
+ // Command Registration
152
+ // ─────────────────────────────────────────────────────────────────────────────
153
+
154
+ /**
155
+ * Register enforcement hook subcommands.
156
+ */
157
+ export function register(parent: Command): void {
158
+ registerCategory(parent, category);
159
+ }
160
+
161
+ // ─────────────────────────────────────────────────────────────────────────────
162
+ // Daemon Handler Registration
163
+ // ─────────────────────────────────────────────────────────────────────────────
164
+
165
+ /**
166
+ * Register handlers for daemon mode.
167
+ */
168
+ export function registerDaemonHandlers(register: RegisterFn): void {
169
+ registerCategoryForDaemon(category, register);
170
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Hook Registry - Auto-discovers hook modules.
3
+ *
4
+ * Each hook file should export a `register` function that takes
5
+ * a Commander Command (the parent 'hooks' command) and registers its subcommands.
6
+ *
7
+ * Skips: index.ts, shared.ts, transcript-parser.ts (utilities)
8
+ */
9
+
10
+ import { readdirSync, statSync } from 'fs';
11
+ import { dirname, join } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ import type { Command } from 'commander';
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+
17
+ // Files to skip (utilities, not hook commands)
18
+ const SKIP_FILES = ['index.ts', 'shared.ts', 'transcript-parser.ts'];
19
+
20
+ export interface HookModule {
21
+ register: (parent: Command) => void;
22
+ }
23
+
24
+ /**
25
+ * Auto-discover and register all hook modules.
26
+ *
27
+ * @param parent - The parent 'hooks' command to register subcommands on
28
+ */
29
+ export async function discoverAndRegisterHooks(parent: Command): Promise<void> {
30
+ const entries = readdirSync(__dirname);
31
+
32
+ for (const entry of entries) {
33
+ const entryPath = join(__dirname, entry);
34
+ const stat = statSync(entryPath);
35
+
36
+ // Skip directories and non-ts files
37
+ if (stat.isDirectory()) continue;
38
+ if (!entry.endsWith('.ts')) continue;
39
+ if (SKIP_FILES.includes(entry)) continue;
40
+
41
+ const moduleName = entry.replace('.ts', '');
42
+ const importPath = `./${moduleName}.js`;
43
+
44
+ try {
45
+ const module = (await import(importPath)) as HookModule;
46
+ if (typeof module.register === 'function') {
47
+ module.register(parent);
48
+ }
49
+ } catch (e) {
50
+ // Skip modules with errors - log for debugging
51
+ console.error(`Warning: Could not load hook module ${moduleName}: ${e}`);
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Lifecycle Hooks
3
+ *
4
+ * Hooks for agent lifecycle events:
5
+ * - Stop: Send notification and kill tmux window
6
+ * - PreCompact: Summarize progress, append to prompt file, kill tmux window
7
+ */
8
+
9
+ import { existsSync } from 'fs';
10
+ import type { Command } from 'commander';
11
+ import {
12
+ HookInput,
13
+ HookCategory,
14
+ RegisterFn,
15
+ outputStopHook,
16
+ outputPreCompact,
17
+ registerCategory,
18
+ registerCategoryForDaemon,
19
+ } from './shared.js';
20
+ import { logHookSuccess } from '../lib/trace-store.js';
21
+ import { sendNotification } from '../lib/notification.js';
22
+ import { killWindow, SESSION_NAME, windowExists, getCurrentSession } from '../lib/tmux.js';
23
+ import { getPromptByNumber } from '../lib/prompts.js';
24
+ import { getCurrentBranch, sanitizeBranchForDir } from '../lib/planning.js';
25
+ import { runCompaction } from '../lib/compaction.js';
26
+
27
+ const HOOK_AGENT_STOP = 'lifecycle agent-stop';
28
+ const HOOK_AGENT_COMPACT = 'lifecycle agent-compact';
29
+
30
+ // ─────────────────────────────────────────────────────────────────────────────
31
+ // Agent Stop
32
+ // ─────────────────────────────────────────────────────────────────────────────
33
+
34
+ /**
35
+ * Handle agent stop lifecycle event.
36
+ *
37
+ * - Sends desktop notification
38
+ * - Kills the tmux window (for prompt-scoped agents that may not close naturally)
39
+ * - Approves the stop
40
+ *
41
+ * Triggered by: Stop matcher "*"
42
+ */
43
+ export function handleAgentStop(_input: HookInput): void {
44
+ const agentId = process.env.AGENT_ID;
45
+ const agentType = process.env.AGENT_TYPE || 'Agent';
46
+ const isPromptScoped = process.env.PROMPT_SCOPED === 'true';
47
+
48
+ // Send notification
49
+ const title = agentId ? `${agentType} Stopped` : 'Agent Stopped';
50
+ const message = agentId ? `Agent ${agentId} has stopped` : 'The agent session has stopped';
51
+
52
+ sendNotification({
53
+ title,
54
+ message,
55
+ type: 'banner',
56
+ });
57
+
58
+ // Kill the tmux window for prompt-scoped agents AND the emergent planner.
59
+ // Non-prompt-scoped agents (except emergent) should remain running (their window stays open).
60
+ // Prompt-scoped agents may not close naturally even with exec, so we
61
+ // explicitly kill them here. The emergent planner is prompt_scoped, but
62
+ // this condition also covers it by agent type for clarity.
63
+ if (isPromptScoped || agentType === 'emergent') {
64
+ const sessionName = getCurrentSession() || SESSION_NAME;
65
+ if (agentId && windowExists(sessionName, agentId)) {
66
+ killWindow(sessionName, agentId);
67
+ }
68
+ }
69
+
70
+ // Approve stop
71
+ outputStopHook('approve', undefined, HOOK_AGENT_STOP);
72
+ }
73
+
74
+ // ─────────────────────────────────────────────────────────────────────────────
75
+ // Pre-Compact
76
+ // ─────────────────────────────────────────────────────────────────────────────
77
+
78
+ /**
79
+ * Handle pre-compaction lifecycle event.
80
+ *
81
+ * When context gets too long and compaction is triggered for a prompt-scoped agent:
82
+ * 1. Run full compaction analysis via oracle:
83
+ * - Analyze conversation for progress, learnings, blockers
84
+ * - Recommend action: continue (keep code) or scratch (discard)
85
+ * - Increment attempts counter in prompt frontmatter
86
+ * - Append detailed progress update to prompt file
87
+ * - Execute recommendation (commit or discard changes)
88
+ * 2. Kill the tmux window (terminate the Claude instance)
89
+ * 3. Event loop can then re-run the prompt with learnings
90
+ *
91
+ * Criteria to run full compaction:
92
+ * - PROMPT_SCOPED=true (agent is prompt-scoped)
93
+ * - PROMPT_NUMBER is set (we know which prompt file to update)
94
+ * - transcript_path exists (we have conversation logs to analyze)
95
+ *
96
+ * Triggered by: PreCompact matcher "*"
97
+ */
98
+ export async function handleAgentCompact(input: HookInput): Promise<void> {
99
+ const agentId = process.env.AGENT_ID;
100
+ const promptNumber = process.env.PROMPT_NUMBER;
101
+ const isPromptScoped = process.env.PROMPT_SCOPED === 'true';
102
+ const transcriptPath = input.transcript_path;
103
+
104
+ const agentType = process.env.AGENT_TYPE;
105
+
106
+ // Only process compaction for prompt executor agents (prompt-scoped with PROMPT_NUMBER).
107
+ // The emergent planner is prompt_scoped (for window naming) but must NOT run compaction —
108
+ // it plans hypotheses, not implementation. Non-prompt-scoped agents pass through too.
109
+ if (!isPromptScoped || !promptNumber || agentType === 'emergent') {
110
+ logHookSuccess(HOOK_AGENT_COMPACT, {
111
+ action: 'skip',
112
+ reason: !isPromptScoped ? 'not_prompt_scoped' : agentType === 'emergent' ? 'emergent_planner' : 'no_prompt_number',
113
+ });
114
+ return outputPreCompact(undefined);
115
+ }
116
+
117
+ // Use current session (not hardcoded SESSION_NAME) since agents may be
118
+ // spawned in whatever session is active, not necessarily 'ah-hub'
119
+ const sessionName = getCurrentSession() || SESSION_NAME;
120
+
121
+ // Get the planning key (sanitized branch name) for directory lookups.
122
+ // The planning directory is .planning/<sanitized-branch>/ (e.g., "feature-core-taskflow-crud").
123
+ const branch = getCurrentBranch();
124
+ const planningKey = sanitizeBranchForDir(branch);
125
+
126
+ // Get the prompt file
127
+ const promptNum = parseInt(promptNumber, 10);
128
+ const prompt = getPromptByNumber(promptNum, planningKey);
129
+
130
+ if (!prompt) {
131
+ // Prompt file not found, kill window and exit
132
+ logHookSuccess(HOOK_AGENT_COMPACT, { action: 'skip', reason: 'no_prompt', promptNum, planningKey });
133
+ if (agentId && windowExists(sessionName, agentId)) {
134
+ killWindow(sessionName, agentId);
135
+ }
136
+ return outputPreCompact(undefined);
137
+ }
138
+
139
+ // Need transcript to run compaction analysis
140
+ if (!transcriptPath || !existsSync(transcriptPath)) {
141
+ sendNotification({
142
+ title: 'Compaction Skipped',
143
+ message: `No transcript available for prompt ${promptNumber}`,
144
+ type: 'banner',
145
+ });
146
+ if (agentId && windowExists(sessionName, agentId)) {
147
+ killWindow(sessionName, agentId);
148
+ }
149
+ return outputPreCompact(undefined, HOOK_AGENT_COMPACT);
150
+ }
151
+
152
+ // Notify that compaction is starting
153
+ sendNotification({
154
+ title: 'Compaction Starting',
155
+ message: `Analyzing prompt ${promptNumber}...`,
156
+ type: 'banner',
157
+ });
158
+
159
+ try {
160
+ // Run full compaction analysis (result written to prompt file)
161
+ await runCompaction({
162
+ conversationLogs: transcriptPath,
163
+ promptFile: prompt.path,
164
+ });
165
+ } catch {
166
+ // Compaction failed - error logged to trace store
167
+ }
168
+
169
+ // Kill the tmux window after compaction completes.
170
+ // PreCompact hook does NOT stop the Claude session - we must explicitly kill it.
171
+ if (agentId && windowExists(sessionName, agentId)) {
172
+ killWindow(sessionName, agentId);
173
+ }
174
+
175
+ outputPreCompact(undefined, HOOK_AGENT_COMPACT);
176
+ }
177
+
178
+ // ─────────────────────────────────────────────────────────────────────────────
179
+ // Hook Category Definition
180
+ // ─────────────────────────────────────────────────────────────────────────────
181
+
182
+ /** Lifecycle hooks category */
183
+ export const category: HookCategory = {
184
+ name: 'lifecycle',
185
+ description: 'Lifecycle hooks (Stop, PreCompact)',
186
+ hooks: [
187
+ {
188
+ name: 'agent-stop',
189
+ description: 'Handle agent stop event',
190
+ handler: handleAgentStop,
191
+ errorFallback: { type: 'outputStopHook', decision: 'approve' },
192
+ logPayload: () => ({ agentId: process.env.AGENT_ID }),
193
+ },
194
+ {
195
+ name: 'agent-compact',
196
+ description: 'Handle pre-compaction event',
197
+ handler: handleAgentCompact,
198
+ errorFallback: { type: 'continue' },
199
+ logPayload: () => ({
200
+ agentId: process.env.AGENT_ID,
201
+ promptNumber: process.env.PROMPT_NUMBER,
202
+ promptScoped: process.env.PROMPT_SCOPED,
203
+ specName: process.env.SPEC_NAME,
204
+ }),
205
+ },
206
+ ],
207
+ };
208
+
209
+ // ─────────────────────────────────────────────────────────────────────────────
210
+ // Command Registration
211
+ // ─────────────────────────────────────────────────────────────────────────────
212
+
213
+ /**
214
+ * Register lifecycle hook subcommands.
215
+ */
216
+ export function register(parent: Command): void {
217
+ registerCategory(parent, category);
218
+ }
219
+
220
+ // ─────────────────────────────────────────────────────────────────────────────
221
+ // Daemon Handler Registration
222
+ // ─────────────────────────────────────────────────────────────────────────────
223
+
224
+ /**
225
+ * Register handlers for daemon mode.
226
+ */
227
+ export function registerDaemonHandlers(register: RegisterFn): void {
228
+ registerCategoryForDaemon(category, register);
229
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Notification Hooks
3
+ *
4
+ * Hooks that send desktop notifications for various Claude Code events:
5
+ * - stop: Agent has stopped working (Stop:*)
6
+ * - compact: Context is being compacted (PreCompact:*)
7
+ *
8
+ * Uses jamf/Notifier for macOS notifications.
9
+ */
10
+
11
+ import type { Command } from 'commander';
12
+ import { HookInput, readHookInput } from './shared.js';
13
+ import { sendGateNotification } from '../lib/notification.js';
14
+ import { logHookStart, logHookSuccess } from '../lib/trace-store.js';
15
+
16
+ const HOOK_STOP = 'notification stop';
17
+ const HOOK_COMPACT = 'notification compact';
18
+
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+ // Stop: Agent stopped
21
+ // ─────────────────────────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Handle stop notification.
25
+ *
26
+ * Sends a notification when the agent has stopped working.
27
+ *
28
+ * Triggered by: Stop matcher "*"
29
+ */
30
+ function handleStopNotification(_input: HookInput): void {
31
+ sendGateNotification('Stopped', 'Agent has finished');
32
+
33
+ // Output approval to allow stop
34
+ logHookSuccess(HOOK_STOP, { action: 'approve' });
35
+ console.log(JSON.stringify({ decision: 'approve' }));
36
+ process.exit(0);
37
+ }
38
+
39
+ // ─────────────────────────────────────────────────────────────────────────────
40
+ // PreCompact: Context compaction
41
+ // ─────────────────────────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * Handle pre-compact notification.
45
+ *
46
+ * Sends a notification when the agent is about to compact context.
47
+ *
48
+ * Triggered by: PreCompact matcher "*"
49
+ */
50
+ function handleCompactNotification(_input: HookInput): void {
51
+ sendGateNotification('Compacting', 'Context is being summarized');
52
+
53
+ // Output to allow compaction to proceed
54
+ logHookSuccess(HOOK_COMPACT, { action: 'continue' });
55
+ console.log(JSON.stringify({ continue: true }));
56
+ process.exit(0);
57
+ }
58
+
59
+ // ─────────────────────────────────────────────────────────────────────────────
60
+ // Command Registration
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+
63
+ /**
64
+ * Register notification hook subcommands.
65
+ */
66
+ export function register(parent: Command): void {
67
+ const notification = parent
68
+ .command('notification')
69
+ .description('Notification hooks (desktop alerts for events)');
70
+
71
+ // ah hooks notification stop
72
+ notification
73
+ .command('stop')
74
+ .description('Handle stop notification (Stop:*)')
75
+ .action(async () => {
76
+ try {
77
+ const input = await readHookInput();
78
+ logHookStart(HOOK_STOP, {});
79
+ handleStopNotification(input);
80
+ } catch {
81
+ // On error, approve stop
82
+ logHookSuccess(HOOK_STOP, { action: 'approve', error: true });
83
+ console.log(JSON.stringify({ decision: 'approve' }));
84
+ process.exit(0);
85
+ }
86
+ });
87
+
88
+ // ah hooks notification compact
89
+ notification
90
+ .command('compact')
91
+ .description('Handle compact notification (PreCompact:*)')
92
+ .action(async () => {
93
+ try {
94
+ const input = await readHookInput();
95
+ logHookStart(HOOK_COMPACT, {});
96
+ handleCompactNotification(input);
97
+ } catch {
98
+ // On error, allow compaction
99
+ logHookSuccess(HOOK_COMPACT, { action: 'continue', error: true });
100
+ console.log(JSON.stringify({ continue: true }));
101
+ process.exit(0);
102
+ }
103
+ });
104
+ }