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,354 @@
1
+ /**
2
+ * Validation Path Consistency Tests
3
+ *
4
+ * Documents behavioral divergences between the two validation paths:
5
+ * - lib/schema.ts: Core library used by validateFile(), CLI commands
6
+ * - hooks/validation.ts: Hook layer used by schema-pre/schema PostToolUse hooks
7
+ *
8
+ * These paths independently implement: frontmatter parsing, schema loading,
9
+ * and frontmatter validation with subtle differences in regex patterns,
10
+ * type branch coverage, return types, and schema.fields fallback behavior.
11
+ *
12
+ * Purpose: Prevent silent drift — when one path is updated, these tests
13
+ * catch cases where the other path would behave differently.
14
+ *
15
+ * Divergences documented:
16
+ * 1. Frontmatter parsing regex (trailing newline requirement)
17
+ * 2. validateFrontmatter type branch coverage (hooks missing boolean/date/object)
18
+ * 3. Return type shape (ValidationResult vs ValidationError[])
19
+ * 4. schema.fields fallback (lib uses it, hooks ignores it)
20
+ */
21
+
22
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
23
+ import {
24
+ extractFrontmatter,
25
+ validateFrontmatter,
26
+ type Schema,
27
+ type ValidationResult,
28
+ } from '../../lib/schema.js';
29
+ import {
30
+ createFixture,
31
+ runHook,
32
+ assertHookAllowed,
33
+ assertHookDenied,
34
+ PROMPT_TEMPLATE,
35
+ type TestFixture,
36
+ } from '../harness/index.js';
37
+
38
+ // ─────────────────────────────────────────────────────────────────────────────
39
+ // Fixture Setup
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+
42
+ let fixture: TestFixture;
43
+
44
+ beforeAll(() => {
45
+ fixture = createFixture({
46
+ name: 'validation-path-consistency',
47
+ files: {
48
+ '.planning/test/prompts/01.md': PROMPT_TEMPLATE('pending', 'Seeded', 1),
49
+ },
50
+ });
51
+ });
52
+
53
+ afterAll(() => {
54
+ fixture.cleanup();
55
+ });
56
+
57
+ // ─────────────────────────────────────────────────────────────────────────────
58
+ // Divergence 1: Frontmatter Parsing Regex
59
+ //
60
+ // lib extractFrontmatter: /^---\n([\s\S]*?)\n---\n([\s\S]*)$/
61
+ // hooks parseFrontmatter: /^---\n([\s\S]*?)\n---/
62
+ //
63
+ // Key difference: lib requires \n after closing --- and captures body via
64
+ // ([\s\S]*)$ — hooks does NOT require trailing newline after closing ---.
65
+ // This means content ending with "---" (no trailing newline) is parsed by
66
+ // hooks but rejected by lib.
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+
69
+ describe('Divergence 1: Frontmatter parsing regex', () => {
70
+ it('lib extractFrontmatter returns null for content without trailing newline after closing ---', () => {
71
+ // Content: "---\nkey: val\n---" — no trailing newline or body
72
+ // lib regex /^---\n([\s\S]*?)\n---\n([\s\S]*)$/ requires \n after ---
73
+ // Expected: lib returns null frontmatter
74
+ // DIVERGENCE: hooks parseFrontmatter /^---\n([\s\S]*?)\n---/ WOULD match this
75
+ const content = '---\nkey: val\n---';
76
+ const libResult = extractFrontmatter(content);
77
+ expect(libResult.frontmatter).toBeNull();
78
+ // If hooks were tested directly, parseFrontmatter would return { key: 'val' }
79
+ });
80
+
81
+ it('lib extractFrontmatter succeeds when trailing newline is present', () => {
82
+ // Both paths agree when trailing newline exists
83
+ const content = '---\nkey: val\n---\n';
84
+ const libResult = extractFrontmatter(content);
85
+ expect(libResult.frontmatter).not.toBeNull();
86
+ expect(libResult.frontmatter!['key']).toBe('val');
87
+ });
88
+
89
+ it('hooks schema-pre denies prompt content without trailing newline after closing ---', async () => {
90
+ // The hooks path uses parseFrontmatter which DOES match without trailing newline,
91
+ // BUT the content below has no required fields, so it gets denied for schema errors
92
+ // (not for missing frontmatter). This confirms hooks successfully parsed the frontmatter.
93
+ const content = '---\nkey: val\n---';
94
+ const result = await runHook(
95
+ 'validation',
96
+ 'schema-pre',
97
+ {
98
+ tool_name: 'Write',
99
+ tool_input: {
100
+ file_path: `${fixture.root}/.planning/test/prompts/no-trailing-newline.md`,
101
+ content,
102
+ },
103
+ },
104
+ fixture
105
+ );
106
+ // Hooks should deny (missing required fields), but importantly NOT for "missing frontmatter" —
107
+ // this proves the hooks regex successfully parsed frontmatter where lib would return null
108
+ assertHookDenied(result);
109
+ });
110
+
111
+ it('both paths agree on standard content with trailing newline and body', async () => {
112
+ // Standard format: both paths should parse identically
113
+ const content = PROMPT_TEMPLATE('pending', 'Consistency Check', 99);
114
+
115
+ // lib path
116
+ const libResult = extractFrontmatter(content);
117
+ expect(libResult.frontmatter).not.toBeNull();
118
+ expect(libResult.frontmatter!['title']).toBe('Consistency Check');
119
+
120
+ // hooks path (via E2E runner)
121
+ const hookResult = await runHook(
122
+ 'validation',
123
+ 'schema-pre',
124
+ {
125
+ tool_name: 'Write',
126
+ tool_input: {
127
+ file_path: `${fixture.root}/.planning/test/prompts/standard-format.md`,
128
+ content,
129
+ },
130
+ },
131
+ fixture
132
+ );
133
+ assertHookAllowed(hookResult);
134
+ });
135
+ });
136
+
137
+ // ─────────────────────────────────────────────────────────────────────────────
138
+ // Divergence 2: validateFrontmatter Type Branch Coverage
139
+ //
140
+ // lib validateField handles: string, integer, boolean, date, enum, array, object
141
+ // hooks validateFrontmatter handles: string, integer, enum, array
142
+ //
143
+ // Missing in hooks: boolean, date, object (with nested property validation)
144
+ // Impact: A schema field typed as boolean/date/object passes hooks validation
145
+ // without type checking, but gets validated by lib. If only hooks enforce
146
+ // (PreToolUse/PostToolUse), incorrect types may slip through.
147
+ // ─────────────────────────────────────────────────────────────────────────────
148
+
149
+ describe('Divergence 2: Type branch coverage', () => {
150
+ it('lib validates boolean fields — rejects non-boolean value', () => {
151
+ const schema: Schema = {
152
+ frontmatter: {
153
+ flag: { type: 'boolean', required: true },
154
+ },
155
+ };
156
+ const result = validateFrontmatter({ flag: 'true' }, schema);
157
+ expect(result.valid).toBe(false);
158
+ expect(result.errors[0].field).toBe('flag');
159
+ // DIVERGENCE: hooks has no 'boolean' case in its switch statement,
160
+ // so { flag: 'true' } would pass hooks validation without type error
161
+ });
162
+
163
+ it('lib validates boolean fields — accepts true', () => {
164
+ const schema: Schema = {
165
+ frontmatter: {
166
+ flag: { type: 'boolean', required: true },
167
+ },
168
+ };
169
+ const result = validateFrontmatter({ flag: true }, schema);
170
+ expect(result.valid).toBe(true);
171
+ });
172
+
173
+ it('lib validates date fields — rejects invalid date string', () => {
174
+ const schema: Schema = {
175
+ frontmatter: {
176
+ created: { type: 'date', required: true },
177
+ },
178
+ };
179
+ const result = validateFrontmatter({ created: 'not-a-date' }, schema);
180
+ expect(result.valid).toBe(false);
181
+ expect(result.errors[0].field).toBe('created');
182
+ // DIVERGENCE: hooks has no 'date' case, so 'not-a-date' would pass hooks validation
183
+ });
184
+
185
+ it('lib validates object fields with nested properties', () => {
186
+ const schema: Schema = {
187
+ frontmatter: {
188
+ config: {
189
+ type: 'object',
190
+ required: true,
191
+ properties: {
192
+ name: { type: 'string', required: true },
193
+ },
194
+ },
195
+ },
196
+ };
197
+ // Missing required nested property
198
+ const result = validateFrontmatter({ config: {} }, schema);
199
+ expect(result.valid).toBe(false);
200
+ expect(result.errors[0].field).toBe('config.name');
201
+ // DIVERGENCE: hooks has no 'object' case and no nested validation,
202
+ // so { config: {} } would pass hooks validation entirely
203
+ });
204
+ });
205
+
206
+ // ─────────────────────────────────────────────────────────────────────────────
207
+ // Divergence 3: Return Type Shape
208
+ //
209
+ // lib validateFrontmatter returns: ValidationResult { valid: boolean, errors: ValidationError[] }
210
+ // where ValidationError = { field, message, expected?, received? }
211
+ // hooks validateFrontmatter returns: ValidationError[] (array only, no 'valid' wrapper)
212
+ // where ValidationError = { field, message } (no expected/received)
213
+ //
214
+ // Impact: Code consuming hooks errors cannot check .valid — must check .length.
215
+ // Error messages also differ in format (hooks uses "Field 'X' must be..." vs
216
+ // lib uses "Expected string" with expected/received metadata).
217
+ // ─────────────────────────────────────────────────────────────────────────────
218
+
219
+ describe('Divergence 3: Return type shape', () => {
220
+ it('lib returns ValidationResult with valid flag and error metadata', () => {
221
+ const schema: Schema = {
222
+ frontmatter: {
223
+ name: { type: 'string', required: true },
224
+ },
225
+ };
226
+ const result: ValidationResult = validateFrontmatter({ name: 42 }, schema);
227
+ // lib returns structured result with valid flag
228
+ expect(typeof result.valid).toBe('boolean');
229
+ expect(result.valid).toBe(false);
230
+ // lib errors include expected/received metadata
231
+ expect(result.errors[0].expected).toBeDefined();
232
+ expect(result.errors[0].received).toBeDefined();
233
+ // DIVERGENCE: hooks returns ValidationError[] directly (no .valid wrapper)
234
+ // and errors only have { field, message } — no expected/received
235
+ });
236
+
237
+ it('lib error message format uses generic phrasing', () => {
238
+ const schema: Schema = {
239
+ frontmatter: {
240
+ count: { type: 'integer', required: true },
241
+ },
242
+ };
243
+ const result = validateFrontmatter({ count: 'not-int' }, schema);
244
+ expect(result.errors[0].message).toBe('Expected integer');
245
+ // DIVERGENCE: hooks would produce "Field 'count' must be an integer"
246
+ // (includes field name in message, different phrasing)
247
+ });
248
+ });
249
+
250
+ // ─────────────────────────────────────────────────────────────────────────────
251
+ // Divergence 4: schema.fields Fallback
252
+ //
253
+ // lib validateFrontmatter: iterates schema.frontmatter || schema.fields || {}
254
+ // hooks validateFrontmatter: returns [] immediately if !schema.frontmatter
255
+ //
256
+ // Impact: Schemas that use `fields` key (e.g., status.yaml) are validated by
257
+ // lib but silently pass hooks validation with zero errors.
258
+ // ─────────────────────────────────────────────────────────────────────────────
259
+
260
+ describe('Divergence 4: schema.fields fallback', () => {
261
+ it('lib validates against schema.fields when schema.frontmatter is absent', () => {
262
+ const schema: Schema = {
263
+ fields: {
264
+ name: { type: 'string', required: true },
265
+ },
266
+ };
267
+ const result = validateFrontmatter({}, schema);
268
+ expect(result.valid).toBe(false);
269
+ expect(result.errors[0].field).toBe('name');
270
+ // DIVERGENCE: hooks returns [] (no errors) because !schema.frontmatter is true
271
+ // and it returns early without checking schema.fields
272
+ });
273
+
274
+ it('lib falls through to empty object when neither frontmatter nor fields exist', () => {
275
+ const schema: Schema = {};
276
+ const result = validateFrontmatter({ anything: 'goes' }, schema);
277
+ expect(result.valid).toBe(true);
278
+ expect(result.errors).toHaveLength(0);
279
+ // Both paths agree here: no fields defined means no validation to do
280
+ });
281
+ });
282
+
283
+ // ─────────────────────────────────────────────────────────────────────────────
284
+ // Agreement Tests: Cases Where Both Paths Agree
285
+ //
286
+ // These verify the happy path where both implementations produce consistent
287
+ // results, serving as regression anchors if either path is refactored.
288
+ // ─────────────────────────────────────────────────────────────────────────────
289
+
290
+ describe('Agreement: both paths produce consistent results', () => {
291
+ it('both paths accept valid string field', () => {
292
+ // lib path
293
+ const schema: Schema = {
294
+ frontmatter: {
295
+ title: { type: 'string', required: true },
296
+ },
297
+ };
298
+ const result = validateFrontmatter({ title: 'valid' }, schema);
299
+ expect(result.valid).toBe(true);
300
+ // hooks path handles string identically (typeof value !== 'string' check)
301
+ });
302
+
303
+ it('both paths reject missing required field', () => {
304
+ const schema: Schema = {
305
+ frontmatter: {
306
+ name: { type: 'string', required: true },
307
+ },
308
+ };
309
+ const result = validateFrontmatter({}, schema);
310
+ expect(result.valid).toBe(false);
311
+ // Both paths check: field.required && (value === undefined || value === null)
312
+ });
313
+
314
+ it('both paths validate array item types consistently', () => {
315
+ const schema: Schema = {
316
+ frontmatter: {
317
+ tools: { type: 'array', required: true, items: 'string' },
318
+ },
319
+ };
320
+ // Valid array
321
+ const valid = validateFrontmatter({ tools: ['a', 'b'] }, schema);
322
+ expect(valid.valid).toBe(true);
323
+
324
+ // Invalid array (number in string array)
325
+ const invalid = validateFrontmatter({ tools: [123, 'b'] }, schema);
326
+ expect(invalid.valid).toBe(false);
327
+ // Both paths use identical item-type checking logic for arrays
328
+ });
329
+
330
+ it('both paths validate enum fields consistently', () => {
331
+ const schema: Schema = {
332
+ frontmatter: {
333
+ status: { type: 'enum', required: true, values: ['pending', 'done'] },
334
+ },
335
+ };
336
+ const valid = validateFrontmatter({ status: 'pending' }, schema);
337
+ expect(valid.valid).toBe(true);
338
+
339
+ const invalid = validateFrontmatter({ status: 'garbage' }, schema);
340
+ expect(invalid.valid).toBe(false);
341
+ // Both paths check: !field.values?.includes(String(value))
342
+ });
343
+
344
+ it('both paths silently pass extra fields not in schema', () => {
345
+ const schema: Schema = {
346
+ frontmatter: {
347
+ name: { type: 'string', required: true },
348
+ },
349
+ };
350
+ const result = validateFrontmatter({ name: 'valid', extra: 'ignored' }, schema);
351
+ expect(result.valid).toBe(true);
352
+ // Neither path validates unknown fields — both iterate only schema-defined fields
353
+ });
354
+ });