@dreki-gg/pi-plan-mode 0.10.1 → 0.14.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 (28) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/extensions/plan-mode/__tests__/atomic-write.test.ts +43 -0
  3. package/extensions/plan-mode/__tests__/html-render.test.ts +43 -0
  4. package/extensions/plan-mode/__tests__/package-skills.test.ts +47 -0
  5. package/extensions/plan-mode/__tests__/plans-manifest.test.ts +37 -0
  6. package/extensions/plan-mode/__tests__/prompts.test.ts +38 -0
  7. package/extensions/plan-mode/__tests__/task-storage.test.ts +44 -0
  8. package/extensions/plan-mode/__tests__/types.test.ts +55 -0
  9. package/extensions/plan-mode/constants.ts +3 -1
  10. package/extensions/plan-mode/html/render.ts +67 -0
  11. package/extensions/plan-mode/html/templates/plan.pug +58 -0
  12. package/extensions/plan-mode/index.ts +79 -42
  13. package/extensions/plan-mode/plan-storage.ts +1 -67
  14. package/extensions/plan-mode/prompts.ts +32 -31
  15. package/extensions/plan-mode/pug.d.ts +5 -0
  16. package/extensions/plan-mode/resume.ts +38 -37
  17. package/extensions/plan-mode/storage/atomic-write.ts +53 -0
  18. package/extensions/plan-mode/storage/plan-storage.ts +47 -0
  19. package/extensions/plan-mode/storage/plans-manifest.ts +57 -0
  20. package/extensions/plan-mode/storage/task-storage.ts +44 -0
  21. package/extensions/plan-mode/tools/submit-plan.ts +40 -88
  22. package/extensions/plan-mode/tools/update-task.ts +82 -0
  23. package/extensions/plan-mode/types.ts +57 -5
  24. package/extensions/plan-mode/ui.ts +10 -10
  25. package/package.json +7 -2
  26. package/skills/technical-options/SKILL.md +89 -0
  27. package/extensions/plan-mode/plans-json.ts +0 -48
  28. package/extensions/plan-mode/tools/update-step.ts +0 -145
@@ -1,145 +0,0 @@
1
- /**
2
- * update_step tool — available during the execution phase.
3
- *
4
- * The executor calls this to mark a plan step as done, skipped, or blocked.
5
- * On "blocked", returns a stop signal so the executor pauses for user intervention.
6
- */
7
-
8
- import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
9
- import { StringEnum } from '@earendil-works/pi-ai';
10
- import { Text } from '@earendil-works/pi-tui';
11
- import { Type } from 'typebox';
12
- import type { PlanData } from '../types.js';
13
-
14
- export interface UpdateStepCallbacks {
15
- getPlan: () => PlanData | undefined;
16
- onStepUpdated: (step: number, status: 'done' | 'skipped' | 'blocked', notes?: string) => void;
17
- }
18
-
19
- export function registerUpdateStepTool(pi: ExtensionAPI, callbacks: UpdateStepCallbacks): void {
20
- pi.registerTool({
21
- name: 'update_step',
22
- label: 'Update Step',
23
- description:
24
- 'Mark a plan step as done, skipped, or blocked. ' +
25
- 'Call this after completing each step. ' +
26
- 'If a step is blocked, execution will pause for user intervention.',
27
- promptSnippet: 'Mark a plan step as done, skipped, or blocked',
28
- promptGuidelines: [
29
- 'Call update_step after completing each plan step to mark it done before moving to the next.',
30
- 'Use update_step with status "skipped" if a step is unnecessary after inspecting the code.',
31
- 'Use update_step with status "blocked" and explain the reason in notes if a step cannot be completed — execution will pause for user input.',
32
- ],
33
- parameters: Type.Object({
34
- step: Type.Number({ description: 'Step number (1-indexed)' }),
35
- status: StringEnum(['done', 'skipped', 'blocked'] as const),
36
- notes: Type.Optional(
37
- Type.String({ description: 'What was done, why skipped, or why blocked' }),
38
- ),
39
- }),
40
-
41
- async execute(_toolCallId, params) {
42
- const plan = callbacks.getPlan();
43
- if (!plan) {
44
- throw new Error('No active plan. Cannot update step.');
45
- }
46
-
47
- const stepIdx = params.step - 1;
48
- if (stepIdx < 0 || stepIdx >= plan.steps.length) {
49
- throw new Error(
50
- `Invalid step number ${params.step}. Plan has ${plan.steps.length} steps.`,
51
- );
52
- }
53
-
54
- const step = plan.steps[stepIdx];
55
- if (step.status !== 'pending') {
56
- throw new Error(
57
- `Step ${params.step} is already "${step.status}". Only pending steps can be updated.`,
58
- );
59
- }
60
-
61
- callbacks.onStepUpdated(params.step, params.status, params.notes);
62
-
63
- const details = {
64
- step: params.step,
65
- status: params.status,
66
- notes: params.notes,
67
- description: step.description,
68
- };
69
-
70
- if (params.status === 'blocked') {
71
- return {
72
- content: [
73
- {
74
- type: 'text' as const,
75
- text: `Step ${params.step} blocked. Execution paused — waiting for user input.`,
76
- },
77
- ],
78
- details,
79
- terminate: true,
80
- };
81
- }
82
-
83
- // Build progress info
84
- const done = plan.steps.filter((s) => s.status === 'done').length;
85
- const skipped = plan.steps.filter((s) => s.status === 'skipped').length;
86
- const resolved = done + skipped;
87
-
88
- const statusEmoji = params.status === 'done' ? '✓' : '⊘';
89
- let text = `${statusEmoji} Step ${params.step} ${params.status}. Progress: ${resolved}/${plan.steps.length}`;
90
- if (params.notes) {
91
- text += ` — ${params.notes}`;
92
- }
93
-
94
- // Find next pending step
95
- const next = plan.steps.find((s) => s.status === 'pending');
96
- if (next) {
97
- const nextIdx = plan.steps.indexOf(next) + 1;
98
- text += `\n\nNext step ${nextIdx}: ${next.description}`;
99
- } else {
100
- text += '\n\nAll steps resolved!';
101
- }
102
-
103
- return {
104
- content: [{ type: 'text' as const, text }],
105
- details,
106
- // Stop the agent when all steps are done so agent_end fires immediately
107
- terminate: !next,
108
- };
109
- },
110
-
111
- renderCall(args, theme) {
112
- const step = (args as { step?: number }).step ?? '?';
113
- const status = (args as { status?: string }).status ?? '';
114
- let content = theme.fg('toolTitle', theme.bold('update_step '));
115
- content += theme.fg('muted', `#${step}`);
116
- if (status) {
117
- const color = status === 'done' ? 'success' : status === 'skipped' ? 'warning' : 'error';
118
- content += ' ' + theme.fg(color, status);
119
- }
120
- return new Text(content, 0, 0);
121
- },
122
-
123
- renderResult(result, _options, theme) {
124
- const details = result.details as {
125
- step?: number;
126
- status?: string;
127
- description?: string;
128
- } | undefined;
129
-
130
- if (!details) {
131
- return new Text(theme.fg('dim', 'Updated'), 0, 0);
132
- }
133
-
134
- const statusMap: Record<string, string> = {
135
- done: theme.fg('success', '✓'),
136
- skipped: theme.fg('warning', '⊘'),
137
- blocked: theme.fg('error', '✗'),
138
- };
139
-
140
- const icon = statusMap[details.status ?? ''] ?? '';
141
- const desc = details.description ?? '';
142
- return new Text(`${icon} Step ${details.step}: ${desc}`, 0, 0);
143
- },
144
- });
145
- }