@tianhai/pi-workflow-kit 0.5.0 → 0.5.1

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.
@@ -0,0 +1,56 @@
1
+ # Cleanup legacy state file and enforce thinking-phase boundaries
2
+
3
+ Date: 2026-04-09
4
+
5
+ ## Problem
6
+
7
+ 1. **Legacy state file still read:** `workflow-monitor.ts` still has `getLegacyStateFilePath()` and a fallback read path for `.pi/superpowers-state.json`. The new filename (`workflow-kit-state.json`) is correct for writes, but the old one is still checked on read.
8
+
9
+ 2. **Brainstorm/plan phases not enforced:** When the agent writes code during brainstorm or plan phase, the extension only warns (strike-based escalation allows first offense). The agent can ignore the warning and proceed with implementation work.
10
+
11
+ 3. **`/workflow-next` tab completions lose the phase word:** When selecting a file artifact completion (e.g. `docs/plans/2026-04-09-foo-design.md`), pi replaces the entire argument prefix including the phase word. `/workflow-next plan des` → select file → `/workflow-next docs/plans/...` ("plan" gone).
12
+
13
+ Note: Tab key does not trigger slash command argument completions at all (pi-side behavior — falls through to file path completion when `force=true`). Argument suggestions only appear on typing. This cannot be fixed on our side.
14
+
15
+ ## Scope
16
+
17
+ Three changes:
18
+
19
+ ### 1. Remove `superpowers-state.json` legacy fallback
20
+
21
+ **`extensions/workflow-monitor.ts`:**
22
+ - Remove `getLegacyStateFilePath()` function
23
+ - In `reconstructState()`, remove the `else if (stateFilePath === undefined)` branch that reads the legacy filename
24
+
25
+ **`tests/extension/workflow-monitor/state-persistence.test.ts`:**
26
+ - Update two tests in `"file-based state persistence"` describe that write/read `superpowers-state.json` to use `workflow-kit-state.json`
27
+ - Fix test name `"getStateFilePath returns .pi/superpowers-state.json in cwd"`
28
+ - Remove the `"state file rename to .pi/workflow-kit-state.json with legacy fallback"` describe block (4 tests) — this migration behavior no longer exists
29
+
30
+ ### 2. Enforce brainstorm/plan phase boundaries
31
+
32
+ **`extensions/workflow-monitor.ts`:**
33
+ - Replace the `maybeEscalate("process", ctx)` call + `pendingProcessWarnings.set(...)` with an immediate `{ blocked: true, reason: ... }` return that includes a reminder about what the agent should be doing instead
34
+ - Remove `"process"` from `ViolationBucket` type and `strikes` record (only `"practice"` remains, still used by TDD)
35
+ - Remove `pendingProcessWarnings` map (no longer needed)
36
+
37
+ ### 3. Fix phase word lost on artifact completion selection
38
+
39
+ **`extensions/workflow-monitor/workflow-next-completions.ts`:**
40
+ - In `listArtifactsForPhase`, prepend the phase to `item.value` so pi's prefix replacement preserves it:
41
+ - `value: "plan docs/plans/2026-04-09-foo-design.md"` (replaces "plan des" → keeps "plan")
42
+ - `label: "docs/plans/2026-04-09-foo-design.md"` (display stays clean)
43
+ - Applies to all phases with artifacts: plan, execute, finalize
44
+
45
+ ### Tests
46
+
47
+ **`tests/extension/workflow-monitor/workflow-next-completions.test.ts`:**
48
+ - Update artifact completion tests to expect `value` with phase prefix (e.g. `"plan docs/plans/..."`)
49
+
50
+ ## What stays the same
51
+
52
+ - `persistState()` already writes only to `.pi/workflow-kit-state.json` — no changes
53
+ - `maybeEscalate()` stays (still used for `"practice"` / TDD violations)
54
+ - The `isThinkingPhase` check (`phase === "brainstorm" || phase === "plan"`) already covers both phases — same treatment
55
+ - `docs/plans/` writes remain allowed during brainstorm/plan phases
56
+ - Tab not triggering argument completions is a pi-side issue — not in scope
@@ -0,0 +1,196 @@
1
+ # Cleanup legacy state, enforce thinking phases, fix autocomplete
2
+
3
+ > **REQUIRED SUB-SKILL:** Use the executing-tasks skill to implement this plan task-by-task.
4
+
5
+ **Goal:** Remove legacy `superpowers-state.json` fallback, block non-plans writes during brainstorm/plan phases, and fix artifact completions losing the phase word.
6
+
7
+ **Architecture:** Three independent changes in `workflow-monitor.ts` and `workflow-next-completions.ts` with corresponding test updates. Each change is self-contained.
8
+
9
+ **Tech Stack:** TypeScript, Node.js, vitest, pi extension API
10
+
11
+ ---
12
+
13
+ ### Task 1: Remove `superpowers-state.json` legacy fallback
14
+
15
+ **Type:** code
16
+ **TDD scenario:** Modifying tested code — run existing tests first
17
+
18
+ **Files:**
19
+ - Modify: `extensions/workflow-monitor.ts:67-100`
20
+ - Modify: `tests/extension/workflow-monitor/state-persistence.test.ts:274-500`
21
+
22
+ **Step 1: Run existing state persistence tests**
23
+
24
+ Run: `npx vitest run tests/extension/workflow-monitor/state-persistence.test.ts`
25
+ Expected: All tests pass
26
+
27
+ **Step 2: Remove `getLegacyStateFilePath` and legacy fallback in source**
28
+
29
+ In `extensions/workflow-monitor.ts`:
30
+
31
+ 1. Delete the `getLegacyStateFilePath` function (3 lines).
32
+ 2. In `reconstructState`, simplify the file-read block — remove the `else if (stateFilePath === undefined)` branch that tries the legacy filename. The remaining code becomes:
33
+
34
+ ```typescript
35
+ if (stateFilePath !== false) {
36
+ try {
37
+ const statePath = stateFilePath ?? getStateFilePath();
38
+ if (fs.existsSync(statePath)) {
39
+ const raw = fs.readFileSync(statePath, "utf-8");
40
+ fileData = JSON.parse(raw);
41
+ }
42
+ } catch (err) {
43
+ log.warn(
44
+ `Failed to read state file, falling back to session entries: ${err instanceof Error ? err.message : err}`,
45
+ );
46
+ }
47
+ }
48
+ ```
49
+
50
+ **Step 3: Update tests**
51
+
52
+ In `tests/extension/workflow-monitor/state-persistence.test.ts`:
53
+
54
+ 1. Fix test name on line 274: `"getStateFilePath returns .pi/superpowers-state.json in cwd"` → `"getStateFilePath returns .pi/workflow-kit-state.json in cwd"`
55
+ 2. On line 318: change `"superpowers-state.json"` → `"workflow-kit-state.json"`
56
+ 3. On line 337: change `"superpowers-state.json"` → `"workflow-kit-state.json"`
57
+ 4. Delete the entire `describe("state file rename to .pi/workflow-kit-state.json with legacy fallback", () => { ... })` block (4 tests, from line ~404 to ~530)
58
+
59
+ **Step 4: Run tests**
60
+
61
+ Run: `npx vitest run tests/extension/workflow-monitor/state-persistence.test.ts`
62
+ Expected: All tests pass
63
+
64
+ **Step 5: Run full test suite**
65
+
66
+ Run: `npx vitest run`
67
+ Expected: All tests pass
68
+
69
+ **Step 6: Commit**
70
+
71
+ ```bash
72
+ git add extensions/workflow-monitor.ts tests/extension/workflow-monitor/state-persistence.test.ts
73
+ git commit -m "refactor: remove superpowers-state.json legacy fallback"
74
+ ```
75
+
76
+ ---
77
+
78
+ ### Task 2: Enforce brainstorm/plan phase boundaries (block immediately)
79
+
80
+ **Type:** code
81
+ **TDD scenario:** Modifying tested code — update existing tests first
82
+
83
+ **Files:**
84
+ - Modify: `extensions/workflow-monitor.ts:135-520`
85
+ - Modify: `tests/extension/workflow-monitor/phase-aware-write-enforcement.test.ts`
86
+
87
+ **Step 1: Run existing enforcement tests**
88
+
89
+ Run: `npx vitest run tests/extension/workflow-monitor/phase-aware-write-enforcement.test.ts`
90
+ Expected: All tests pass
91
+
92
+ **Step 2: Update source — block immediately instead of escalating**
93
+
94
+ In `extensions/workflow-monitor.ts`:
95
+
96
+ 1. Remove `pendingProcessWarnings` map declaration (line ~135).
97
+ 2. Change `ViolationBucket` type from `"process" | "practice"` to `"practice"`.
98
+ 3. Change `strikes` from `{ process: 0, practice: 0 }` to `{ practice: 0 }`.
99
+ 4. Change `sessionAllowed` type to `Partial<Record<"practice", boolean>>`.
100
+ 5. In `maybeEscalate`, change parameter type from `ViolationBucket` to `"practice"`.
101
+ 6. In `tool_result` handler, remove the `pendingProcessWarnings.get(toolCallId)` / `.delete(toolCallId)` block (3 lines).
102
+ 7. In `tool_call` handler (~line 506-516), replace the `maybeEscalate("process", ctx)` + `pendingProcessWarnings.set(...)` block with:
103
+
104
+ ```typescript
105
+ return {
106
+ blocked: true,
107
+ reason:
108
+ `⚠️ PROCESS VIOLATION: Wrote ${filePath} during ${phase} phase.\n` +
109
+ "During brainstorming/planning you may only write to docs/plans/. " +
110
+ "Read code and docs to understand the problem, then discuss the design before implementing.",
111
+ };
112
+ ```
113
+
114
+ **Step 3: Update tests**
115
+
116
+ In `tests/extension/workflow-monitor/phase-aware-write-enforcement.test.ts`:
117
+
118
+ 1. The first test (`"warns when writing outside docs/plans during brainstorm"`) currently checks for an injected warning in the tool result. After the change, the write is blocked in `on_tool_call` before it executes, so `on_tool_result` is never reached. Replace the test to check that `onToolCall` returns `{ blocked: true }` with a `reason` containing `"PROCESS VIOLATION"` and `"brainstorm"`. Remove the `onToolResult` call and its assertion.
119
+
120
+ 2. Add a new test: `"blocks immediately on first violation during plan phase"` — same pattern as brainstorm but with `currentPhase: "plan"`, verify `{ blocked: true, reason: expect.stringContaining("PROCESS VIOLATION") }`.
121
+
122
+ 3. The test `"second process violation hard-blocks (interactive)"` is no longer relevant — blocking is immediate now. Delete it.
123
+
124
+ **Step 4: Run enforcement tests**
125
+
126
+ Run: `npx vitest run tests/extension/workflow-monitor/phase-aware-write-enforcement.test.ts`
127
+ Expected: All tests pass
128
+
129
+ **Step 5: Run full test suite**
130
+
131
+ Run: `npx vitest run`
132
+ Expected: All tests pass
133
+
134
+ **Step 6: Commit**
135
+
136
+ ```bash
137
+ git add extensions/workflow-monitor.ts tests/extension/workflow-monitor/phase-aware-write-enforcement.test.ts
138
+ git commit -m "feat: block writes outside docs/plans immediately during brainstorm/plan phases"
139
+ ```
140
+
141
+ ---
142
+
143
+ ### Task 3: Fix artifact completions losing the phase word
144
+
145
+ **Type:** code
146
+ **TDD scenario:** Modifying tested code — update existing tests first
147
+
148
+ **Files:**
149
+ - Modify: `extensions/workflow-monitor/workflow-next-completions.ts:50-60`
150
+ - Modify: `tests/extension/workflow-monitor/workflow-next-command.test.ts`
151
+
152
+ **Step 1: Run existing completion tests**
153
+
154
+ Run: `npx vitest run tests/extension/workflow-monitor/workflow-next-command.test.ts`
155
+ Expected: All tests pass
156
+
157
+ **Step 2: Update source — prepend phase to artifact values**
158
+
159
+ In `extensions/workflow-monitor/workflow-next-completions.ts`, in `listArtifactsForPhase`, the function needs the `phase` parameter included in `item.value`. Change the final `.map()`:
160
+
161
+ ```typescript
162
+ // Before:
163
+ .map((relPath) => ({ value: relPath, label: relPath }));
164
+
165
+ // After:
166
+ .map((relPath) => ({ value: `${phase} ${relPath}`, label: relPath }));
167
+ ```
168
+
169
+ This ensures pi's `applyCompletion` replaces the full prefix (e.g. `"plan des"`) with `"plan docs/plans/..."` — preserving the phase word.
170
+
171
+ **Step 3: Update tests**
172
+
173
+ In `tests/extension/workflow-monitor/workflow-next-command.test.ts`, update these tests to expect `value` with the phase prefix:
174
+
175
+ 1. `"suggests only design artifacts for plan phase"` — change `value: "docs/plans/..."` to `value: "plan docs/plans/..."`, keep `label` unchanged.
176
+
177
+ 2. `"filters plan artifact suggestions by typed prefix"` — same value change.
178
+
179
+ 3. `"suggests only implementation artifacts for execute and finalize"` — change execute value to `"execute docs/plans/..."` and finalize value to `"finalize docs/plans/..."`.
180
+
181
+ **Step 4: Run completion tests**
182
+
183
+ Run: `npx vitest run tests/extension/workflow-monitor/workflow-next-command.test.ts`
184
+ Expected: All tests pass
185
+
186
+ **Step 5: Run full test suite**
187
+
188
+ Run: `npx vitest run`
189
+ Expected: All tests pass
190
+
191
+ **Step 6: Commit**
192
+
193
+ ```bash
194
+ git add extensions/workflow-monitor/workflow-next-completions.ts tests/extension/workflow-monitor/workflow-next-command.test.ts
195
+ git commit -m "fix: preserve phase word in /workflow-next artifact completions"
196
+ ```
@@ -7,3 +7,9 @@
7
7
  * This tool id intentionally remains unchanged across the rebrand.
8
8
  */
9
9
  export const PLAN_TRACKER_TOOL_NAME = "plan_tracker";
10
+
11
+ /**
12
+ * Custom entry type written by workflow-monitor's /workflow-reset so that
13
+ * plan-tracker's reconstructState picks up an empty task list.
14
+ */
15
+ export const PLAN_TRACKER_CLEARED_TYPE = "plan_tracker_cleared";
@@ -10,7 +10,7 @@ import { StringEnum } from "@mariozechner/pi-ai";
10
10
  import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
11
11
  import { Text } from "@mariozechner/pi-tui";
12
12
  import { type Static, Type } from "@sinclair/typebox";
13
- import { PLAN_TRACKER_TOOL_NAME } from "./constants.js";
13
+ import { PLAN_TRACKER_CLEARED_TYPE, PLAN_TRACKER_TOOL_NAME } from "./constants.js";
14
14
 
15
15
  export type TaskStatus = "pending" | "in_progress" | "complete" | "blocked";
16
16
  export type TaskPhase =
@@ -208,6 +208,12 @@ export default function (pi: ExtensionAPI) {
208
208
  const entries = ctx.sessionManager.getBranch();
209
209
  for (let i = entries.length - 1; i >= 0; i--) {
210
210
  const entry = entries[i];
211
+ // Check for explicit clear signal (written by /workflow-reset)
212
+ // biome-ignore lint/suspicious/noExplicitAny: pi SDK session entry type
213
+ if (entry.type === "custom" && (entry as any).customType === PLAN_TRACKER_CLEARED_TYPE) {
214
+ tasks = [];
215
+ break;
216
+ }
211
217
  if (entry.type !== "message") continue;
212
218
  const msg = entry.message;
213
219
  if (msg.role !== "toolResult" || msg.toolName !== PLAN_TRACKER_TOOL_NAME) continue;
@@ -42,7 +42,7 @@ function listArtifactsForPhase(phase: WorkflowNextPhase, typedPrefix: string): A
42
42
  .filter((name) => name.endsWith(suffix))
43
43
  .map((name) => path.join("docs", "plans", name))
44
44
  .filter((relPath) => relPath.startsWith(typedPrefix))
45
- .map((relPath) => ({ value: relPath, label: relPath }));
45
+ .map((relPath) => ({ value: `${phase} ${relPath}`, label: relPath }));
46
46
 
47
47
  return items.length > 0 ? items : null;
48
48
  } catch {
@@ -194,15 +194,37 @@ export class WorkflowTracker {
194
194
  if (!PLANS_DIR_RE.test(path)) return false;
195
195
 
196
196
  if (DESIGN_RE.test(path)) {
197
- const changedArtifact = this.recordArtifact("brainstorm", path);
198
- const changedPhase = this.advanceTo("brainstorm");
199
- return changedArtifact || changedPhase;
197
+ // Only advance if we haven't already passed the brainstorm phase.
198
+ // Writing a design doc during plan/execute/finalize (e.g., updating
199
+ // the plan) must NOT reset workflow state.
200
+ const curIdx = this.state.currentPhase ? WORKFLOW_PHASES.indexOf(this.state.currentPhase) : -1;
201
+ if (curIdx > WORKFLOW_PHASES.indexOf("brainstorm")) {
202
+ return this.recordArtifact("brainstorm", path);
203
+ }
204
+ let changed = false;
205
+ changed = this.recordArtifact("brainstorm", path) || changed;
206
+ // Activating and immediately completing: the design doc is the
207
+ // deliverable that signals brainstorm is done. Do NOT mark prompted
208
+ // so the agent_end boundary prompt still fires to offer session handoff.
209
+ changed = this.advanceTo("brainstorm") || changed;
210
+ changed = this.completeCurrent() || changed;
211
+ return changed;
200
212
  }
201
213
 
202
214
  if (IMPLEMENTATION_RE.test(path)) {
203
- const changedArtifact = this.recordArtifact("plan", path);
204
- const changedPhase = this.advanceTo("plan");
205
- return changedArtifact || changedPhase;
215
+ // Only advance if we haven't already passed the plan phase.
216
+ const curIdx = this.state.currentPhase ? WORKFLOW_PHASES.indexOf(this.state.currentPhase) : -1;
217
+ if (curIdx > WORKFLOW_PHASES.indexOf("plan")) {
218
+ return this.recordArtifact("plan", path);
219
+ }
220
+ let changed = false;
221
+ changed = this.recordArtifact("plan", path) || changed;
222
+ // Activating and immediately completing: the implementation plan
223
+ // is the deliverable that signals plan phase is done. Do NOT mark
224
+ // prompted so the agent_end boundary prompt still fires.
225
+ changed = this.advanceTo("plan") || changed;
226
+ changed = this.completeCurrent() || changed;
227
+ return changed;
206
228
  }
207
229
 
208
230
  return false;
@@ -8,14 +8,12 @@
8
8
  * - Register workflow_reference tool for on-demand reference content
9
9
  */
10
10
 
11
- import * as fs from "node:fs";
12
11
  import * as path from "node:path";
13
12
  import { StringEnum } from "@mariozechner/pi-ai";
14
13
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
15
14
  import { Text } from "@mariozechner/pi-tui";
16
15
  import { Type } from "@sinclair/typebox";
17
- import { PLAN_TRACKER_TOOL_NAME } from "./constants.js";
18
- import { log } from "./lib/logging.js";
16
+ import { PLAN_TRACKER_CLEARED_TYPE, PLAN_TRACKER_TOOL_NAME } from "./constants.js";
19
17
  import type { PlanTrackerDetails } from "./plan-tracker.js";
20
18
  import { getCurrentGitRef } from "./workflow-monitor/git";
21
19
  import { loadReference, REFERENCE_TOPICS } from "./workflow-monitor/reference-tool";
@@ -64,75 +62,32 @@ async function selectValue<T extends string>(
64
62
 
65
63
  const SUPERPOWERS_STATE_ENTRY_TYPE = "superpowers_state";
66
64
 
67
- function getLegacyStateFilePath(): string {
68
- return path.join(process.cwd(), ".pi", "superpowers-state.json");
69
- }
70
-
71
- export function getStateFilePath(): string {
72
- return path.join(process.cwd(), ".pi", "workflow-kit-state.json");
73
- }
74
-
75
- export function reconstructState(ctx: ExtensionContext, handler: WorkflowHandler, stateFilePath?: string | false) {
65
+ export function reconstructState(ctx: ExtensionContext, handler: WorkflowHandler) {
76
66
  handler.resetState();
77
67
 
78
- // Read both file-based and session-based state, then pick the newer one.
79
- let fileData: (Record<string, unknown> & { savedAt?: number }) | null = null;
80
- let sessionData: (Record<string, unknown> & { savedAt?: number }) | null = null;
81
-
82
- if (stateFilePath !== false) {
83
- try {
84
- const newPath = stateFilePath ?? getStateFilePath();
85
- if (fs.existsSync(newPath)) {
86
- const raw = fs.readFileSync(newPath, "utf-8");
87
- fileData = JSON.parse(raw);
88
- } else if (stateFilePath === undefined) {
89
- // Legacy fallback: try the old filename only when no explicit path is given.
90
- const legacyPath = getLegacyStateFilePath();
91
- if (fs.existsSync(legacyPath)) {
92
- const raw = fs.readFileSync(legacyPath, "utf-8");
93
- fileData = JSON.parse(raw);
94
- }
95
- }
96
- } catch (err) {
97
- log.warn(
98
- `Failed to read state file, falling back to session entries: ${err instanceof Error ? err.message : err}`,
99
- );
100
- }
101
- }
102
-
103
- // Scan session branch for most recent superpowers state entry
68
+ // Scan session branch for most recent superpowers state entry.
69
+ // The session branch IS the single source of truth no file-based
70
+ // persistence needed since pi's journal survives restarts and reloads.
104
71
  const entries = ctx.sessionManager.getBranch();
105
72
  for (let i = entries.length - 1; i >= 0; i--) {
106
73
  const entry = entries[i];
107
74
  // biome-ignore lint/suspicious/noExplicitAny: pi SDK session entry type
108
75
  if (entry.type === "custom" && (entry as any).customType === SUPERPOWERS_STATE_ENTRY_TYPE) {
109
76
  // biome-ignore lint/suspicious/noExplicitAny: pi SDK session entry type
110
- sessionData = (entry as any).data;
111
- break;
77
+ handler.setFullState((entry as any).data);
78
+ return;
112
79
  }
113
80
  // Migration fallback: old-format workflow-only entries
114
81
  // biome-ignore lint/suspicious/noExplicitAny: pi SDK session entry type
115
82
  if (entry.type === "custom" && (entry as any).customType === WORKFLOW_TRACKER_ENTRY_TYPE) {
116
83
  // biome-ignore lint/suspicious/noExplicitAny: pi SDK session entry type
117
- sessionData = { workflow: (entry as any).data };
118
- break;
84
+ handler.setFullState({ workflow: (entry as any).data });
85
+ return;
119
86
  }
120
87
  }
121
88
 
122
- // Pick the newer source when both are available; otherwise use whichever exists.
123
- if (fileData && sessionData) {
124
- const fileSavedAt = fileData.savedAt ?? 0;
125
- const sessionSavedAt = sessionData.savedAt ?? 0;
126
- const winner = fileSavedAt >= sessionSavedAt ? fileData : sessionData;
127
- handler.setFullState(winner);
128
- } else if (fileData) {
129
- handler.setFullState(fileData);
130
- } else if (sessionData) {
131
- handler.setFullState(sessionData);
132
- } else {
133
- // No entries found — reset to fresh defaults
134
- handler.setFullState({});
135
- }
89
+ // No entries found reset to fresh defaults
90
+ handler.setFullState({});
136
91
  }
137
92
 
138
93
  export default function (pi: ExtensionAPI) {
@@ -143,10 +98,9 @@ export default function (pi: ExtensionAPI) {
143
98
  const pendingViolations = new Map<string, Violation>();
144
99
  const pendingVerificationViolations = new Map<string, VerificationViolation>();
145
100
  const pendingBranchGates = new Map<string, string>();
146
- const pendingProcessWarnings = new Map<string, string>();
147
101
 
148
- type ViolationBucket = "process" | "practice";
149
- const strikes: Record<ViolationBucket, number> = { process: 0, practice: 0 };
102
+ type ViolationBucket = "practice";
103
+ const strikes: Record<ViolationBucket, number> = { practice: 0 };
150
104
  const sessionAllowed: Partial<Record<ViolationBucket, boolean>> = {};
151
105
 
152
106
  async function maybeEscalate(bucket: ViolationBucket, ctx: ExtensionContext): Promise<"allow" | "block"> {
@@ -180,14 +134,6 @@ export default function (pi: ExtensionAPI) {
180
134
  const persistState = () => {
181
135
  const stateWithTimestamp = { ...handler.getFullState(), savedAt: Date.now() };
182
136
  pi.appendEntry(SUPERPOWERS_STATE_ENTRY_TYPE, stateWithTimestamp);
183
- // Also persist to file for cross-session survival
184
- try {
185
- const statePath = getStateFilePath();
186
- fs.mkdirSync(path.dirname(statePath), { recursive: true });
187
- fs.writeFileSync(statePath, JSON.stringify(stateWithTimestamp, null, 2));
188
- } catch (err) {
189
- log.warn(`Failed to persist state file: ${err instanceof Error ? err.message : err}`);
190
- }
191
137
  };
192
138
 
193
139
  const phaseToSkill: Record<string, string> = {
@@ -230,10 +176,7 @@ export default function (pi: ExtensionAPI) {
230
176
  pendingViolations.clear();
231
177
  pendingVerificationViolations.clear();
232
178
  pendingBranchGates.clear();
233
- pendingProcessWarnings.clear();
234
- strikes.process = 0;
235
179
  strikes.practice = 0;
236
- delete sessionAllowed.process;
237
180
  delete sessionAllowed.practice;
238
181
  branchNoticeShown = false;
239
182
  branchConfirmed = false;
@@ -514,16 +457,13 @@ export default function (pi: ExtensionAPI) {
514
457
  const isPlansWrite = resolved.startsWith(plansRoot);
515
458
 
516
459
  if (isThinkingPhase && !isPlansWrite) {
517
- const escalation = await maybeEscalate("process", ctx);
518
- if (escalation === "block") {
519
- return { blocked: true };
520
- }
521
-
522
- pendingProcessWarnings.set(
523
- toolCallId,
524
- `⚠️ PROCESS VIOLATION: Wrote ${filePath} during ${phase} phase.\n` +
525
- "During brainstorming/planning you may only write to docs/plans/. Stop and return to docs/plans/ or advance workflow phases intentionally.",
526
- );
460
+ return {
461
+ blocked: true,
462
+ reason:
463
+ `⚠️ PROCESS VIOLATION: Wrote ${filePath} during ${phase} phase.\n` +
464
+ "During brainstorming/planning you may only write to docs/plans/. " +
465
+ "Read code and docs to understand the problem, then discuss the design before implementing.",
466
+ };
527
467
  }
528
468
 
529
469
  changed = handler.handleFileWritten(filePath) || changed;
@@ -615,12 +555,6 @@ export default function (pi: ExtensionAPI) {
615
555
  }
616
556
  }
617
557
  pendingViolations.delete(toolCallId);
618
-
619
- const processWarning = pendingProcessWarnings.get(toolCallId);
620
- if (processWarning) {
621
- injected.push(processWarning);
622
- }
623
- pendingProcessWarnings.delete(toolCallId);
624
558
  }
625
559
 
626
560
  // Handle bash results (test runs, commits, investigation)
@@ -822,6 +756,8 @@ export default function (pi: ExtensionAPI) {
822
756
  description: "Reset workflow tracker to fresh state for a new task",
823
757
  async handler(_args, ctx) {
824
758
  handler.resetState();
759
+ // Emit a clear signal so plan-tracker also reconstructs to empty.
760
+ pi.appendEntry(PLAN_TRACKER_CLEARED_TYPE, { clearedAt: Date.now() });
825
761
  persistState();
826
762
  updateWidget(ctx);
827
763
  if (ctx.hasUI) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tianhai/pi-workflow-kit",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Workflow skills and enforcement extensions for pi",
5
5
  "keywords": [
6
6
  "pi-package"