@proletariat/cli 0.3.35 → 0.3.36

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 (108) hide show
  1. package/dist/commands/agent/auth.d.ts +12 -2
  2. package/dist/commands/agent/auth.js +128 -4
  3. package/dist/commands/agent/list.js +16 -7
  4. package/dist/commands/agent/status.js +32 -4
  5. package/dist/commands/board/watch.js +6 -0
  6. package/dist/commands/branch/list.d.ts +1 -0
  7. package/dist/commands/branch/list.js +43 -12
  8. package/dist/commands/branch/where.js +3 -2
  9. package/dist/commands/category/list.d.ts +2 -1
  10. package/dist/commands/category/list.js +38 -13
  11. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  12. package/dist/commands/{claude.js → claude/index.js} +12 -12
  13. package/dist/commands/claude/open.d.ts +13 -0
  14. package/dist/commands/claude/open.js +175 -0
  15. package/dist/commands/diet.js +18 -2
  16. package/dist/commands/docker/logs.js +7 -3
  17. package/dist/commands/docker/shell.js +6 -0
  18. package/dist/commands/docker/start.js +20 -4
  19. package/dist/commands/docker/sync.d.ts +4 -0
  20. package/dist/commands/docker/sync.js +30 -2
  21. package/dist/commands/epic/show.d.ts +13 -0
  22. package/dist/commands/epic/show.js +16 -0
  23. package/dist/commands/epic/view.js +27 -0
  24. package/dist/commands/execution/config.d.ts +0 -4
  25. package/dist/commands/execution/config.js +10 -32
  26. package/dist/commands/execution/index.js +2 -1
  27. package/dist/commands/execution/logs.js +1 -1
  28. package/dist/commands/execution/stop.js +2 -1
  29. package/dist/commands/execution/view.js +22 -26
  30. package/dist/commands/init.js +2 -19
  31. package/dist/commands/label/create.js +2 -1
  32. package/dist/commands/label/delete.js +2 -1
  33. package/dist/commands/label/group/create.js +2 -1
  34. package/dist/commands/label/group/list.js +2 -1
  35. package/dist/commands/label/list.js +2 -1
  36. package/dist/commands/mcp-server.js +25 -0
  37. package/dist/commands/phase/template/list.js +2 -1
  38. package/dist/commands/project/create.js +3 -4
  39. package/dist/commands/project/update.js +5 -6
  40. package/dist/commands/pull.js +24 -0
  41. package/dist/commands/session/create.d.ts +19 -0
  42. package/dist/commands/session/create.js +102 -0
  43. package/dist/commands/session/health.js +1 -20
  44. package/dist/commands/session/index.js +14 -1
  45. package/dist/commands/session/list.js +26 -7
  46. package/dist/commands/session/peek.d.ts +38 -0
  47. package/dist/commands/session/peek.js +316 -0
  48. package/dist/commands/session/poke.d.ts +27 -0
  49. package/dist/commands/session/poke.js +219 -0
  50. package/dist/commands/spec/view.js +29 -0
  51. package/dist/commands/template/list.js +2 -1
  52. package/dist/commands/theme/add-names.d.ts +4 -0
  53. package/dist/commands/theme/add-names.js +11 -1
  54. package/dist/commands/theme/create.d.ts +2 -0
  55. package/dist/commands/theme/create.js +8 -0
  56. package/dist/commands/ticket/bulk.js +2 -2
  57. package/dist/commands/ticket/complete.js +2 -2
  58. package/dist/commands/ticket/create.js +21 -0
  59. package/dist/commands/ticket/delete.js +8 -0
  60. package/dist/commands/ticket/edit.js +25 -0
  61. package/dist/commands/ticket/index.js +2 -2
  62. package/dist/commands/ticket/move.js +25 -2
  63. package/dist/commands/ticket/resolve.js +3 -4
  64. package/dist/commands/ticket/show.d.ts +13 -0
  65. package/dist/commands/ticket/show.js +16 -0
  66. package/dist/commands/ticket/template/list.js +2 -1
  67. package/dist/commands/ticket/view.d.ts +0 -1
  68. package/dist/commands/ticket/view.js +30 -1
  69. package/dist/commands/work/index.js +4 -0
  70. package/dist/commands/work/start.js +169 -94
  71. package/dist/commands/work/status.d.ts +14 -0
  72. package/dist/commands/work/status.js +60 -0
  73. package/dist/commands/workflow/index.js +2 -1
  74. package/dist/commands/workflow/show.d.ts +13 -0
  75. package/dist/commands/workflow/show.js +16 -0
  76. package/dist/commands/workspace/add.js +15 -0
  77. package/dist/commands/workspace/list.js +2 -1
  78. package/dist/commands/workspace/prune.js +5 -5
  79. package/dist/lib/branch/index.d.ts +1 -0
  80. package/dist/lib/execution/config.d.ts +15 -1
  81. package/dist/lib/execution/config.js +28 -0
  82. package/dist/lib/execution/runners.d.ts +11 -0
  83. package/dist/lib/execution/runners.js +53 -19
  84. package/dist/lib/execution/session-utils.d.ts +11 -1
  85. package/dist/lib/execution/session-utils.js +26 -1
  86. package/dist/lib/execution/storage.d.ts +5 -0
  87. package/dist/lib/execution/storage.js +18 -3
  88. package/dist/lib/execution/types.d.ts +2 -0
  89. package/dist/lib/mcp/tools/board.js +4 -6
  90. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  91. package/dist/lib/mcp/tools/epic.js +8 -3
  92. package/dist/lib/mcp/tools/spec.js +1 -1
  93. package/dist/lib/mcp/tools/ticket.js +11 -9
  94. package/dist/lib/mcp/tools/work.js +96 -6
  95. package/dist/lib/mcp/types.d.ts +10 -0
  96. package/dist/lib/multiline-input.js +2 -1
  97. package/dist/lib/pmo/base-command.js +4 -4
  98. package/dist/lib/pmo/storage/actions.js +1 -1
  99. package/dist/lib/pmo/storage/base.js +195 -50
  100. package/dist/lib/pmo/storage/types.d.ts +1 -0
  101. package/dist/lib/prompt-command.d.ts +20 -0
  102. package/dist/lib/prompt-command.js +38 -2
  103. package/dist/lib/prompt-json.d.ts +36 -4
  104. package/dist/lib/prompt-json.js +129 -7
  105. package/dist/lib/styles.d.ts +37 -0
  106. package/dist/lib/styles.js +73 -0
  107. package/oclif.manifest.json +3259 -2701
  108. package/package.json +1 -1
@@ -3,6 +3,8 @@
3
3
  */
4
4
  import { z } from 'zod';
5
5
  import { formatTicket, errorResponse, strictTool } from '../helpers.js';
6
+ import { spawnAgentForTicket } from '../../execution/spawner.js';
7
+ import { createEphemeralAgent } from '../../agents/commands.js';
6
8
  export function registerWorkTools(server, ctx) {
7
9
  strictTool(server, 'work_status', 'Get current work status (in-progress tickets)', {}, async () => {
8
10
  try {
@@ -31,7 +33,7 @@ export function registerWorkTools(server, ctx) {
31
33
  return errorResponse(error);
32
34
  }
33
35
  });
34
- strictTool(server, 'work_start', 'Start working on a ticket (moves to In Progress)', {
36
+ strictTool(server, 'work_assign', 'Assign a ticket to someone for work preparation (does NOT move to In Progress — use the CLI "work start" command to actually start work with an agent/session)', {
35
37
  ticket_id: z.string().describe('Ticket ID'),
36
38
  assignee: z.string().optional().describe('Who is working'),
37
39
  }, async (params) => {
@@ -42,16 +44,14 @@ export function registerWorkTools(server, ctx) {
42
44
  if (params.assignee) {
43
45
  await ctx.storage.updateTicket(params.ticket_id, { assignee: params.assignee });
44
46
  }
45
- const columns = ctx.storage.getColumnNames(ticket.projectId);
46
- const progressCol = columns.find((c) => c.toLowerCase().includes('progress') || c.toLowerCase().includes('doing')) || columns[Math.min(1, columns.length - 1)];
47
- const moved = await ctx.storage.moveTicket(ticket.projectId, params.ticket_id, progressCol);
47
+ const updated = await ctx.storage.getTicket(params.ticket_id);
48
48
  return {
49
49
  content: [{
50
50
  type: 'text',
51
51
  text: JSON.stringify({
52
52
  success: true,
53
- ticket: formatTicket(moved),
54
- message: `Started ${moved.id}: ${moved.title}`,
53
+ ticket: formatTicket(updated),
54
+ message: `Assigned ${updated.id}: ${updated.title}${params.assignee ? ` to ${params.assignee}` : ''}`,
55
55
  }, null, 2),
56
56
  }],
57
57
  };
@@ -129,4 +129,94 @@ export function registerWorkTools(server, ctx) {
129
129
  return errorResponse(error);
130
130
  }
131
131
  });
132
+ strictTool(server, 'work_start', 'Start work on a ticket — spawns an agent with a running container/session, creates a git branch, and moves ticket to In Progress only after successful spawn', {
133
+ ticket_id: z.string().describe('Ticket ID to start work on'),
134
+ agent: z.string().optional().describe('Agent name to assign (defaults to creating an ephemeral agent)'),
135
+ environment: z.enum(['devcontainer', 'host']).optional().describe('Execution environment (default: devcontainer if available)'),
136
+ display_mode: z.enum(['background', 'terminal']).optional().describe('Display mode (default: background — MCP runs headless)'),
137
+ skip_permissions: z.boolean().optional().describe('Skip permission prompts (danger mode, default: false)'),
138
+ create_pr: z.boolean().optional().describe('Create PR when work is ready (default: false)'),
139
+ }, async (params) => {
140
+ try {
141
+ // Require workspace context for spawn operations
142
+ if (!ctx.getWorkspaceContext) {
143
+ throw new Error('Workspace not initialized. work_start requires a workspace with agents. Run "prlt init" first.');
144
+ }
145
+ const { workspaceInfo, executionStorage, db, pmoPath } = ctx.getWorkspaceContext();
146
+ // Validate ticket exists
147
+ const ticket = await ctx.storage.getTicket(params.ticket_id);
148
+ if (!ticket)
149
+ throw new Error(`Ticket not found: ${params.ticket_id}`);
150
+ // Check ticket is not blocked
151
+ if (ticket.blockedBy && ticket.blockedBy.length > 0) {
152
+ throw new Error(`Ticket ${ticket.id} is blocked by: ${ticket.blockedBy.join(', ')}. Resolve blockers first.`);
153
+ }
154
+ // Check for existing running execution
155
+ const runningExec = executionStorage.getRunningExecution(ticket.id);
156
+ if (runningExec) {
157
+ throw new Error(`Ticket ${ticket.id} already has a running execution: ${runningExec.id} (agent: ${runningExec.agentName})`);
158
+ }
159
+ // Select or create agent
160
+ let agentName;
161
+ if (params.agent) {
162
+ // Use specified agent — verify it exists
163
+ const agentExists = workspaceInfo.agents.some(a => a.name === params.agent);
164
+ if (!agentExists) {
165
+ throw new Error(`Agent "${params.agent}" not found. Available agents: ${workspaceInfo.agents.map(a => a.name).join(', ') || 'none'}`);
166
+ }
167
+ agentName = params.agent;
168
+ }
169
+ else {
170
+ // Create an ephemeral agent (default for MCP)
171
+ const ephemeralResult = await createEphemeralAgent(workspaceInfo);
172
+ agentName = ephemeralResult.name;
173
+ }
174
+ // Spawn the agent with background mode (MCP is headless)
175
+ const displayMode = params.display_mode || 'background';
176
+ const environment = params.environment;
177
+ const result = await spawnAgentForTicket(ticket, agentName, ctx.storage, executionStorage, workspaceInfo, db, pmoPath, {
178
+ environment,
179
+ displayMode,
180
+ skipPermissions: params.skip_permissions ?? false,
181
+ createPR: params.create_pr ?? false,
182
+ skipRemoteCheck: false,
183
+ });
184
+ if (result.success) {
185
+ // Fetch updated ticket to return current state
186
+ const updatedTicket = await ctx.storage.getTicket(params.ticket_id);
187
+ return {
188
+ content: [{
189
+ type: 'text',
190
+ text: JSON.stringify({
191
+ success: true,
192
+ executionId: result.executionId,
193
+ ticketId: result.ticketId,
194
+ agent: result.agentName,
195
+ status: 'running',
196
+ ticket: updatedTicket ? formatTicket(updatedTicket) : undefined,
197
+ message: `Started work on ${ticket.id}: ${ticket.title} (agent: ${agentName})`,
198
+ }, null, 2),
199
+ }],
200
+ };
201
+ }
202
+ else {
203
+ return {
204
+ content: [{
205
+ type: 'text',
206
+ text: JSON.stringify({
207
+ success: false,
208
+ ticketId: result.ticketId,
209
+ agent: result.agentName,
210
+ error: result.error || 'Spawn failed',
211
+ message: `Failed to start work on ${ticket.id}: ${result.error}`,
212
+ }, null, 2),
213
+ }],
214
+ isError: true,
215
+ };
216
+ }
217
+ }
218
+ catch (error) {
219
+ return errorResponse(error);
220
+ }
221
+ });
132
222
  }
@@ -3,9 +3,19 @@
3
3
  */
4
4
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
5
  import type { SQLiteStorage } from '../pmo/storage/index.js';
6
+ import type Database from 'better-sqlite3';
7
+ import type { WorkspaceInfo } from '../agents/commands.js';
8
+ import type { ExecutionStorage } from '../execution/storage.js';
6
9
  export interface McpToolContext {
7
10
  storage: SQLiteStorage;
8
11
  runCommand: (cmd: string) => string;
12
+ /** Workspace info — available when running in a workspace with agents */
13
+ getWorkspaceContext?: () => {
14
+ workspaceInfo: WorkspaceInfo;
15
+ executionStorage: ExecutionStorage;
16
+ db: Database.Database;
17
+ pmoPath: string;
18
+ };
9
19
  }
10
20
  export type McpToolResult = {
11
21
  content: Array<{
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import * as readline from 'node:readline';
17
17
  import chalk from 'chalk';
18
+ import { isNonTTY } from './prompt-json.js';
18
19
  // ANSI escape codes for terminal control
19
20
  const ESC = '\u001B';
20
21
  const CSI = `${ESC}[`;
@@ -83,7 +84,7 @@ function showCursor() {
83
84
  export async function multiLineInput(options) {
84
85
  const { message, default: defaultValue = '', hint = 'Ctrl+D to finish, Ctrl+C to cancel', required = false, validate, } = options;
85
86
  // If not a TTY, return the default value
86
- if (!process.stdin.isTTY) {
87
+ if (isNonTTY()) {
87
88
  return { value: defaultValue, cancelled: false };
88
89
  }
89
90
  return new Promise((resolve) => {
@@ -3,7 +3,7 @@ import inquirer from 'inquirer';
3
3
  import { getPMOContext } from './pmo-context.js';
4
4
  import { styles } from '../styles.js';
5
5
  import { PromptCommand } from '../prompt-command.js';
6
- import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, } from '../prompt-json.js';
6
+ import { shouldOutputJson, isNonTTY, outputPromptAsJson, outputErrorAsJson, createMetadata, } from '../prompt-json.js';
7
7
  /**
8
8
  * Base flags for JSON/agent mode support
9
9
  * Include these in your command's flags by spreading: ...jsonModeFlags
@@ -168,7 +168,7 @@ export class PMOCommand extends PromptCommand {
168
168
  return numA - numB;
169
169
  });
170
170
  // Auto-detect non-TTY: switch to JSON mode when no TTY present
171
- const effectiveJsonMode = options?.jsonMode ?? (!process.stdin.isTTY
171
+ const effectiveJsonMode = options?.jsonMode ?? (isNonTTY()
172
172
  ? {
173
173
  flags: { json: true },
174
174
  commandName: this.id ?? 'unknown',
@@ -234,7 +234,7 @@ export class PMOCommand extends PromptCommand {
234
234
  async selectFromList(options) {
235
235
  const { message, items, getName, getValue, getCommand, jsonMode, allowCancel = false, cancelValue, } = options;
236
236
  // Auto-detect non-TTY: switch to JSON mode when no TTY present
237
- const effectiveJsonMode = jsonMode ?? (!process.stdin.isTTY
237
+ const effectiveJsonMode = jsonMode ?? (isNonTTY()
238
238
  ? { flags: { json: true }, commandName: this.id ?? 'unknown' }
239
239
  : null);
240
240
  // Build choices with command field
@@ -285,7 +285,7 @@ export class PMOCommand extends PromptCommand {
285
285
  async promptForInput(options) {
286
286
  const { message, fieldName, defaultValue, validate, jsonMode } = options;
287
287
  // Auto-detect non-TTY: switch to JSON mode when no TTY present
288
- const effectiveJsonMode = jsonMode ?? (!process.stdin.isTTY
288
+ const effectiveJsonMode = jsonMode ?? (isNonTTY()
289
289
  ? { flags: { json: true }, commandName: this.id ?? 'unknown', commandHint: '', example: undefined }
290
290
  : null);
291
291
  // Check for JSON mode
@@ -169,7 +169,7 @@ export class ActionStorage {
169
169
  ? JSON.parse(row.default_category)
170
170
  : undefined,
171
171
  defaultMoveToCategory: row.default_category,
172
- modifiesCode: row.is_builtin === 1,
172
+ modifiesCode: row.modifies_code === 1,
173
173
  isBuiltin: row.is_builtin === 1,
174
174
  createdAt: new Date(row.created_at),
175
175
  };
@@ -693,6 +693,200 @@ git add -A && prlt commit "your change" && git push
693
693
  modifiesCode: true,
694
694
  position: 3,
695
695
  },
696
+ {
697
+ id: 'review',
698
+ name: 'Code Review',
699
+ description: 'Review the implementation and post feedback on the PR',
700
+ prompt: `${PRLT_USAGE_RULE}
701
+
702
+ ---
703
+
704
+ # Action: Code Review
705
+
706
+ Review this ticket's implementation thoroughly:
707
+ - Check for bugs, edge cases, and potential issues
708
+ - Look for security vulnerabilities
709
+ - Verify it meets all acceptance criteria
710
+ - Check code quality and maintainability
711
+ - Suggest improvements if appropriate
712
+
713
+ After reviewing, determine your verdict:
714
+ - **APPROVE**: Code is ready to merge, no significant issues
715
+ - **REQUEST_CHANGES**: There are issues that must be fixed before merging
716
+ - **COMMENT**: General feedback, no blocking issues but some suggestions
717
+
718
+ Do NOT modify any code. This is a read-only review.`,
719
+ endPrompt: `When you have finished reviewing, post your review on the PR using \`gh pr review\`.
720
+
721
+ Choose the appropriate command based on your verdict:
722
+
723
+ **If approving:**
724
+ \`\`\`bash
725
+ gh pr review --approve --body "## Code Review
726
+
727
+ ### What looks good
728
+ - ...
729
+
730
+ ### Verdict
731
+ APPROVED - Code is ready to merge."
732
+ \`\`\`
733
+
734
+ **If requesting changes:**
735
+ \`\`\`bash
736
+ gh pr review --request-changes --body "## Code Review
737
+
738
+ ### What looks good
739
+ - ...
740
+
741
+ ### Concerns
742
+ - ...
743
+
744
+ ### Suggested improvements
745
+ - ...
746
+
747
+ ### Verdict
748
+ REQUEST CHANGES - Issues must be addressed before merging."
749
+ \`\`\`
750
+
751
+ **If commenting:**
752
+ \`\`\`bash
753
+ gh pr review --comment --body "## Code Review
754
+
755
+ ### What looks good
756
+ - ...
757
+
758
+ ### Suggestions
759
+ - ...
760
+
761
+ ### Verdict
762
+ COMMENT - Some suggestions but no blocking issues."
763
+ \`\`\`
764
+
765
+ Format the body with: what looks good, concerns (if any), suggested improvements (if any), and your verdict.
766
+
767
+ No commits are needed for code review.`,
768
+ suggestedForCategories: ['started', 'completed'],
769
+ modifiesCode: false,
770
+ position: 4,
771
+ },
772
+ {
773
+ id: 'review-fix',
774
+ name: 'Review & Fix',
775
+ description: 'Review the implementation, fix issues, and post feedback on the PR',
776
+ prompt: `${PRLT_USAGE_RULE}
777
+
778
+ ---
779
+
780
+ # Action: Review & Fix
781
+
782
+ Review this ticket's implementation thoroughly and fix any issues found:
783
+ - Check for bugs, edge cases, and potential issues
784
+ - Look for security vulnerabilities
785
+ - Verify it meets all acceptance criteria
786
+ - Check code quality and maintainability
787
+ - Fix any issues you find directly in the code
788
+
789
+ **IMPORTANT: Commit and push frequently!**
790
+ - Commit after each fix or logical group of changes
791
+ - Push after every 1-2 commits to save your work
792
+
793
+ \`\`\`bash
794
+ git add -A && prlt commit "fix: address code review findings" && git push
795
+ \`\`\``,
796
+ endPrompt: `When you have finished reviewing and fixing:
797
+
798
+ 1. **If issues were found and fixed**, post a review summary and push your fixes:
799
+ \`\`\`bash
800
+ gh pr review --comment --body "## Code Review & Fix Summary
801
+
802
+ ### Issues found and fixed
803
+ - ...
804
+
805
+ ### What looks good
806
+ - ...
807
+
808
+ ### Changes made
809
+ - ...
810
+ "
811
+ prlt commit "fix: address code review findings"
812
+ git push
813
+ \`\`\`
814
+
815
+ 2. **If no issues were found**, approve the PR:
816
+ \`\`\`bash
817
+ gh pr review --approve --body "## Code Review
818
+
819
+ ### What looks good
820
+ - ...
821
+
822
+ ### Verdict
823
+ APPROVED - Code looks great, no issues found."
824
+ \`\`\``,
825
+ suggestedForCategories: ['started', 'completed'],
826
+ modifiesCode: true,
827
+ position: 5,
828
+ },
829
+ {
830
+ id: 'revise',
831
+ name: 'Revise',
832
+ description: 'Pull the branch, read PR review feedback, implement fixes, and push',
833
+ prompt: `${PRLT_USAGE_RULE}
834
+
835
+ ---
836
+
837
+ # Action: Revise
838
+
839
+ Address the feedback on this ticket's pull request:
840
+
841
+ 1. **Pull the latest branch** to ensure you have the most recent code:
842
+ \`\`\`bash
843
+ git pull
844
+ \`\`\`
845
+
846
+ 2. **Read all review comments and requested changes** from the PR:
847
+ \`\`\`bash
848
+ gh pr view
849
+ gh api repos/{owner}/{repo}/pulls/{number}/comments
850
+ \`\`\`
851
+
852
+ 3. **Understand each piece of feedback** before making changes — read carefully and understand the reviewer's intent
853
+
854
+ 4. **Implement the requested fixes** and address each review comment:
855
+ - Make the necessary code changes to address each point
856
+ - Respond to questions with explanations
857
+ - Ensure all requested changes are addressed
858
+
859
+ **IMPORTANT: Commit and push frequently!**
860
+ - Commit after each fix or logical group of changes
861
+ - Push after every 1-2 commits to save your work
862
+
863
+ \`\`\`bash
864
+ git add -A && prlt commit "fix: address PR review feedback" && git push
865
+ \`\`\``,
866
+ endPrompt: `After addressing all feedback:
867
+
868
+ 1. **Commit your changes**:
869
+ \`\`\`bash
870
+ git add -A
871
+ prlt commit "fix: address PR review feedback"
872
+ \`\`\`
873
+
874
+ 2. **Push your changes**:
875
+ \`\`\`bash
876
+ git push
877
+ \`\`\`
878
+
879
+ 3. **Optionally reply to resolved review threads** using the GitHub API:
880
+ \`\`\`bash
881
+ gh api repos/{owner}/{repo}/pulls/{number}/comments
882
+ \`\`\`
883
+
884
+ The PR will be updated automatically with your pushed changes.`,
885
+ suggestedForCategories: ['completed'],
886
+ defaultMoveToCategory: 'started',
887
+ modifiesCode: true,
888
+ position: 6,
889
+ },
696
890
  {
697
891
  id: 'test',
698
892
  name: 'Write Tests',
@@ -733,56 +927,7 @@ git add -A && prlt commit "add tests for X" && git push
733
927
  **IMPORTANT:** Use the global \`prlt\` command.`,
734
928
  suggestedForCategories: ['started', 'completed'],
735
929
  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,
930
+ position: 7,
786
931
  },
787
932
  ];
788
933
  // Use INSERT OR REPLACE to always update builtin actions with latest prompts
@@ -169,6 +169,7 @@ export interface WorkActionRow {
169
169
  prompt: string;
170
170
  end_prompt: string | null;
171
171
  default_category: string | null;
172
+ modifies_code: number;
172
173
  is_builtin: number;
173
174
  position: number;
174
175
  created_at: string;
@@ -30,6 +30,26 @@ import { type JsonFlags } from './prompt-json.js';
30
30
  * ```
31
31
  */
32
32
  export declare abstract class PromptCommand extends Command {
33
+ /**
34
+ * TTY-aware log method - strips ANSI codes and emoji in non-TTY mode.
35
+ *
36
+ * Use this instead of this.log() when outputting styled text (chalk colors, emoji prefixes).
37
+ * In TTY mode, outputs styled text as-is. In non-TTY mode, strips ANSI and emoji.
38
+ *
39
+ * @param message - The styled message (may contain ANSI codes and emoji)
40
+ * @param args - Additional arguments passed to this.log()
41
+ */
42
+ protected logPlain(message?: string, ...args: string[]): void;
43
+ /**
44
+ * Check if plain output mode is active (non-TTY, PRLT_PLAIN, NO_COLOR).
45
+ * Convenience wrapper for use in commands.
46
+ */
47
+ protected get isPlain(): boolean;
48
+ /**
49
+ * Check if running in non-TTY environment.
50
+ * Convenience wrapper for use in commands.
51
+ */
52
+ protected get isNonTTY(): boolean;
33
53
  /**
34
54
  * Prompt wrapper - drop-in replacement for inquirer.prompt
35
55
  *
@@ -1,6 +1,7 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import inquirer from 'inquirer';
3
- import { isAgentMode, outputPromptAsJson, createMetadata, normalizeChoices, } from './prompt-json.js';
3
+ import { isAgentMode, isNonTTY, outputPromptAsJson, createMetadata, normalizeChoices, } from './prompt-json.js';
4
+ import { isPlainOutput, plainText } from './styles.js';
4
5
  /**
5
6
  * Lightweight base command with prompt() method for JSON mode support.
6
7
  *
@@ -31,6 +32,41 @@ import { isAgentMode, outputPromptAsJson, createMetadata, normalizeChoices, } fr
31
32
  * ```
32
33
  */
33
34
  export class PromptCommand extends Command {
35
+ /**
36
+ * TTY-aware log method - strips ANSI codes and emoji in non-TTY mode.
37
+ *
38
+ * Use this instead of this.log() when outputting styled text (chalk colors, emoji prefixes).
39
+ * In TTY mode, outputs styled text as-is. In non-TTY mode, strips ANSI and emoji.
40
+ *
41
+ * @param message - The styled message (may contain ANSI codes and emoji)
42
+ * @param args - Additional arguments passed to this.log()
43
+ */
44
+ logPlain(message, ...args) {
45
+ if (message === undefined) {
46
+ this.log();
47
+ return;
48
+ }
49
+ if (isPlainOutput()) {
50
+ this.log(plainText(message), ...args);
51
+ }
52
+ else {
53
+ this.log(message, ...args);
54
+ }
55
+ }
56
+ /**
57
+ * Check if plain output mode is active (non-TTY, PRLT_PLAIN, NO_COLOR).
58
+ * Convenience wrapper for use in commands.
59
+ */
60
+ get isPlain() {
61
+ return isPlainOutput();
62
+ }
63
+ /**
64
+ * Check if running in non-TTY environment.
65
+ * Convenience wrapper for use in commands.
66
+ */
67
+ get isNonTTY() {
68
+ return isNonTTY();
69
+ }
34
70
  /**
35
71
  * Prompt wrapper - drop-in replacement for inquirer.prompt
36
72
  *
@@ -73,7 +109,7 @@ export class PromptCommand extends Command {
73
109
  */
74
110
  async prompt(questions, jsonModeConfig) {
75
111
  // Auto-detect non-TTY: switch to JSON mode when no TTY present
76
- if (!jsonModeConfig && !process.stdin.isTTY) {
112
+ if (!jsonModeConfig && isNonTTY()) {
77
113
  jsonModeConfig = { flags: { json: true }, commandName: this.id ?? 'unknown' };
78
114
  }
79
115
  // Check for JSON/agent mode
@@ -193,6 +193,27 @@ export interface ExecutionResultJsonOutput {
193
193
  * Union type for all JSON output types
194
194
  */
195
195
  export type JsonOutput = PromptJsonOutput | SuccessJsonOutput | ErrorJsonOutput | DryRunJsonOutput | ConfirmationNeededJsonOutput | ExecutionResultJsonOutput;
196
+ /**
197
+ * All valid JSON envelope type discriminators.
198
+ * Used for contract tests and schema validation.
199
+ */
200
+ export declare const JSON_ENVELOPE_TYPES: readonly ["prompt", "success", "error", "dry-run", "confirmation_needed", "execution_result"];
201
+ export type JsonEnvelopeType = typeof JSON_ENVELOPE_TYPES[number];
202
+ /**
203
+ * Required fields per envelope type for contract validation.
204
+ * Tests use this to verify no fields are accidentally removed.
205
+ */
206
+ export declare const JSON_ENVELOPE_REQUIRED_FIELDS: Record<JsonEnvelopeType, string[]>;
207
+ /**
208
+ * Validate that a parsed JSON object conforms to the machine-mode envelope schema.
209
+ *
210
+ * Returns an array of validation errors (empty = valid).
211
+ * Useful for contract tests and runtime validation of JSON output.
212
+ *
213
+ * @param obj - Parsed JSON object to validate
214
+ * @returns Array of validation error strings (empty if valid)
215
+ */
216
+ export declare function validateJsonEnvelope(obj: unknown): string[];
196
217
  /**
197
218
  * Flags interface for JSON mode detection
198
219
  */
@@ -211,9 +232,18 @@ export interface MachineOutputFlags {
211
232
  machine?: boolean;
212
233
  }
213
234
  /**
214
- * Check if the current environment is non-TTY (piped output)
235
+ * Check if the current environment is non-TTY (piped input or output)
236
+ *
237
+ * Uses the "either" strategy: returns true if EITHER stdin OR stdout is non-TTY.
238
+ * This covers the primary use case of scripts/agents calling prlt as a subprocess,
239
+ * where both stdin and stdout are typically non-TTY.
240
+ *
241
+ * Returns true if:
242
+ * - stdin is not a TTY (e.g., piped input)
243
+ * - stdout is not a TTY (e.g., piped output)
244
+ * - PRLT_JSON=1 environment variable is set (overrides TTY detection)
215
245
  *
216
- * @returns true if stdout is not a TTY (e.g., piped to another process)
246
+ * @returns true if either stdin or stdout is not a TTY, or PRLT_JSON=1 is set
217
247
  */
218
248
  export declare function isNonTTY(): boolean;
219
249
  /**
@@ -221,7 +251,8 @@ export declare function isNonTTY(): boolean;
221
251
  *
222
252
  * Returns true if:
223
253
  * - The --json flag is set (or -m/--machine aliases)
224
- * - The environment is non-TTY (piped output)
254
+ * - The PRLT_JSON=1 environment variable is set
255
+ * - Either stdin or stdout is non-TTY (piped input/output)
225
256
  *
226
257
  * @param flags - Command flags object
227
258
  * @returns true if JSON mode should be used
@@ -236,7 +267,8 @@ export declare const isAgentMode: typeof shouldOutputJson;
236
267
  *
237
268
  * Returns true if:
238
269
  * - The --json flag is set (or -m/--machine aliases)
239
- * - The environment is non-TTY (piped output)
270
+ * - The PRLT_JSON=1 environment variable is set
271
+ * - Either stdin or stdout is non-TTY (piped input/output)
240
272
  *
241
273
  * @param flags - Command flags object
242
274
  * @returns true if machine-readable output mode should be used