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,198 @@
1
+ /**
2
+ * BaseCommand - Foundation for all ah CLI commands
3
+ *
4
+ * Provides:
5
+ * - Agent context tracking (AGENT_TYPE, PROMPT_NUMBER, SPEC_NAME)
6
+ * - JSON vs human-friendly output modes
7
+ * - Standard error handling
8
+ * - Trace logging (SQLite + JSONL)
9
+ */
10
+
11
+ import { logCommandStart, logCommandSuccess, logCommandError } from './trace-store.js';
12
+
13
+ export interface CommandContext {
14
+ /** Agent type (executor, coordinator, planner, judge, ideation, pr-reviewer) */
15
+ agentType?: string;
16
+ /** Current prompt number (e.g., "01") */
17
+ promptNumber?: string;
18
+ /** Current spec name */
19
+ specName?: string;
20
+ /** Output format */
21
+ json: boolean;
22
+ /** Verbose logging */
23
+ verbose: boolean;
24
+ }
25
+
26
+ export interface CommandResult<T = unknown> {
27
+ success: boolean;
28
+ data?: T;
29
+ error?: string;
30
+ details?: string;
31
+ }
32
+
33
+ /**
34
+ * Get context from environment variables (set by tmux window spawner)
35
+ */
36
+ export function getEnvContext(): Partial<CommandContext> {
37
+ return {
38
+ agentType: process.env.AGENT_TYPE,
39
+ promptNumber: process.env.PROMPT_NUMBER,
40
+ specName: process.env.SPEC_NAME,
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Format output based on context
46
+ */
47
+ export function formatOutput<T>(result: CommandResult<T>, context: CommandContext): string {
48
+ if (context.json) {
49
+ return JSON.stringify(result, null, 2);
50
+ }
51
+
52
+ if (!result.success) {
53
+ let output = `Error: ${result.error}`;
54
+ if (result.details) {
55
+ output += `\n${result.details}`;
56
+ }
57
+ return output;
58
+ }
59
+
60
+ if (typeof result.data === 'string') {
61
+ return result.data;
62
+ }
63
+
64
+ return JSON.stringify(result.data, null, 2);
65
+ }
66
+
67
+ /**
68
+ * Parse common command options into context
69
+ */
70
+ export function parseContext(options: {
71
+ agent?: string;
72
+ json?: boolean;
73
+ verbose?: boolean;
74
+ }): CommandContext {
75
+ const envContext = getEnvContext();
76
+ return {
77
+ agentType: options.agent || envContext.agentType,
78
+ promptNumber: envContext.promptNumber,
79
+ specName: envContext.specName,
80
+ json: options.json ?? false,
81
+ verbose: options.verbose ?? false,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Execute a command with standard error handling and trace logging
87
+ */
88
+ export async function executeCommand<T>(
89
+ name: string,
90
+ context: CommandContext,
91
+ fn: () => Promise<CommandResult<T>>,
92
+ args: Record<string, unknown> = {}
93
+ ): Promise<void> {
94
+ // Log command start to trace store
95
+ logCommandStart(name, { ...args, context });
96
+
97
+ try {
98
+ const result = await fn();
99
+
100
+ const output = formatOutput(result, context);
101
+ if (result.success) {
102
+ // Log success to trace store
103
+ logCommandSuccess(name, { data: result.data });
104
+ console.log(output);
105
+ } else {
106
+ // Log error to trace store
107
+ logCommandError(name, result.error || 'Unknown error', args);
108
+ console.error(output);
109
+ process.exit(1);
110
+ }
111
+ } catch (err) {
112
+ const error = err instanceof Error ? err.message : String(err);
113
+
114
+ // Log error to trace store
115
+ logCommandError(name, error, args);
116
+
117
+ const result: CommandResult<T> = {
118
+ success: false,
119
+ error: `Command failed: ${error}`,
120
+ };
121
+ console.error(formatOutput(result, context));
122
+ process.exit(1);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Add common options to a commander command
128
+ */
129
+ export function addCommonOptions(cmd: { option: (flags: string, description: string) => unknown }) {
130
+ cmd.option('--agent <type>', 'Agent type for logging context');
131
+ cmd.option('--json', 'Output as JSON (for agent consumption)');
132
+ cmd.option('-v, --verbose', 'Enable verbose logging');
133
+ return cmd;
134
+ }
135
+
136
+ /**
137
+ * Wrap a Commander action handler with trace logging.
138
+ * Use this for commands that don't use executeCommand.
139
+ *
140
+ * @example
141
+ * .action(tracedAction('specs persist', async (path, options) => {
142
+ * // command implementation
143
+ * }))
144
+ */
145
+ export function tracedAction<TArgs extends unknown[]>(
146
+ commandName: string,
147
+ handler: (...args: TArgs) => Promise<void> | void
148
+ ): (...args: TArgs) => Promise<void> {
149
+ return async (...args: TArgs) => {
150
+ // Extract args for logging (filter out Commander object at the end)
151
+ const logArgs: Record<string, unknown> = {};
152
+ args.slice(0, -1).forEach((arg, i) => {
153
+ logArgs[`arg${i}`] = arg;
154
+ });
155
+
156
+ logCommandStart(commandName, logArgs);
157
+
158
+ try {
159
+ await handler(...args);
160
+ // If we get here without process.exit, it succeeded
161
+ logCommandSuccess(commandName, {});
162
+ } catch (err) {
163
+ const error = err instanceof Error ? err.message : String(err);
164
+ logCommandError(commandName, error, logArgs);
165
+ throw err; // Re-throw to let Commander handle it
166
+ }
167
+ };
168
+ }
169
+
170
+ /**
171
+ * BaseCommand - Abstract base class for command implementations.
172
+ * Provides helper methods for success/error responses.
173
+ */
174
+ export abstract class BaseCommand {
175
+ abstract readonly name: string;
176
+ abstract readonly description: string;
177
+
178
+ abstract defineArguments(cmd: import('commander').Command): void;
179
+ abstract execute(args: Record<string, unknown>): Promise<CommandResult>;
180
+
181
+ /**
182
+ * Create a success result
183
+ */
184
+ protected success<T>(data: T): CommandResult<T> {
185
+ return { success: true, data };
186
+ }
187
+
188
+ /**
189
+ * Create an error result
190
+ */
191
+ protected error(code: string, message: string, details?: string): CommandResult {
192
+ return {
193
+ success: false,
194
+ error: `${code}: ${message}`,
195
+ details,
196
+ };
197
+ }
198
+ }
@@ -0,0 +1,343 @@
1
+ /**
2
+ * CLI Daemon - TUI-hosted socket server for fast hook execution.
3
+ *
4
+ * When the TUI is running, hooks connect via Unix socket instead of
5
+ * spawning a fresh Node.js process. This eliminates ~400ms startup
6
+ * overhead per hook invocation.
7
+ *
8
+ * The daemon runs hooks by intercepting stdout and process.exit(),
9
+ * so hooks don't need any modification.
10
+ *
11
+ * Socket path: .allhands/harness/.cache/cli-daemon.sock
12
+ */
13
+
14
+ import { createServer, createConnection, type Server, type Socket } from 'net';
15
+ import { existsSync, mkdirSync, unlinkSync } from 'fs';
16
+ import { dirname, join } from 'path';
17
+ import type { HookInput } from '../hooks/shared.js';
18
+
19
+ // Signal class to catch process.exit() calls
20
+ class ExitSignal extends Error {
21
+ constructor(public code: number = 0) {
22
+ super(`Exit with code ${code}`);
23
+ this.name = 'ExitSignal';
24
+ }
25
+ }
26
+
27
+ // Hook handler type - takes input and may call process.exit/write to stdout
28
+ type HookHandler = (input: HookInput) => void | Promise<void>;
29
+
30
+ // Registry of hook handlers, keyed by "category.name"
31
+ const handlers = new Map<string, HookHandler>();
32
+
33
+ export interface DaemonCommand {
34
+ cmd: 'hook' | 'ping' | 'shutdown' | 'list';
35
+ category?: string; // e.g., 'context', 'validation', 'observability'
36
+ name?: string; // e.g., 'tldr-inject', 'signature'
37
+ input?: HookInput;
38
+ }
39
+
40
+ /**
41
+ * Get the socket path for the CLI daemon.
42
+ */
43
+ export function getSocketPath(projectDir: string): string {
44
+ return join(projectDir, '.allhands', 'harness', '.cache', 'cli-daemon.sock');
45
+ }
46
+
47
+ /**
48
+ * Check if the daemon socket exists.
49
+ */
50
+ export function isDaemonRunning(projectDir: string): boolean {
51
+ return existsSync(getSocketPath(projectDir));
52
+ }
53
+
54
+ /**
55
+ * Register a hook handler for daemon mode.
56
+ */
57
+ export function registerHandler(category: string, name: string, handler: HookHandler): void {
58
+ handlers.set(`${category}.${name}`, handler);
59
+ }
60
+
61
+ /**
62
+ * Run a hook handler with intercepted I/O.
63
+ * Captures stdout and catches process.exit() calls.
64
+ */
65
+ async function runWithInterceptedIO(handler: HookHandler, input: HookInput): Promise<string> {
66
+ let output = '';
67
+
68
+ // Save originals
69
+ const originalExit = process.exit;
70
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
71
+ const originalViaDaemon = process.env.AH_VIA_DAEMON;
72
+
73
+ // Mark as running via daemon for trace logging
74
+ process.env.AH_VIA_DAEMON = '1';
75
+
76
+ // Intercept process.exit
77
+ process.exit = ((code?: number) => {
78
+ throw new ExitSignal(code ?? 0);
79
+ }) as typeof process.exit;
80
+
81
+ // Intercept stdout.write
82
+ process.stdout.write = ((
83
+ chunk: string | Uint8Array,
84
+ encodingOrCallback?: BufferEncoding | ((err?: Error) => void),
85
+ callback?: (err?: Error) => void
86
+ ): boolean => {
87
+ const str = typeof chunk === 'string' ? chunk : chunk.toString();
88
+ output += str;
89
+ // Call callback if provided
90
+ const cb = typeof encodingOrCallback === 'function' ? encodingOrCallback : callback;
91
+ if (cb) cb();
92
+ return true;
93
+ }) as typeof process.stdout.write;
94
+
95
+ try {
96
+ await handler(input);
97
+ } catch (e) {
98
+ if (!(e instanceof ExitSignal)) {
99
+ throw e;
100
+ }
101
+ // ExitSignal is expected - hook completed normally
102
+ } finally {
103
+ // Restore originals
104
+ process.exit = originalExit;
105
+ process.stdout.write = originalStdoutWrite;
106
+ if (originalViaDaemon === undefined) {
107
+ delete process.env.AH_VIA_DAEMON;
108
+ } else {
109
+ process.env.AH_VIA_DAEMON = originalViaDaemon;
110
+ }
111
+ }
112
+
113
+ return output.trim();
114
+ }
115
+
116
+ /**
117
+ * Process a daemon command.
118
+ */
119
+ async function processCommand(command: DaemonCommand): Promise<unknown> {
120
+ switch (command.cmd) {
121
+ case 'hook': {
122
+ if (!command.category || !command.name) {
123
+ return { success: false, error: 'Missing category or name' };
124
+ }
125
+
126
+ const key = `${command.category}.${command.name}`;
127
+ const handler = handlers.get(key);
128
+
129
+ if (!handler) {
130
+ // Handler not registered - return fallback signal
131
+ return { success: true, output: '', fallback: true };
132
+ }
133
+
134
+ try {
135
+ const input: HookInput = command.input || {};
136
+ // Claude Code PostToolUse sends tool_response; normalize to tool_result
137
+ if (input.tool_response !== undefined && input.tool_result === undefined) {
138
+ input.tool_result = input.tool_response;
139
+ }
140
+ const output = await runWithInterceptedIO(handler, input);
141
+ return { success: true, output };
142
+ } catch (e) {
143
+ return {
144
+ success: false,
145
+ error: e instanceof Error ? e.message : String(e),
146
+ };
147
+ }
148
+ }
149
+
150
+ case 'list':
151
+ return {
152
+ success: true,
153
+ handlers: Array.from(handlers.keys()),
154
+ };
155
+
156
+ case 'ping':
157
+ return { success: true, pong: true, handlers: handlers.size };
158
+
159
+ case 'shutdown':
160
+ return { success: true, shutting_down: true };
161
+
162
+ default:
163
+ return { success: false, error: 'Unknown command' };
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Handle a client connection.
169
+ */
170
+ function createConnectionHandler() {
171
+ return function handleConnection(socket: Socket): void {
172
+ let buffer = '';
173
+
174
+ socket.on('data', async (data) => {
175
+ buffer += data.toString();
176
+
177
+ // Process complete JSON messages (newline-delimited)
178
+ const lines = buffer.split('\n');
179
+ buffer = lines.pop() ?? '';
180
+
181
+ for (const line of lines) {
182
+ if (!line.trim()) continue;
183
+
184
+ try {
185
+ const command = JSON.parse(line) as DaemonCommand;
186
+ const result = await processCommand(command);
187
+ socket.write(JSON.stringify(result) + '\n');
188
+
189
+ // Handle shutdown after responding
190
+ if (command.cmd === 'shutdown') {
191
+ process.nextTick(() => socket.end());
192
+ }
193
+ } catch (e) {
194
+ socket.write(JSON.stringify({
195
+ success: false,
196
+ error: e instanceof Error ? e.message : String(e),
197
+ }) + '\n');
198
+ }
199
+ }
200
+ });
201
+
202
+ socket.on('error', () => {
203
+ // Client disconnected, ignore
204
+ });
205
+ };
206
+ }
207
+
208
+ /**
209
+ * CLI Daemon instance.
210
+ */
211
+ export class CLIDaemon {
212
+ private server: Server | null = null;
213
+ private socketPath: string;
214
+
215
+ constructor(private projectDir: string) {
216
+ this.socketPath = getSocketPath(projectDir);
217
+ }
218
+
219
+ /**
220
+ * Start the daemon server.
221
+ */
222
+ async start(): Promise<void> {
223
+ if (this.server) return;
224
+
225
+ // Ensure cache directory exists
226
+ const cacheDir = dirname(this.socketPath);
227
+ if (!existsSync(cacheDir)) {
228
+ mkdirSync(cacheDir, { recursive: true });
229
+ }
230
+
231
+ // Check if socket exists and is in use
232
+ if (existsSync(this.socketPath)) {
233
+ const isInUse = await this.isSocketInUse();
234
+ if (isInUse) {
235
+ // Another daemon is running - don't start a second one
236
+ console.error('CLI daemon already running, skipping start');
237
+ return;
238
+ }
239
+ // Socket exists but is stale - clean it up
240
+ try {
241
+ unlinkSync(this.socketPath);
242
+ } catch {
243
+ // Ignore
244
+ }
245
+ }
246
+
247
+ // Load and register hook handlers
248
+ await this.loadHandlers();
249
+
250
+ this.server = createServer(createConnectionHandler());
251
+
252
+ this.server.listen(this.socketPath, () => {
253
+ // Socket ready
254
+ });
255
+
256
+ this.server.on('error', (e) => {
257
+ console.error('CLI daemon error:', e);
258
+ });
259
+ }
260
+
261
+ /**
262
+ * Load hook modules and register their handlers.
263
+ * Each module exports handler functions that we register here.
264
+ */
265
+ private async loadHandlers(): Promise<void> {
266
+ try {
267
+ // Import hook modules - they'll register their handlers
268
+ const contextModule = await import('../hooks/context.js');
269
+ const observabilityModule = await import('../hooks/observability.js');
270
+ const validationModule = await import('../hooks/validation.js');
271
+ const lifecycleModule = await import('../hooks/lifecycle.js');
272
+ const enforcementModule = await import('../hooks/enforcement.js');
273
+
274
+ // Register handlers from each module if they export a registerDaemonHandlers function
275
+ for (const mod of [contextModule, observabilityModule, validationModule, lifecycleModule, enforcementModule]) {
276
+ if (typeof mod.registerDaemonHandlers === 'function') {
277
+ mod.registerDaemonHandlers(registerHandler);
278
+ }
279
+ }
280
+ } catch (e) {
281
+ console.error('Failed to load hook modules:', e);
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Stop the daemon server.
287
+ */
288
+ stop(): void {
289
+ if (!this.server) return;
290
+
291
+ this.server.close();
292
+ this.server = null;
293
+
294
+ // Clean up socket file
295
+ if (existsSync(this.socketPath)) {
296
+ try {
297
+ unlinkSync(this.socketPath);
298
+ } catch {
299
+ // Ignore
300
+ }
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Check if the socket is currently in use by another daemon.
306
+ * Attempts to connect to the socket - if successful, it's in use.
307
+ */
308
+ private async isSocketInUse(): Promise<boolean> {
309
+ return new Promise((resolve) => {
310
+ const client = createConnection(this.socketPath);
311
+
312
+ const timeout = setTimeout(() => {
313
+ client.destroy();
314
+ resolve(false); // Timed out - socket is stale
315
+ }, 500);
316
+
317
+ client.on('connect', () => {
318
+ clearTimeout(timeout);
319
+ // Send a ping to verify it's actually a daemon
320
+ client.write('{"cmd":"ping"}\n');
321
+ });
322
+
323
+ client.on('data', () => {
324
+ clearTimeout(timeout);
325
+ client.destroy();
326
+ resolve(true); // Got a response - daemon is running
327
+ });
328
+
329
+ client.on('error', () => {
330
+ clearTimeout(timeout);
331
+ client.destroy();
332
+ resolve(false); // Connection failed - socket is stale
333
+ });
334
+ });
335
+ }
336
+
337
+ /**
338
+ * Get count of registered handlers.
339
+ */
340
+ getHandlerCount(): number {
341
+ return handlers.size;
342
+ }
343
+ }