@proletariat/cli 0.3.35 → 0.3.40

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 (148) hide show
  1. package/README.md +37 -2
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/agent/auth.d.ts +12 -2
  4. package/dist/commands/agent/auth.js +128 -4
  5. package/dist/commands/agent/list.js +16 -7
  6. package/dist/commands/agent/status.js +32 -4
  7. package/dist/commands/board/watch.js +6 -0
  8. package/dist/commands/branch/list.d.ts +1 -0
  9. package/dist/commands/branch/list.js +43 -12
  10. package/dist/commands/branch/where.js +9 -19
  11. package/dist/commands/category/list.d.ts +2 -1
  12. package/dist/commands/category/list.js +38 -13
  13. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  14. package/dist/commands/{claude.js → claude/index.js} +12 -12
  15. package/dist/commands/claude/open.d.ts +13 -0
  16. package/dist/commands/claude/open.js +175 -0
  17. package/dist/commands/diet.js +18 -2
  18. package/dist/commands/docker/logs.js +7 -3
  19. package/dist/commands/docker/shell.js +6 -0
  20. package/dist/commands/docker/start.js +20 -4
  21. package/dist/commands/docker/sync.d.ts +4 -0
  22. package/dist/commands/docker/sync.js +30 -2
  23. package/dist/commands/epic/show.d.ts +13 -0
  24. package/dist/commands/epic/show.js +16 -0
  25. package/dist/commands/epic/ticket.js +7 -24
  26. package/dist/commands/epic/view.js +27 -0
  27. package/dist/commands/execution/config.d.ts +0 -4
  28. package/dist/commands/execution/config.js +14 -46
  29. package/dist/commands/execution/index.js +2 -1
  30. package/dist/commands/execution/logs.js +7 -1
  31. package/dist/commands/execution/stop.js +2 -1
  32. package/dist/commands/execution/view.js +30 -26
  33. package/dist/commands/init.js +2 -19
  34. package/dist/commands/label/create.js +2 -1
  35. package/dist/commands/label/delete.js +2 -1
  36. package/dist/commands/label/group/create.js +2 -1
  37. package/dist/commands/label/group/list.js +2 -1
  38. package/dist/commands/label/list.js +2 -1
  39. package/dist/commands/mcp-server.js +27 -1
  40. package/dist/commands/phase/template/list.js +2 -1
  41. package/dist/commands/pmo/init.js +12 -40
  42. package/dist/commands/project/create.js +3 -4
  43. package/dist/commands/project/update.js +5 -6
  44. package/dist/commands/pull.js +24 -0
  45. package/dist/commands/qa/index.d.ts +54 -0
  46. package/dist/commands/qa/index.js +762 -0
  47. package/dist/commands/repo/view.js +2 -8
  48. package/dist/commands/session/attach.js +4 -4
  49. package/dist/commands/session/create.d.ts +19 -0
  50. package/dist/commands/session/create.js +102 -0
  51. package/dist/commands/session/health.js +4 -23
  52. package/dist/commands/session/index.js +14 -1
  53. package/dist/commands/session/list.js +9 -8
  54. package/dist/commands/session/peek.d.ts +38 -0
  55. package/dist/commands/session/peek.js +316 -0
  56. package/dist/commands/session/poke.d.ts +27 -0
  57. package/dist/commands/session/poke.js +219 -0
  58. package/dist/commands/spec/view.js +29 -0
  59. package/dist/commands/template/list.js +2 -1
  60. package/dist/commands/theme/add-names.d.ts +4 -0
  61. package/dist/commands/theme/add-names.js +11 -1
  62. package/dist/commands/theme/create.d.ts +2 -0
  63. package/dist/commands/theme/create.js +8 -0
  64. package/dist/commands/ticket/bulk.js +2 -2
  65. package/dist/commands/ticket/complete.js +2 -2
  66. package/dist/commands/ticket/create.js +21 -0
  67. package/dist/commands/ticket/delete.js +8 -0
  68. package/dist/commands/ticket/edit.js +25 -0
  69. package/dist/commands/ticket/epic.js +17 -43
  70. package/dist/commands/ticket/index.js +2 -2
  71. package/dist/commands/ticket/move.js +25 -2
  72. package/dist/commands/ticket/resolve.js +3 -4
  73. package/dist/commands/ticket/show.d.ts +13 -0
  74. package/dist/commands/ticket/show.js +16 -0
  75. package/dist/commands/ticket/template/list.js +2 -1
  76. package/dist/commands/ticket/view.d.ts +0 -1
  77. package/dist/commands/ticket/view.js +30 -1
  78. package/dist/commands/work/index.js +4 -0
  79. package/dist/commands/work/spawn-all.js +1 -1
  80. package/dist/commands/work/spawn.js +15 -4
  81. package/dist/commands/work/start.js +186 -103
  82. package/dist/commands/work/status.d.ts +14 -0
  83. package/dist/commands/work/status.js +60 -0
  84. package/dist/commands/work/watch.js +1 -1
  85. package/dist/commands/workflow/index.js +2 -1
  86. package/dist/commands/workflow/show.d.ts +13 -0
  87. package/dist/commands/workflow/show.js +16 -0
  88. package/dist/commands/workspace/add.js +15 -0
  89. package/dist/commands/workspace/list.js +2 -1
  90. package/dist/commands/workspace/prune.js +7 -7
  91. package/dist/hooks/init.js +10 -2
  92. package/dist/lib/agents/commands.d.ts +5 -0
  93. package/dist/lib/agents/commands.js +143 -97
  94. package/dist/lib/branch/index.d.ts +1 -0
  95. package/dist/lib/database/drizzle-schema.d.ts +465 -0
  96. package/dist/lib/database/drizzle-schema.js +53 -0
  97. package/dist/lib/database/index.d.ts +47 -1
  98. package/dist/lib/database/index.js +138 -20
  99. package/dist/lib/execution/config.d.ts +15 -1
  100. package/dist/lib/execution/config.js +28 -0
  101. package/dist/lib/execution/runners.d.ts +45 -0
  102. package/dist/lib/execution/runners.js +187 -26
  103. package/dist/lib/execution/session-utils.d.ts +16 -1
  104. package/dist/lib/execution/session-utils.js +71 -4
  105. package/dist/lib/execution/spawner.js +15 -2
  106. package/dist/lib/execution/storage.d.ts +6 -1
  107. package/dist/lib/execution/storage.js +35 -5
  108. package/dist/lib/execution/types.d.ts +3 -0
  109. package/dist/lib/mcp/tools/board.js +4 -6
  110. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  111. package/dist/lib/mcp/tools/epic.js +8 -3
  112. package/dist/lib/mcp/tools/index.d.ts +1 -0
  113. package/dist/lib/mcp/tools/index.js +1 -0
  114. package/dist/lib/mcp/tools/spec.js +1 -1
  115. package/dist/lib/mcp/tools/ticket.js +11 -9
  116. package/dist/lib/mcp/tools/tmux.d.ts +16 -0
  117. package/dist/lib/mcp/tools/tmux.js +182 -0
  118. package/dist/lib/mcp/tools/work.js +148 -6
  119. package/dist/lib/mcp/types.d.ts +10 -0
  120. package/dist/lib/multiline-input.js +2 -1
  121. package/dist/lib/pmo/base-command.js +4 -4
  122. package/dist/lib/pmo/schema.d.ts +1 -1
  123. package/dist/lib/pmo/schema.js +1 -0
  124. package/dist/lib/pmo/storage/actions.js +1 -1
  125. package/dist/lib/pmo/storage/base.js +402 -50
  126. package/dist/lib/pmo/storage/dependencies.d.ts +1 -0
  127. package/dist/lib/pmo/storage/dependencies.js +11 -3
  128. package/dist/lib/pmo/storage/epics.js +1 -1
  129. package/dist/lib/pmo/storage/helpers.d.ts +4 -4
  130. package/dist/lib/pmo/storage/helpers.js +36 -26
  131. package/dist/lib/pmo/storage/projects.d.ts +2 -0
  132. package/dist/lib/pmo/storage/projects.js +207 -119
  133. package/dist/lib/pmo/storage/specs.d.ts +2 -0
  134. package/dist/lib/pmo/storage/specs.js +274 -188
  135. package/dist/lib/pmo/storage/tickets.d.ts +2 -0
  136. package/dist/lib/pmo/storage/tickets.js +350 -290
  137. package/dist/lib/pmo/storage/types.d.ts +1 -0
  138. package/dist/lib/pmo/storage/views.d.ts +2 -0
  139. package/dist/lib/pmo/storage/views.js +183 -130
  140. package/dist/lib/prompt-command.d.ts +20 -0
  141. package/dist/lib/prompt-command.js +38 -2
  142. package/dist/lib/prompt-json.d.ts +41 -4
  143. package/dist/lib/prompt-json.js +138 -7
  144. package/dist/lib/styles.d.ts +37 -0
  145. package/dist/lib/styles.js +73 -0
  146. package/oclif.manifest.json +4046 -3385
  147. package/package.json +11 -6
  148. package/LICENSE +0 -190
@@ -178,6 +178,39 @@ export function runMigrations(db) {
178
178
  }
179
179
  }
180
180
  }
181
+ // Migration: Migrate old ticket_dependencies schema (blocked_by_ticket_id) to new format
182
+ if (tableExists(T.ticket_dependencies)) {
183
+ const depsColumns = db.pragma(`table_info(${T.ticket_dependencies})`);
184
+ const depsColumnNames = new Set(depsColumns.map(c => c.name));
185
+ if (depsColumnNames.has('blocked_by_ticket_id') && !depsColumnNames.has('depends_on_ticket_id')) {
186
+ // Old schema detected - migrate to new format
187
+ try {
188
+ // Create new table with correct schema
189
+ db.exec(`
190
+ CREATE TABLE pmo_ticket_dependencies_new (
191
+ ticket_id TEXT NOT NULL REFERENCES ${T.tickets}(id) ON DELETE RESTRICT,
192
+ depends_on_ticket_id TEXT NOT NULL REFERENCES ${T.tickets}(id) ON DELETE RESTRICT,
193
+ dependency_type TEXT NOT NULL DEFAULT 'blocks' CHECK (dependency_type IN ('blocks', 'relates_to', 'duplicates')),
194
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
195
+ PRIMARY KEY (ticket_id, depends_on_ticket_id, dependency_type),
196
+ CHECK (ticket_id != depends_on_ticket_id)
197
+ )
198
+ `);
199
+ // Copy existing blocking relationships (old schema: ticket_id is blocked by blocked_by_ticket_id)
200
+ db.exec(`
201
+ INSERT OR IGNORE INTO pmo_ticket_dependencies_new (ticket_id, depends_on_ticket_id, dependency_type, created_at)
202
+ SELECT ticket_id, blocked_by_ticket_id, 'blocks', created_at
203
+ FROM ${T.ticket_dependencies}
204
+ `);
205
+ // Replace old table with new one
206
+ db.exec(`DROP TABLE ${T.ticket_dependencies}`);
207
+ db.exec(`ALTER TABLE pmo_ticket_dependencies_new RENAME TO ${T.ticket_dependencies}`);
208
+ }
209
+ catch {
210
+ // Migration may have already been applied partially
211
+ }
212
+ }
213
+ }
181
214
  // Migration: Convert legacy priority values (URGENT/HIGH/MEDIUM/LOW) to P0-P3
182
215
  if (tableExists(T.tickets)) {
183
216
  try {
@@ -252,6 +285,19 @@ export function runMigrations(db) {
252
285
  // Column may already exist
253
286
  }
254
287
  }
288
+ // Migration: Add error_message column to agent_work table (TKT-1082)
289
+ if (tableExists(T.agent_work)) {
290
+ const agentWorkColumns = db.pragma(`table_info(${T.agent_work})`);
291
+ const agentWorkColumnNames = new Set(agentWorkColumns.map(c => c.name));
292
+ if (!agentWorkColumnNames.has('error_message')) {
293
+ try {
294
+ db.exec(`ALTER TABLE ${T.agent_work} ADD COLUMN error_message TEXT`);
295
+ }
296
+ catch {
297
+ // Column may already exist
298
+ }
299
+ }
300
+ }
255
301
  // Migration: Reassign orphaned tickets (TKT-940)
256
302
  // Tickets with project_id that doesn't match any existing project are "orphaned".
257
303
  // This can happen when a 'default' project never existed or was deleted.
@@ -693,6 +739,361 @@ git add -A && prlt commit "your change" && git push
693
739
  modifiesCode: true,
694
740
  position: 3,
695
741
  },
742
+ {
743
+ id: 'review',
744
+ name: 'Code Review',
745
+ description: 'Review the implementation and post feedback on the PR',
746
+ prompt: `${PRLT_USAGE_RULE}
747
+
748
+ ---
749
+
750
+ # Action: Code Review
751
+
752
+ Review this ticket's implementation thoroughly:
753
+ - Check for bugs, edge cases, and potential issues
754
+ - Look for security vulnerabilities
755
+ - Verify it meets all acceptance criteria
756
+ - Check code quality and maintainability
757
+ - Suggest improvements if appropriate
758
+
759
+ After reviewing, determine your verdict:
760
+ - **APPROVE**: Code is ready to merge, no significant issues
761
+ - **REQUEST_CHANGES**: There are issues that must be fixed before merging
762
+ - **COMMENT**: General feedback, no blocking issues but some suggestions
763
+
764
+ Do NOT modify any code. This is a read-only review.`,
765
+ endPrompt: `When you have finished reviewing, post your review on the PR using \`gh pr review\`.
766
+
767
+ Choose the appropriate command based on your verdict:
768
+
769
+ **If approving:**
770
+ \`\`\`bash
771
+ gh pr review --approve --body "## Code Review
772
+
773
+ ### What looks good
774
+ - ...
775
+
776
+ ### Verdict
777
+ APPROVED - Code is ready to merge."
778
+ \`\`\`
779
+
780
+ **If requesting changes:**
781
+ \`\`\`bash
782
+ gh pr review --request-changes --body "## Code Review
783
+
784
+ ### What looks good
785
+ - ...
786
+
787
+ ### Concerns
788
+ - ...
789
+
790
+ ### Suggested improvements
791
+ - ...
792
+
793
+ ### Verdict
794
+ REQUEST CHANGES - Issues must be addressed before merging."
795
+ \`\`\`
796
+
797
+ **If commenting:**
798
+ \`\`\`bash
799
+ gh pr review --comment --body "## Code Review
800
+
801
+ ### What looks good
802
+ - ...
803
+
804
+ ### Suggestions
805
+ - ...
806
+
807
+ ### Verdict
808
+ COMMENT - Some suggestions but no blocking issues."
809
+ \`\`\`
810
+
811
+ Format the body with: what looks good, concerns (if any), suggested improvements (if any), and your verdict.
812
+
813
+ No commits are needed for code review.`,
814
+ suggestedForCategories: ['started', 'completed'],
815
+ modifiesCode: false,
816
+ position: 4,
817
+ },
818
+ {
819
+ id: 'review-fix',
820
+ name: 'Review & Fix',
821
+ description: 'Review the implementation, fix issues, and post feedback on the PR',
822
+ prompt: `${PRLT_USAGE_RULE}
823
+
824
+ ---
825
+
826
+ # Action: Review & Fix
827
+
828
+ Review this ticket's implementation thoroughly and fix any issues found:
829
+ - Check for bugs, edge cases, and potential issues
830
+ - Look for security vulnerabilities
831
+ - Verify it meets all acceptance criteria
832
+ - Check code quality and maintainability
833
+ - Fix any issues you find directly in the code
834
+
835
+ **IMPORTANT: Commit and push frequently!**
836
+ - Commit after each fix or logical group of changes
837
+ - Push after every 1-2 commits to save your work
838
+
839
+ \`\`\`bash
840
+ git add -A && prlt commit "fix: address code review findings" && git push
841
+ \`\`\``,
842
+ endPrompt: `When you have finished reviewing and fixing:
843
+
844
+ 1. **If issues were found and fixed**, post a review summary and push your fixes:
845
+ \`\`\`bash
846
+ gh pr review --comment --body "## Code Review & Fix Summary
847
+
848
+ ### Issues found and fixed
849
+ - ...
850
+
851
+ ### What looks good
852
+ - ...
853
+
854
+ ### Changes made
855
+ - ...
856
+ "
857
+ prlt commit "fix: address code review findings"
858
+ git push
859
+ \`\`\`
860
+
861
+ 2. **If no issues were found**, approve the PR:
862
+ \`\`\`bash
863
+ gh pr review --approve --body "## Code Review
864
+
865
+ ### What looks good
866
+ - ...
867
+
868
+ ### Verdict
869
+ APPROVED - Code looks great, no issues found."
870
+ \`\`\``,
871
+ suggestedForCategories: ['started', 'completed'],
872
+ modifiesCode: true,
873
+ position: 5,
874
+ },
875
+ {
876
+ id: 'revise',
877
+ name: 'Revise',
878
+ description: 'Pull the branch, read PR review feedback, implement fixes, and push',
879
+ prompt: `${PRLT_USAGE_RULE}
880
+
881
+ ---
882
+
883
+ # Action: Revise
884
+
885
+ Address the feedback on this ticket's pull request:
886
+
887
+ 1. **Pull the latest branch** to ensure you have the most recent code:
888
+ \`\`\`bash
889
+ git pull
890
+ \`\`\`
891
+
892
+ 2. **Read all review comments and requested changes** from the PR:
893
+ \`\`\`bash
894
+ gh pr view
895
+ gh api repos/{owner}/{repo}/pulls/{number}/comments
896
+ \`\`\`
897
+
898
+ 3. **Understand each piece of feedback** before making changes — read carefully and understand the reviewer's intent
899
+
900
+ 4. **Implement the requested fixes** and address each review comment:
901
+ - Make the necessary code changes to address each point
902
+ - Respond to questions with explanations
903
+ - Ensure all requested changes are addressed
904
+
905
+ **IMPORTANT: Commit and push frequently!**
906
+ - Commit after each fix or logical group of changes
907
+ - Push after every 1-2 commits to save your work
908
+
909
+ \`\`\`bash
910
+ git add -A && prlt commit "fix: address PR review feedback" && git push
911
+ \`\`\``,
912
+ endPrompt: `After addressing all feedback:
913
+
914
+ 1. **Commit your changes**:
915
+ \`\`\`bash
916
+ git add -A
917
+ prlt commit "fix: address PR review feedback"
918
+ \`\`\`
919
+
920
+ 2. **Push your changes**:
921
+ \`\`\`bash
922
+ git push
923
+ \`\`\`
924
+
925
+ 3. **Optionally reply to resolved review threads** using the GitHub API:
926
+ \`\`\`bash
927
+ gh api repos/{owner}/{repo}/pulls/{number}/comments
928
+ \`\`\`
929
+
930
+ The PR will be updated automatically with your pushed changes.`,
931
+ suggestedForCategories: ['completed'],
932
+ defaultMoveToCategory: 'started',
933
+ modifiesCode: true,
934
+ position: 6,
935
+ },
936
+ {
937
+ id: 'explore-cli',
938
+ name: 'Explore CLI',
939
+ description: 'AI QA agent that autonomously explores the interactive CLI to discover bugs',
940
+ prompt: `${PRLT_USAGE_RULE}
941
+
942
+ ---
943
+
944
+ # Action: Explore CLI (Autonomous QA)
945
+
946
+ You are an AI QA tester for the prlt CLI. You have access to a tmux session where the CLI is running.
947
+ Your job is to **systematically explore every menu, try every option, and find bugs**.
948
+
949
+ ## Your Tools
950
+
951
+ - **tmux_send_keys** — send keystrokes to the tmux session (typing, arrows, Enter, Escape, Ctrl+C, etc.)
952
+ - **tmux_capture_pane** — read the current terminal screen to see what's displayed
953
+ - **tmux_start_session** — start a new tmux session to run the CLI in
954
+ - **tmux_list_sessions** — list active tmux sessions
955
+ - **ticket_create** — file a bug ticket when you find something broken
956
+
957
+ ## Getting Started
958
+
959
+ 1. Start a tmux session for testing:
960
+ \`\`\`
961
+ tmux_start_session({ session: "qa-test", command: "prlt" })
962
+ \`\`\`
963
+ 2. Wait a moment, then capture the screen to see the main menu:
964
+ \`\`\`
965
+ tmux_capture_pane({ session: "qa-test" })
966
+ \`\`\`
967
+ 3. Begin systematic exploration.
968
+
969
+ ## Exploration Strategies
970
+
971
+ Work through these systematically. After each action, always capture the screen to see the result.
972
+
973
+ ### 1. Navigate Every Top-Level Menu Item
974
+ - Use arrow keys (Up/Down) to highlight each option
975
+ - Press Enter to select each one
976
+ - Capture the screen to see what happens
977
+ - Press Escape or Ctrl+C to go back
978
+
979
+ ### 2. Test Every Submenu Option
980
+ - For each menu item, explore all sub-options
981
+ - Try selecting each choice in every list/menu
982
+
983
+ ### 3. Test Input Handling
984
+ - **Valid inputs**: Normal expected values
985
+ - **Empty inputs**: Just press Enter without typing anything
986
+ - **Invalid inputs**: Random strings, numbers where text is expected, etc.
987
+ - **Long strings**: Very long ticket titles, descriptions (100+ characters)
988
+ - **Special characters**: Quotes, backslashes, Unicode (emojis, CJK characters)
989
+ - **SQL injection-like**: \`'; DROP TABLE --\` style strings
990
+ - **Boundary values**: 0, -1, 999999, etc. for numeric inputs
991
+
992
+ ### 4. Test Cancellation Flows
993
+ - Press Ctrl+C mid-flow (should exit gracefully, no crash)
994
+ - Press Escape in menus (should go back)
995
+ - Press q or Q in menus (common quit shortcut)
996
+ - Rapidly press Escape multiple times
997
+
998
+ ### 5. Test Navigation Edge Cases
999
+ - Rapid arrow key presses (Up Up Up Down Down Down)
1000
+ - Arrow keys at the top/bottom of lists (should not crash)
1001
+ - Tab key in various contexts
1002
+ - Home/End keys
1003
+
1004
+ ### 6. Test Error Recovery
1005
+ - Navigate to ticket operations without any tickets
1006
+ - Try to create items with duplicate names
1007
+ - Try operations on non-existent IDs
1008
+
1009
+ ## What to Look For
1010
+
1011
+ - **Crashes**: Unhandled exceptions, stack traces visible on screen
1012
+ - **Rendering glitches**: Items cut off, wrong alignment, overlapping text, garbled output
1013
+ - **Wrong data**: Incorrect values displayed, stale data, missing information
1014
+ - **Missing options**: Menu items that should exist but don't
1015
+ - **Broken navigation**: Arrow keys don't work, can't go back, infinite loops
1016
+ - **Unhelpful errors**: Generic "Error" with no details, or raw error objects
1017
+ - **Hangs**: Screen freezes, no response to input (wait 10 seconds before declaring a hang)
1018
+ - **Screen overflow**: Content pushed off screen, no scrolling available
1019
+ - **Partial renders**: Screen shows half-drawn UI elements
1020
+ - **State corruption**: Operations succeed but data is wrong afterward
1021
+
1022
+ ## Cross-Validation
1023
+
1024
+ Compare what you see on screen with what the MCP tools return:
1025
+ - After creating a ticket via the interactive menu, use \`ticket_list\` to verify it was created correctly
1026
+ - After moving a ticket, verify the status changed via \`ticket_show\`
1027
+ - Check that counts displayed in menus match actual data
1028
+
1029
+ ## When You Find a Bug
1030
+
1031
+ 1. **Document exact reproduction steps** — which keys you pressed, in what order
1032
+ 2. **Capture the screen** showing the bug
1033
+ 3. **File a ticket** using the ticket_create MCP tool:
1034
+ - Title: Clear description of the bug
1035
+ - Category: "bug"
1036
+ - Priority: P1 for crashes/data loss, P2 for rendering/UX issues, P3 for minor issues
1037
+ - Description: Include exact reproduction steps, screen capture, and expected vs actual behavior
1038
+
1039
+ ## Session Management
1040
+
1041
+ - If the CLI crashes, restart it: kill the session and start a new one
1042
+ - If you get stuck, use Ctrl+C to escape, or kill and restart the session
1043
+ - Periodically capture the screen even when not expecting changes (catch intermittent issues)
1044
+
1045
+ ## Test Prioritization
1046
+
1047
+ Start with the most commonly used flows, then move to edge cases:
1048
+ 1. Main menu navigation
1049
+ 2. Ticket operations (create, list, view, edit, move)
1050
+ 3. Project operations
1051
+ 4. Board view
1052
+ 5. Work/session operations
1053
+ 6. Epic and spec operations
1054
+ 7. Settings and configuration
1055
+ 8. Edge cases and stress testing`,
1056
+ endPrompt: `## Wrap-Up
1057
+
1058
+ When you've completed your exploration:
1059
+
1060
+ 1. **Summarize your findings**: List all bugs found with their ticket IDs
1061
+ 2. **Note areas not tested**: If you couldn't reach certain features, list them
1062
+ 3. **Rate overall CLI quality**: Brief assessment of stability, UX, and completeness
1063
+
1064
+ Output a structured summary:
1065
+ \`\`\`
1066
+ ## Exploratory QA Summary
1067
+
1068
+ ### Bugs Filed
1069
+ - TKT-XXX: [brief description]
1070
+ - TKT-YYY: [brief description]
1071
+
1072
+ ### Areas Tested
1073
+ - [ ] Main menu navigation
1074
+ - [ ] Ticket CRUD
1075
+ - [ ] Project operations
1076
+ - [ ] Board view
1077
+ - [ ] Work sessions
1078
+ - [ ] Epics & specs
1079
+ - [ ] Error handling
1080
+ - [ ] Input validation
1081
+
1082
+ ### Areas Not Tested
1083
+ - [list any areas you couldn't reach]
1084
+
1085
+ ### Overall Assessment
1086
+ [Brief quality assessment]
1087
+ \`\`\`
1088
+
1089
+ Clean up your tmux session:
1090
+ \`\`\`
1091
+ tmux_kill_session({ session: "qa-test" })
1092
+ \`\`\``,
1093
+ suggestedForCategories: [],
1094
+ modifiesCode: false,
1095
+ position: 8,
1096
+ },
696
1097
  {
697
1098
  id: 'test',
698
1099
  name: 'Write Tests',
@@ -733,56 +1134,7 @@ git add -A && prlt commit "add tests for X" && git push
733
1134
  **IMPORTANT:** Use the global \`prlt\` command.`,
734
1135
  suggestedForCategories: ['started', 'completed'],
735
1136
  modifiesCode: true,
736
- position: 4,
737
- },
738
- {
739
- id: 'review',
740
- name: 'Code Review',
741
- description: 'Review the implementation for issues',
742
- prompt: `Review this ticket's implementation thoroughly:
743
- - Check for bugs, edge cases, and potential issues
744
- - Look for security vulnerabilities
745
- - Verify it meets all acceptance criteria
746
- - Check code quality and maintainability
747
- - Suggest improvements if appropriate
748
-
749
- Output a review summary with your findings and any concerns.`,
750
- endPrompt: `When you have finished reviewing, output a detailed review summary with:
751
- - ✅ What looks good
752
- - ⚠️ Concerns or potential issues
753
- - 🔧 Suggested improvements
754
- - 📋 Verdict: Approve, Request Changes, or Needs Discussion
755
-
756
- No commits are needed for code review.`,
757
- suggestedForCategories: ['started', 'completed'],
758
- modifiesCode: false,
759
- position: 5,
760
- },
761
- {
762
- id: 'revise',
763
- name: 'Revise',
764
- description: 'Address PR feedback and review comments',
765
- prompt: `${PRLT_USAGE_RULE}
766
-
767
- ---
768
-
769
- # Action: Revise
770
-
771
- Address the feedback on this ticket's pull request:
772
- - Review all comments and requested changes carefully
773
- - Make the necessary code changes to address each point
774
- - Respond to questions with explanations
775
- - Push updates to the PR branch
776
- - Mark resolved conversations as resolved`,
777
- endPrompt: `After addressing the feedback:
778
- 1. Commit your changes using \`prlt commit "your message"\`
779
- 2. Push your changes: \`git push\`
780
-
781
- The PR will be updated automatically.`,
782
- suggestedForCategories: ['completed'],
783
- defaultMoveToCategory: 'started',
784
- modifiesCode: true,
785
- position: 6,
1137
+ position: 7,
786
1138
  },
787
1139
  ];
788
1140
  // Use INSERT OR REPLACE to always update builtin actions with latest prompts
@@ -16,6 +16,7 @@ export declare class DependencyStorage {
16
16
  deleteTicketDependency(ticketId: string, dependsOnTicketId: string, dependencyType?: TicketDependencyType): Promise<void>;
17
17
  /**
18
18
  * List dependencies for a ticket.
19
+ * For relates_to dependencies, also returns reverse relationships (symmetric).
19
20
  */
20
21
  listTicketDependencies(ticketId: string): Promise<TicketDependency[]>;
21
22
  /**
@@ -63,14 +63,22 @@ export class DependencyStorage {
63
63
  }
64
64
  /**
65
65
  * List dependencies for a ticket.
66
+ * For relates_to dependencies, also returns reverse relationships (symmetric).
66
67
  */
67
68
  async listTicketDependencies(ticketId) {
68
69
  const rows = this.ctx.db.prepare(`
69
70
  SELECT ticket_id, depends_on_ticket_id, dependency_type, created_at
70
71
  FROM ${T.ticket_dependencies}
71
72
  WHERE ticket_id = ?
73
+
74
+ UNION
75
+
76
+ SELECT depends_on_ticket_id AS ticket_id, ticket_id AS depends_on_ticket_id, dependency_type, created_at
77
+ FROM ${T.ticket_dependencies}
78
+ WHERE depends_on_ticket_id = ? AND dependency_type = 'relates_to'
79
+
72
80
  ORDER BY created_at DESC
73
- `).all(ticketId);
81
+ `).all(ticketId, ticketId);
74
82
  return rows.map((row) => ({
75
83
  ticketId: row.ticket_id,
76
84
  dependsOnTicketId: row.depends_on_ticket_id,
@@ -92,7 +100,7 @@ export class DependencyStorage {
92
100
  LEFT JOIN ${T.workflow_statuses} ws ON t.status_id = ws.id
93
101
  WHERE d.ticket_id = ? AND d.dependency_type = 'blocks'
94
102
  `).all(ticketId);
95
- return Promise.all(rows.map((row) => rowToTicket(this.ctx.db, row)));
103
+ return Promise.all(rows.map((row) => rowToTicket(this.ctx.drizzle, row)));
96
104
  }
97
105
  /**
98
106
  * Get tickets that depend on this ticket (blocking).
@@ -108,7 +116,7 @@ export class DependencyStorage {
108
116
  LEFT JOIN ${T.workflow_statuses} ws ON t.status_id = ws.id
109
117
  WHERE d.depends_on_ticket_id = ? AND d.dependency_type = 'blocks'
110
118
  `).all(ticketId);
111
- return Promise.all(rows.map((row) => rowToTicket(this.ctx.db, row)));
119
+ return Promise.all(rows.map((row) => rowToTicket(this.ctx.drizzle, row)));
112
120
  }
113
121
  /**
114
122
  * Check if a ticket is blocked by incomplete dependencies.
@@ -186,7 +186,7 @@ export class EpicStorage {
186
186
  WHERE t.project_id = ? AND t.epic_id = ?
187
187
  ORDER BY ws.position, t.position ASC, t.created_at ASC
188
188
  `).all(projectId, epicId);
189
- return Promise.all(rows.map((row) => rowToTicket(this.ctx.db, row)));
189
+ return Promise.all(rows.map((row) => rowToTicket(this.ctx.drizzle, row)));
190
190
  }
191
191
  /**
192
192
  * Link a ticket to an epic.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Helper functions for converting database rows to domain types.
3
3
  */
4
- import Database from 'better-sqlite3';
4
+ import type { DrizzleDB } from '../../database/drizzle.js';
5
5
  import { AcceptanceCriterion, Spec, StateCategory, Ticket } from '../types.js';
6
6
  import { SpecRow, TicketRow, WorkflowStatusRow } from './types.js';
7
7
  /**
@@ -16,13 +16,13 @@ import { SpecRow, TicketRow, WorkflowStatusRow } from './types.js';
16
16
  export declare function wrapSqliteError(entityType: string, operation: 'create' | 'update' | 'delete', err: unknown): never;
17
17
  /**
18
18
  * Convert a database row to a Ticket object.
19
- * Fetches related data (subtasks, metadata, status info).
19
+ * Fetches related data (subtasks, metadata, status info) using Drizzle ORM.
20
20
  */
21
- export declare function rowToTicket(db: Database.Database, row: TicketRow): Promise<Ticket>;
21
+ export declare function rowToTicket(drizzle: DrizzleDB, row: TicketRow): Promise<Ticket>;
22
22
  /**
23
23
  * Get acceptance criteria for a ticket (sync version).
24
24
  */
25
- export declare function getAcceptanceCriteriaSync(db: Database.Database, ticketId: string): AcceptanceCriterion[];
25
+ export declare function getAcceptanceCriteriaSync(drizzle: DrizzleDB, ticketId: string): AcceptanceCriterion[];
26
26
  /**
27
27
  * Convert a database row to a Spec object.
28
28
  */
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Helper functions for converting database rows to domain types.
3
3
  */
4
- import { PMO_TABLES } from '../schema.js';
4
+ import { eq, asc } from 'drizzle-orm';
5
+ import { pmoSubtasks, pmoTicketMetadata, pmoWorkflowStatuses, pmoTicketAcceptanceCriteria, } from '../../database/drizzle-schema.js';
5
6
  import { PMOError, normalizePriority, } from '../types.js';
6
7
  /**
7
8
  * Check if an error is a SQLite UNIQUE constraint violation.
@@ -61,31 +62,37 @@ export function wrapSqliteError(entityType, operation, err) {
61
62
  // Re-throw unknown errors
62
63
  throw err;
63
64
  }
64
- const T = PMO_TABLES;
65
65
  /**
66
66
  * Convert a database row to a Ticket object.
67
- * Fetches related data (subtasks, metadata, status info).
67
+ * Fetches related data (subtasks, metadata, status info) using Drizzle ORM.
68
68
  */
69
- export async function rowToTicket(db, row) {
69
+ export async function rowToTicket(drizzle, row) {
70
70
  // Get subtasks
71
- const subtasks = db
72
- .prepare(`SELECT * FROM ${T.subtasks} WHERE ticket_id = ? ORDER BY position`)
73
- .all(row.id);
71
+ const subtaskRows = drizzle
72
+ .select()
73
+ .from(pmoSubtasks)
74
+ .where(eq(pmoSubtasks.ticketId, row.id))
75
+ .orderBy(asc(pmoSubtasks.position))
76
+ .all();
74
77
  // Get metadata
75
- const metaRows = db
76
- .prepare(`SELECT key, value FROM ${T.ticket_metadata} WHERE ticket_id = ?`)
77
- .all(row.id);
78
+ const metaRows = drizzle
79
+ .select({ key: pmoTicketMetadata.key, value: pmoTicketMetadata.value })
80
+ .from(pmoTicketMetadata)
81
+ .where(eq(pmoTicketMetadata.ticketId, row.id))
82
+ .all();
78
83
  const metadata = {};
79
84
  for (const m of metaRows) {
80
- metadata[m.key] = m.value;
85
+ metadata[m.key] = m.value || '';
81
86
  }
82
87
  // Get status info from workflow_statuses
83
88
  let statusName;
84
89
  let statusCategory;
85
90
  if (row.status_id) {
86
- const statusRow = db
87
- .prepare(`SELECT name, category FROM ${T.workflow_statuses} WHERE id = ?`)
88
- .get(row.status_id);
91
+ const statusRow = drizzle
92
+ .select({ name: pmoWorkflowStatuses.name, category: pmoWorkflowStatuses.category })
93
+ .from(pmoWorkflowStatuses)
94
+ .where(eq(pmoWorkflowStatuses.id, row.status_id))
95
+ .get();
89
96
  if (statusRow) {
90
97
  statusName = statusRow.name;
91
98
  statusCategory = statusRow.category;
@@ -126,14 +133,14 @@ export async function rowToTicket(db, row) {
126
133
  branch: row.branch || undefined,
127
134
  specId: row.spec_id || undefined,
128
135
  epicId: row.epic_id || undefined,
129
- subtasks: subtasks.map((st) => ({
136
+ subtasks: subtaskRows.map((st) => ({
130
137
  id: st.id,
131
138
  title: st.title,
132
- done: st.done === 1,
139
+ done: st.done ?? false,
133
140
  })),
134
141
  labels,
135
142
  metadata,
136
- acceptanceCriteria: getAcceptanceCriteriaSync(db, row.id),
143
+ acceptanceCriteria: getAcceptanceCriteriaSync(drizzle, row.id),
137
144
  createdAt: new Date(row.created_at),
138
145
  updatedAt: new Date(row.updated_at),
139
146
  lastSyncedFromSpec: row.last_synced_from_spec
@@ -148,18 +155,21 @@ export async function rowToTicket(db, row) {
148
155
  /**
149
156
  * Get acceptance criteria for a ticket (sync version).
150
157
  */
151
- export function getAcceptanceCriteriaSync(db, ticketId) {
152
- const rows = db
153
- .prepare(`SELECT * FROM ${T.ticket_acceptance_criteria} WHERE ticket_id = ? ORDER BY position`)
154
- .all(ticketId);
158
+ export function getAcceptanceCriteriaSync(drizzle, ticketId) {
159
+ const rows = drizzle
160
+ .select()
161
+ .from(pmoTicketAcceptanceCriteria)
162
+ .where(eq(pmoTicketAcceptanceCriteria.ticketId, ticketId))
163
+ .orderBy(asc(pmoTicketAcceptanceCriteria.position))
164
+ .all();
155
165
  return rows.map((row) => ({
156
166
  id: row.id,
157
- ticketId: row.ticket_id,
167
+ ticketId: row.ticketId,
158
168
  criterion: row.criterion,
159
- verifiable: row.verifiable === 1,
160
- verified: row.verified === 1,
161
- verifiedAt: row.verified_at ? new Date(row.verified_at) : undefined,
162
- verifiedBy: row.verified_by || undefined,
169
+ verifiable: row.verifiable ?? true,
170
+ verified: row.verified ?? false,
171
+ verifiedAt: row.verifiedAt ? new Date(row.verifiedAt) : undefined,
172
+ verifiedBy: row.verifiedBy || undefined,
163
173
  position: row.position,
164
174
  }));
165
175
  }
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Project operations.
3
3
  * Board columns are now derived from workflow statuses (single source of truth).
4
+ *
5
+ * This module uses Drizzle ORM for type-safe database queries.
4
6
  */
5
7
  import { Board, BoardConfig, Project, ProjectFilter } from '../types.js';
6
8
  import { StorageContext } from './types.js';