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,418 @@
1
+ /**
2
+ * Schema Validation Library
3
+ *
4
+ * Loads YAML schema definitions and validates frontmatter/content against them.
5
+ * Schemas are the single source of truth for file structure requirements.
6
+ */
7
+
8
+ import { readFileSync, existsSync, readdirSync } from 'fs';
9
+ import { join, dirname } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import { parse as parseYaml } from 'yaml';
12
+ import { minimatch } from 'minimatch';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ export interface SchemaField {
18
+ type: 'string' | 'integer' | 'boolean' | 'date' | 'enum' | 'array' | 'object';
19
+ required?: boolean;
20
+ default?: unknown;
21
+ description?: string;
22
+ values?: string[]; // for enum type
23
+ items?: string; // for array type (item type)
24
+ properties?: Record<string, SchemaField>; // for object type
25
+ }
26
+
27
+ export interface BodySection {
28
+ name: string;
29
+ required: boolean;
30
+ description?: string;
31
+ }
32
+
33
+ export interface Schema {
34
+ frontmatter?: Record<string, SchemaField>;
35
+ fields?: Record<string, SchemaField>; // alternative to frontmatter for status.yaml
36
+ body?: {
37
+ description?: string;
38
+ sections?: BodySection[];
39
+ };
40
+ }
41
+
42
+ export interface ValidationError {
43
+ field: string;
44
+ message: string;
45
+ expected?: string;
46
+ received?: string;
47
+ }
48
+
49
+ export interface ValidationResult {
50
+ valid: boolean;
51
+ errors: ValidationError[];
52
+ }
53
+
54
+ const schemaCache = new Map<string, Schema>();
55
+
56
+ /**
57
+ * Get the schema directory path
58
+ * Path: harness/src/lib/ -> harness/src/ -> harness/ -> .allhands/ -> schemas/
59
+ */
60
+ function getSchemaDir(): string {
61
+ return join(__dirname, '..', '..', '..', 'schemas');
62
+ }
63
+
64
+ /**
65
+ * Load a schema by type name
66
+ */
67
+ export function loadSchema(type: string): Schema | null {
68
+ if (schemaCache.has(type)) {
69
+ return schemaCache.get(type)!;
70
+ }
71
+
72
+ const schemaPath = join(getSchemaDir(), `${type}.yaml`);
73
+ if (!existsSync(schemaPath)) {
74
+ return null;
75
+ }
76
+
77
+ try {
78
+ const content = readFileSync(schemaPath, 'utf-8');
79
+ const schema = parseYaml(content) as Schema;
80
+ schemaCache.set(type, schema);
81
+ return schema;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * List available schema types
89
+ */
90
+ export function listSchemas(): string[] {
91
+ const schemaDir = getSchemaDir();
92
+ if (!existsSync(schemaDir)) {
93
+ return [];
94
+ }
95
+
96
+ return readdirSync(schemaDir)
97
+ .filter((f) => f.endsWith('.yaml'))
98
+ .map((f) => f.replace('.yaml', ''));
99
+ }
100
+
101
+ /**
102
+ * Validate a value against a field schema
103
+ */
104
+ function validateField(
105
+ value: unknown,
106
+ field: SchemaField,
107
+ fieldName: string
108
+ ): ValidationError | null {
109
+ // Check required
110
+ if (field.required && (value === undefined || value === null)) {
111
+ return {
112
+ field: fieldName,
113
+ message: `Required field is missing`,
114
+ expected: field.type,
115
+ received: 'undefined',
116
+ };
117
+ }
118
+
119
+ // If not required and not present, use default or skip
120
+ if (value === undefined || value === null) {
121
+ return null;
122
+ }
123
+
124
+ // Type validation
125
+ switch (field.type) {
126
+ case 'string':
127
+ if (typeof value !== 'string') {
128
+ return {
129
+ field: fieldName,
130
+ message: `Expected string`,
131
+ expected: 'string',
132
+ received: typeof value,
133
+ };
134
+ }
135
+ break;
136
+
137
+ case 'integer':
138
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
139
+ return {
140
+ field: fieldName,
141
+ message: `Expected integer`,
142
+ expected: 'integer',
143
+ received: typeof value,
144
+ };
145
+ }
146
+ break;
147
+
148
+ case 'boolean':
149
+ if (typeof value !== 'boolean') {
150
+ return {
151
+ field: fieldName,
152
+ message: `Expected boolean`,
153
+ expected: 'boolean',
154
+ received: typeof value,
155
+ };
156
+ }
157
+ break;
158
+
159
+ case 'date':
160
+ if (typeof value !== 'string' || isNaN(Date.parse(value))) {
161
+ return {
162
+ field: fieldName,
163
+ message: `Expected ISO 8601 date string`,
164
+ expected: 'date (ISO 8601)',
165
+ received: String(value),
166
+ };
167
+ }
168
+ break;
169
+
170
+ case 'enum':
171
+ if (!field.values?.includes(String(value))) {
172
+ return {
173
+ field: fieldName,
174
+ message: `Value must be one of: ${field.values?.join(', ')}`,
175
+ expected: field.values?.join(' | '),
176
+ received: String(value),
177
+ };
178
+ }
179
+ break;
180
+
181
+ case 'array':
182
+ if (!Array.isArray(value)) {
183
+ return {
184
+ field: fieldName,
185
+ message: `Expected array`,
186
+ expected: 'array',
187
+ received: typeof value,
188
+ };
189
+ }
190
+ if (field.items) {
191
+ const itemType = field.items;
192
+ const invalidItems = itemType === 'integer'
193
+ ? value.some(item => typeof item !== 'number' || !Number.isInteger(item))
194
+ : value.some(item => typeof item !== itemType);
195
+ if (invalidItems) {
196
+ return {
197
+ field: fieldName,
198
+ message: `Array contains non-${itemType} items`,
199
+ expected: `${itemType}[]`,
200
+ received: `mixed array`,
201
+ };
202
+ }
203
+ }
204
+ break;
205
+
206
+ case 'object':
207
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
208
+ return {
209
+ field: fieldName,
210
+ message: `Expected object`,
211
+ expected: 'object',
212
+ received: typeof value,
213
+ };
214
+ }
215
+ // Validate nested properties if defined
216
+ if (field.properties) {
217
+ for (const [propName, propSchema] of Object.entries(field.properties)) {
218
+ const propValue = (value as Record<string, unknown>)[propName];
219
+ const error = validateField(propValue, propSchema, `${fieldName}.${propName}`);
220
+ if (error) {
221
+ return error;
222
+ }
223
+ }
224
+ }
225
+ break;
226
+ }
227
+
228
+ return null;
229
+ }
230
+
231
+ /**
232
+ * Validate frontmatter against a schema
233
+ */
234
+ export function validateFrontmatter(
235
+ frontmatter: Record<string, unknown>,
236
+ schema: Schema
237
+ ): ValidationResult {
238
+ const errors: ValidationError[] = [];
239
+ const fields = schema.frontmatter || schema.fields || {};
240
+
241
+ for (const [fieldName, fieldSchema] of Object.entries(fields)) {
242
+ const error = validateField(frontmatter[fieldName], fieldSchema, fieldName);
243
+ if (error) {
244
+ errors.push(error);
245
+ }
246
+ }
247
+
248
+ return {
249
+ valid: errors.length === 0,
250
+ errors,
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Extract frontmatter from markdown content
256
+ */
257
+ export function extractFrontmatter(content: string): {
258
+ frontmatter: Record<string, unknown> | null;
259
+ body: string;
260
+ } {
261
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
262
+ const match = content.match(frontmatterRegex);
263
+
264
+ if (!match) {
265
+ return { frontmatter: null, body: content };
266
+ }
267
+
268
+ try {
269
+ const frontmatter = parseYaml(match[1]) as Record<string, unknown>;
270
+ return { frontmatter, body: match[2] };
271
+ } catch {
272
+ return { frontmatter: null, body: content };
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Validate a file's content against its schema type
278
+ */
279
+ export function validateFile(
280
+ content: string,
281
+ schemaType: string
282
+ ): ValidationResult {
283
+ const schema = loadSchema(schemaType);
284
+ if (!schema) {
285
+ return {
286
+ valid: false,
287
+ errors: [{ field: '_schema', message: `Unknown schema type: ${schemaType}` }],
288
+ };
289
+ }
290
+
291
+ const { frontmatter } = extractFrontmatter(content);
292
+ if (!frontmatter) {
293
+ return {
294
+ valid: false,
295
+ errors: [{ field: '_frontmatter', message: 'Missing or invalid frontmatter' }],
296
+ };
297
+ }
298
+
299
+ return validateFrontmatter(frontmatter, schema);
300
+ }
301
+
302
+ /**
303
+ * Apply defaults from schema to frontmatter
304
+ */
305
+ export function applyDefaults(
306
+ frontmatter: Record<string, unknown>,
307
+ schema: Schema
308
+ ): Record<string, unknown> {
309
+ const result = { ...frontmatter };
310
+ const fields = schema.frontmatter || schema.fields || {};
311
+
312
+ for (const [fieldName, fieldSchema] of Object.entries(fields)) {
313
+ if (result[fieldName] === undefined && fieldSchema.default !== undefined) {
314
+ result[fieldName] = fieldSchema.default;
315
+ }
316
+ }
317
+
318
+ return result;
319
+ }
320
+
321
+ /**
322
+ * Format validation errors for display
323
+ */
324
+ export function formatErrors(result: ValidationResult): string {
325
+ if (result.valid) {
326
+ return 'Validation passed';
327
+ }
328
+
329
+ return result.errors
330
+ .map((e) => {
331
+ let msg = `• ${e.field}: ${e.message}`;
332
+ if (e.expected) msg += ` (expected: ${e.expected})`;
333
+ if (e.received) msg += ` (got: ${e.received})`;
334
+ return msg;
335
+ })
336
+ .join('\n');
337
+ }
338
+
339
+ // ─────────────────────────────────────────────────────────────────────────────
340
+ // Schema Type Detection
341
+ // ─────────────────────────────────────────────────────────────────────────────
342
+
343
+ export type SchemaType = 'prompt' | 'alignment' | 'spec' | 'documentation' | 'solution' | 'validation-suite' | 'skill' | 'workflow';
344
+
345
+ interface SchemaPattern {
346
+ pattern: string;
347
+ schemaType: SchemaType;
348
+ }
349
+
350
+ const SCHEMA_PATTERNS: SchemaPattern[] = [
351
+ { pattern: '.planning/**/prompts/*.md', schemaType: 'prompt' },
352
+ { pattern: '.planning/**/alignment.md', schemaType: 'alignment' },
353
+ { pattern: 'specs/**/*.spec.md', schemaType: 'spec' },
354
+ { pattern: 'specs/roadmap/**/*.spec.md', schemaType: 'spec' },
355
+ { pattern: 'docs/solutions/**/*.md', schemaType: 'solution' },
356
+ { pattern: 'docs/**/*.md', schemaType: 'documentation' },
357
+ { pattern: '.allhands/validation/*.md', schemaType: 'validation-suite' },
358
+ { pattern: '.allhands/skills/*/SKILL.md', schemaType: 'skill' },
359
+ { pattern: '.allhands/workflows/*.md', schemaType: 'workflow' },
360
+ ];
361
+
362
+ /**
363
+ * Detect which schema type applies to a file path.
364
+ * Uses glob pattern matching against known schema patterns.
365
+ *
366
+ * @param filePath - Absolute or relative file path
367
+ * @param projectDir - Optional project root directory for relative path calculation
368
+ * @returns The detected schema type, or null if no schema applies
369
+ */
370
+ export function detectSchemaType(filePath: string, projectDir?: string): SchemaType | null {
371
+ // Make path relative to project
372
+ let relativePath = filePath;
373
+ if (projectDir && filePath.startsWith(projectDir)) {
374
+ relativePath = filePath.slice(projectDir.length + 1);
375
+ }
376
+
377
+ for (const { pattern, schemaType } of SCHEMA_PATTERNS) {
378
+ if (minimatch(relativePath, pattern)) {
379
+ return schemaType;
380
+ }
381
+ }
382
+ return null;
383
+ }
384
+
385
+ /**
386
+ * Infer schema type from file path using string matching.
387
+ * Fallback method when glob patterns don't match.
388
+ *
389
+ * @param file - File path to analyze
390
+ * @returns The inferred schema type, or null if unknown
391
+ */
392
+ export function inferSchemaType(file: string): SchemaType | null {
393
+ if (file.includes('/prompts/') || file.match(/prompt.*\.md$/i)) {
394
+ return 'prompt';
395
+ }
396
+ if (file.includes('alignment') || file.match(/alignment\.md$/i)) {
397
+ return 'alignment';
398
+ }
399
+ if (file.includes('/specs/') || file.endsWith('.spec.md')) {
400
+ return 'spec';
401
+ }
402
+ if (file.includes('/docs/solutions/') && file.endsWith('.md')) {
403
+ return 'solution';
404
+ }
405
+ if (file.includes('/docs/') && file.endsWith('.md')) {
406
+ return 'documentation';
407
+ }
408
+ if (file.includes('/validation/') && file.endsWith('.md')) {
409
+ return 'validation-suite';
410
+ }
411
+ if (file.includes('/skills/') && file.endsWith('SKILL.md')) {
412
+ return 'skill';
413
+ }
414
+ if (file.includes('/workflows/') && file.endsWith('.md')) {
415
+ return 'workflow';
416
+ }
417
+ return null;
418
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Agent Profile Schema
3
+ *
4
+ * Zod schema for validating agent profile YAML files.
5
+ * Replaces the static YAML schema with runtime validation.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+ import { TemplateVarNameSchema, validateTemplateString, type TemplateVarName } from './template-vars.js';
10
+
11
+ /**
12
+ * Raw agent profile as parsed from YAML (snake_case)
13
+ */
14
+ export const RawAgentProfileSchema = z.object({
15
+ name: z.string().min(1).describe('Agent identifier (also used as tmux window name)'),
16
+
17
+ flow: z.string().min(1).describe('Flow file name relative to .allhands/flows/'),
18
+
19
+ prompt_scoped: z
20
+ .boolean()
21
+ .default(false)
22
+ .describe('If true, multiple instances can run (one per prompt)'),
23
+
24
+ message_template: z
25
+ .string()
26
+ .optional()
27
+ .describe('Template string with ${VAR} interpolation'),
28
+
29
+ template_vars: z
30
+ .array(TemplateVarNameSchema)
31
+ .optional()
32
+ .describe('Required variables for message_template'),
33
+
34
+ // TUI integration
35
+ tui_action: z
36
+ .string()
37
+ .optional()
38
+ .describe('TUI action name that spawns this agent (e.g., "ideation", "compound")'),
39
+
40
+ tui_label: z
41
+ .string()
42
+ .optional()
43
+ .describe('Display label in TUI (defaults to capitalized name)'),
44
+
45
+ tui_requires_spec: z
46
+ .boolean()
47
+ .default(false)
48
+ .describe('If true, TUI action requires an active spec'),
49
+
50
+ non_coding: z
51
+ .boolean()
52
+ .default(false)
53
+ .describe('If true, agent is non-coding (affects some behaviors)'),
54
+ });
55
+
56
+ export type RawAgentProfile = z.infer<typeof RawAgentProfileSchema>;
57
+
58
+ /**
59
+ * Normalized agent profile (camelCase, with defaults applied)
60
+ */
61
+ export interface AgentProfile {
62
+ name: string;
63
+ flow: string;
64
+ promptScoped: boolean;
65
+ messageTemplate?: string;
66
+ templateVars: TemplateVarName[];
67
+ tuiAction?: string;
68
+ tuiLabel?: string;
69
+ tuiRequiresSpec: boolean;
70
+ nonCoding: boolean;
71
+ }
72
+
73
+ /**
74
+ * Transform raw YAML profile to normalized TypeScript interface
75
+ */
76
+ export function normalizeProfile(raw: RawAgentProfile): AgentProfile {
77
+ return {
78
+ name: raw.name,
79
+ flow: raw.flow,
80
+ promptScoped: raw.prompt_scoped,
81
+ messageTemplate: raw.message_template,
82
+ templateVars: (raw.template_vars ?? []) as TemplateVarName[],
83
+ tuiAction: raw.tui_action,
84
+ tuiLabel: raw.tui_label,
85
+ tuiRequiresSpec: raw.tui_requires_spec,
86
+ nonCoding: raw.non_coding,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Validation result for an agent profile
92
+ */
93
+ export interface ProfileValidation {
94
+ valid: boolean;
95
+ errors: string[];
96
+ warnings: string[];
97
+ }
98
+
99
+ /**
100
+ * Validate an agent profile beyond basic schema validation.
101
+ * Checks:
102
+ * - Template variables in message_template match template_vars list
103
+ * - No unknown template variables
104
+ */
105
+ export function validateProfileSemantics(profile: AgentProfile): ProfileValidation {
106
+ const errors: string[] = [];
107
+ const warnings: string[] = [];
108
+
109
+ // Validate message_template references
110
+ if (profile.messageTemplate) {
111
+ const templateCheck = validateTemplateString(profile.messageTemplate);
112
+
113
+ if (!templateCheck.valid) {
114
+ errors.push(`Invalid template variables in message_template: ${templateCheck.invalidVars.join(', ')}`);
115
+ }
116
+
117
+ // Check that all referenced vars are in template_vars
118
+ const referencedVars = profile.messageTemplate.match(/\$\{([^}]+)\}/g)?.map((m) => m.slice(2, -1)) ?? [];
119
+
120
+ for (const refVar of referencedVars) {
121
+ if (!profile.templateVars.includes(refVar as TemplateVarName)) {
122
+ warnings.push(`Template references ${refVar} but it's not in template_vars list`);
123
+ }
124
+ }
125
+
126
+ // Check for unused template_vars
127
+ for (const declaredVar of profile.templateVars) {
128
+ if (!referencedVars.includes(declaredVar)) {
129
+ warnings.push(`template_vars declares ${declaredVar} but it's not used in message_template`);
130
+ }
131
+ }
132
+ } else if (profile.templateVars.length > 0) {
133
+ warnings.push('template_vars defined but no message_template to use them');
134
+ }
135
+
136
+ return {
137
+ valid: errors.length === 0,
138
+ errors,
139
+ warnings,
140
+ };
141
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Template Variables Registry
3
+ *
4
+ * Single source of truth for all template variables used in agent profiles.
5
+ * Provides type safety, runtime validation, and self-documentation.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+
10
+ /**
11
+ * All valid template variables that can be used in agent message_template fields.
12
+ * Each variable has a Zod schema for validation and a description.
13
+ */
14
+ export const TemplateVars = {
15
+ // Path variables
16
+ SPEC_PATH: z.string().describe('Path to spec file'),
17
+ ALIGNMENT_PATH: z.string().describe('Path to alignment doc'),
18
+ PROMPTS_FOLDER: z.string().describe('Path to prompts directory'),
19
+ PROMPT_PATH: z.string().describe('Path to specific prompt file'),
20
+ OUTPUT_PATH: z.string().describe('Output file path'),
21
+ PLANNING_FOLDER: z.string().describe('Path to .planning/{branch} directory'),
22
+
23
+ // Identifier variables
24
+ SPEC_NAME: z.string().describe('Current spec name'),
25
+ PROMPT_NUMBER: z
26
+ .string()
27
+ .regex(/^\d{2}$/)
28
+ .describe('Prompt number as two digits (01, 02, etc.)'),
29
+
30
+ // Branch/context variables
31
+ BRANCH: z.string().describe('Current git branch name'),
32
+
33
+ // Spec metadata variables
34
+ SPEC_TYPE: z
35
+ .string()
36
+ .describe('Spec type from frontmatter (milestone, investigation, optimization, refactor, documentation, triage)'),
37
+
38
+ // Workflow domain variables
39
+ WORKFLOW_DOMAIN_PATH: z
40
+ .string()
41
+ .describe('Path to workflow domain config file in .allhands/workflows/'),
42
+
43
+ // Emergent planner variables
44
+ HYPOTHESIS_DOMAINS: z
45
+ .string()
46
+ .describe('Comma-separated list of hypothesis domains from settings.json'),
47
+ } as const;
48
+
49
+ /**
50
+ * Union type of all valid template variable names
51
+ */
52
+ export type TemplateVarName = keyof typeof TemplateVars;
53
+
54
+ /**
55
+ * Array of all valid template variable names (for runtime checks)
56
+ */
57
+ export const TEMPLATE_VAR_NAMES = Object.keys(TemplateVars) as TemplateVarName[];
58
+
59
+ /**
60
+ * Context object passed to template resolution.
61
+ * All variables are optional - validation happens against profile requirements.
62
+ */
63
+ export type TemplateContext = Partial<Record<TemplateVarName, string | null>>;
64
+
65
+ /**
66
+ * Zod schema for validating a single template variable name
67
+ */
68
+ export const TemplateVarNameSchema = z.enum(TEMPLATE_VAR_NAMES as [TemplateVarName, ...TemplateVarName[]]);
69
+
70
+ /**
71
+ * Validate that a string is a valid template variable name
72
+ */
73
+ export function isValidTemplateVar(name: string): name is TemplateVarName {
74
+ return TEMPLATE_VAR_NAMES.includes(name as TemplateVarName);
75
+ }
76
+
77
+ /**
78
+ * Validate a context object against required variables.
79
+ * Returns errors for missing or invalid variables.
80
+ */
81
+ export function validateContext(
82
+ context: TemplateContext,
83
+ requiredVars: TemplateVarName[]
84
+ ): { valid: boolean; errors: string[] } {
85
+ const errors: string[] = [];
86
+
87
+ for (const varName of requiredVars) {
88
+ const value = context[varName];
89
+
90
+ // Reject undefined or empty string
91
+ if (value === undefined || value === '') {
92
+ errors.push(`Missing required template variable: ${varName}`);
93
+ continue;
94
+ }
95
+
96
+ // Validate value against the variable's schema
97
+ const schema = TemplateVars[varName];
98
+ const result = schema.safeParse(value);
99
+
100
+ if (!result.success) {
101
+ errors.push(`Invalid value for ${varName}: ${result.error.issues[0]?.message}`);
102
+ }
103
+ }
104
+
105
+ return {
106
+ valid: errors.length === 0,
107
+ errors,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Get description for a template variable
113
+ */
114
+ export function getTemplateVarDescription(name: TemplateVarName): string {
115
+ return TemplateVars[name].description ?? name;
116
+ }
117
+
118
+ /**
119
+ * Extract template variable references from a template string.
120
+ * Returns array of variable names found in ${VAR_NAME} patterns.
121
+ */
122
+ export function extractTemplateVars(template: string): string[] {
123
+ const matches = template.matchAll(/\$\{([^}]+)\}/g);
124
+ return [...matches].map((m) => m[1]);
125
+ }
126
+
127
+ /**
128
+ * Validate that all variables referenced in a template are valid.
129
+ */
130
+ export function validateTemplateString(template: string): { valid: boolean; invalidVars: string[] } {
131
+ const refs = extractTemplateVars(template);
132
+ const invalidVars = refs.filter((v) => !isValidTemplateVar(v));
133
+
134
+ return {
135
+ valid: invalidVars.length === 0,
136
+ invalidVars,
137
+ };
138
+ }