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,73 @@
1
+ /**
2
+ * Schema Command (Agent-Facing)
3
+ *
4
+ * Outputs schema definitions for file types.
5
+ * Agents use this to understand how to write valid files.
6
+ *
7
+ * Usage: ah schema <type> [property]
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import { existsSync, readFileSync, readdirSync } from 'fs';
12
+ import { join, dirname } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ import { parse, stringify } from 'yaml';
15
+ import { tracedAction } from '../lib/base-command.js';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+
20
+ export function register(program: Command): void {
21
+ program
22
+ .command('schema <type> [property]')
23
+ .description('Output schema for a file type (prompt, alignment, spec, documentation). Optionally specify a top-level property to inspect.')
24
+ .option('--json', 'Output as JSON instead of YAML')
25
+ .action(tracedAction('schema', async (type: string, property: string | undefined, options: { json?: boolean }) => {
26
+ // Path: harness/src/commands/ -> harness/src/ -> harness/ -> .allhands/ -> schemas/
27
+ const schemaDir = join(__dirname, '..', '..', '..', 'schemas');
28
+ const schemaPath = join(schemaDir, `${type}.yaml`);
29
+
30
+ if (!existsSync(schemaPath)) {
31
+ const available = getAvailableSchemas(schemaDir);
32
+ console.error(`Schema not found: ${type}`);
33
+ console.error(`Available schemas: ${available.join(', ')}`);
34
+ process.exit(1);
35
+ }
36
+
37
+ const content = readFileSync(schemaPath, 'utf-8');
38
+ const parsed = parse(content);
39
+
40
+ // If a property is specified, extract only that top-level property
41
+ if (property) {
42
+ if (!(property in parsed)) {
43
+ const availableProps = Object.keys(parsed);
44
+ console.warn(`Warning: Property '${property}' not found in schema '${type}'`);
45
+ console.warn(`Available top-level properties: ${availableProps.join(', ')}`);
46
+ process.exit(1);
47
+ }
48
+
49
+ const subset = { [property]: parsed[property] };
50
+ if (options.json) {
51
+ console.log(JSON.stringify(subset, null, 2));
52
+ } else {
53
+ console.log(stringify(subset));
54
+ }
55
+ return;
56
+ }
57
+
58
+ // Output full schema
59
+ if (options.json) {
60
+ console.log(JSON.stringify(parsed, null, 2));
61
+ } else {
62
+ console.log(content);
63
+ }
64
+ }));
65
+ }
66
+
67
+ function getAvailableSchemas(schemaDir: string): string[] {
68
+ if (!existsSync(schemaDir)) return [];
69
+
70
+ return readdirSync(schemaDir)
71
+ .filter((f) => f.endsWith('.yaml'))
72
+ .map((f) => f.replace('.yaml', ''));
73
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Skills Command (Agent-Facing)
3
+ *
4
+ * Lists and discovers skills for domain expertise.
5
+ * Agents use this to find relevant skills for their tasks.
6
+ *
7
+ * Usage: ah skills list
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
12
+ import { join, dirname } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ import { parse as parseYaml } from 'yaml';
15
+ import { tracedAction } from '../lib/base-command.js';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+
20
+ interface SkillFrontmatter {
21
+ name: string;
22
+ description: string;
23
+ globs: string[];
24
+ version?: string;
25
+ license?: string;
26
+ }
27
+
28
+ interface SkillEntry {
29
+ name: string;
30
+ description: string;
31
+ globs: string[];
32
+ file: string;
33
+ }
34
+
35
+ /**
36
+ * Extract frontmatter from markdown content
37
+ */
38
+ function extractFrontmatter(content: string): Record<string, unknown> | null {
39
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---/;
40
+ const match = content.match(frontmatterRegex);
41
+
42
+ if (!match) {
43
+ return null;
44
+ }
45
+
46
+ try {
47
+ return parseYaml(match[1]) as Record<string, unknown>;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Get the skills directory path
55
+ * Path: harness/src/commands/ -> harness/src/ -> harness/ -> .allhands/ -> skills/
56
+ */
57
+ function getSkillsDir(): string {
58
+ return join(__dirname, '..', '..', '..', 'skills');
59
+ }
60
+
61
+ /**
62
+ * List all skills by reading SKILL.md files and extracting frontmatter
63
+ */
64
+ function listSkills(): SkillEntry[] {
65
+ const dir = getSkillsDir();
66
+
67
+ if (!existsSync(dir)) {
68
+ return [];
69
+ }
70
+
71
+ const entries = readdirSync(dir);
72
+ const skills: SkillEntry[] = [];
73
+
74
+ for (const entry of entries) {
75
+ const entryPath = join(dir, entry);
76
+ const stat = statSync(entryPath);
77
+
78
+ // Skip non-directories
79
+ if (!stat.isDirectory()) continue;
80
+
81
+ // Look for SKILL.md in the directory
82
+ const skillFile = join(entryPath, 'SKILL.md');
83
+ if (!existsSync(skillFile)) continue;
84
+
85
+ const content = readFileSync(skillFile, 'utf-8');
86
+ const frontmatter = extractFrontmatter(content) as SkillFrontmatter | null;
87
+
88
+ if (frontmatter && frontmatter.name && frontmatter.description && frontmatter.globs) {
89
+ skills.push({
90
+ name: frontmatter.name,
91
+ description: frontmatter.description,
92
+ globs: frontmatter.globs,
93
+ file: `.allhands/skills/${entry}/SKILL.md`,
94
+ });
95
+ }
96
+ }
97
+
98
+ return skills;
99
+ }
100
+
101
+ export function register(program: Command): void {
102
+ const cmd = program
103
+ .command('skills')
104
+ .description('Discover and list skills for domain expertise');
105
+
106
+ cmd
107
+ .command('list')
108
+ .description('List all skills with their descriptions and glob patterns')
109
+ .option('--json', 'Output as JSON (default)')
110
+ .action(tracedAction('skills list', async () => {
111
+ const skills = listSkills();
112
+
113
+ if (skills.length === 0) {
114
+ console.log(JSON.stringify({
115
+ success: true,
116
+ skills: [],
117
+ message: 'No skills found. Create skills in .allhands/skills/<name>/SKILL.md using `ah schema skill` for the file structure.',
118
+ }, null, 2));
119
+ return;
120
+ }
121
+
122
+ console.log(JSON.stringify({
123
+ success: true,
124
+ skills,
125
+ count: skills.length,
126
+ }, null, 2));
127
+ }));
128
+ }
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Solutions Command (Agent-Facing)
3
+ *
4
+ * Grep-based search for documented solutions in docs/solutions/.
5
+ * Uses frontmatter fields (tags, module, component, symptoms) for precise matching.
6
+ *
7
+ * Usage:
8
+ * ah solutions search <query> Search solutions by keywords
9
+ * ah solutions search <query> --full Include full content of matches
10
+ * ah solutions list List all solution categories
11
+ * ah solutions list <category> List solutions in a category
12
+ */
13
+
14
+ import { Command } from 'commander';
15
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
16
+ import { basename, join } from 'path';
17
+ import { parse } from 'yaml';
18
+ import { tracedAction } from '../lib/base-command.js';
19
+
20
+ const getProjectRoot = (): string => {
21
+ return process.env.PROJECT_ROOT || process.cwd();
22
+ };
23
+
24
+ const getSolutionsDir = (): string => {
25
+ return join(getProjectRoot(), 'docs', 'solutions');
26
+ };
27
+
28
+ interface SolutionFrontmatter {
29
+ title: string;
30
+ date: string;
31
+ spec?: string;
32
+ problem_type: string;
33
+ component: string;
34
+ symptoms: string[];
35
+ root_cause: string;
36
+ severity: string;
37
+ tags: string[];
38
+ source?: string;
39
+ }
40
+
41
+ interface SolutionMatch {
42
+ path: string;
43
+ frontmatter: SolutionFrontmatter;
44
+ score: number;
45
+ matchedFields: string[];
46
+ }
47
+
48
+ /**
49
+ * Extract keywords from a search query.
50
+ * Handles quoted phrases and splits on whitespace.
51
+ */
52
+ function extractKeywords(query: string): string[] {
53
+ const keywords: string[] = [];
54
+
55
+ // Extract quoted phrases first
56
+ const quotedRegex = /"([^"]+)"/g;
57
+ let match;
58
+ while ((match = quotedRegex.exec(query)) !== null) {
59
+ keywords.push(match[1].toLowerCase());
60
+ }
61
+
62
+ // Remove quoted phrases and split remaining on whitespace
63
+ const remaining = query.replace(quotedRegex, '').trim();
64
+ if (remaining) {
65
+ keywords.push(...remaining.toLowerCase().split(/\s+/).filter(k => k.length > 0));
66
+ }
67
+
68
+ return keywords;
69
+ }
70
+
71
+ /**
72
+ * Parse YAML frontmatter from a markdown file.
73
+ */
74
+ function parseFrontmatter(filePath: string): SolutionFrontmatter | null {
75
+ try {
76
+ const content = readFileSync(filePath, 'utf-8');
77
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
78
+ if (!fmMatch) return null;
79
+
80
+ const parsed = parse(fmMatch[1]) as SolutionFrontmatter;
81
+ return parsed;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Score how well a solution matches the search keywords.
89
+ */
90
+ function scoreSolution(fm: SolutionFrontmatter, keywords: string[]): { score: number; matchedFields: string[] } {
91
+ let score = 0;
92
+ const matchedFields: string[] = [];
93
+
94
+ for (const keyword of keywords) {
95
+ // Title match (high weight)
96
+ if (fm.title?.toLowerCase().includes(keyword)) {
97
+ score += 3;
98
+ if (!matchedFields.includes('title')) matchedFields.push('title');
99
+ }
100
+
101
+ // Tags match (high weight)
102
+ if (fm.tags?.some(t => t.toLowerCase().includes(keyword))) {
103
+ score += 3;
104
+ if (!matchedFields.includes('tags')) matchedFields.push('tags');
105
+ }
106
+
107
+ // Component match (medium weight)
108
+ if (fm.component?.toLowerCase().includes(keyword)) {
109
+ score += 2;
110
+ if (!matchedFields.includes('component')) matchedFields.push('component');
111
+ }
112
+
113
+ // Symptoms match (medium weight)
114
+ if (fm.symptoms?.some(s => s.toLowerCase().includes(keyword))) {
115
+ score += 2;
116
+ if (!matchedFields.includes('symptoms')) matchedFields.push('symptoms');
117
+ }
118
+
119
+ // Problem type match (low weight)
120
+ if (fm.problem_type?.toLowerCase().includes(keyword)) {
121
+ score += 1;
122
+ if (!matchedFields.includes('problem_type')) matchedFields.push('problem_type');
123
+ }
124
+
125
+ // Root cause match (low weight)
126
+ if (fm.root_cause?.toLowerCase().replace(/_/g, ' ').includes(keyword)) {
127
+ score += 1;
128
+ if (!matchedFields.includes('root_cause')) matchedFields.push('root_cause');
129
+ }
130
+ }
131
+
132
+ return { score, matchedFields };
133
+ }
134
+
135
+ /**
136
+ * Find all solution files in the solutions directory.
137
+ */
138
+ function findSolutionFiles(dir: string): string[] {
139
+ const files: string[] = [];
140
+
141
+ if (!existsSync(dir)) return files;
142
+
143
+ const entries = readdirSync(dir);
144
+ for (const entry of entries) {
145
+ const fullPath = join(dir, entry);
146
+ const stat = statSync(fullPath);
147
+
148
+ if (stat.isDirectory()) {
149
+ files.push(...findSolutionFiles(fullPath));
150
+ } else if (entry.endsWith('.md') && !entry.startsWith('README')) {
151
+ files.push(fullPath);
152
+ }
153
+ }
154
+
155
+ return files;
156
+ }
157
+
158
+ /**
159
+ * Get all category directories.
160
+ */
161
+ function getCategories(dir: string): string[] {
162
+ if (!existsSync(dir)) return [];
163
+
164
+ return readdirSync(dir)
165
+ .filter(entry => {
166
+ const fullPath = join(dir, entry);
167
+ return statSync(fullPath).isDirectory();
168
+ })
169
+ .sort();
170
+ }
171
+
172
+ /**
173
+ * Search for solutions matching the query.
174
+ */
175
+ function searchSolutions(query: string, limit: number = 10): SolutionMatch[] {
176
+ const solutionsDir = getSolutionsDir();
177
+ const keywords = extractKeywords(query);
178
+
179
+ if (keywords.length === 0) {
180
+ return [];
181
+ }
182
+
183
+ const files = findSolutionFiles(solutionsDir);
184
+ const matches: SolutionMatch[] = [];
185
+
186
+ for (const file of files) {
187
+ const frontmatter = parseFrontmatter(file);
188
+ if (!frontmatter) continue;
189
+
190
+ const { score, matchedFields } = scoreSolution(frontmatter, keywords);
191
+
192
+ if (score > 0) {
193
+ // Make path relative to project root
194
+ const relativePath = file.replace(getProjectRoot() + '/', '');
195
+ matches.push({
196
+ path: relativePath,
197
+ frontmatter,
198
+ score,
199
+ matchedFields,
200
+ });
201
+ }
202
+ }
203
+
204
+ // Sort by score descending
205
+ matches.sort((a, b) => b.score - a.score);
206
+
207
+ return matches.slice(0, limit);
208
+ }
209
+
210
+ /**
211
+ * Get full content of a solution file (without frontmatter).
212
+ */
213
+ function getSolutionContent(filePath: string): string | null {
214
+ try {
215
+ const fullPath = filePath.startsWith('/') ? filePath : join(getProjectRoot(), filePath);
216
+ const content = readFileSync(fullPath, 'utf-8');
217
+ // Remove frontmatter
218
+ return content.replace(/^---\n[\s\S]*?\n---\n/, '').trim();
219
+ } catch {
220
+ return null;
221
+ }
222
+ }
223
+
224
+ export function register(program: Command): void {
225
+ const solutionsCmd = program
226
+ .command('solutions')
227
+ .description('Search and browse documented solutions');
228
+
229
+ // Search command
230
+ solutionsCmd
231
+ .command('search <query>')
232
+ .description('Search solutions by keywords (searches title, tags, component, symptoms)')
233
+ .option('--full', 'Include full content of matched solutions')
234
+ .option('--limit <n>', 'Maximum number of results', '10')
235
+ .action(tracedAction('solutions search', async (query: string, options: { full?: boolean; limit?: string }) => {
236
+ const limit = parseInt(options.limit || '10', 10);
237
+ const matches = searchSolutions(query, limit);
238
+
239
+ if (matches.length === 0) {
240
+ console.log(JSON.stringify({
241
+ success: true,
242
+ query,
243
+ keywords: extractKeywords(query),
244
+ results: [],
245
+ message: 'No matching solutions found',
246
+ }, null, 2));
247
+ return;
248
+ }
249
+
250
+ // Format output
251
+ const results = matches.map(match => {
252
+ const result: Record<string, unknown> = {
253
+ path: match.path,
254
+ title: match.frontmatter.title,
255
+ score: match.score,
256
+ matched_fields: match.matchedFields,
257
+ severity: match.frontmatter.severity,
258
+ problem_type: match.frontmatter.problem_type,
259
+ component: match.frontmatter.component,
260
+ tags: match.frontmatter.tags,
261
+ };
262
+
263
+ if (options.full) {
264
+ result.content = getSolutionContent(match.path);
265
+ }
266
+
267
+ return result;
268
+ });
269
+
270
+ console.log(JSON.stringify({
271
+ success: true,
272
+ query,
273
+ keywords: extractKeywords(query),
274
+ result_count: results.length,
275
+ results,
276
+ }, null, 2));
277
+ }));
278
+
279
+ // List command
280
+ solutionsCmd
281
+ .command('list [category]')
282
+ .description('List solution categories or solutions in a category')
283
+ .action(tracedAction('solutions list', async (category?: string) => {
284
+ const solutionsDir = getSolutionsDir();
285
+
286
+ if (!category) {
287
+ // List all categories
288
+ const categories = getCategories(solutionsDir);
289
+
290
+ if (categories.length === 0) {
291
+ console.log(JSON.stringify({
292
+ success: true,
293
+ message: 'No solution categories found. Create docs/solutions/<category>/ directories.',
294
+ categories: [],
295
+ }, null, 2));
296
+ return;
297
+ }
298
+
299
+ // Count solutions in each category
300
+ const categoryCounts = categories.map(cat => {
301
+ const catDir = join(solutionsDir, cat);
302
+ const files = findSolutionFiles(catDir);
303
+ return { category: cat, count: files.length };
304
+ });
305
+
306
+ console.log(JSON.stringify({
307
+ success: true,
308
+ categories: categoryCounts,
309
+ }, null, 2));
310
+ return;
311
+ }
312
+
313
+ // List solutions in specific category
314
+ const categoryDir = join(solutionsDir, category);
315
+
316
+ if (!existsSync(categoryDir)) {
317
+ const available = getCategories(solutionsDir);
318
+ console.log(JSON.stringify({
319
+ success: false,
320
+ error: `Category not found: ${category}`,
321
+ available_categories: available,
322
+ }, null, 2));
323
+ process.exit(1);
324
+ }
325
+
326
+ const files = findSolutionFiles(categoryDir);
327
+ const solutions = files.map(file => {
328
+ const fm = parseFrontmatter(file);
329
+ const relativePath = file.replace(getProjectRoot() + '/', '');
330
+ return {
331
+ path: relativePath,
332
+ title: fm?.title || basename(file, '.md'),
333
+ date: fm?.date,
334
+ severity: fm?.severity,
335
+ tags: fm?.tags || [],
336
+ };
337
+ });
338
+
339
+ // Sort by date descending
340
+ solutions.sort((a, b) => {
341
+ if (!a.date) return 1;
342
+ if (!b.date) return -1;
343
+ return b.date.localeCompare(a.date);
344
+ });
345
+
346
+ console.log(JSON.stringify({
347
+ success: true,
348
+ category,
349
+ solution_count: solutions.length,
350
+ solutions,
351
+ }, null, 2));
352
+ }));
353
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Spawn commands - agent spawning via OpenCode SDK.
3
+ *
4
+ * Commands:
5
+ * ah spawn codesearch "<query>" [--budget <n>] [--steps <n>]
6
+ */
7
+
8
+ import { Command } from "commander";
9
+ import { readFileSync } from "fs";
10
+ import { dirname, join } from "path";
11
+ import { fileURLToPath } from "url";
12
+ import { AgentRunner } from "../lib/opencode/index.js";
13
+ import { BaseCommand, type CommandResult } from "../lib/base-command.js";
14
+ import { loadProjectSettings } from "../hooks/shared.js";
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+
18
+ const getProjectRoot = (): string => {
19
+ return process.env.PROJECT_ROOT || process.cwd();
20
+ };
21
+
22
+ // Load prompt
23
+ const CODESEARCH_PROMPT_PATH = join(__dirname, "../lib/opencode/prompts/codesearch.md");
24
+ const getCodesearchPrompt = (): string => readFileSync(CODESEARCH_PROMPT_PATH, "utf-8");
25
+
26
+ // Defaults
27
+ const DEFAULT_TOOL_BUDGET = 12;
28
+ const DEFAULT_STEPS_LIMIT = 20;
29
+ const DEFAULT_TIMEOUT_MS = 120000; // 2 min
30
+
31
+ // Output types
32
+ interface CodeResult {
33
+ file: string;
34
+ line_start: number;
35
+ line_end: number;
36
+ code: string;
37
+ relevance: "high" | "medium" | "low";
38
+ match_type: "structural" | "text" | "semantic";
39
+ context?: string;
40
+ }
41
+
42
+ interface CodesearchOutput {
43
+ results: CodeResult[];
44
+ warnings: string[];
45
+ dev_notes: {
46
+ tool_budget_used: number;
47
+ tools_invoked: string[];
48
+ tools_failed: string[];
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Codesearch command - spawn code search agent with ast-grep, ripgrep, and LSP.
54
+ */
55
+ class CodesearchCommand extends BaseCommand {
56
+ readonly name = "codesearch";
57
+ readonly description = "AI code search with structural (ast-grep), text (ripgrep), and semantic (LSP) tools";
58
+
59
+ defineArguments(cmd: Command): void {
60
+ cmd
61
+ .argument("<query>", "Code search query (natural language or pattern)")
62
+ .option("--budget <n>", "Soft tool budget hint for the agent", String(DEFAULT_TOOL_BUDGET))
63
+ .option("--steps <n>", "Hard step limit for agent iterations", String(DEFAULT_STEPS_LIMIT));
64
+ }
65
+
66
+ async execute(args: Record<string, unknown>): Promise<CommandResult> {
67
+ const query = args.query as string;
68
+ const settings = loadProjectSettings();
69
+ const toolBudget = parseInt(
70
+ (args.budget as string) ??
71
+ String(settings?.opencodeSdk?.codesearchToolBudget ?? DEFAULT_TOOL_BUDGET),
72
+ 10
73
+ );
74
+ const stepsLimit = parseInt((args.steps as string) ?? String(DEFAULT_STEPS_LIMIT), 10);
75
+
76
+ if (!query) {
77
+ return this.error("validation_error", "query is required");
78
+ }
79
+
80
+ const projectRoot = getProjectRoot();
81
+ const runner = new AgentRunner(projectRoot);
82
+
83
+ // Build user message with budget context
84
+ const userMessage = `## Search Query
85
+ ${query}
86
+
87
+ ## Budget
88
+ - Tool budget (soft): ${toolBudget} tool calls
89
+ - Available tools: ast-grep MCP (structural), grep (text), read, lsp, glob
90
+ - Prioritize structural matches (ast-grep) for code patterns, text (grep) for literals/comments
91
+
92
+ Respond with JSON matching the required schema.`;
93
+
94
+ try {
95
+ const result = await runner.run<CodesearchOutput>(
96
+ {
97
+ name: "codesearch",
98
+ systemPrompt: getCodesearchPrompt(),
99
+ timeoutMs: DEFAULT_TIMEOUT_MS,
100
+ steps: stepsLimit,
101
+ // MCP servers can be configured via environment or passed explicitly
102
+ // mcp: {
103
+ // "ast-grep": {
104
+ // type: "local",
105
+ // command: ["uvx", "--from", "ast-grep-mcp", "ast-grep-mcp"],
106
+ // },
107
+ // },
108
+ },
109
+ userMessage
110
+ );
111
+
112
+ if (!result.success) {
113
+ return this.error("agent_error", result.error ?? "Unknown agent error");
114
+ }
115
+
116
+ const data = result.data!;
117
+
118
+ // Warnings are included in the response data
119
+
120
+ return this.success({
121
+ query,
122
+ result_count: data.results.length,
123
+ results: data.results,
124
+ warnings: data.warnings,
125
+ dev_notes: data.dev_notes,
126
+ metadata: result.metadata,
127
+ });
128
+ } catch (error) {
129
+ const message = error instanceof Error ? error.message : String(error);
130
+ return this.error("spawn_error", message);
131
+ }
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Register spawn commands on the given commander program.
137
+ */
138
+ export function register(program: Command): void {
139
+ const spawnCmd = program
140
+ .command("spawn")
141
+ .description("Spawn sub-agents for specialized tasks");
142
+
143
+ const codesearch = new CodesearchCommand();
144
+ const cmd = spawnCmd.command(codesearch.name).description(codesearch.description);
145
+ codesearch.defineArguments(cmd);
146
+ cmd.action(async (...args) => {
147
+ const opts = args[args.length - 2] as Record<string, unknown>;
148
+ const cmdObj = args[args.length - 1] as Command;
149
+ const positionalArgs = cmdObj.args;
150
+
151
+ // Map positional args to named args based on command definition
152
+ const namedArgs: Record<string, unknown> = { ...opts };
153
+ if (positionalArgs[0]) namedArgs.query = positionalArgs[0];
154
+
155
+ const result = await codesearch.execute(namedArgs);
156
+ console.log(JSON.stringify(result, null, 2));
157
+ });
158
+ }