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,61 @@
1
+ /**
2
+ * Notification Commands
3
+ *
4
+ * Desktop notifications via jamf/Notifier (macOS).
5
+ * For direct CLI notification sending.
6
+ *
7
+ * Commands:
8
+ * - ah notify send <title> <message> - Send a notification
9
+ *
10
+ * For hook-based notifications (idle, elicitation), see:
11
+ * ah hooks notification --help
12
+ *
13
+ * Requires: https://github.com/jamf/Notifier
14
+ * Install: brew install --cask notifier
15
+ */
16
+
17
+ import { Command } from 'commander';
18
+ import { sendNotification } from '../lib/notification.js';
19
+ import { tracedAction } from '../lib/base-command.js';
20
+
21
+ export function register(program: Command): void {
22
+ const notify = program
23
+ .command('notify')
24
+ .description('Desktop notifications (direct CLI use)');
25
+
26
+ // ah notify send <title> <message>
27
+ notify
28
+ .command('send <title> <message>')
29
+ .description('Send a system notification')
30
+ .option('--sound <name>', 'Sound name (macOS system sounds)')
31
+ .option('-t, --type <type>', 'Notification type: banner or alert', 'banner')
32
+ .option('--json', 'Output as JSON')
33
+ .action(
34
+ tracedAction('notify send', async (
35
+ title: string,
36
+ message: string,
37
+ options: { sound?: string; type?: string; json?: boolean }
38
+ ) => {
39
+ const type = options.type as 'banner' | 'alert' | undefined;
40
+ const sent = sendNotification({
41
+ title,
42
+ message,
43
+ sound: options.sound,
44
+ type,
45
+ });
46
+
47
+ const result = sent
48
+ ? { success: true, sent: true, title, message }
49
+ : { success: false, sent: false, reason: 'notifier not available or failed' };
50
+
51
+ if (options.json) {
52
+ console.log(JSON.stringify(result, null, 2));
53
+ } else if (sent) {
54
+ console.log(`Notification sent: ${title}`);
55
+ } else {
56
+ console.error('Failed to send notification (notifier not installed?)');
57
+ process.exit(1);
58
+ }
59
+ })
60
+ );
61
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Oracle Commands (Agent-Facing)
3
+ *
4
+ * Multi-provider LLM inference for agent tasks.
5
+ * Uses the standardized oracle library for provider integration.
6
+ *
7
+ * Commands:
8
+ * - ah oracle ask <query> - Raw LLM inference with file context
9
+ * - ah oracle pr-build [--branch] [--dry-run] - Create PR with generated description
10
+ *
11
+ */
12
+
13
+ import { Command } from 'commander';
14
+ import { tracedAction } from '../lib/base-command.js';
15
+ import {
16
+ ask,
17
+ getDefaultProvider,
18
+ PROVIDERS,
19
+ type ProviderName,
20
+ } from '../lib/llm.js';
21
+ import { buildPR } from '../lib/oracle.js';
22
+
23
+ export function register(program: Command): void {
24
+ const oracle = program
25
+ .command('oracle')
26
+ .description('Multi-provider LLM inference');
27
+
28
+ // ah oracle ask
29
+ oracle
30
+ .command('ask <query>')
31
+ .description('Raw LLM inference with optional file context')
32
+ .option('--provider <provider>', 'LLM provider (gemini | openai)', getDefaultProvider())
33
+ .option('--model <model>', 'Override default model')
34
+ .option('--files <files...>', 'Files to include as context')
35
+ .option('--context <context>', 'Additional context')
36
+ .option('--json', 'Output as JSON')
37
+ .action(tracedAction('oracle ask', async (query: string, options: {
38
+ provider: ProviderName;
39
+ model?: string;
40
+ files?: string[];
41
+ context?: string;
42
+ json?: boolean;
43
+ }) => {
44
+ // Validate provider
45
+ if (!PROVIDERS[options.provider]) {
46
+ if (options.json) {
47
+ console.log(JSON.stringify({ success: false, error: 'Invalid provider. Use: gemini, openai' }));
48
+ } else {
49
+ console.error('Error: Invalid provider. Use: gemini, openai');
50
+ }
51
+ process.exit(1);
52
+ }
53
+
54
+ try {
55
+ const result = await ask(query, {
56
+ provider: options.provider,
57
+ model: options.model,
58
+ files: options.files,
59
+ context: options.context,
60
+ });
61
+
62
+ if (options.json) {
63
+ console.log(JSON.stringify({
64
+ success: true,
65
+ content: result.text,
66
+ model: result.model,
67
+ provider: result.provider,
68
+ duration_ms: result.durationMs,
69
+ }, null, 2));
70
+ return;
71
+ }
72
+
73
+ console.log(result.text);
74
+ } catch (e) {
75
+ const error = e instanceof Error ? e.message : String(e);
76
+ if (options.json) {
77
+ console.log(JSON.stringify({ success: false, error }));
78
+ } else {
79
+ console.error(`Error: ${error}`);
80
+ }
81
+ process.exit(1);
82
+ }
83
+ }));
84
+
85
+ // ah oracle pr-build
86
+ oracle
87
+ .command('pr-build')
88
+ .description('Create PR with generated description')
89
+ .option('--spec <spec>', 'Spec to create PR for (defaults to active)')
90
+ .option('--dry-run', 'Generate description without creating PR')
91
+ .option('--json', 'Output as JSON')
92
+ .action(tracedAction('oracle pr-build', async (options: {
93
+ spec?: string;
94
+ dryRun?: boolean;
95
+ json?: boolean;
96
+ }) => {
97
+ try {
98
+ // Import planning utils here to avoid circular dependency
99
+ const { getCurrentBranch, sanitizeBranchForDir, planningDirExists } = await import('../lib/planning.js');
100
+ const { getSpecForBranch } = await import('../lib/specs.js');
101
+
102
+ let spec = options.spec;
103
+ if (!spec) {
104
+ // Use current branch to find spec
105
+ const branch = getCurrentBranch();
106
+ const currentSpec = getSpecForBranch(branch);
107
+ if (currentSpec) {
108
+ spec = sanitizeBranchForDir(branch);
109
+ }
110
+ }
111
+ if (!spec) {
112
+ console.error('Error: No spec for current branch. Checkout a spec branch first.');
113
+ return;
114
+ }
115
+ const result = await buildPR(spec, undefined, options.dryRun);
116
+
117
+ if (options.json) {
118
+ console.log(JSON.stringify({
119
+ success: result.success,
120
+ pr_url: result.prUrl,
121
+ pr_number: result.prNumber,
122
+ title: result.title,
123
+ body: result.body,
124
+ review_steps: result.reviewSteps,
125
+ dry_run: options.dryRun || false,
126
+ }, null, 2));
127
+ return;
128
+ }
129
+
130
+ if (options.dryRun) {
131
+ console.log(`\n=== PR Preview (Dry Run) ===\n`);
132
+ console.log(`Title: ${result.title}`);
133
+ console.log(`\nBody:\n${result.body}`);
134
+ console.log(`\n=== Review Steps (Posted as Comment) ===\n`);
135
+ console.log(result.reviewSteps);
136
+ return;
137
+ }
138
+
139
+ if (result.success && result.prUrl) {
140
+ console.log(`\n=== PR Created ===\n`);
141
+ console.log(`URL: ${result.prUrl}`);
142
+ console.log(`Number: #${result.prNumber}`);
143
+ console.log(`Title: ${result.title}`);
144
+ } else {
145
+ console.error(`Failed to create PR: ${result.body}`);
146
+ process.exit(1);
147
+ }
148
+ } catch (e) {
149
+ const error = e instanceof Error ? e.message : String(e);
150
+ if (options.json) {
151
+ console.log(JSON.stringify({ success: false, error }));
152
+ } else {
153
+ console.error(`Error: ${error}`);
154
+ }
155
+ process.exit(1);
156
+ }
157
+ }));
158
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Perplexity Commands (Agent-Facing)
3
+ *
4
+ * Web search with citations using Perplexity's sonar-pro model.
5
+ * Matches the configuration used by the Perplexity MCP server.
6
+ *
7
+ * Commands:
8
+ * - ah perplexity research <query> - Web search with citations
9
+ * --challenge: Challenge findings using Grok X/Twitter search
10
+ */
11
+
12
+ import { Command } from 'commander';
13
+ import { tracedAction } from '../lib/base-command.js';
14
+
15
+ interface PerplexityResponse {
16
+ choices?: Array<{
17
+ message?: {
18
+ content?: string;
19
+ };
20
+ }>;
21
+ citations?: string[];
22
+ }
23
+
24
+ interface GrokResponse {
25
+ choices?: Array<{
26
+ message?: {
27
+ content?: string;
28
+ };
29
+ }>;
30
+ citations?: string[];
31
+ }
32
+
33
+ const GROK_CHALLENGER_PROMPT = `You are a critical research challenger. Given research findings, search X to:
34
+
35
+ 1. CHALLENGE: Find contradicting opinions, failed implementations, known issues
36
+ 2. ALTERNATIVES: Surface newer/better tools the research may have missed
37
+ 3. TRENDS: Identify emerging patterns that could affect the recommendations
38
+ 4. SENTIMENT: Gauge real developer satisfaction vs marketing claims
39
+ 5. DISCUSSIONS: Find where the best practitioners are discussing this topic
40
+
41
+ Be skeptical. Surface what the research missed or got wrong. Focus on recent posts (last 6 months).`;
42
+
43
+ const GROK_TIMEOUT = 120000;
44
+
45
+ const PERPLEXITY_TIMEOUT = parseInt(process.env.PERPLEXITY_TIMEOUT_MS ?? '60000', 10);
46
+
47
+ export function register(program: Command): void {
48
+ const perplexity = program
49
+ .command('perplexity')
50
+ .description('Web search with citations');
51
+
52
+ // ah perplexity research
53
+ perplexity
54
+ .command('research <query>')
55
+ .description('Web search with citations (sonar-pro model)')
56
+ .option('--json', 'Output as JSON')
57
+ .option('--challenge', 'Challenge findings using Grok X/Twitter search')
58
+ .action(tracedAction('perplexity research', async (query: string, options: { json?: boolean; challenge?: boolean }) => {
59
+ const apiKey = process.env.PERPLEXITY_API_KEY;
60
+ if (!apiKey) {
61
+ if (options.json) {
62
+ console.log(JSON.stringify({ success: false, error: 'PERPLEXITY_API_KEY not set' }));
63
+ } else {
64
+ console.error('Error: PERPLEXITY_API_KEY not set in environment');
65
+ }
66
+ process.exit(1);
67
+ }
68
+
69
+ try {
70
+ const response = await callPerplexityApi(apiKey, query);
71
+
72
+ const content = response.choices?.[0]?.message?.content ?? '';
73
+ const citations = response.citations ?? [];
74
+
75
+ // If --challenge flag, use Grok to challenge the findings
76
+ let challengeContent: string | undefined;
77
+ let challengeCitations: string[] | undefined;
78
+
79
+ if (options.challenge) {
80
+ const grokApiKey = process.env.X_AI_API_KEY;
81
+ if (!grokApiKey) {
82
+ if (options.json) {
83
+ console.log(JSON.stringify({ success: false, error: 'X_AI_API_KEY not set (required for --challenge)' }));
84
+ } else {
85
+ console.error('Error: X_AI_API_KEY not set in environment (required for --challenge)');
86
+ }
87
+ process.exit(1);
88
+ }
89
+
90
+ const grokResponse = await callGrokChallengeApi(grokApiKey, query, content);
91
+ challengeContent = grokResponse.choices?.[0]?.message?.content ?? '';
92
+ challengeCitations = grokResponse.citations ?? [];
93
+ }
94
+
95
+ if (options.json) {
96
+ const result: Record<string, unknown> = {
97
+ success: true,
98
+ query,
99
+ content,
100
+ citations,
101
+ };
102
+ if (options.challenge) {
103
+ result.challenge = {
104
+ content: challengeContent,
105
+ citations: challengeCitations,
106
+ };
107
+ }
108
+ console.log(JSON.stringify(result, null, 2));
109
+ return;
110
+ }
111
+
112
+ console.log('Research Results:');
113
+ console.log();
114
+ console.log(content);
115
+
116
+ if (citations.length > 0) {
117
+ console.log();
118
+ console.log('Citations:');
119
+ for (let i = 0; i < citations.length; i++) {
120
+ console.log(` [${i + 1}] ${citations[i]}`);
121
+ }
122
+ }
123
+
124
+ if (options.challenge && challengeContent) {
125
+ console.log();
126
+ console.log('---');
127
+ console.log();
128
+ console.log('Challenge (via X/Twitter):');
129
+ console.log();
130
+ console.log(challengeContent);
131
+
132
+ if (challengeCitations && challengeCitations.length > 0) {
133
+ console.log();
134
+ console.log('Challenge Sources:');
135
+ for (const citation of challengeCitations) {
136
+ console.log(` - ${citation}`);
137
+ }
138
+ }
139
+ }
140
+ } catch (e) {
141
+ const error = e instanceof Error ? e.message : String(e);
142
+ if (options.json) {
143
+ console.log(JSON.stringify({ success: false, error }));
144
+ } else {
145
+ console.error(`Error: ${error}`);
146
+ }
147
+ process.exit(1);
148
+ }
149
+ }));
150
+ }
151
+
152
+ async function callPerplexityApi(apiKey: string, query: string): Promise<PerplexityResponse> {
153
+ const controller = new AbortController();
154
+ const timeout = setTimeout(() => controller.abort(), PERPLEXITY_TIMEOUT);
155
+
156
+ try {
157
+ const response = await fetch('https://api.perplexity.ai/chat/completions', {
158
+ method: 'POST',
159
+ headers: {
160
+ Authorization: `Bearer ${apiKey}`,
161
+ 'Content-Type': 'application/json',
162
+ },
163
+ body: JSON.stringify({
164
+ model: 'sonar-pro',
165
+ messages: [{ role: 'user', content: query }],
166
+ }),
167
+ signal: controller.signal,
168
+ });
169
+
170
+ if (!response.ok) {
171
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
172
+ }
173
+
174
+ return (await response.json()) as PerplexityResponse;
175
+ } finally {
176
+ clearTimeout(timeout);
177
+ }
178
+ }
179
+
180
+ async function callGrokChallengeApi(
181
+ apiKey: string,
182
+ query: string,
183
+ findings: string
184
+ ): Promise<GrokResponse> {
185
+ const controller = new AbortController();
186
+ const timeout = setTimeout(() => controller.abort(), GROK_TIMEOUT);
187
+
188
+ const userPrompt = `Original query: ${query}
189
+
190
+ Research findings to challenge:
191
+ ${findings}
192
+
193
+ Search X to challenge these findings.`;
194
+
195
+ try {
196
+ const response = await fetch('https://api.x.ai/v1/chat/completions', {
197
+ method: 'POST',
198
+ headers: {
199
+ Authorization: `Bearer ${apiKey}`,
200
+ 'Content-Type': 'application/json',
201
+ },
202
+ body: JSON.stringify({
203
+ model: 'grok-4-1-fast',
204
+ messages: [
205
+ { role: 'system', content: GROK_CHALLENGER_PROMPT },
206
+ { role: 'user', content: userPrompt },
207
+ ],
208
+ }),
209
+ signal: controller.signal,
210
+ });
211
+
212
+ if (!response.ok) {
213
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
214
+ }
215
+
216
+ return (await response.json()) as GrokResponse;
217
+ } finally {
218
+ clearTimeout(timeout);
219
+ }
220
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Planning Command - Manage .planning/ directories (branch-keyed)
3
+ *
4
+ * Commands:
5
+ * - ah planning status - Show planning status for current branch
6
+ * - ah planning list - List all planning directories
7
+ * - ah planning ensure - Ensure planning dir exists for current branch
8
+ *
9
+ * In the branch-keyed model:
10
+ * - Planning directories are keyed by sanitized branch name (feature/foo → feature-foo)
11
+ * - The spec's frontmatter.branch field is the source of truth
12
+ * - Current git branch determines the active context
13
+ */
14
+
15
+ import { Command } from 'commander';
16
+ import {
17
+ initializeStatus,
18
+ readStatus,
19
+ getPlanningPaths,
20
+ planningDirExists,
21
+ getCurrentBranch,
22
+ sanitizeBranchForDir,
23
+ ensurePlanningDir,
24
+ listPlanningDirs,
25
+ resetPlanningArtifacts,
26
+ } from '../lib/planning.js';
27
+ import { getSpecForBranch } from '../lib/specs.js';
28
+ import { tracedAction } from '../lib/base-command.js';
29
+
30
+ export function register(program: Command): void {
31
+ const cmd = program
32
+ .command('planning')
33
+ .description('Manage .planning/ directories (branch-keyed)');
34
+
35
+ // ah planning status
36
+ cmd
37
+ .command('status')
38
+ .description('Show planning status for current branch')
39
+ .option('--json', 'Output as JSON')
40
+ .action(tracedAction('planning status', async (options: { json?: boolean }) => {
41
+ const cwd = process.cwd();
42
+ const branch = getCurrentBranch(cwd);
43
+ const dirKey = sanitizeBranchForDir(branch);
44
+
45
+ // Find spec for current branch
46
+ const spec = getSpecForBranch(branch, cwd);
47
+ const status = readStatus(dirKey, cwd);
48
+
49
+ if (!spec) {
50
+ if (options.json) {
51
+ console.log(JSON.stringify({
52
+ success: true,
53
+ hasSpec: false,
54
+ hasPlanning: !!status,
55
+ branch,
56
+ message: 'No spec for this branch',
57
+ }, null, 2));
58
+ } else {
59
+ console.log(`Branch: ${branch}`);
60
+ console.log('No spec linked to this branch.');
61
+ if (status) {
62
+ console.log(` (Planning dir exists: .planning/${dirKey}/)`);
63
+ }
64
+ }
65
+ return;
66
+ }
67
+
68
+ if (!status) {
69
+ if (options.json) {
70
+ console.log(JSON.stringify({
71
+ success: true,
72
+ hasSpec: true,
73
+ hasPlanning: false,
74
+ branch,
75
+ spec: {
76
+ id: spec.id,
77
+ path: spec.path,
78
+ },
79
+ message: 'Spec exists but no planning directory. Run "ah planning ensure".',
80
+ }, null, 2));
81
+ } else {
82
+ console.log(`Branch: ${branch}`);
83
+ console.log(`Spec: ${spec.id}`);
84
+ console.log('No planning directory. Run "ah planning ensure" to create it.');
85
+ }
86
+ return;
87
+ }
88
+
89
+ if (options.json) {
90
+ console.log(JSON.stringify({
91
+ success: true,
92
+ hasSpec: true,
93
+ hasPlanning: true,
94
+ branch,
95
+ planningDir: `.planning/${dirKey}/`,
96
+ specInfo: {
97
+ id: spec.id,
98
+ path: spec.path,
99
+ },
100
+ ...status,
101
+ }, null, 2));
102
+ } else {
103
+ console.log(`Branch: ${branch}`);
104
+ console.log(`Spec: ${spec.id} (${spec.path})`);
105
+ console.log(`Planning: .planning/${dirKey}/`);
106
+ console.log(`Stage: ${status.stage}`);
107
+ if (status.pr) {
108
+ console.log(`PR: ${status.pr.url}`);
109
+ }
110
+ }
111
+ }));
112
+
113
+ // ah planning list
114
+ cmd
115
+ .command('list')
116
+ .description('List all planning directories')
117
+ .option('--json', 'Output as JSON')
118
+ .action(tracedAction('planning list', async (options: { json?: boolean }) => {
119
+ const cwd = process.cwd();
120
+ const dirs = listPlanningDirs(cwd);
121
+
122
+ if (options.json) {
123
+ console.log(JSON.stringify({
124
+ success: true,
125
+ count: dirs.length,
126
+ dirs,
127
+ }, null, 2));
128
+ } else {
129
+ if (dirs.length === 0) {
130
+ console.log('No planning directories found.');
131
+ return;
132
+ }
133
+
134
+ console.log(`Found ${dirs.length} planning director${dirs.length === 1 ? 'y' : 'ies'}:\n`);
135
+ for (const dir of dirs) {
136
+ const marker = dir.isCurrent ? '→ ' : ' ';
137
+ console.log(`${marker}${dir.key}/`);
138
+ console.log(` Spec: ${dir.specPath}`);
139
+ console.log(` Stage: ${dir.stage}`);
140
+ }
141
+ }
142
+ }));
143
+
144
+ // ah planning ensure
145
+ cmd
146
+ .command('ensure')
147
+ .description('Ensure planning directory exists for current branch')
148
+ .option('--json', 'Output as JSON')
149
+ .action(tracedAction('planning ensure', async (options: { json?: boolean }) => {
150
+ const cwd = process.cwd();
151
+ const branch = getCurrentBranch(cwd);
152
+
153
+ // Find spec for this branch
154
+ const spec = getSpecForBranch(branch, cwd);
155
+
156
+ if (!spec) {
157
+ if (options.json) {
158
+ console.log(JSON.stringify({
159
+ success: false,
160
+ error: `No spec found for branch: ${branch}`,
161
+ branch,
162
+ }, null, 2));
163
+ } else {
164
+ console.error(`No spec found for branch: ${branch}`);
165
+ console.error('Use "ah specs current" to check branch-spec mapping.');
166
+ }
167
+ process.exit(1);
168
+ }
169
+
170
+ // Use sanitized branch name for directory
171
+ const dirKey = sanitizeBranchForDir(branch);
172
+ const alreadyExists = planningDirExists(dirKey, cwd);
173
+
174
+ if (!alreadyExists) {
175
+ // Create planning directory structure
176
+ ensurePlanningDir(dirKey, cwd);
177
+
178
+ // Initialize status file with original branch for collision detection
179
+ initializeStatus(dirKey, spec.path, branch, cwd);
180
+ }
181
+
182
+ const status = readStatus(dirKey, cwd);
183
+
184
+ if (options.json) {
185
+ console.log(JSON.stringify({
186
+ success: true,
187
+ branch,
188
+ specId: spec.id,
189
+ specPath: spec.path,
190
+ planningDir: `.planning/${dirKey}/`,
191
+ created: !alreadyExists,
192
+ status,
193
+ }, null, 2));
194
+ } else {
195
+ if (alreadyExists) {
196
+ console.log(`Planning directory exists: .planning/${dirKey}/`);
197
+ } else {
198
+ console.log(`Created planning directory: .planning/${dirKey}/`);
199
+ }
200
+ console.log(` Branch: ${branch}`);
201
+ console.log(` Spec: ${spec.id} (${spec.path})`);
202
+ console.log(` Stage: ${status?.stage || 'planning'}`);
203
+ }
204
+ }));
205
+
206
+ // ah planning reset
207
+ cmd
208
+ .command('reset')
209
+ .description('Reset planning artifacts for current branch (deletes prompts and alignment doc, resets stage to planning)')
210
+ .option('--json', 'Output as JSON')
211
+ .action(tracedAction('planning reset', async (options: { json?: boolean }) => {
212
+ const cwd = process.cwd();
213
+ const branch = getCurrentBranch(cwd);
214
+ const dirKey = sanitizeBranchForDir(branch);
215
+
216
+ if (!planningDirExists(dirKey, cwd)) {
217
+ if (options.json) {
218
+ console.log(JSON.stringify({
219
+ success: false,
220
+ error: `No planning directory for branch: ${branch}`,
221
+ branch,
222
+ }, null, 2));
223
+ } else {
224
+ console.error(`No planning directory for branch: ${branch}`);
225
+ }
226
+ process.exit(1);
227
+ }
228
+
229
+ const wasReset = resetPlanningArtifacts(dirKey, cwd);
230
+
231
+ if (options.json) {
232
+ console.log(JSON.stringify({
233
+ success: wasReset,
234
+ branch,
235
+ planningDir: `.planning/${dirKey}/`,
236
+ message: 'Planning artifacts cleared — spec revision requires re-planning',
237
+ }, null, 2));
238
+ } else {
239
+ console.log('Planning artifacts cleared — spec revision requires re-planning');
240
+ console.log(` Branch: ${branch}`);
241
+ console.log(` Planning: .planning/${dirKey}/`);
242
+ console.log(` Stage: planning`);
243
+ }
244
+ }));
245
+ }