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,522 @@
1
+ /**
2
+ * Prompt Management and Picker
3
+ *
4
+ * Handles prompt file operations and the prompt picker algorithm
5
+ * for the execution loop.
6
+ */
7
+
8
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
9
+ import { join, basename } from 'path';
10
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
11
+ import { lockSync, unlockSync } from 'proper-lockfile';
12
+ import { getPlanningPaths } from './planning.js';
13
+
14
+ export type PromptStatus = 'pending' | 'in_progress' | 'done';
15
+ export type PromptPriority = 'high' | 'medium' | 'low';
16
+
17
+ export interface PromptFrontmatter {
18
+ number: number;
19
+ title: string;
20
+ status: PromptStatus;
21
+ dependencies: number[];
22
+ priority: PromptPriority;
23
+ attempts: number;
24
+ commits: string[];
25
+ created: string;
26
+ updated: string;
27
+ }
28
+
29
+ export interface PromptFile {
30
+ path: string;
31
+ filename: string;
32
+ frontmatter: PromptFrontmatter;
33
+ body: string;
34
+ rawContent: string;
35
+ }
36
+
37
+ export interface PickerResult {
38
+ prompt: PromptFile | null;
39
+ reason: string;
40
+ stats: {
41
+ total: number;
42
+ pending: number;
43
+ inProgress: number;
44
+ done: number;
45
+ blocked: number;
46
+ };
47
+ }
48
+
49
+ const PRIORITY_ORDER: Record<PromptPriority, number> = {
50
+ high: 0,
51
+ medium: 1,
52
+ low: 2,
53
+ };
54
+
55
+ /**
56
+ * Execute a function with file locking to prevent race conditions.
57
+ */
58
+ function withFileLock<T>(filePath: string, fn: () => T): T {
59
+ if (!existsSync(filePath)) {
60
+ // File doesn't exist yet, no locking needed
61
+ return fn();
62
+ }
63
+
64
+ lockSync(filePath);
65
+ try {
66
+ return fn();
67
+ } finally {
68
+ try {
69
+ unlockSync(filePath);
70
+ } catch {
71
+ // Ignore unlock errors
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Parse a prompt file
78
+ */
79
+ export function parsePromptFile(filePath: string): PromptFile | null {
80
+ if (!existsSync(filePath)) {
81
+ return null;
82
+ }
83
+
84
+ try {
85
+ const rawContent = readFileSync(filePath, 'utf-8');
86
+ const frontmatterMatch = rawContent.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
87
+
88
+ if (!frontmatterMatch) {
89
+ return null;
90
+ }
91
+
92
+ const frontmatter = parseYaml(frontmatterMatch[1]) as PromptFrontmatter;
93
+ const body = frontmatterMatch[2];
94
+
95
+ return {
96
+ path: filePath,
97
+ filename: basename(filePath),
98
+ frontmatter,
99
+ body,
100
+ rawContent,
101
+ };
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Load all prompt files from the planning directory for a spec
109
+ */
110
+ export function loadAllPrompts(spec: string, cwd?: string): PromptFile[] {
111
+ const paths = getPlanningPaths(spec, cwd);
112
+
113
+ if (!existsSync(paths.prompts)) {
114
+ return [];
115
+ }
116
+
117
+ const files = readdirSync(paths.prompts)
118
+ .filter((f) => f.endsWith('.md'))
119
+ .map((f) => join(paths.prompts, f));
120
+
121
+ const prompts: PromptFile[] = [];
122
+ for (const file of files) {
123
+ const prompt = parsePromptFile(file);
124
+ if (prompt) {
125
+ prompts.push(prompt);
126
+ }
127
+ }
128
+
129
+ return prompts;
130
+ }
131
+
132
+ /**
133
+ * Get the next available prompt number for a spec
134
+ * Returns the highest existing prompt number + 1
135
+ */
136
+ export function getNextPromptNumber(spec: string, cwd?: string): number {
137
+ const prompts = loadAllPrompts(spec, cwd);
138
+
139
+ if (prompts.length === 0) {
140
+ return 1;
141
+ }
142
+
143
+ const highestNumber = Math.max(...prompts.map((p) => p.frontmatter.number));
144
+ return highestNumber + 1;
145
+ }
146
+
147
+ /**
148
+ * Check if a prompt's dependencies are satisfied
149
+ */
150
+ export function dependenciesSatisfied(
151
+ prompt: PromptFile,
152
+ allPrompts: PromptFile[]
153
+ ): boolean {
154
+ const deps = prompt.frontmatter.dependencies || [];
155
+ if (deps.length === 0) return true;
156
+
157
+ const donePromptNumbers = new Set(
158
+ allPrompts
159
+ .filter((p) => p.frontmatter.status === 'done')
160
+ .map((p) => p.frontmatter.number)
161
+ );
162
+
163
+ return deps.every((depNum) => donePromptNumbers.has(depNum));
164
+ }
165
+
166
+ /**
167
+ * Pick the next prompt to execute for a spec
168
+ *
169
+ * Algorithm:
170
+ * 1. Filter to pending/in_progress prompts with satisfied dependencies
171
+ * 2. Exclude prompts already being executed (via excludePrompts)
172
+ * 3. Prefer in_progress over pending (resume interrupted work)
173
+ * 4. Sort by priority (high > medium > low)
174
+ * 5. Within same priority, sort by number (lower first)
175
+ *
176
+ * @param spec - The spec/planning key
177
+ * @param cwd - Working directory
178
+ * @param excludePrompts - Prompt numbers to exclude (already being executed)
179
+ */
180
+ export function pickNextPrompt(
181
+ spec: string,
182
+ cwd?: string,
183
+ excludePrompts: number[] = []
184
+ ): PickerResult {
185
+ const prompts = loadAllPrompts(spec, cwd);
186
+ const excludeSet = new Set(excludePrompts);
187
+
188
+ const stats = {
189
+ total: prompts.length,
190
+ pending: 0,
191
+ inProgress: 0,
192
+ done: 0,
193
+ blocked: 0,
194
+ };
195
+
196
+ // Count by status
197
+ for (const p of prompts) {
198
+ switch (p.frontmatter.status) {
199
+ case 'pending':
200
+ stats.pending++;
201
+ break;
202
+ case 'in_progress':
203
+ stats.inProgress++;
204
+ break;
205
+ case 'done':
206
+ stats.done++;
207
+ break;
208
+ }
209
+ }
210
+
211
+ if (prompts.length === 0) {
212
+ return {
213
+ prompt: null,
214
+ reason: 'No prompt files found',
215
+ stats,
216
+ };
217
+ }
218
+
219
+ // Filter to actionable prompts (excluding those already being executed)
220
+ const actionable = prompts.filter((p) => {
221
+ if (p.frontmatter.status === 'done') return false;
222
+ if (excludeSet.has(p.frontmatter.number)) return false;
223
+ if (!dependenciesSatisfied(p, prompts)) {
224
+ stats.blocked++;
225
+ return false;
226
+ }
227
+ return true;
228
+ });
229
+
230
+ // Recalculate pending count (some may be blocked)
231
+ stats.pending = stats.pending - stats.blocked;
232
+
233
+ if (actionable.length === 0) {
234
+ if (stats.done === stats.total) {
235
+ return {
236
+ prompt: null,
237
+ reason: 'All prompts completed',
238
+ stats,
239
+ };
240
+ }
241
+ // Check if all remaining are either blocked or already being executed
242
+ const beingExecuted = excludePrompts.length;
243
+ if (beingExecuted > 0) {
244
+ return {
245
+ prompt: null,
246
+ reason: `${beingExecuted} prompt(s) in progress, remaining blocked by dependencies`,
247
+ stats,
248
+ };
249
+ }
250
+ return {
251
+ prompt: null,
252
+ reason: 'No actionable prompts (all remaining are blocked by dependencies)',
253
+ stats,
254
+ };
255
+ }
256
+
257
+ // Sort: in_progress first, then by priority, then by number
258
+ actionable.sort((a, b) => {
259
+ // In-progress prompts first (resume interrupted work)
260
+ if (a.frontmatter.status === 'in_progress' && b.frontmatter.status !== 'in_progress') {
261
+ return -1;
262
+ }
263
+ if (b.frontmatter.status === 'in_progress' && a.frontmatter.status !== 'in_progress') {
264
+ return 1;
265
+ }
266
+
267
+ // Then by priority
268
+ const priorityDiff =
269
+ PRIORITY_ORDER[a.frontmatter.priority] - PRIORITY_ORDER[b.frontmatter.priority];
270
+ if (priorityDiff !== 0) return priorityDiff;
271
+
272
+ // Then by number
273
+ return a.frontmatter.number - b.frontmatter.number;
274
+ });
275
+
276
+ const selected = actionable[0];
277
+ const reason =
278
+ selected.frontmatter.status === 'in_progress'
279
+ ? `Resuming in-progress prompt ${selected.frontmatter.number}`
280
+ : `Selected prompt ${selected.frontmatter.number} (${selected.frontmatter.priority} priority)`;
281
+
282
+ return {
283
+ prompt: selected,
284
+ reason,
285
+ stats,
286
+ };
287
+ }
288
+
289
+ /**
290
+ * Update a prompt file's frontmatter
291
+ */
292
+ export function updatePromptFrontmatter(
293
+ filePath: string,
294
+ updates: Partial<PromptFrontmatter>
295
+ ): PromptFile | null {
296
+ return withFileLock(filePath, () => {
297
+ const prompt = parsePromptFile(filePath);
298
+ if (!prompt) return null;
299
+
300
+ const updatedFrontmatter = {
301
+ ...prompt.frontmatter,
302
+ ...updates,
303
+ updated: new Date().toISOString(),
304
+ };
305
+
306
+ const newContent = `---
307
+ ${stringifyYaml(updatedFrontmatter).trim()}
308
+ ---
309
+ ${prompt.body}`;
310
+
311
+ writeFileSync(filePath, newContent);
312
+ return parsePromptFile(filePath);
313
+ });
314
+ }
315
+
316
+ /**
317
+ * Mark a prompt as in_progress
318
+ */
319
+ export function markPromptInProgress(filePath: string): PromptFile | null {
320
+ return updatePromptFrontmatter(filePath, { status: 'in_progress' });
321
+ }
322
+
323
+ /**
324
+ * Mark a prompt as done
325
+ */
326
+ export function markPromptDone(filePath: string): PromptFile | null {
327
+ return updatePromptFrontmatter(filePath, { status: 'done' });
328
+ }
329
+
330
+ /**
331
+ * Increment prompt attempts counter
332
+ */
333
+ export function incrementPromptAttempts(filePath: string): PromptFile | null {
334
+ const prompt = parsePromptFile(filePath);
335
+ if (!prompt) return null;
336
+
337
+ return updatePromptFrontmatter(filePath, {
338
+ attempts: (prompt.frontmatter.attempts || 0) + 1,
339
+ });
340
+ }
341
+
342
+ /**
343
+ * Create a new prompt file for a spec
344
+ */
345
+ export function createPrompt(
346
+ number: number,
347
+ title: string,
348
+ tasks: string[],
349
+ options: {
350
+ dependencies?: number[];
351
+ priority?: PromptPriority;
352
+ acceptanceCriteria?: string[];
353
+ } = {},
354
+ spec: string,
355
+ cwd?: string
356
+ ): string {
357
+ const paths = getPlanningPaths(spec, cwd);
358
+ const now = new Date().toISOString();
359
+
360
+ const frontmatter: PromptFrontmatter = {
361
+ number,
362
+ title,
363
+ status: 'pending',
364
+ dependencies: options.dependencies || [],
365
+ priority: options.priority || 'medium',
366
+ attempts: 0,
367
+ commits: [],
368
+ created: now,
369
+ updated: now,
370
+ };
371
+
372
+ const tasksList = tasks.map((t, i) => `${i + 1}. ${t}`).join('\n');
373
+ const criteriaSection = options.acceptanceCriteria
374
+ ? `\n## Acceptance Criteria\n\n${options.acceptanceCriteria.map((c) => `- ${c}`).join('\n')}\n`
375
+ : '';
376
+
377
+ const content = `---
378
+ ${stringifyYaml(frontmatter).trim()}
379
+ ---
380
+
381
+ ## Tasks
382
+
383
+ ${tasksList}
384
+ ${criteriaSection}
385
+ ## Progress
386
+
387
+ <!-- Agent-updated section -->
388
+
389
+ `;
390
+
391
+ const slug = title.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
392
+ const filename = `${String(number).padStart(2, '0')}-${slug}.md`;
393
+ const filePath = join(paths.prompts, filename);
394
+
395
+ writeFileSync(filePath, content);
396
+ return filePath;
397
+ }
398
+
399
+ /**
400
+ * Get prompt by number for a spec
401
+ */
402
+ export function getPromptByNumber(
403
+ number: number,
404
+ spec: string,
405
+ cwd?: string
406
+ ): PromptFile | null {
407
+ const prompts = loadAllPrompts(spec, cwd);
408
+ return prompts.find((p) => p.frontmatter.number === number) || null;
409
+ }
410
+
411
+ /**
412
+ * Append content to a prompt's Progress section
413
+ *
414
+ * Format appended:
415
+ * ### Attempt N (timestamp)
416
+ * **Result**: Continue | Scratch | **Progress**: NN%
417
+ *
418
+ * **Key Learnings**:
419
+ * - Learning 1
420
+ * - Learning 2
421
+ *
422
+ * **Blockers**: Blocker description
423
+ *
424
+ * **Preserved**: `file1.ts`, `file2.ts`
425
+ */
426
+ export function appendToProgressSection(
427
+ filePath: string,
428
+ content: string
429
+ ): PromptFile | null {
430
+ return withFileLock(filePath, () => {
431
+ const prompt = parsePromptFile(filePath);
432
+ if (!prompt) return null;
433
+
434
+ // Find the Progress section
435
+ const progressMarker = '## Progress';
436
+ const progressIndex = prompt.body.indexOf(progressMarker);
437
+
438
+ if (progressIndex === -1) {
439
+ // No Progress section found - append to end
440
+ const newBody = prompt.body + '\n## Progress\n\n' + content + '\n';
441
+ const newContent = `---
442
+ ${stringifyYaml(prompt.frontmatter).trim()}
443
+ ---
444
+ ${newBody}`;
445
+ writeFileSync(filePath, newContent);
446
+ return parsePromptFile(filePath);
447
+ }
448
+
449
+ // Insert content after Progress section header and any existing content
450
+ // Find the next section (## header) or end of file
451
+ const afterProgress = prompt.body.substring(progressIndex + progressMarker.length);
452
+ const nextSectionMatch = afterProgress.match(/\n## [^\n]+/);
453
+
454
+ let insertPoint: number;
455
+ if (nextSectionMatch && nextSectionMatch.index !== undefined) {
456
+ // Insert before the next section
457
+ insertPoint = progressIndex + progressMarker.length + nextSectionMatch.index;
458
+ } else {
459
+ // No next section - append to end
460
+ insertPoint = prompt.body.length;
461
+ }
462
+
463
+ const newBody =
464
+ prompt.body.substring(0, insertPoint).trimEnd() +
465
+ '\n\n' +
466
+ content +
467
+ '\n' +
468
+ prompt.body.substring(insertPoint);
469
+
470
+ const newContent = `---
471
+ ${stringifyYaml(prompt.frontmatter).trim()}
472
+ ---
473
+ ${newBody}`;
474
+
475
+ writeFileSync(filePath, newContent);
476
+ return parsePromptFile(filePath);
477
+ });
478
+ }
479
+
480
+ /**
481
+ * Increment attempts and return the new attempt number
482
+ * (Alias for incrementPromptAttempts that returns the count)
483
+ */
484
+ export function incrementAttempts(filePath: string): number {
485
+ const prompt = parsePromptFile(filePath);
486
+ if (!prompt) return 1;
487
+
488
+ const newAttempts = (prompt.frontmatter.attempts || 0) + 1;
489
+ updatePromptFrontmatter(filePath, { attempts: newAttempts });
490
+ return newAttempts;
491
+ }
492
+
493
+ /**
494
+ * Add a commit hash to a prompt's commits array
495
+ *
496
+ * Commits are stored in chronological order (oldest first).
497
+ * This tracks all work done on the prompt, including failed attempts.
498
+ */
499
+ export function addCommitToPrompt(filePath: string, commitHash: string): PromptFile | null {
500
+ const prompt = parsePromptFile(filePath);
501
+ if (!prompt) return null;
502
+
503
+ const commits = prompt.frontmatter.commits || [];
504
+
505
+ // Avoid duplicates
506
+ if (commits.includes(commitHash)) {
507
+ return prompt;
508
+ }
509
+
510
+ return updatePromptFrontmatter(filePath, {
511
+ commits: [...commits, commitHash],
512
+ });
513
+ }
514
+
515
+ /**
516
+ * Get all commits for a prompt
517
+ */
518
+ export function getPromptCommits(filePath: string): string[] {
519
+ const prompt = parsePromptFile(filePath);
520
+ if (!prompt) return [];
521
+ return prompt.frontmatter.commits || [];
522
+ }