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,617 @@
1
+ /**
2
+ * Validation Hooks
3
+ *
4
+ * PostToolUse hooks that run diagnostics on edited files:
5
+ * - Python: pyright + ruff (if available)
6
+ * - TypeScript: tsc --noEmit
7
+ * - Schema validation for schema-managed markdown files
8
+ */
9
+
10
+ import { execSync } from 'child_process';
11
+ import type { Command } from 'commander';
12
+ import { existsSync, readFileSync } from 'fs';
13
+ import { dirname, extname, join, relative } from 'path';
14
+ import { minimatch } from 'minimatch';
15
+ import {
16
+ detectSchemaType,
17
+ type SchemaType,
18
+ loadSchema as loadSchemaFromLib,
19
+ extractFrontmatter,
20
+ validateFrontmatter as validateFrontmatterFromLib,
21
+ type ValidationError,
22
+ } from '../lib/schema.js';
23
+ import {
24
+ HookInput,
25
+ HookCategory,
26
+ RegisterFn,
27
+ allowTool,
28
+ blockTool,
29
+ denyTool,
30
+ FormatConfig,
31
+ getProjectDir,
32
+ loadProjectSettings,
33
+ outputContext,
34
+ registerCategory,
35
+ registerCategoryForDaemon,
36
+ } from './shared.js';
37
+
38
+ // ─────────────────────────────────────────────────────────────────────────────
39
+ // Hook Names
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+
42
+ const HOOK_DIAGNOSTICS = 'validation diagnostics';
43
+ const HOOK_SCHEMA = 'validation schema';
44
+ const HOOK_FORMAT = 'validation format';
45
+ const HOOK_SCHEMA_PRE = 'validation schema-pre';
46
+
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+ // Types
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+
51
+ interface DiagnosticResult {
52
+ tool: string;
53
+ errors: string[];
54
+ }
55
+
56
+ // ─────────────────────────────────────────────────────────────────────────────
57
+ // Tool Detection
58
+ // ─────────────────────────────────────────────────────────────────────────────
59
+
60
+ function isToolAvailable(tool: string): boolean {
61
+ try {
62
+ execSync(`which ${tool}`, { stdio: 'pipe' });
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+ // Python Diagnostics
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+
73
+ function runPyrightDiagnostics(filePath: string): DiagnosticResult | null {
74
+ if (!isToolAvailable('pyright')) {
75
+ return null;
76
+ }
77
+
78
+ try {
79
+ execSync(`pyright --outputjson "${filePath}"`, {
80
+ encoding: 'utf-8',
81
+ stdio: ['pipe', 'pipe', 'pipe'],
82
+ });
83
+ return null; // No errors
84
+ } catch (e: unknown) {
85
+ const error = e as { stdout?: string };
86
+ if (error.stdout) {
87
+ try {
88
+ const output = JSON.parse(error.stdout);
89
+ const diagnostics = output.generalDiagnostics || [];
90
+ const errors = diagnostics
91
+ .filter((d: { severity: string }) => d.severity === 'error')
92
+ .map((d: { file: string; range: { start: { line: number } }; message: string }) => {
93
+ const line = d.range?.start?.line ?? 0;
94
+ return `${d.file}:${line}: ${d.message}`;
95
+ })
96
+ .slice(0, 5); // Limit to 5 errors
97
+
98
+ if (errors.length > 0) {
99
+ return { tool: 'pyright', errors };
100
+ }
101
+ } catch {
102
+ // Parse error, skip
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ }
108
+
109
+ function runRuffDiagnostics(filePath: string): DiagnosticResult | null {
110
+ if (!isToolAvailable('ruff')) {
111
+ return null;
112
+ }
113
+
114
+ try {
115
+ execSync(`ruff check "${filePath}" --output-format=text`, {
116
+ encoding: 'utf-8',
117
+ stdio: ['pipe', 'pipe', 'pipe'],
118
+ });
119
+ return null; // No errors
120
+ } catch (e: unknown) {
121
+ const error = e as { stdout?: string };
122
+ if (error.stdout) {
123
+ const lines = error.stdout.trim().split('\n').filter(Boolean).slice(0, 5);
124
+ if (lines.length > 0) {
125
+ return { tool: 'ruff', errors: lines };
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+ }
131
+
132
+ // ─────────────────────────────────────────────────────────────────────────────
133
+ // TypeScript Diagnostics
134
+ // ─────────────────────────────────────────────────────────────────────────────
135
+
136
+ /**
137
+ * Find the nearest tsconfig.json by walking up from the file's directory.
138
+ */
139
+ function findTsConfig(filePath: string): string | null {
140
+ let dir = dirname(filePath);
141
+ const root = '/';
142
+
143
+ while (dir !== root) {
144
+ const candidate = join(dir, 'tsconfig.json');
145
+ if (existsSync(candidate)) {
146
+ return candidate;
147
+ }
148
+ const parent = dirname(dir);
149
+ if (parent === dir) break; // Reached filesystem root
150
+ dir = parent;
151
+ }
152
+
153
+ return null;
154
+ }
155
+
156
+ function runTscDiagnostics(filePath: string): DiagnosticResult | null {
157
+ if (!isToolAvailable('tsc')) {
158
+ return null;
159
+ }
160
+
161
+ // Find the project's tsconfig.json to get correct compiler options
162
+ const tsconfig = findTsConfig(filePath);
163
+
164
+ try {
165
+ // Run tsc on the whole project and filter for this file's errors
166
+ // This ensures we use the correct tsconfig settings (esModuleInterop, target, etc.)
167
+ // tsc outputs paths relative to tsconfig dir, so convert the absolute filePath to match
168
+ const tscDir = tsconfig ? dirname(tsconfig) : undefined;
169
+ const grepPath = tsconfig ? relative(dirname(tsconfig), filePath) : filePath;
170
+ const tscCmd = tsconfig
171
+ ? `tsc --noEmit -p "${tsconfig}" 2>&1 | grep -E "^${grepPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}" || true`
172
+ : `tsc --noEmit "${filePath}"`;
173
+
174
+ const output = execSync(tscCmd, {
175
+ encoding: 'utf-8',
176
+ stdio: ['pipe', 'pipe', 'pipe'],
177
+ cwd: tscDir,
178
+ });
179
+
180
+ if (output.trim()) {
181
+ const lines = output.trim().split('\n').filter(Boolean).slice(0, 5);
182
+ if (lines.length > 0) {
183
+ return { tool: 'tsc', errors: lines };
184
+ }
185
+ }
186
+ return null; // No errors
187
+ } catch (e: unknown) {
188
+ const error = e as { stdout?: string; stderr?: string };
189
+ const output = error.stdout || error.stderr || '';
190
+ if (output) {
191
+ const lines = output.trim().split('\n').filter(Boolean).slice(0, 5);
192
+ if (lines.length > 0) {
193
+ return { tool: 'tsc', errors: lines };
194
+ }
195
+ }
196
+ return null;
197
+ }
198
+ }
199
+
200
+ // ─────────────────────────────────────────────────────────────────────────────
201
+ // Main Diagnostics Runner
202
+ // ─────────────────────────────────────────────────────────────────────────────
203
+
204
+ /**
205
+ * Run diagnostics on an edited file.
206
+ *
207
+ * Triggered by: PostToolUse matcher "(Write|Edit)"
208
+ */
209
+ export function runDiagnostics(input: HookInput): void {
210
+ const filePath = input.tool_input?.file_path as string | undefined;
211
+
212
+ if (!filePath) {
213
+ allowTool(HOOK_DIAGNOSTICS);
214
+ }
215
+
216
+ const ext = extname(filePath!).toLowerCase();
217
+ const results: DiagnosticResult[] = [];
218
+
219
+ // Python files
220
+ if (ext === '.py') {
221
+ const pyright = runPyrightDiagnostics(filePath!);
222
+ if (pyright) results.push(pyright);
223
+
224
+ const ruff = runRuffDiagnostics(filePath!);
225
+ if (ruff) results.push(ruff);
226
+ }
227
+
228
+ // TypeScript files
229
+ if (ext === '.ts' || ext === '.tsx') {
230
+ const tsc = runTscDiagnostics(filePath!);
231
+ if (tsc) results.push(tsc);
232
+ }
233
+
234
+ // Output context if there are errors
235
+ if (results.length > 0) {
236
+ const context = formatDiagnosticsContext(results);
237
+ outputContext(context, HOOK_DIAGNOSTICS);
238
+ }
239
+
240
+ allowTool(HOOK_DIAGNOSTICS);
241
+ }
242
+
243
+ /**
244
+ * Format diagnostic results as context string.
245
+ */
246
+ function formatDiagnosticsContext(results: DiagnosticResult[]): string {
247
+ const parts: string[] = ['## Diagnostics'];
248
+
249
+ for (const result of results) {
250
+ parts.push(`\n### ${result.tool}`);
251
+ result.errors.forEach((e) => parts.push(e));
252
+ }
253
+
254
+ parts.push('\nPlease fix these issues before continuing.');
255
+
256
+ return parts.join('\n');
257
+ }
258
+
259
+ // ─────────────────────────────────────────────────────────────────────────────
260
+ // Schema Validation
261
+ // ─────────────────────────────────────────────────────────────────────────────
262
+
263
+ /**
264
+ * Detect which schema applies to a file path (wrapper for shared function)
265
+ */
266
+ function detectSchemaTypeLocal(filePath: string): SchemaType | null {
267
+ return detectSchemaType(filePath, getProjectDir());
268
+ }
269
+
270
+ /**
271
+ * Extract folder name from skill file path
272
+ * e.g., ".allhands/skills/my-skill/SKILL.md" -> "my-skill"
273
+ */
274
+ function extractSkillFolderName(filePath: string): string | null {
275
+ const match = filePath.match(/\.allhands\/skills\/([^/]+)\/SKILL\.md$/);
276
+ return match ? match[1] : null;
277
+ }
278
+
279
+ /**
280
+ * Validate skill-specific rules (name must match folder)
281
+ */
282
+ function validateSkillSpecificRules(
283
+ filePath: string,
284
+ frontmatter: Record<string, unknown>
285
+ ): ValidationError[] {
286
+ const errors: ValidationError[] = [];
287
+ const folderName = extractSkillFolderName(filePath);
288
+
289
+ if (folderName && frontmatter.name && frontmatter.name !== folderName) {
290
+ errors.push({
291
+ field: 'name',
292
+ message: `Skill name '${frontmatter.name}' must match containing folder '${folderName}'`,
293
+ });
294
+ }
295
+
296
+ return errors;
297
+ }
298
+
299
+ /**
300
+ * Run schema validation on a file.
301
+ * Delegates to lib/schema.ts for parsing and validation.
302
+ * Returns validation errors or null if valid.
303
+ */
304
+ function runSchemaValidation(filePath: string): ValidationError[] | null {
305
+ // Detect schema type
306
+ const schemaType = detectSchemaTypeLocal(filePath);
307
+ if (!schemaType) {
308
+ // Not a schema-managed file
309
+ return null;
310
+ }
311
+
312
+ // Load schema (cached in lib)
313
+ const schema = loadSchemaFromLib(schemaType);
314
+ if (!schema) {
315
+ return null;
316
+ }
317
+
318
+ // Read file content
319
+ if (!existsSync(filePath)) {
320
+ return null;
321
+ }
322
+
323
+ const content = readFileSync(filePath, 'utf-8');
324
+
325
+ // Parse frontmatter using lib
326
+ const { frontmatter } = extractFrontmatter(content);
327
+ if (!frontmatter) {
328
+ return [{
329
+ field: 'frontmatter',
330
+ message: 'File is missing valid YAML frontmatter (---...---)',
331
+ }];
332
+ }
333
+
334
+ // Validate against schema using lib
335
+ const result = validateFrontmatterFromLib(frontmatter, schema);
336
+ const errors: ValidationError[] = [...result.errors];
337
+
338
+ // Add skill-specific validation
339
+ if (schemaType === 'skill') {
340
+ errors.push(...validateSkillSpecificRules(filePath, frontmatter));
341
+ }
342
+
343
+ return errors;
344
+ }
345
+
346
+ /**
347
+ * Run schema validation on content (for PreToolUse before file is written).
348
+ * Delegates to lib/schema.ts for parsing and validation.
349
+ * Returns validation errors or null if valid/not schema-managed.
350
+ */
351
+ function runSchemaValidationOnContent(filePath: string, content: string): ValidationError[] | null {
352
+ // Detect schema type
353
+ const schemaType = detectSchemaTypeLocal(filePath);
354
+ if (!schemaType) {
355
+ // Not a schema-managed file
356
+ return null;
357
+ }
358
+
359
+ // Load schema (cached in lib)
360
+ const schema = loadSchemaFromLib(schemaType);
361
+ if (!schema) {
362
+ return null;
363
+ }
364
+
365
+ // Parse frontmatter from content using lib
366
+ const { frontmatter } = extractFrontmatter(content);
367
+ if (!frontmatter) {
368
+ return [{
369
+ field: 'frontmatter',
370
+ message: 'File is missing valid YAML frontmatter (---...---)',
371
+ }];
372
+ }
373
+
374
+ // Validate against schema using lib
375
+ const result = validateFrontmatterFromLib(frontmatter, schema);
376
+ const errors: ValidationError[] = [...result.errors];
377
+
378
+ // Add skill-specific validation
379
+ if (schemaType === 'skill') {
380
+ errors.push(...validateSkillSpecificRules(filePath, frontmatter));
381
+ }
382
+
383
+ return errors;
384
+ }
385
+
386
+ /**
387
+ * Format validation errors as context string
388
+ */
389
+ function formatSchemaErrors(errors: ValidationError[], schemaType: string): string {
390
+ const parts: string[] = [`## Schema Validation Errors (${schemaType})`];
391
+
392
+ for (const error of errors) {
393
+ parts.push(`- ${error.message}`);
394
+ }
395
+
396
+ parts.push('\nPlease fix the frontmatter to match the schema. Run `ah schema ' + schemaType + '` to see the expected format.');
397
+
398
+ return parts.join('\n');
399
+ }
400
+
401
+ // ─────────────────────────────────────────────────────────────────────────────
402
+ // Auto-Format
403
+ // ─────────────────────────────────────────────────────────────────────────────
404
+
405
+ /**
406
+ * Get the format command for a specific file.
407
+ * Checks patterns first, then falls back to default command.
408
+ */
409
+ function getFormatCommand(config: FormatConfig, filePath: string): string | null {
410
+ // Check patterns first (most specific)
411
+ if (config.patterns) {
412
+ for (const pattern of config.patterns) {
413
+ if (minimatch(filePath, pattern.match) || filePath.endsWith(pattern.match.replace('*', ''))) {
414
+ return pattern.command;
415
+ }
416
+ }
417
+ }
418
+
419
+ // Fall back to default command
420
+ return config.command || null;
421
+ }
422
+
423
+ /**
424
+ * Run auto-format on an edited file.
425
+ *
426
+ * Reads format configuration from .allhands/settings.json:
427
+ * {
428
+ * "validation": {
429
+ * "format": {
430
+ * "enabled": true,
431
+ * "command": "pnpm format",
432
+ * "patterns": [
433
+ * { "match": "*.py", "command": "ruff format" }
434
+ * ]
435
+ * }
436
+ * }
437
+ * }
438
+ *
439
+ * Triggered by: PostToolUse matcher "(Write|Edit)"
440
+ */
441
+ export function runFormat(input: HookInput): void {
442
+ const settings = loadProjectSettings();
443
+ const formatConfig = settings?.validation?.format;
444
+
445
+ // Check if formatting is enabled
446
+ if (!formatConfig?.enabled) {
447
+ return allowTool(HOOK_FORMAT);
448
+ }
449
+
450
+ const filePath = input.tool_input?.file_path as string | undefined;
451
+ if (!filePath) {
452
+ return allowTool(HOOK_FORMAT);
453
+ }
454
+
455
+ const command = getFormatCommand(formatConfig!, filePath!);
456
+ if (!command) {
457
+ return allowTool(HOOK_FORMAT);
458
+ }
459
+
460
+ try {
461
+ // Run format command on the file
462
+ // Use || true pattern to ensure non-blocking (format failures shouldn't stop the agent)
463
+ execSync(`${command} "${filePath}"`, {
464
+ cwd: getProjectDir(),
465
+ stdio: ['pipe', 'pipe', 'pipe'],
466
+ timeout: 30000, // 30 second timeout
467
+ });
468
+ } catch {
469
+ // Format failures are non-blocking
470
+ // Could optionally log or output context here
471
+ }
472
+
473
+ allowTool(HOOK_FORMAT);
474
+ }
475
+
476
+ /**
477
+ * Validate schema-managed markdown files (PostToolUse).
478
+ *
479
+ * Triggered by: PostToolUse matcher "(Write|Edit)"
480
+ */
481
+ export function validateSchema(input: HookInput): void {
482
+ const filePath = input.tool_input?.file_path as string | undefined;
483
+
484
+ if (!filePath) {
485
+ return allowTool(HOOK_SCHEMA);
486
+ }
487
+
488
+ const errors = runSchemaValidation(filePath!);
489
+
490
+ if (errors && errors.length > 0) {
491
+ const schemaType = detectSchemaTypeLocal(filePath!) || 'unknown';
492
+ const context = formatSchemaErrors(errors, schemaType);
493
+ blockTool(context, HOOK_SCHEMA);
494
+ }
495
+
496
+ allowTool(HOOK_SCHEMA);
497
+ }
498
+
499
+ /**
500
+ * Validate schema-managed markdown files before write/edit (PreToolUse).
501
+ *
502
+ * Triggered by: PreToolUse matcher "(Write|Edit)"
503
+ */
504
+ export function validateSchemaPre(input: HookInput): void {
505
+ const toolName = input.tool_name as string | undefined;
506
+ const filePath = input.tool_input?.file_path as string | undefined;
507
+
508
+ if (!filePath) {
509
+ return allowTool(HOOK_SCHEMA_PRE);
510
+ }
511
+
512
+ let contentToValidate: string | undefined;
513
+
514
+ if (toolName === 'Write') {
515
+ // Write tool provides content directly
516
+ contentToValidate = input.tool_input?.content as string | undefined;
517
+ } else if (toolName === 'Edit') {
518
+ // Edit tool provides old_string and new_string - we need to compute the result
519
+ const oldString = input.tool_input?.old_string as string | undefined;
520
+ const newString = input.tool_input?.new_string as string | undefined;
521
+ const replaceAll = input.tool_input?.replace_all as boolean | undefined;
522
+
523
+ if (oldString === undefined || newString === undefined) {
524
+ return allowTool(HOOK_SCHEMA_PRE);
525
+ }
526
+
527
+ // Read current file content
528
+ if (!existsSync(filePath)) {
529
+ return allowTool(HOOK_SCHEMA_PRE);
530
+ }
531
+
532
+ const currentContent = readFileSync(filePath, 'utf-8');
533
+
534
+ // Apply the edit to get the resulting content
535
+ if (replaceAll) {
536
+ contentToValidate = currentContent.split(oldString).join(newString);
537
+ } else {
538
+ contentToValidate = currentContent.replace(oldString, newString);
539
+ }
540
+ }
541
+
542
+ if (!contentToValidate) {
543
+ return allowTool(HOOK_SCHEMA_PRE);
544
+ }
545
+
546
+ const errors = runSchemaValidationOnContent(filePath, contentToValidate);
547
+
548
+ if (errors && errors.length > 0) {
549
+ const schemaType = detectSchemaTypeLocal(filePath) || 'unknown';
550
+ const context = formatSchemaErrors(errors, schemaType);
551
+ denyTool(context, HOOK_SCHEMA_PRE);
552
+ }
553
+
554
+ allowTool(HOOK_SCHEMA_PRE);
555
+ }
556
+
557
+ // ─────────────────────────────────────────────────────────────────────────────
558
+ // Hook Category Definition
559
+ // ─────────────────────────────────────────────────────────────────────────────
560
+
561
+ /** Validation hooks category */
562
+ export const category: HookCategory = {
563
+ name: 'validation',
564
+ description: 'Validation hooks',
565
+ hooks: [
566
+ {
567
+ name: 'diagnostics',
568
+ description: 'Run diagnostics on edited files (PostToolUse)',
569
+ handler: runDiagnostics,
570
+ errorFallback: { type: 'allowTool' },
571
+ logPayload: (input) => ({ tool: input.tool_name, file: input.tool_input?.file_path }),
572
+ },
573
+ {
574
+ name: 'schema',
575
+ description: 'Validate schema-managed markdown files (PostToolUse)',
576
+ handler: validateSchema,
577
+ errorFallback: { type: 'allowTool' },
578
+ logPayload: (input) => ({ tool: input.tool_name, file: input.tool_input?.file_path }),
579
+ },
580
+ {
581
+ name: 'format',
582
+ description: 'Auto-format edited files (PostToolUse)',
583
+ handler: runFormat,
584
+ errorFallback: { type: 'allowTool' },
585
+ logPayload: (input) => ({ tool: input.tool_name, file: input.tool_input?.file_path }),
586
+ },
587
+ {
588
+ name: 'schema-pre',
589
+ description: 'Validate schema-managed markdown files before write/edit (PreToolUse)',
590
+ handler: validateSchemaPre,
591
+ errorFallback: { type: 'allowTool' },
592
+ logPayload: (input) => ({ tool: input.tool_name, file: input.tool_input?.file_path }),
593
+ },
594
+ ],
595
+ };
596
+
597
+ // ─────────────────────────────────────────────────────────────────────────────
598
+ // Command Registration
599
+ // ─────────────────────────────────────────────────────────────────────────────
600
+
601
+ /**
602
+ * Register validation hook subcommands.
603
+ */
604
+ export function register(parent: Command): void {
605
+ registerCategory(parent, category);
606
+ }
607
+
608
+ // ─────────────────────────────────────────────────────────────────────────────
609
+ // Daemon Handler Registration
610
+ // ─────────────────────────────────────────────────────────────────────────────
611
+
612
+ /**
613
+ * Register handlers for daemon mode.
614
+ */
615
+ export function registerDaemonHandlers(register: RegisterFn): void {
616
+ registerCategoryForDaemon(category, register);
617
+ }