@dreki-gg/pi-plan-mode 0.3.1 → 0.5.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.
- package/CHANGELOG.md +26 -0
- package/README.md +86 -3
- package/bin/clean-plans.js +117 -0
- package/extensions/plan-mode/index.ts +59 -8
- package/extensions/plan-mode/plans-json.ts +53 -0
- package/extensions/plan-mode/utils.ts +17 -0
- package/package.json +9 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @dreki-gg/pi-plan-mode
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`32797ff`](https://github.com/dreki-gg/pi-extensions/commit/32797ff18d968e22c6c44e95c46e3393d8928cef) Thanks [@jalbarrang](https://github.com/jalbarrang)! - feat(plan-mode): add Windows compatibility — replace Unix shell commands with cross-platform Bun/Node APIs
|
|
8
|
+
|
|
9
|
+
Plan-mode no longer shells out to `cat`, `bash`, or `mkdir` via `pi.exec()`. File I/O now uses `Bun.file()` / `Bun.write()` and `node:fs/promises` `mkdir`, making the extension fully cross-platform. Destructive and safe command pattern lists now include Windows equivalents (`del`, `rd`, `copy`, `move`, `powershell`, `dir`, `where`, `tasklist`, etc.).
|
|
10
|
+
|
|
11
|
+
Also fixes Windows compatibility in three other packages:
|
|
12
|
+
|
|
13
|
+
- **browser-tools**: `spawn` now uses `shell: true` on Windows so `.cmd` wrappers resolve correctly; `shellEscape` uses double-quote style on Windows; install guidance is platform-aware (Homebrew shown only on macOS).
|
|
14
|
+
- **subagent**: `spawn` uses `shell: true` on Windows when the command is bare `pi`, allowing `pi.cmd` resolution.
|
|
15
|
+
- **lsp**: `globalConfigPath()` now uses `os.homedir()` on Windows instead of the unreliable `process.env.HOME`.
|
|
16
|
+
|
|
17
|
+
## 0.4.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- [`c86c935`](https://github.com/dreki-gg/pi-extensions/commit/c86c9352150a5bed61602243c8164bdd5d679745) Thanks [@jalbarrang](https://github.com/jalbarrang)! - Add plans.json lifecycle tracking and CLI for cleaning completed plans
|
|
22
|
+
|
|
23
|
+
- Extension now writes `.plans/plans.json` to track plan status (`in-progress` / `done`) with timestamps and titles
|
|
24
|
+
- Plans are recorded as `in-progress` when created, marked `done` when all execution steps complete
|
|
25
|
+
- New `pi-plan-mode clean` CLI (`npx @dreki-gg/pi-plan-mode clean [--dry-run]`) removes completed plan directories while preserving in-flight plans
|
|
26
|
+
- Cleanup step added to publish.yml workflow to auto-clean done plans on merge to main
|
|
27
|
+
- Removed stale `docs/plans/` from browser-tools and subagent packages
|
|
28
|
+
|
|
3
29
|
## 0.3.1
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -67,12 +67,84 @@ When **Execute Plan** is selected:
|
|
|
67
67
|
|
|
68
68
|
```
|
|
69
69
|
.plans/
|
|
70
|
+
├── plans.json # Tracking manifest — plan status lifecycle
|
|
70
71
|
└── add-auth-middleware/
|
|
71
|
-
├── PLAN.md
|
|
72
|
-
├── START-PROMPT.md
|
|
73
|
-
└── ...
|
|
72
|
+
├── PLAN.md # Numbered plan with context
|
|
73
|
+
├── START-PROMPT.md # Self-contained executor handoff prompt
|
|
74
|
+
└── ... # Optional supporting files
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
### plans.json
|
|
78
|
+
|
|
79
|
+
The extension automatically maintains `.plans/plans.json` to track plan lifecycle:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"add-auth-middleware": {
|
|
84
|
+
"status": "in-progress",
|
|
85
|
+
"title": "Add Authentication Middleware with JWT Support",
|
|
86
|
+
"created": "2026-05-08T12:00:00.000Z",
|
|
87
|
+
"completed": null
|
|
88
|
+
},
|
|
89
|
+
"fix-ci-flakes": {
|
|
90
|
+
"status": "done",
|
|
91
|
+
"title": "Fix CI Flaky Tests",
|
|
92
|
+
"created": "2026-05-07T10:00:00.000Z",
|
|
93
|
+
"completed": "2026-05-07T14:30:00.000Z"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Plans start as `"in-progress"` when created and are marked `"done"` when all execution steps complete. This prevents accidental deletion of in-flight plans.
|
|
99
|
+
|
|
100
|
+
## Cleaning completed plans
|
|
101
|
+
|
|
102
|
+
Use the CLI to remove completed plan directories:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Preview what would be deleted
|
|
106
|
+
npx @dreki-gg/pi-plan-mode clean --dry-run
|
|
107
|
+
|
|
108
|
+
# Delete completed plans and update plans.json
|
|
109
|
+
npx @dreki-gg/pi-plan-mode clean
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
In-flight plans (`"status": "in-progress"`) are never touched.
|
|
113
|
+
|
|
114
|
+
### GitHub Actions
|
|
115
|
+
|
|
116
|
+
Clean done plans automatically after merge — similar to changesets:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
name: Clean Plans
|
|
120
|
+
|
|
121
|
+
on:
|
|
122
|
+
push:
|
|
123
|
+
branches: [main]
|
|
124
|
+
paths: ['.plans/**']
|
|
125
|
+
|
|
126
|
+
jobs:
|
|
127
|
+
clean:
|
|
128
|
+
runs-on: ubuntu-latest
|
|
129
|
+
steps:
|
|
130
|
+
- uses: actions/checkout@v5
|
|
131
|
+
- uses: actions/setup-node@v4
|
|
132
|
+
with:
|
|
133
|
+
node-version: '24'
|
|
134
|
+
- run: npx @dreki-gg/pi-plan-mode clean
|
|
135
|
+
- name: Commit cleanup
|
|
136
|
+
run: |
|
|
137
|
+
git config user.name "github-actions[bot]"
|
|
138
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
139
|
+
git add .plans/
|
|
140
|
+
git diff --cached --quiet || git commit -m "chore: clean completed plans"
|
|
141
|
+
git push
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Should you gitignore `.plans/`?
|
|
145
|
+
|
|
146
|
+
**No.** Commit your plans — they provide decision history and execution context. Use the `clean` CLI to remove done plans after merge, keeping the directory lean. Plans are execution blueprints, not permanent documentation; for lasting decisions, use ADRs.
|
|
147
|
+
|
|
76
148
|
## Footer indicators
|
|
77
149
|
|
|
78
150
|
- `📝 plan` — plan mode active (opus-4-6:medium, strict bash)
|
|
@@ -81,3 +153,14 @@ When **Execute Plan** is selected:
|
|
|
81
153
|
## Bash safety
|
|
82
154
|
|
|
83
155
|
In plan mode, bash is restricted to read-only commands (ls, grep, git status, cat, rg, etc.). Destructive commands (rm, mv, git commit, etc.) are blocked.
|
|
156
|
+
|
|
157
|
+
## CLI reference
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
pi-plan-mode clean [--dry-run]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
| Option | Description |
|
|
164
|
+
| ----------- | ------------------------------------------------ |
|
|
165
|
+
| `clean` | Remove completed plan directories, update manifest |
|
|
166
|
+
| `--dry-run` | Show what would be deleted without deleting |
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI to clean completed plans from `.plans/`.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx @dreki-gg/pi-plan-mode clean [--dry-run]
|
|
7
|
+
*
|
|
8
|
+
* Reads `.plans/plans.json`, deletes directories for plans with status "done",
|
|
9
|
+
* and updates `plans.json` to remove them.
|
|
10
|
+
*
|
|
11
|
+
* Designed for use in GitHub Actions after merge — similar to changesets.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync, rmSync, readdirSync, existsSync } from 'node:fs';
|
|
15
|
+
import { resolve, join } from 'node:path';
|
|
16
|
+
|
|
17
|
+
const PLANS_DIR = '.plans';
|
|
18
|
+
const PLANS_JSON = join(PLANS_DIR, 'plans.json');
|
|
19
|
+
|
|
20
|
+
function main() {
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const command = args[0];
|
|
23
|
+
|
|
24
|
+
if (command !== 'clean') {
|
|
25
|
+
console.error('Usage: pi-plan-mode clean [--dry-run]\n');
|
|
26
|
+
console.error('Commands:');
|
|
27
|
+
console.error(' clean Remove completed plan directories and update plans.json\n');
|
|
28
|
+
console.error('Options:');
|
|
29
|
+
console.error(' --dry-run Show what would be deleted without actually deleting');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const dryRun = args.includes('--dry-run');
|
|
34
|
+
const plansJsonPath = resolve(PLANS_JSON);
|
|
35
|
+
|
|
36
|
+
if (!existsSync(plansJsonPath)) {
|
|
37
|
+
console.log('No .plans/plans.json found — nothing to clean.');
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @type {Record<string, { status: string; title: string; created: string; completed: string | null }>} */
|
|
42
|
+
let manifest;
|
|
43
|
+
try {
|
|
44
|
+
manifest = JSON.parse(readFileSync(plansJsonPath, 'utf-8'));
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(`Failed to parse ${PLANS_JSON}:`, err);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const donePlans = Object.entries(manifest).filter(([, entry]) => entry.status === 'done');
|
|
51
|
+
const inFlightPlans = Object.entries(manifest).filter(
|
|
52
|
+
([, entry]) => entry.status === 'in-progress',
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (donePlans.length === 0) {
|
|
56
|
+
console.log('No completed plans to clean.');
|
|
57
|
+
if (inFlightPlans.length > 0) {
|
|
58
|
+
console.log(`\n${inFlightPlans.length} plan(s) still in progress:`);
|
|
59
|
+
for (const [name, entry] of inFlightPlans) {
|
|
60
|
+
console.log(` ○ ${name} — ${entry.title}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(dryRun ? 'Dry run — would clean:\n' : 'Cleaning completed plans:\n');
|
|
67
|
+
|
|
68
|
+
let cleaned = 0;
|
|
69
|
+
for (const [name, entry] of donePlans) {
|
|
70
|
+
const planPath = resolve(join(PLANS_DIR, name));
|
|
71
|
+
const exists = existsSync(planPath);
|
|
72
|
+
|
|
73
|
+
if (dryRun) {
|
|
74
|
+
console.log(` ✓ ${name} — ${entry.title}${exists ? '' : ' (directory already missing)'}`);
|
|
75
|
+
} else {
|
|
76
|
+
if (exists) {
|
|
77
|
+
rmSync(planPath, { recursive: true, force: true });
|
|
78
|
+
console.log(` ✓ Deleted ${PLANS_DIR}/${name} — ${entry.title}`);
|
|
79
|
+
} else {
|
|
80
|
+
console.log(` ✓ ${name} — directory already missing, removing from manifest`);
|
|
81
|
+
}
|
|
82
|
+
delete manifest[name];
|
|
83
|
+
cleaned++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!dryRun) {
|
|
88
|
+
const remaining = Object.keys(manifest).length;
|
|
89
|
+
if (remaining === 0) {
|
|
90
|
+
rmSync(plansJsonPath, { force: true });
|
|
91
|
+
// Remove .plans/ if completely empty
|
|
92
|
+
const plansDir = resolve(PLANS_DIR);
|
|
93
|
+
try {
|
|
94
|
+
if (existsSync(plansDir) && readdirSync(plansDir).length === 0) {
|
|
95
|
+
rmSync(plansDir, { recursive: true, force: true });
|
|
96
|
+
console.log(`\nRemoved empty ${PLANS_DIR}/`);
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// Directory might have other contents
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
writeFileSync(plansJsonPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`\nCleaned ${cleaned} plan(s).`);
|
|
106
|
+
if (remaining > 0) {
|
|
107
|
+
console.log(`${remaining} plan(s) still in progress.`);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
console.log(`\n${donePlans.length} plan(s) would be cleaned.`);
|
|
111
|
+
if (inFlightPlans.length > 0) {
|
|
112
|
+
console.log(`${inFlightPlans.length} plan(s) still in progress (will be kept).`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main();
|
|
@@ -22,12 +22,19 @@ import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
|
22
22
|
import type { AssistantMessage, TextContent } from '@earendil-works/pi-ai';
|
|
23
23
|
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
24
24
|
import { Key } from '@earendil-works/pi-tui';
|
|
25
|
+
import { mkdir } from 'node:fs/promises';
|
|
25
26
|
import {
|
|
26
27
|
extractTodoItems,
|
|
27
28
|
isSafeCommand,
|
|
28
29
|
markCompletedSteps,
|
|
29
30
|
type TodoItem,
|
|
30
31
|
} from './utils.js';
|
|
32
|
+
import {
|
|
33
|
+
extractPlanTitle,
|
|
34
|
+
readPlansJson,
|
|
35
|
+
serializePlansJson,
|
|
36
|
+
type PlansManifest,
|
|
37
|
+
} from './plans-json.js';
|
|
31
38
|
|
|
32
39
|
// ── Tool sets ────────────────────────────────────────────────────────────────
|
|
33
40
|
// Plan phase: read-only + edit/write (for .plans/ files only, enforced by prompt)
|
|
@@ -99,6 +106,28 @@ export default function planMode(pi: ExtensionAPI): void {
|
|
|
99
106
|
});
|
|
100
107
|
}
|
|
101
108
|
|
|
109
|
+
// ── plans.json tracking ───────────────────────────────────────────────────
|
|
110
|
+
async function updatePlansManifest(
|
|
111
|
+
planName: string,
|
|
112
|
+
status: 'in-progress' | 'done',
|
|
113
|
+
title?: string,
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
const manifest = await readPlansJson();
|
|
116
|
+
const existing = manifest[planName];
|
|
117
|
+
const now = new Date().toISOString();
|
|
118
|
+
|
|
119
|
+
manifest[planName] = {
|
|
120
|
+
status,
|
|
121
|
+
title: title ?? existing?.title ?? 'Untitled plan',
|
|
122
|
+
created: existing?.created ?? now,
|
|
123
|
+
completed: status === 'done' ? now : null,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
await mkdir('.plans', { recursive: true });
|
|
127
|
+
const content = serializePlansJson(manifest);
|
|
128
|
+
await Bun.write('.plans/plans.json', content);
|
|
129
|
+
}
|
|
130
|
+
|
|
102
131
|
// ── UI updates ────────────────────────────────────────────────────────────
|
|
103
132
|
function updateUI(ctx: ExtensionContext): void {
|
|
104
133
|
const { theme } = ctx.ui;
|
|
@@ -384,7 +413,29 @@ Execute each step in order. You MUST include [DONE:n] in your response after com
|
|
|
384
413
|
const match = path.match(/\.plans\/([^/]+)\//);
|
|
385
414
|
if (match && !planDir) {
|
|
386
415
|
planDir = `.plans/${match[1]}`;
|
|
416
|
+
const planName = match[1];
|
|
417
|
+
|
|
418
|
+
// Read PLAN.md to extract the title for plans.json
|
|
419
|
+
let title = 'Untitled plan';
|
|
420
|
+
if (path.endsWith('PLAN.md')) {
|
|
421
|
+
try {
|
|
422
|
+
const content = await Bun.file(path).text();
|
|
423
|
+
title = extractPlanTitle(content);
|
|
424
|
+
} catch {
|
|
425
|
+
// Fall through
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
await updatePlansManifest(planName, 'in-progress', title);
|
|
387
429
|
persist();
|
|
430
|
+
} else if (match && planDir && path.endsWith('PLAN.md')) {
|
|
431
|
+
// planDir already set but PLAN.md just written — update title
|
|
432
|
+
try {
|
|
433
|
+
const content = await Bun.file(path).text();
|
|
434
|
+
const title = extractPlanTitle(content);
|
|
435
|
+
await updatePlansManifest(match[1], 'in-progress', title);
|
|
436
|
+
} catch {
|
|
437
|
+
// Fall through
|
|
438
|
+
}
|
|
388
439
|
}
|
|
389
440
|
});
|
|
390
441
|
|
|
@@ -393,6 +444,12 @@ Execute each step in order. You MUST include [DONE:n] in your response after com
|
|
|
393
444
|
// Check execution completion
|
|
394
445
|
if (executing && todos.length > 0) {
|
|
395
446
|
if (todos.every((t) => t.completed)) {
|
|
447
|
+
// Mark plan as done in plans.json
|
|
448
|
+
if (planDir) {
|
|
449
|
+
const planName = planDir.replace(/^\.plans\//, '');
|
|
450
|
+
await updatePlansManifest(planName, 'done');
|
|
451
|
+
}
|
|
452
|
+
|
|
396
453
|
const list = todos.map((t) => `~~${t.text}~~`).join('\n');
|
|
397
454
|
pi.sendMessage(
|
|
398
455
|
{
|
|
@@ -439,10 +496,7 @@ Execute each step in order. You MUST include [DONE:n] in your response after com
|
|
|
439
496
|
// Read the plan to extract todos
|
|
440
497
|
let planContent = '';
|
|
441
498
|
try {
|
|
442
|
-
|
|
443
|
-
if (result.code === 0) {
|
|
444
|
-
planContent = result.stdout;
|
|
445
|
-
}
|
|
499
|
+
planContent = await Bun.file(planMdPath).text();
|
|
446
500
|
} catch {
|
|
447
501
|
// Fall through — will use empty plan content
|
|
448
502
|
}
|
|
@@ -455,10 +509,7 @@ Execute each step in order. You MUST include [DONE:n] in your response after com
|
|
|
455
509
|
// Read the start prompt for clean handoff
|
|
456
510
|
let startPrompt = '';
|
|
457
511
|
try {
|
|
458
|
-
|
|
459
|
-
if (result.code === 0) {
|
|
460
|
-
startPrompt = result.stdout.trim();
|
|
461
|
-
}
|
|
512
|
+
startPrompt = (await Bun.file(startPromptPath).text()).trim();
|
|
462
513
|
} catch {
|
|
463
514
|
// Fall through
|
|
464
515
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads and writes `.plans/plans.json` — the tracking manifest for plan lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Schema:
|
|
5
|
+
* ```json
|
|
6
|
+
* {
|
|
7
|
+
* "<plan-name>": {
|
|
8
|
+
* "status": "in-progress" | "done",
|
|
9
|
+
* "title": "Human-readable plan title",
|
|
10
|
+
* "created": "2026-05-08T12:00:00.000Z",
|
|
11
|
+
* "completed": "2026-05-08T13:00:00.000Z" | null
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface PlanEntry {
|
|
18
|
+
status: 'in-progress' | 'done';
|
|
19
|
+
title: string;
|
|
20
|
+
created: string;
|
|
21
|
+
completed: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type PlansManifest = Record<string, PlanEntry>;
|
|
25
|
+
|
|
26
|
+
const PLANS_JSON = '.plans/plans.json';
|
|
27
|
+
|
|
28
|
+
/** Read plans.json, returning current manifest (empty object if missing). */
|
|
29
|
+
export async function readPlansJson(): Promise<PlansManifest> {
|
|
30
|
+
try {
|
|
31
|
+
const file = Bun.file(PLANS_JSON);
|
|
32
|
+
if (await file.exists()) {
|
|
33
|
+
const text = await file.text();
|
|
34
|
+
if (text.trim()) {
|
|
35
|
+
return JSON.parse(text) as PlansManifest;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// File doesn't exist or isn't valid JSON
|
|
40
|
+
}
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Serialize the manifest to a formatted JSON string. */
|
|
45
|
+
export function serializePlansJson(manifest: PlansManifest): string {
|
|
46
|
+
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Extract the plan title from the first `# ...` heading in PLAN.md content. */
|
|
50
|
+
export function extractPlanTitle(planContent: string): string {
|
|
51
|
+
const match = planContent.match(/^#\s+(.+)$/m);
|
|
52
|
+
return match ? match[1].trim() : 'Untitled plan';
|
|
53
|
+
}
|
|
@@ -43,6 +43,17 @@ const DESTRUCTIVE_PATTERNS = [
|
|
|
43
43
|
/\bsystemctl\s+(start|stop|restart|enable|disable)/i,
|
|
44
44
|
/\bservice\s+\S+\s+(start|stop|restart)/i,
|
|
45
45
|
/\b(vim?|nano|emacs|code|subl)\b/i,
|
|
46
|
+
// Windows equivalents
|
|
47
|
+
/\bdel\b/i,
|
|
48
|
+
/\brd\b/i,
|
|
49
|
+
/\bcopy\b/i,
|
|
50
|
+
/\bmove\b/i,
|
|
51
|
+
/\bren\b/i,
|
|
52
|
+
/\brename\b/i,
|
|
53
|
+
/\bicacls\b/i,
|
|
54
|
+
/\battrib\b/i,
|
|
55
|
+
/\bpowershell\b/i,
|
|
56
|
+
/\bpwsh\b/i,
|
|
46
57
|
];
|
|
47
58
|
|
|
48
59
|
// ── Safe read-only bash patterns (allowed in plan mode) ─────────────────────
|
|
@@ -98,6 +109,12 @@ const SAFE_PATTERNS = [
|
|
|
98
109
|
/^\s*fd\b/,
|
|
99
110
|
/^\s*bat\b/,
|
|
100
111
|
/^\s*eza\b/,
|
|
112
|
+
// Windows equivalents
|
|
113
|
+
/^\s*dir\b/,
|
|
114
|
+
/^\s*where\b/,
|
|
115
|
+
/^\s*set\b/,
|
|
116
|
+
/^\s*systeminfo\b/,
|
|
117
|
+
/^\s*tasklist\b/,
|
|
101
118
|
];
|
|
102
119
|
|
|
103
120
|
export function isSafeCommand(command: string): boolean {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreki-gg/pi-plan-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Two-phase planning workflow for pi — plan with claude-opus-4-6:medium, execute with gpt-5.5:low, with .plans/ file-based handoff",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -13,17 +13,21 @@
|
|
|
13
13
|
"directory": "packages/plan-mode"
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"pi-plan-mode": "./bin/clean-plans.js"
|
|
18
|
+
},
|
|
16
19
|
"files": [
|
|
17
20
|
"extensions",
|
|
21
|
+
"bin",
|
|
18
22
|
"README.md",
|
|
19
23
|
"CHANGELOG.md",
|
|
20
24
|
"package.json"
|
|
21
25
|
],
|
|
22
26
|
"scripts": {
|
|
23
27
|
"typecheck": "tsc --noEmit",
|
|
24
|
-
"lint": "oxlint extensions",
|
|
25
|
-
"format": "oxfmt --write extensions",
|
|
26
|
-
"format:check": "oxfmt --check extensions"
|
|
28
|
+
"lint": "oxlint extensions bin",
|
|
29
|
+
"format": "oxfmt --write extensions bin",
|
|
30
|
+
"format:check": "oxfmt --check extensions bin"
|
|
27
31
|
},
|
|
28
32
|
"pi": {
|
|
29
33
|
"extensions": [
|
|
@@ -32,6 +36,7 @@
|
|
|
32
36
|
},
|
|
33
37
|
"devDependencies": {
|
|
34
38
|
"@types/node": "24",
|
|
39
|
+
"bun-types": "latest",
|
|
35
40
|
"oxfmt": "^0.43.0",
|
|
36
41
|
"oxlint": "^1.58.0",
|
|
37
42
|
"typescript": "^6.0.0"
|