@tianhai/pi-workflow-kit 0.5.1 → 0.6.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/README.md +44 -494
- package/docs/developer-usage-guide.md +41 -401
- package/docs/oversight-model.md +13 -34
- package/docs/workflow-phases.md +32 -46
- package/extensions/workflow-guard.ts +67 -0
- package/package.json +3 -7
- package/skills/brainstorming/SKILL.md +16 -59
- package/skills/executing-tasks/SKILL.md +26 -227
- package/skills/finalizing/SKILL.md +33 -0
- package/skills/writing-plans/SKILL.md +23 -132
- package/ROADMAP.md +0 -16
- package/agents/code-reviewer.md +0 -18
- package/agents/config.ts +0 -5
- package/agents/implementer.md +0 -26
- package/agents/spec-reviewer.md +0 -13
- package/agents/worker.md +0 -17
- package/docs/plans/completed/2026-04-09-cleanup-legacy-state-and-enforce-think-phases-design.md +0 -56
- package/docs/plans/completed/2026-04-09-cleanup-legacy-state-and-enforce-think-phases-implementation.md +0 -196
- package/docs/plans/completed/2026-04-09-workflow-next-autocomplete-design.md +0 -185
- package/docs/plans/completed/2026-04-09-workflow-next-autocomplete-implementation.md +0 -334
- package/docs/plans/completed/2026-04-09-workflow-next-handoff-state-design.md +0 -251
- package/docs/plans/completed/2026-04-09-workflow-next-handoff-state-implementation.md +0 -253
- package/extensions/constants.ts +0 -15
- package/extensions/lib/logging.ts +0 -138
- package/extensions/plan-tracker.ts +0 -502
- package/extensions/subagent/agents.ts +0 -144
- package/extensions/subagent/concurrency.ts +0 -52
- package/extensions/subagent/env.ts +0 -47
- package/extensions/subagent/index.ts +0 -1181
- package/extensions/subagent/lifecycle.ts +0 -25
- package/extensions/subagent/timeout.ts +0 -13
- package/extensions/workflow-monitor/debug-monitor.ts +0 -98
- package/extensions/workflow-monitor/git.ts +0 -31
- package/extensions/workflow-monitor/heuristics.ts +0 -58
- package/extensions/workflow-monitor/investigation.ts +0 -52
- package/extensions/workflow-monitor/reference-tool.ts +0 -42
- package/extensions/workflow-monitor/skip-confirmation.ts +0 -19
- package/extensions/workflow-monitor/tdd-monitor.ts +0 -137
- package/extensions/workflow-monitor/test-runner.ts +0 -37
- package/extensions/workflow-monitor/verification-monitor.ts +0 -61
- package/extensions/workflow-monitor/warnings.ts +0 -81
- package/extensions/workflow-monitor/workflow-handler.ts +0 -358
- package/extensions/workflow-monitor/workflow-next-completions.ts +0 -68
- package/extensions/workflow-monitor/workflow-next-state.ts +0 -112
- package/extensions/workflow-monitor/workflow-tracker.ts +0 -253
- package/extensions/workflow-monitor/workflow-transitions.ts +0 -55
- package/extensions/workflow-monitor.ts +0 -872
- package/skills/dispatching-parallel-agents/SKILL.md +0 -194
- package/skills/receiving-code-review/SKILL.md +0 -196
- package/skills/systematic-debugging/SKILL.md +0 -170
- package/skills/systematic-debugging/condition-based-waiting-example.ts +0 -158
- package/skills/systematic-debugging/condition-based-waiting.md +0 -115
- package/skills/systematic-debugging/defense-in-depth.md +0 -122
- package/skills/systematic-debugging/find-polluter.sh +0 -63
- package/skills/systematic-debugging/reference/rationalizations.md +0 -61
- package/skills/systematic-debugging/root-cause-tracing.md +0 -169
- package/skills/test-driven-development/SKILL.md +0 -266
- package/skills/test-driven-development/reference/examples.md +0 -101
- package/skills/test-driven-development/reference/rationalizations.md +0 -67
- package/skills/test-driven-development/reference/when-stuck.md +0 -33
- package/skills/test-driven-development/testing-anti-patterns.md +0 -299
- package/skills/using-git-worktrees/SKILL.md +0 -231
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import type { SessionEntry } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
export const WORKFLOW_PHASES = ["brainstorm", "plan", "execute", "finalize"] as const;
|
|
4
|
-
|
|
5
|
-
export type Phase = (typeof WORKFLOW_PHASES)[number];
|
|
6
|
-
export type PhaseStatus = "pending" | "active" | "complete" | "skipped";
|
|
7
|
-
|
|
8
|
-
export interface WorkflowTrackerState {
|
|
9
|
-
phases: Record<Phase, PhaseStatus>;
|
|
10
|
-
currentPhase: Phase | null;
|
|
11
|
-
artifacts: Record<Phase, string | null>;
|
|
12
|
-
prompted: Record<Phase, boolean>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type TransitionBoundary = "design_committed" | "plan_ready" | "execution_complete";
|
|
16
|
-
|
|
17
|
-
export function computeBoundaryToPrompt(state: WorkflowTrackerState): TransitionBoundary | null {
|
|
18
|
-
if (state.phases.brainstorm === "complete" && !state.prompted.brainstorm) {
|
|
19
|
-
return "design_committed";
|
|
20
|
-
}
|
|
21
|
-
if (state.phases.plan === "complete" && !state.prompted.plan) {
|
|
22
|
-
return "plan_ready";
|
|
23
|
-
}
|
|
24
|
-
if (state.phases.execute === "complete" && !state.prompted.execute) {
|
|
25
|
-
return "execution_complete";
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function cloneState(state: WorkflowTrackerState): WorkflowTrackerState {
|
|
31
|
-
return JSON.parse(JSON.stringify(state)) as WorkflowTrackerState;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function emptyState(): WorkflowTrackerState {
|
|
35
|
-
const phases = Object.fromEntries(WORKFLOW_PHASES.map((p) => [p, "pending"])) as Record<Phase, PhaseStatus>;
|
|
36
|
-
|
|
37
|
-
const artifacts = Object.fromEntries(WORKFLOW_PHASES.map((p) => [p, null])) as Record<Phase, string | null>;
|
|
38
|
-
|
|
39
|
-
const prompted = Object.fromEntries(WORKFLOW_PHASES.map((p) => [p, false])) as Record<Phase, boolean>;
|
|
40
|
-
|
|
41
|
-
return { phases, currentPhase: null, artifacts, prompted };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const WORKFLOW_TRACKER_ENTRY_TYPE = "workflow_tracker_state";
|
|
45
|
-
|
|
46
|
-
export function parseSkillName(line: string): string | null {
|
|
47
|
-
const slashMatch = line.match(/^\s*\/skill:([^\s]+)/);
|
|
48
|
-
const xmlMatch = line.match(/<skill\s+name="([^"]+)"/);
|
|
49
|
-
return slashMatch?.[1] ?? xmlMatch?.[1] ?? null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const SKILL_TO_PHASE: Record<string, Phase> = {
|
|
53
|
-
brainstorming: "brainstorm",
|
|
54
|
-
"writing-plans": "plan",
|
|
55
|
-
"using-git-worktrees": "plan", // pre-execute worktree setup belongs to plan
|
|
56
|
-
"executing-tasks": "execute",
|
|
57
|
-
"systematic-debugging": "execute", // used within execute phase
|
|
58
|
-
"dispatching-parallel-agents": "execute", // used within execute phase
|
|
59
|
-
"test-driven-development": "execute", // makes TDD skill phase-aware
|
|
60
|
-
"receiving-code-review": "finalize", // post-PR external review
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export function resolveSkillPhase(skill: string, state: WorkflowTrackerState | null | undefined): Phase | null {
|
|
64
|
-
if (skill === "executing-tasks") {
|
|
65
|
-
if (state?.currentPhase === "finalize" || state?.phases.execute === "complete") {
|
|
66
|
-
return "finalize";
|
|
67
|
-
}
|
|
68
|
-
return "execute";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return SKILL_TO_PHASE[skill] ?? null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const PLANS_DIR_RE = /^docs\/plans\//;
|
|
75
|
-
const DESIGN_RE = /-design\.md$/;
|
|
76
|
-
const IMPLEMENTATION_RE = /-implementation\.md$/;
|
|
77
|
-
|
|
78
|
-
export class WorkflowTracker {
|
|
79
|
-
private state: WorkflowTrackerState = emptyState();
|
|
80
|
-
|
|
81
|
-
getState(): WorkflowTrackerState {
|
|
82
|
-
return cloneState(this.state);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
setState(state: WorkflowTrackerState): void {
|
|
86
|
-
this.state = cloneState(state);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
reset(): void {
|
|
90
|
-
this.state = emptyState();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
advanceTo(phase: Phase): boolean {
|
|
94
|
-
const current = this.state.currentPhase;
|
|
95
|
-
const nextIdx = WORKFLOW_PHASES.indexOf(phase);
|
|
96
|
-
|
|
97
|
-
if (current) {
|
|
98
|
-
const curIdx = WORKFLOW_PHASES.indexOf(current);
|
|
99
|
-
if (nextIdx <= curIdx) {
|
|
100
|
-
// Backward or same-phase navigation = new task. Reset everything.
|
|
101
|
-
this.reset();
|
|
102
|
-
// Fall through to activate the target phase below.
|
|
103
|
-
} else {
|
|
104
|
-
// Forward advance: auto-complete the current phase.
|
|
105
|
-
if (this.state.phases[current] === "active") {
|
|
106
|
-
this.state.phases[current] = "complete";
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
for (const p of WORKFLOW_PHASES) {
|
|
112
|
-
if (p !== phase && this.state.phases[p] === "active") {
|
|
113
|
-
this.state.phases[p] = "complete";
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
this.state.currentPhase = phase;
|
|
118
|
-
if (this.state.phases[phase] === "pending") {
|
|
119
|
-
this.state.phases[phase] = "active";
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
skipPhase(phase: Phase): boolean {
|
|
126
|
-
const status = this.state.phases[phase];
|
|
127
|
-
if (status !== "pending" && status !== "active") return false;
|
|
128
|
-
this.state.phases[phase] = "skipped";
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
skipPhases(phases: Phase[]): boolean {
|
|
133
|
-
let changed = false;
|
|
134
|
-
for (const p of phases) changed = this.skipPhase(p) || changed;
|
|
135
|
-
return changed;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
completeCurrent(): boolean {
|
|
139
|
-
const phase = this.state.currentPhase;
|
|
140
|
-
if (!phase) return false;
|
|
141
|
-
if (this.state.phases[phase] === "complete") return false;
|
|
142
|
-
this.state.phases[phase] = "complete";
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
recordArtifact(phase: Phase, path: string): boolean {
|
|
147
|
-
if (this.state.artifacts[phase] === path) return false;
|
|
148
|
-
this.state.artifacts[phase] = path;
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
markPrompted(phase: Phase): boolean {
|
|
153
|
-
if (this.state.prompted[phase]) return false;
|
|
154
|
-
this.state.prompted[phase] = true;
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
onInputText(text: string): boolean {
|
|
159
|
-
const lines = text.split(/\r?\n/);
|
|
160
|
-
let changed = false;
|
|
161
|
-
|
|
162
|
-
for (const line of lines) {
|
|
163
|
-
const skill = parseSkillName(line);
|
|
164
|
-
if (!skill) continue;
|
|
165
|
-
const phase = resolveSkillPhase(skill, this.state);
|
|
166
|
-
if (!phase) continue;
|
|
167
|
-
// Guard against backward navigation: skills shared across phases (e.g. executing-tasks
|
|
168
|
-
// covers both execute and finalize) must not reset state when re-invoked in a later phase.
|
|
169
|
-
const currentIdx = this.state.currentPhase ? WORKFLOW_PHASES.indexOf(this.state.currentPhase) : -1;
|
|
170
|
-
const targetIdx = WORKFLOW_PHASES.indexOf(phase);
|
|
171
|
-
if (targetIdx <= currentIdx) continue;
|
|
172
|
-
if (this.advanceTo(phase)) changed = true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return changed;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
onSkillFileRead(path: string): boolean {
|
|
179
|
-
const match = path.match(/\/skills\/([^/]+)\/SKILL\.md$/);
|
|
180
|
-
if (!match) return false;
|
|
181
|
-
const phase = resolveSkillPhase(match[1], this.state);
|
|
182
|
-
if (!phase) return false;
|
|
183
|
-
// Guard against backward navigation: some skills (e.g. executing-tasks) serve
|
|
184
|
-
// multiple phases. Re-reading their SKILL.md during a later phase (e.g. finalize)
|
|
185
|
-
// must not reset workflow state. Rely on plan_tracker init or explicit /workflow-reset
|
|
186
|
-
// to restart from scratch.
|
|
187
|
-
const currentIdx = this.state.currentPhase ? WORKFLOW_PHASES.indexOf(this.state.currentPhase) : -1;
|
|
188
|
-
const targetIdx = WORKFLOW_PHASES.indexOf(phase);
|
|
189
|
-
if (targetIdx <= currentIdx) return false;
|
|
190
|
-
return this.advanceTo(phase);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
onFileWritten(path: string): boolean {
|
|
194
|
-
if (!PLANS_DIR_RE.test(path)) return false;
|
|
195
|
-
|
|
196
|
-
if (DESIGN_RE.test(path)) {
|
|
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;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (IMPLEMENTATION_RE.test(path)) {
|
|
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;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
onPlanTrackerInit(): boolean {
|
|
234
|
-
return this.advanceTo("execute");
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
static reconstructFromBranch(branch: SessionEntry[]): WorkflowTrackerState | null {
|
|
238
|
-
let last: WorkflowTrackerState | null = null;
|
|
239
|
-
|
|
240
|
-
for (const entry of branch) {
|
|
241
|
-
if (entry.type !== "custom") continue;
|
|
242
|
-
// biome-ignore lint/suspicious/noExplicitAny: pi SDK session entry type
|
|
243
|
-
if ((entry as any).customType !== WORKFLOW_TRACKER_ENTRY_TYPE) continue;
|
|
244
|
-
// biome-ignore lint/suspicious/noExplicitAny: pi SDK session entry type
|
|
245
|
-
const data = (entry as any).data as WorkflowTrackerState | undefined;
|
|
246
|
-
if (data && typeof data === "object") {
|
|
247
|
-
last = cloneState(data);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return last;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { Phase, TransitionBoundary } from "./workflow-tracker";
|
|
2
|
-
|
|
3
|
-
export type TransitionChoice = "next" | "fresh" | "skip" | "discuss";
|
|
4
|
-
|
|
5
|
-
export interface TransitionPrompt {
|
|
6
|
-
boundary: TransitionBoundary;
|
|
7
|
-
title: string;
|
|
8
|
-
nextPhase: Phase;
|
|
9
|
-
artifactPath: string | null;
|
|
10
|
-
options: { choice: TransitionChoice; label: string }[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const BASE_OPTIONS: TransitionPrompt["options"] = [
|
|
14
|
-
{ choice: "next", label: "Next step (this session)" },
|
|
15
|
-
{ choice: "fresh", label: "Fresh session → next step" },
|
|
16
|
-
{ choice: "skip", label: "Skip" },
|
|
17
|
-
{ choice: "discuss", label: "Discuss" },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
export function getTransitionPrompt(boundary: TransitionBoundary, artifactPath: string | null): TransitionPrompt {
|
|
21
|
-
switch (boundary) {
|
|
22
|
-
case "design_committed":
|
|
23
|
-
return {
|
|
24
|
-
boundary,
|
|
25
|
-
title: "Design committed. What next?",
|
|
26
|
-
nextPhase: "plan",
|
|
27
|
-
artifactPath,
|
|
28
|
-
options: BASE_OPTIONS,
|
|
29
|
-
};
|
|
30
|
-
case "plan_ready":
|
|
31
|
-
return {
|
|
32
|
-
boundary,
|
|
33
|
-
title: "Plan ready. What next?",
|
|
34
|
-
nextPhase: "execute",
|
|
35
|
-
artifactPath,
|
|
36
|
-
options: BASE_OPTIONS,
|
|
37
|
-
};
|
|
38
|
-
case "execution_complete":
|
|
39
|
-
return {
|
|
40
|
-
boundary,
|
|
41
|
-
title: "All tasks complete. What next?",
|
|
42
|
-
nextPhase: "finalize",
|
|
43
|
-
artifactPath,
|
|
44
|
-
options: BASE_OPTIONS,
|
|
45
|
-
};
|
|
46
|
-
default:
|
|
47
|
-
return {
|
|
48
|
-
boundary,
|
|
49
|
-
title: "What next?",
|
|
50
|
-
nextPhase: "plan",
|
|
51
|
-
artifactPath,
|
|
52
|
-
options: BASE_OPTIONS,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|