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,227 @@
1
+ /**
2
+ * Actions Pane - Left sidebar with agent spawners, toggles, and controls
3
+ *
4
+ * All actions are always visible — agents exit early if nothing to do.
5
+ *
6
+ * Layout (vertical):
7
+ * [1] Coordinator [2] New Initiative [3] Planner
8
+ * [4] Review Jury [5] E2E Test Plan [6] PR Action
9
+ * [7] Address PR Review [8] Compound [9] Complete
10
+ * [0] Switch Workspace [-] Custom Flow
11
+ * ━━ Toggles ━━
12
+ * [O] Loop [P] Parallel
13
+ * ━━ Controls ━━
14
+ * [V] View Logs [C] Clear Logs [R] Refresh
15
+ * [Q] Quit
16
+ */
17
+
18
+ import blessed from 'blessed';
19
+ import type { PRActionState } from './index.js';
20
+
21
+ export interface ActionItem {
22
+ id: string;
23
+ label: string;
24
+ key?: string;
25
+ type: 'action' | 'toggle' | 'separator';
26
+ highlight?: boolean;
27
+ checked?: boolean;
28
+ }
29
+
30
+ export interface ToggleState {
31
+ loopEnabled: boolean;
32
+ parallelEnabled: boolean;
33
+ prActionState: PRActionState;
34
+ }
35
+
36
+ const PANE_WIDTH = 24;
37
+ const HEADER_HEIGHT = 3;
38
+
39
+ export function createActionsPane(
40
+ screen: blessed.Widgets.Screen,
41
+ toggleState: ToggleState,
42
+ selectedIndex?: number
43
+ ): blessed.Widgets.BoxElement {
44
+ // Create outer container (non-scrollable, holds border and help text)
45
+ const container = blessed.box({
46
+ parent: screen,
47
+ top: HEADER_HEIGHT,
48
+ left: 0,
49
+ width: PANE_WIDTH,
50
+ height: `100%-${HEADER_HEIGHT}`,
51
+ border: {
52
+ type: 'line',
53
+ },
54
+ label: ' Actions ',
55
+ tags: true,
56
+ style: {
57
+ border: {
58
+ fg: '#4A34C5',
59
+ },
60
+ },
61
+ });
62
+
63
+ // Calculate content area height (container minus borders minus help text lines)
64
+ const containerHeight = typeof container.height === 'number' ? container.height : (screen.height as number) - HEADER_HEIGHT;
65
+ const contentHeight = containerHeight - 4; // 2 for borders, 2 for help text lines
66
+
67
+ // Create scrollable content area inside the container
68
+ const scrollArea = blessed.box({
69
+ parent: container,
70
+ top: 0,
71
+ left: 0,
72
+ width: '100%-2',
73
+ height: contentHeight,
74
+ tags: true,
75
+ scrollable: true,
76
+ alwaysScroll: true,
77
+ scrollbar: {
78
+ ch: '┃',
79
+ track: {
80
+ bg: 'black',
81
+ },
82
+ style: {
83
+ fg: '#4A34C5',
84
+ },
85
+ },
86
+ });
87
+
88
+ const items = buildActionItems(toggleState);
89
+ const { content, selectedLineNumber } = formatActionsContent(items, selectedIndex);
90
+
91
+ scrollArea.setContent(content);
92
+
93
+ // Scroll to ensure selected item is visible
94
+ if (selectedLineNumber !== undefined && selectedLineNumber >= 0) {
95
+ const visibleHeight = contentHeight;
96
+
97
+ // Only scroll if selected line would be outside visible area
98
+ if (selectedLineNumber >= visibleHeight) {
99
+ // Scroll to put selected line in the middle of visible area when possible
100
+ const scrollOffset = Math.max(0, selectedLineNumber - Math.floor(visibleHeight / 2));
101
+ scrollArea.scrollTo(scrollOffset);
102
+ }
103
+ }
104
+
105
+ // Add help text at bottom of container (fixed position, outside scroll area)
106
+ blessed.text({
107
+ parent: container,
108
+ bottom: 1,
109
+ left: 1,
110
+ content: '{#5c6370-fg}Tab: Switch Pane{/#5c6370-fg}',
111
+ tags: true,
112
+ });
113
+ blessed.text({
114
+ parent: container,
115
+ bottom: 0,
116
+ left: 1,
117
+ content: '{#5c6370-fg}j/k: Navigate{/#5c6370-fg}',
118
+ tags: true,
119
+ });
120
+
121
+ return container;
122
+ }
123
+
124
+ interface ActionsContentResult {
125
+ content: string;
126
+ selectedLineNumber?: number;
127
+ }
128
+
129
+ function formatActionsContent(items: ActionItem[], selectedIndex?: number): ActionsContentResult {
130
+ const lines: string[] = [];
131
+ let selectableIndex = 0;
132
+ let selectedLineNumber: number | undefined;
133
+
134
+ for (const item of items) {
135
+ if (item.type === 'separator') {
136
+ lines.push(`{#6366f1-fg}${item.label}{/#6366f1-fg}`);
137
+ } else {
138
+ const isSelected = selectedIndex === selectableIndex;
139
+
140
+ if (isSelected) {
141
+ selectedLineNumber = lines.length;
142
+ }
143
+
144
+ const content = formatItemContent(item, isSelected);
145
+ lines.push(content);
146
+ selectableIndex++;
147
+ }
148
+ }
149
+
150
+ return { content: lines.join('\n'), selectedLineNumber };
151
+ }
152
+
153
+ export function buildActionItems(toggleState: ToggleState): ActionItem[] {
154
+ const prLabel = getPRActionLabel(toggleState.prActionState);
155
+
156
+ return [
157
+ // Agent spawners — all always visible
158
+ { id: 'coordinator', label: 'Coordinator', key: '1', type: 'action' },
159
+ { id: 'new-initiative', label: 'New Initiative', key: '2', type: 'action' },
160
+ { id: 'planner', label: 'Planner', key: '3', type: 'action' },
161
+ { id: 'review-jury', label: 'Review Jury', key: '4', type: 'action' },
162
+ { id: 'e2e-test-planner', label: 'E2E Test Plan', key: '5', type: 'action' },
163
+ { id: 'pr-action', label: prLabel, key: '6', type: 'action' },
164
+ { id: 'review-pr', label: 'Address PR Review', key: '7', type: 'action' },
165
+ { id: 'compound', label: 'Compound', key: '8', type: 'action' },
166
+ { id: 'mark-completed', label: 'Complete', key: '9', type: 'action' },
167
+ { id: 'switch-spec', label: 'Switch Workspace', key: '0', type: 'action' },
168
+ { id: 'custom-flow', label: 'Custom Flow', key: '-', type: 'action' },
169
+ { id: 'initiative-steering', label: 'Steer Initiative', key: '=', type: 'action' },
170
+ // Spacing before toggles
171
+ { id: 'spacer-1', label: '', type: 'separator' },
172
+ { id: 'separator-toggles', label: '━━ Toggles ━━', type: 'separator' },
173
+ { id: 'toggle-loop', label: 'Loop', key: 'O', type: 'toggle', checked: toggleState.loopEnabled },
174
+ { id: 'toggle-parallel', label: 'Parallel', key: 'P', type: 'toggle', checked: toggleState.parallelEnabled },
175
+ // Spacing before controls
176
+ { id: 'spacer-2', label: '', type: 'separator' },
177
+ { id: 'separator-bottom', label: '━━ Controls ━━', type: 'separator' },
178
+ { id: 'view-logs', label: 'View Logs', key: 'V', type: 'action' },
179
+ { id: 'clear-logs', label: 'Clear Logs', key: 'C', type: 'action' },
180
+ { id: 'refresh', label: 'Refresh', key: 'R', type: 'action' },
181
+ { id: 'quit', label: 'Quit', key: 'Q', type: 'action' },
182
+ ];
183
+ }
184
+
185
+ function getPRActionLabel(state: PRActionState): string {
186
+ switch (state) {
187
+ case 'create-pr':
188
+ return 'Create PR';
189
+ case 'awaiting-review':
190
+ return 'Awaiting Review...';
191
+ case 'rerun-pr-review':
192
+ return 'Rerun PR Review';
193
+ }
194
+ }
195
+
196
+ function formatItemContent(item: ActionItem, isSelected: boolean): string {
197
+ const prefix = item.key ? `{#818cf8-fg}[${item.key}]{/#818cf8-fg} ` : ' ';
198
+ let label = item.label;
199
+
200
+ // Toggle checkbox
201
+ if (item.type === 'toggle') {
202
+ const checkbox = item.checked ? '{#10b981-fg}[x]{/#10b981-fg}' : '{#5c6370-fg}[ ]{/#5c6370-fg}';
203
+ label = `${checkbox} ${label}`;
204
+ }
205
+
206
+ // Apply styling
207
+ let style = '';
208
+ let endStyle = '';
209
+
210
+ if (isSelected) {
211
+ style = '{#a78bfa-fg}{bold}▸ ';
212
+ endStyle = '{/bold}{/#a78bfa-fg}';
213
+ // For selected items, use plain prefix without colors
214
+ return `${style}${item.key ? `[${item.key}] ` : ''}${label}${endStyle}`;
215
+ } else if (item.highlight) {
216
+ style = '{#f59e0b-fg}';
217
+ endStyle = '{/#f59e0b-fg}';
218
+ }
219
+
220
+ return `${style}${prefix}${label}${endStyle}`;
221
+ }
222
+
223
+ export function getSelectableItems(toggleState: ToggleState): ActionItem[] {
224
+ return buildActionItems(toggleState).filter(
225
+ (item) => item.type !== 'separator'
226
+ );
227
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * File Viewer Modal - Scrollable modal for viewing markdown files
3
+ *
4
+ * Used for viewing:
5
+ * - Spec files
6
+ * - Alignment documents
7
+ * - E2E test plans
8
+ * - Prompt files
9
+ *
10
+ * Navigation:
11
+ * - j/k: Scroll one line up/down
12
+ * - u/d: Page up/down
13
+ * - g: Jump to top
14
+ * - G: Jump to bottom
15
+ * - Esc: Close modal
16
+ */
17
+
18
+ import blessed from 'blessed';
19
+ import { readFileSync, existsSync } from 'fs';
20
+ import { join } from 'path';
21
+ import { parse as parseYaml } from 'yaml';
22
+
23
+ export interface FileViewerOptions {
24
+ title: string;
25
+ filePath: string;
26
+ onClose: () => void;
27
+ }
28
+
29
+ export interface FileViewer {
30
+ box: blessed.Widgets.BoxElement;
31
+ destroy: () => void;
32
+ scrollUp: (lines?: number) => void;
33
+ scrollDown: (lines?: number) => void;
34
+ scrollToTop: () => void;
35
+ scrollToBottom: () => void;
36
+ }
37
+
38
+ export function createFileViewer(
39
+ screen: blessed.Widgets.Screen,
40
+ options: FileViewerOptions
41
+ ): FileViewer | null {
42
+ const { title, filePath, onClose } = options;
43
+
44
+ // Check if file exists
45
+ if (!existsSync(filePath)) {
46
+ return null;
47
+ }
48
+
49
+ // Read file content
50
+ let content: string;
51
+ try {
52
+ content = readFileSync(filePath, 'utf-8');
53
+ } catch {
54
+ return null;
55
+ }
56
+
57
+ // Calculate modal size (80% of screen)
58
+ const width = Math.floor((screen.width as number) * 0.8);
59
+ const height = Math.floor((screen.height as number) * 0.8);
60
+
61
+ // Create outer container (non-scrollable, holds border and help text)
62
+ const container = blessed.box({
63
+ parent: screen,
64
+ top: 'center',
65
+ left: 'center',
66
+ width,
67
+ height,
68
+ border: 'line',
69
+ label: ` ${title} `,
70
+ tags: true,
71
+ style: {
72
+ border: {
73
+ fg: '#c4b5fd',
74
+ bold: true,
75
+ },
76
+ fg: '#e0e7ff',
77
+ },
78
+ });
79
+
80
+ // Calculate content area height (container minus borders minus help text)
81
+ // Container inner area = height - 2 (borders), minus 1 for help text = height - 3
82
+ const contentHeight = height - 4; // Be more conservative to ensure we don't clip
83
+
84
+ // Create scrollable content area inside the container
85
+ const scrollArea = blessed.box({
86
+ parent: container,
87
+ top: 0,
88
+ left: 0,
89
+ width: '100%-2', // Fill width minus scrollbar space
90
+ height: contentHeight,
91
+ tags: true,
92
+ scrollable: true,
93
+ alwaysScroll: true,
94
+ keys: false,
95
+ vi: false,
96
+ scrollbar: {
97
+ ch: '┃',
98
+ track: {
99
+ bg: 'black',
100
+ },
101
+ style: {
102
+ fg: '#4A34C5',
103
+ },
104
+ },
105
+ style: {
106
+ fg: '#e0e7ff',
107
+ },
108
+ });
109
+
110
+ // Set content
111
+ scrollArea.setContent(content);
112
+
113
+ // Focus the container for key events
114
+ container.focus();
115
+
116
+ // Track scroll position
117
+ let scrollPosition = 0;
118
+ const contentLines = content.split('\n').length;
119
+ const visibleLines = contentHeight;
120
+ // Allow scrolling to show the last line at the top of the view
121
+ // This ensures we can always reach the true bottom of the file
122
+ const maxScroll = Math.max(0, contentLines);
123
+
124
+ function updateScroll(): void {
125
+ scrollArea.scrollTo(scrollPosition);
126
+ screen.render();
127
+ }
128
+
129
+ function scrollUp(lines: number = 1): void {
130
+ scrollPosition = Math.max(0, scrollPosition - lines);
131
+ updateScroll();
132
+ }
133
+
134
+ function scrollDown(lines: number = 1): void {
135
+ scrollPosition = Math.min(maxScroll, scrollPosition + lines);
136
+ updateScroll();
137
+ }
138
+
139
+ function scrollToTop(): void {
140
+ scrollPosition = 0;
141
+ updateScroll();
142
+ }
143
+
144
+ function scrollToBottom(): void {
145
+ scrollPosition = maxScroll;
146
+ updateScroll();
147
+ }
148
+
149
+ // Set up key bindings on container
150
+ container.key(['j'], () => scrollDown(1));
151
+ container.key(['k'], () => scrollUp(1));
152
+ container.key(['u'], () => scrollUp(Math.floor(visibleLines / 2)));
153
+ container.key(['d'], () => scrollDown(Math.floor(visibleLines / 2)));
154
+ container.key(['g'], () => scrollToTop());
155
+ container.key(['S-g'], () => scrollToBottom()); // Shift+G
156
+ container.key(['escape'], () => onClose());
157
+
158
+ // Add help text at bottom of container (fixed position, outside scroll area)
159
+ blessed.text({
160
+ parent: container,
161
+ bottom: 0,
162
+ left: 1,
163
+ content: '{#5c6370-fg}j/k:scroll u/d:page g/G:top/bottom Esc:close{/#5c6370-fg}',
164
+ tags: true,
165
+ });
166
+
167
+ // Initial render
168
+ screen.render();
169
+
170
+ return {
171
+ box: container,
172
+ destroy: () => container.destroy(),
173
+ scrollUp,
174
+ scrollDown,
175
+ scrollToTop,
176
+ scrollToBottom,
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Check if a planning file exists for the given spec and file type
182
+ * Tries multiple folder name formats to handle branch name variations
183
+ */
184
+ export function getPlanningFilePath(
185
+ cwd: string,
186
+ spec: string,
187
+ fileType: 'alignment' | 'e2e_test_plan'
188
+ ): string | null {
189
+ // Try multiple filename variations (underscore vs hyphen)
190
+ const filenames = fileType === 'alignment'
191
+ ? ['alignment.md']
192
+ : ['e2e-test-plan.md', 'e2e_test_plan.md'];
193
+
194
+ // Try multiple folder name variations to handle branch naming differences
195
+ // e.g., "feature/core-taskflow" might be stored as "feature-core-taskflow"
196
+ const folderVariations = [
197
+ spec, // Original: feature/core-taskflow
198
+ spec.replace(/\//g, '-'), // Slashes to hyphens: feature-core-taskflow
199
+ spec.replace(/[/\\]/g, '-'), // All path separators to hyphens
200
+ ];
201
+
202
+ for (const folderName of folderVariations) {
203
+ for (const filename of filenames) {
204
+ const filePath = join(cwd, '.planning', folderName, filename);
205
+ if (existsSync(filePath)) {
206
+ return filePath;
207
+ }
208
+ }
209
+ }
210
+
211
+ return null;
212
+ }
213
+
214
+ /**
215
+ * Get the spec file path for a spec
216
+ * First tries to read from status.yaml, then falls back to common locations
217
+ */
218
+ export function getSpecFilePath(cwd: string, specId: string): string | null {
219
+ // Try variations of the spec ID (slashes to hyphens, etc.)
220
+ const specIdVariations = [
221
+ specId,
222
+ specId.replace(/\//g, '-'),
223
+ specId.replace(/[/\\]/g, '-'),
224
+ ];
225
+
226
+ // First, try to read the spec path from status.yaml in the planning directory
227
+ for (const id of specIdVariations) {
228
+ const statusPath = join(cwd, '.planning', id, 'status.yaml');
229
+ if (existsSync(statusPath)) {
230
+ try {
231
+ const content = readFileSync(statusPath, 'utf-8');
232
+ const status = parseYaml(content) as { spec?: string };
233
+ if (status?.spec) {
234
+ // The spec path in status.yaml might be relative or absolute
235
+ const specPath = status.spec.startsWith('/')
236
+ ? status.spec
237
+ : join(cwd, status.spec);
238
+ if (existsSync(specPath)) {
239
+ return specPath;
240
+ }
241
+ }
242
+ } catch {
243
+ // Ignore parse errors, continue to fallback
244
+ }
245
+ }
246
+ }
247
+
248
+ // Fallback: try common spec file locations
249
+ const locations: string[] = [];
250
+
251
+ for (const id of specIdVariations) {
252
+ // Check specs/ folder
253
+ locations.push(join(cwd, 'specs', `${id}.spec.md`));
254
+ locations.push(join(cwd, 'specs', `${id}.md`));
255
+ // Check .specs/ folder
256
+ locations.push(join(cwd, '.specs', `${id}.spec.md`));
257
+ locations.push(join(cwd, '.specs', `${id}.md`));
258
+ // Check .planning/ folder (spec.md inside the planning dir)
259
+ locations.push(join(cwd, '.planning', id, 'spec.md'));
260
+ locations.push(join(cwd, '.planning', id, `${id}.spec.md`));
261
+ }
262
+
263
+ for (const filePath of locations) {
264
+ if (existsSync(filePath)) {
265
+ return filePath;
266
+ }
267
+ }
268
+
269
+ return null;
270
+ }