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,605 @@
1
+ /**
2
+ * Planning Directory Management
3
+ *
4
+ * Handles .planning/ directory structure:
5
+ * - .planning/{branch}/prompts/ - Prompt files for execution
6
+ * - .planning/{branch}/alignment.md - Alignment doc with decisions
7
+ * - .planning/{branch}/status.yaml - Session state
8
+ *
9
+ * In the branch-keyed model:
10
+ * - Planning directories are keyed by sanitized branch name (feature/foo → feature-foo)
11
+ * - The spec's frontmatter.branch field is the source of truth for which branch belongs to which spec
12
+ * - Current git branch determines the active spec via findSpecByBranch()
13
+ */
14
+
15
+ import { execSync } from 'child_process';
16
+ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
17
+ import { join } from 'path';
18
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
19
+ import { getBaseBranch } from './git.js';
20
+
21
+ /**
22
+ * Locked branch patterns - branches that should never have planning dirs.
23
+ * Includes BASE_BRANCH, common protected branches, and worktree/quick prefixes.
24
+ */
25
+ const LOCKED_BRANCH_NAMES = new Set([
26
+ 'main',
27
+ 'master',
28
+ 'develop',
29
+ 'dev',
30
+ 'stage',
31
+ 'staging',
32
+ 'prod',
33
+ 'production',
34
+ ]);
35
+
36
+ const LOCKED_BRANCH_PREFIXES = ['wt-', 'quick/'];
37
+
38
+ /**
39
+ * Sanitize a branch name for use as a directory name.
40
+ * Converts slashes and other non-safe characters to hyphens.
41
+ * Example: feature/foo-bar → feature-foo-bar
42
+ */
43
+ export function sanitizeBranchForDir(branch: string): string {
44
+ return branch.replace(/[^a-zA-Z0-9_-]/g, '-');
45
+ }
46
+
47
+ /**
48
+ * Check if a branch is a "locked" branch that should not have planning.
49
+ * Locked branches: BASE_BRANCH, main, develop, dev, stage, staging, prod, production, wt-*, quick/*
50
+ */
51
+ export function isLockedBranch(branch: string): boolean {
52
+ // Check if it's the configured base branch
53
+ const baseBranch = getBaseBranch();
54
+ if (branch === baseBranch) {
55
+ return true;
56
+ }
57
+
58
+ // Check against known locked names
59
+ if (LOCKED_BRANCH_NAMES.has(branch)) {
60
+ return true;
61
+ }
62
+
63
+ // Check prefixes
64
+ for (const prefix of LOCKED_BRANCH_PREFIXES) {
65
+ if (branch.startsWith(prefix)) {
66
+ return true;
67
+ }
68
+ }
69
+
70
+ return false;
71
+ }
72
+
73
+ export interface LoopConfig {
74
+ enabled?: boolean; // Deprecated: loop always starts disabled, not persisted
75
+ parallel?: boolean; // Parallel execution enabled (persisted per spec)
76
+ iteration: number;
77
+ }
78
+
79
+ export interface PRStatus {
80
+ url: string;
81
+ number: number;
82
+ created: string;
83
+ }
84
+
85
+ export interface PRReviewStatus {
86
+ reviewCycle: number;
87
+ lastReviewTime: string | null;
88
+ lastReviewRunTime: string | null; // When we started waiting for review
89
+ status: 'pending' | 'reviewing' | 'completed' | 'none';
90
+ }
91
+
92
+ export interface StatusFile {
93
+ name: string; // Directory key (sanitized branch name)
94
+ branch?: string; // Original branch name (for collision detection)
95
+ spec: string; // Path to spec file
96
+ stage: 'planning' | 'executing' | 'reviewing' | 'pr' | 'compound' | 'steering';
97
+ loop: LoopConfig;
98
+ compound_run: boolean;
99
+ created: string;
100
+ updated: string;
101
+ pr?: PRStatus;
102
+ prReview?: PRReviewStatus;
103
+ }
104
+
105
+ export interface AlignmentFrontmatter {
106
+ name: string; // Spec name
107
+ spec: string; // Path to spec file
108
+ created: string;
109
+ updated: string;
110
+ }
111
+
112
+ export interface DecisionEntry {
113
+ promptNumber: number;
114
+ promptTitle: string;
115
+ decision: string;
116
+ files: string[];
117
+ summary: string;
118
+ }
119
+
120
+ /**
121
+ * Get the current git branch name
122
+ */
123
+ export function getCurrentBranch(cwd?: string): string {
124
+ try {
125
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
126
+ cwd: cwd || process.cwd(),
127
+ encoding: 'utf-8',
128
+ stdio: ['pipe', 'pipe', 'pipe'],
129
+ }).trim();
130
+ return branch;
131
+ } catch {
132
+ return 'main';
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Get the root of the git repository
138
+ */
139
+ export function getGitRoot(cwd?: string): string {
140
+ try {
141
+ const root = execSync('git rev-parse --show-toplevel', {
142
+ cwd: cwd || process.cwd(),
143
+ encoding: 'utf-8',
144
+ stdio: ['pipe', 'pipe', 'pipe'],
145
+ }).trim();
146
+ return root;
147
+ } catch {
148
+ return process.cwd();
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Get the .planning directory path for a key (sanitized branch name)
154
+ */
155
+ export function getPlanningDir(key: string, cwd?: string): string {
156
+ const gitRoot = getGitRoot(cwd);
157
+ return join(gitRoot, '.planning', key);
158
+ }
159
+
160
+ /**
161
+ * Get paths within the planning directory for a key
162
+ */
163
+ export function getPlanningPaths(key: string, cwd?: string) {
164
+ const planningDir = getPlanningDir(key, cwd);
165
+ return {
166
+ root: planningDir,
167
+ prompts: join(planningDir, 'prompts'),
168
+ alignment: join(planningDir, 'alignment.md'),
169
+ status: join(planningDir, 'status.yaml'),
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Ensure the .planning directory structure exists for a key
175
+ */
176
+ export function ensurePlanningDir(key: string, cwd?: string): void {
177
+ const paths = getPlanningPaths(key, cwd);
178
+
179
+ // Create directories
180
+ mkdirSync(paths.root, { recursive: true });
181
+ mkdirSync(paths.prompts, { recursive: true });
182
+ }
183
+
184
+ /**
185
+ * Check if planning directory exists for a key
186
+ */
187
+ export function planningDirExists(key: string, cwd?: string): boolean {
188
+ const paths = getPlanningPaths(key, cwd);
189
+ return existsSync(paths.root);
190
+ }
191
+
192
+ /**
193
+ * Read the status file for a key
194
+ */
195
+ export function readStatus(key: string, cwd?: string): StatusFile | null {
196
+ const paths = getPlanningPaths(key, cwd);
197
+
198
+ if (!existsSync(paths.status)) {
199
+ return null;
200
+ }
201
+
202
+ try {
203
+ const content = readFileSync(paths.status, 'utf-8');
204
+ return parseYaml(content) as StatusFile;
205
+ } catch {
206
+ return null;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Check if a branch matches the status file's original branch.
212
+ * Returns true if they match or if no branch is stored (backwards compatibility).
213
+ * Returns false if there's a collision (different branches sanitized to same key).
214
+ */
215
+ export function validateBranchForStatus(
216
+ currentBranch: string,
217
+ key: string,
218
+ cwd?: string
219
+ ): { valid: boolean; storedBranch?: string } {
220
+ const status = readStatus(key, cwd);
221
+ if (!status) {
222
+ return { valid: true }; // No status file, no collision
223
+ }
224
+
225
+ if (!status.branch) {
226
+ return { valid: true }; // Old status file without branch, assume valid
227
+ }
228
+
229
+ if (status.branch === currentBranch) {
230
+ return { valid: true, storedBranch: status.branch };
231
+ }
232
+
233
+ // Collision detected: different branch maps to same key
234
+ return { valid: false, storedBranch: status.branch };
235
+ }
236
+
237
+ /**
238
+ * Write the status file for a key
239
+ */
240
+ export function writeStatus(status: StatusFile, key: string, cwd?: string): void {
241
+ const paths = getPlanningPaths(key, cwd);
242
+ ensurePlanningDir(key, cwd);
243
+
244
+ const content = stringifyYaml(status);
245
+ writeFileSync(paths.status, content);
246
+ }
247
+
248
+ /**
249
+ * Update specific fields in the status file for a key
250
+ */
251
+ export function updateStatus(
252
+ updates: Partial<StatusFile>,
253
+ key: string,
254
+ cwd?: string
255
+ ): StatusFile {
256
+ const current = readStatus(key, cwd);
257
+ if (!current) {
258
+ throw new Error('No status file exists. Initialize planning first.');
259
+ }
260
+
261
+ const updated: StatusFile = {
262
+ ...current,
263
+ ...updates,
264
+ updated: new Date().toISOString(),
265
+ };
266
+
267
+ writeStatus(updated, key, cwd);
268
+ return updated;
269
+ }
270
+
271
+ /**
272
+ * Create initial status file for a new planning directory
273
+ *
274
+ * @param key - The directory key (sanitized branch name)
275
+ * @param specPath - Path to the spec file
276
+ * @param originalBranch - Original branch name (for collision detection)
277
+ * @param cwd - Working directory
278
+ */
279
+ export function initializeStatus(
280
+ key: string,
281
+ specPath: string,
282
+ originalBranch?: string | null,
283
+ cwd?: string
284
+ ): StatusFile {
285
+ const now = new Date().toISOString();
286
+
287
+ const status: StatusFile = {
288
+ name: key,
289
+ branch: originalBranch ?? undefined,
290
+ spec: specPath,
291
+ stage: 'planning',
292
+ loop: {
293
+ iteration: 0,
294
+ },
295
+ compound_run: false,
296
+ created: now,
297
+ updated: now,
298
+ };
299
+
300
+ writeStatus(status, key, cwd);
301
+ return status;
302
+ }
303
+
304
+ /**
305
+ * Read the alignment doc frontmatter for a key
306
+ */
307
+ export function readAlignmentFrontmatter(
308
+ key: string,
309
+ cwd?: string
310
+ ): AlignmentFrontmatter | null {
311
+ const paths = getPlanningPaths(key, cwd);
312
+
313
+ if (!existsSync(paths.alignment)) {
314
+ return null;
315
+ }
316
+
317
+ try {
318
+ const content = readFileSync(paths.alignment, 'utf-8');
319
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
320
+ if (!frontmatterMatch) return null;
321
+
322
+ return parseYaml(frontmatterMatch[1]) as AlignmentFrontmatter;
323
+ } catch {
324
+ return null;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Read the full alignment doc for a key
330
+ */
331
+ export function readAlignment(key: string, cwd?: string): string | null {
332
+ const paths = getPlanningPaths(key, cwd);
333
+
334
+ if (!existsSync(paths.alignment)) {
335
+ return null;
336
+ }
337
+
338
+ return readFileSync(paths.alignment, 'utf-8');
339
+ }
340
+
341
+ /**
342
+ * Create initial alignment doc for a spec
343
+ */
344
+ export function initializeAlignment(
345
+ specName: string,
346
+ specPath: string,
347
+ overview: string,
348
+ hardRequirements: string[],
349
+ cwd?: string
350
+ ): void {
351
+ const paths = getPlanningPaths(specName, cwd);
352
+ ensurePlanningDir(specName, cwd);
353
+
354
+ const now = new Date().toISOString();
355
+ const frontmatter = stringifyYaml({
356
+ name: specName,
357
+ spec: specPath,
358
+ created: now,
359
+ updated: now,
360
+ });
361
+
362
+ const requirementsList = hardRequirements.map((r) => `- ${r}`).join('\n');
363
+
364
+ const content = `---
365
+ ${frontmatter.trim()}
366
+ ---
367
+
368
+ ## Overview
369
+
370
+ ${overview}
371
+
372
+ ## Hard Requirements
373
+
374
+ ${requirementsList}
375
+
376
+ ## Key Decisions
377
+
378
+ <!-- Decisions appended by executing agents -->
379
+
380
+ `;
381
+
382
+ writeFileSync(paths.alignment, content);
383
+ }
384
+
385
+ /**
386
+ * Append a decision to the alignment doc for a key
387
+ */
388
+ export function appendDecision(
389
+ entry: DecisionEntry,
390
+ key: string,
391
+ cwd?: string
392
+ ): void {
393
+ const paths = getPlanningPaths(key, cwd);
394
+
395
+ if (!existsSync(paths.alignment)) {
396
+ throw new Error('No alignment doc exists. Initialize planning first.');
397
+ }
398
+
399
+ const content = readFileSync(paths.alignment, 'utf-8');
400
+ const filesList = entry.files.map((f) => `\`${f}\``).join(', ');
401
+ const promptLink = `./prompts/${String(entry.promptNumber).padStart(2, '0')}-${entry.promptTitle.toLowerCase().replace(/\s+/g, '-')}.md`;
402
+
403
+ const decisionBlock = `
404
+ ### Prompt ${String(entry.promptNumber).padStart(2, '0')}: ${entry.promptTitle}
405
+
406
+ **Decision**: ${entry.decision}
407
+
408
+ **Files**: ${filesList}
409
+
410
+ **Summary**: ${entry.summary}
411
+
412
+ **Link**: \`${promptLink}\`
413
+ `;
414
+
415
+ // Update frontmatter updated timestamp
416
+ const updatedContent = content.replace(
417
+ /^(---\n[\s\S]*?updated:\s*).+(\n---)/m,
418
+ `$1${new Date().toISOString()}$2`
419
+ );
420
+
421
+ writeFileSync(paths.alignment, updatedContent + decisionBlock);
422
+ }
423
+
424
+ /**
425
+ * List all prompt files in the planning directory for a key
426
+ */
427
+ export function listPromptFiles(key: string, cwd?: string): string[] {
428
+ const paths = getPlanningPaths(key, cwd);
429
+
430
+ if (!existsSync(paths.prompts)) {
431
+ return [];
432
+ }
433
+
434
+ return readdirSync(paths.prompts)
435
+ .filter((f) => f.endsWith('.md'))
436
+ .sort();
437
+ }
438
+
439
+ /**
440
+ * Get alignment doc token count estimate (rough) for a key
441
+ */
442
+ export function getAlignmentTokenCount(key: string, cwd?: string): number {
443
+ const content = readAlignment(key, cwd);
444
+ if (!content) return 0;
445
+
446
+ // Rough estimate: ~4 chars per token
447
+ return Math.ceil(content.length / 4);
448
+ }
449
+
450
+ /**
451
+ * Update PR status in status file for a key
452
+ */
453
+ export function updatePRStatus(
454
+ url: string,
455
+ number: number,
456
+ key: string,
457
+ cwd?: string
458
+ ): StatusFile {
459
+ return updateStatus(
460
+ {
461
+ pr: {
462
+ url,
463
+ number,
464
+ created: new Date().toISOString(),
465
+ },
466
+ },
467
+ key,
468
+ cwd
469
+ );
470
+ }
471
+
472
+ /**
473
+ * Update PR review status in status file for a key
474
+ */
475
+ export function updatePRReviewStatus(
476
+ state: Partial<PRReviewStatus>,
477
+ key: string,
478
+ cwd?: string
479
+ ): StatusFile {
480
+ const current = readStatus(key, cwd);
481
+ if (!current) {
482
+ throw new Error('No status file exists. Initialize planning first.');
483
+ }
484
+
485
+ const currentPRReview = current.prReview || {
486
+ reviewCycle: 0,
487
+ lastReviewTime: null,
488
+ lastReviewRunTime: null,
489
+ status: 'none' as const,
490
+ };
491
+
492
+ return updateStatus(
493
+ {
494
+ prReview: {
495
+ ...currentPRReview,
496
+ ...state,
497
+ },
498
+ },
499
+ key,
500
+ cwd
501
+ );
502
+ }
503
+
504
+ /**
505
+ * Reset planning artifacts for re-ideation.
506
+ *
507
+ * Deletes all prompt files and the alignment doc, then resets
508
+ * status.yaml stage to 'planning'. Keeps the status.yaml and
509
+ * directory structure intact.
510
+ *
511
+ * @param key - The directory key (sanitized branch name)
512
+ * @param cwd - Working directory
513
+ * @returns true if artifacts were reset, false if no planning dir exists
514
+ */
515
+ export function resetPlanningArtifacts(key: string, cwd?: string): boolean {
516
+ const paths = getPlanningPaths(key, cwd);
517
+
518
+ if (!existsSync(paths.root)) {
519
+ return false;
520
+ }
521
+
522
+ // Delete all files in prompts/ subdirectory
523
+ if (existsSync(paths.prompts)) {
524
+ const promptFiles = readdirSync(paths.prompts);
525
+ for (const file of promptFiles) {
526
+ unlinkSync(join(paths.prompts, file));
527
+ }
528
+ }
529
+
530
+ // Delete alignment.md if it exists
531
+ if (existsSync(paths.alignment)) {
532
+ unlinkSync(paths.alignment);
533
+ }
534
+
535
+ // Reset status.yaml stage to 'planning'
536
+ if (existsSync(paths.status)) {
537
+ const status = readStatus(key, cwd);
538
+ if (status) {
539
+ updateStatus({ stage: 'planning' }, key, cwd);
540
+ }
541
+ }
542
+
543
+ return true;
544
+ }
545
+
546
+ // ============================================================================
547
+ // Planning Directory Listing
548
+ // ============================================================================
549
+
550
+ export interface PlanningInfo {
551
+ /** Directory key (sanitized branch name) */
552
+ key: string;
553
+ /** Path to spec file */
554
+ specPath: string;
555
+ /** Current stage */
556
+ stage: string;
557
+ /** Whether this is for the current git branch */
558
+ isCurrent: boolean;
559
+ }
560
+
561
+ /**
562
+ * List all planning directories
563
+ */
564
+ export function listPlanningDirs(cwd?: string): PlanningInfo[] {
565
+ const gitRoot = getGitRoot(cwd);
566
+ const planningRoot = join(gitRoot, '.planning');
567
+
568
+ if (!existsSync(planningRoot)) {
569
+ return [];
570
+ }
571
+
572
+ const currentBranch = getCurrentBranch(cwd);
573
+ const currentKey = sanitizeBranchForDir(currentBranch);
574
+ const entries = readdirSync(planningRoot, { withFileTypes: true });
575
+ const dirs: PlanningInfo[] = [];
576
+
577
+ for (const entry of entries) {
578
+ // Skip non-directories and hidden files
579
+ if (!entry.isDirectory() || entry.name.startsWith('.')) {
580
+ continue;
581
+ }
582
+
583
+ const statusPath = join(planningRoot, entry.name, 'status.yaml');
584
+ if (!existsSync(statusPath)) {
585
+ continue;
586
+ }
587
+
588
+ try {
589
+ const content = readFileSync(statusPath, 'utf-8');
590
+ const status = parseYaml(content) as StatusFile;
591
+
592
+ dirs.push({
593
+ key: entry.name,
594
+ specPath: status.spec,
595
+ stage: status.stage,
596
+ isCurrent: entry.name === currentKey,
597
+ });
598
+ } catch {
599
+ // Skip malformed status files
600
+ continue;
601
+ }
602
+ }
603
+
604
+ return dirs;
605
+ }