@yycholla/pi-dynamic-workflows 3.3.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/LICENSE +22 -0
- package/README.md +325 -0
- package/assets/readme/hero.png +0 -0
- package/assets/readme/hero.svg +70 -0
- package/assets/readme/package-cover.png +0 -0
- package/assets/readme/package-cover.svg +66 -0
- package/assets/readme/workflow.png +0 -0
- package/assets/readme/workflow.svg +77 -0
- package/dist/accept-workflow-guidance.d.ts +14 -0
- package/dist/accept-workflow-guidance.js +53 -0
- package/dist/adversarial-review.d.ts +30 -0
- package/dist/adversarial-review.js +107 -0
- package/dist/agent-history.d.ts +20 -0
- package/dist/agent-history.js +122 -0
- package/dist/agent-registry.d.ts +83 -0
- package/dist/agent-registry.js +190 -0
- package/dist/agent.d.ts +364 -0
- package/dist/agent.js +714 -0
- package/dist/builtin-commands.d.ts +19 -0
- package/dist/builtin-commands.js +251 -0
- package/dist/builtin-workflows.d.ts +45 -0
- package/dist/builtin-workflows.js +121 -0
- package/dist/code-review.d.ts +26 -0
- package/dist/code-review.js +181 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +44 -0
- package/dist/deep-research.d.ts +30 -0
- package/dist/deep-research.js +124 -0
- package/dist/display.d.ts +134 -0
- package/dist/display.js +248 -0
- package/dist/effort-command.d.ts +28 -0
- package/dist/effort-command.js +68 -0
- package/dist/enums.d.ts +69 -0
- package/dist/enums.js +78 -0
- package/dist/errors.d.ts +113 -0
- package/dist/errors.js +140 -0
- package/dist/extension-reload.d.ts +37 -0
- package/dist/extension-reload.js +78 -0
- package/dist/fs-persistence.d.ts +63 -0
- package/dist/fs-persistence.js +102 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +35 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.js +66 -0
- package/dist/model-routing.d.ts +30 -0
- package/dist/model-routing.js +50 -0
- package/dist/model-spec.d.ts +29 -0
- package/dist/model-spec.js +252 -0
- package/dist/model-tier-config.d.ts +133 -0
- package/dist/model-tier-config.js +249 -0
- package/dist/run-persistence.d.ts +180 -0
- package/dist/run-persistence.js +294 -0
- package/dist/saved-commands.d.ts +28 -0
- package/dist/saved-commands.js +100 -0
- package/dist/shared-store.d.ts +98 -0
- package/dist/shared-store.js +212 -0
- package/dist/structured-output.d.ts +19 -0
- package/dist/structured-output.js +30 -0
- package/dist/task-panel.d.ts +61 -0
- package/dist/task-panel.js +422 -0
- package/dist/usage-limit-scheduler.d.ts +145 -0
- package/dist/usage-limit-scheduler.js +368 -0
- package/dist/web-tools.d.ts +20 -0
- package/dist/web-tools.js +120 -0
- package/dist/workflow-authoring-coverage.d.ts +70 -0
- package/dist/workflow-authoring-coverage.js +421 -0
- package/dist/workflow-authoring-reference.d.ts +20 -0
- package/dist/workflow-authoring-reference.js +156 -0
- package/dist/workflow-capability-contract.d.ts +131 -0
- package/dist/workflow-capability-contract.js +604 -0
- package/dist/workflow-commands.d.ts +18 -0
- package/dist/workflow-commands.js +260 -0
- package/dist/workflow-comprehension.d.ts +133 -0
- package/dist/workflow-comprehension.js +1321 -0
- package/dist/workflow-context-measurement.d.ts +72 -0
- package/dist/workflow-context-measurement.js +213 -0
- package/dist/workflow-control-tool.d.ts +30 -0
- package/dist/workflow-control-tool.js +176 -0
- package/dist/workflow-delivery-choice.d.ts +20 -0
- package/dist/workflow-delivery-choice.js +48 -0
- package/dist/workflow-editor.d.ts +93 -0
- package/dist/workflow-editor.js +363 -0
- package/dist/workflow-manager.d.ts +492 -0
- package/dist/workflow-manager.js +1124 -0
- package/dist/workflow-paths.d.ts +22 -0
- package/dist/workflow-paths.js +46 -0
- package/dist/workflow-release-gate.d.ts +39 -0
- package/dist/workflow-release-gate.js +309 -0
- package/dist/workflow-saved.d.ts +38 -0
- package/dist/workflow-saved.js +126 -0
- package/dist/workflow-settings.d.ts +70 -0
- package/dist/workflow-settings.js +131 -0
- package/dist/workflow-tool.d.ts +71 -0
- package/dist/workflow-tool.js +367 -0
- package/dist/workflow-ui.d.ts +182 -0
- package/dist/workflow-ui.js +1587 -0
- package/dist/workflow.d.ts +333 -0
- package/dist/workflow.js +1151 -0
- package/dist/workflows-models-command.d.ts +31 -0
- package/dist/workflows-models-command.js +156 -0
- package/dist/worktree.d.ts +25 -0
- package/dist/worktree.js +61 -0
- package/extensions/workflow.ts +151 -0
- package/package.json +104 -0
- package/skills/workflow-authoring/SKILL.md +30 -0
- package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
- package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
- package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
- package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
- package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
- package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
- package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
- package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
- package/skills/workflow-authoring/examples/structured-output.js +37 -0
- package/skills/workflow-authoring/examples/tournament.js +81 -0
- package/skills/workflow-authoring/examples/validated-gate.js +63 -0
- package/skills/workflow-authoring/references/capabilities.md +41 -0
- package/skills/workflow-authoring/references/capability-details.md +357 -0
- package/skills/workflow-authoring/references/common-helpers.md +4 -0
- package/skills/workflow-authoring/references/debugging.md +27 -0
- package/skills/workflow-authoring/references/focused-recipes.md +11 -0
- package/skills/workflow-authoring/references/helpers.md +9 -0
- package/skills/workflow-authoring/references/lifecycle.md +33 -0
- package/skills/workflow-authoring/references/pattern-selection.md +15 -0
- package/skills/workflow-authoring/references/quality-helpers.md +8 -0
- package/skills/workflow-authoring/references/registry-ownership.md +15 -0
- package/skills/workflow-authoring/references/retry-helper.md +5 -0
- package/skills/workflow-authoring/references/review.md +43 -0
- package/skills/workflow-authoring/references/runtime.md +27 -0
- package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
- package/skills/workflow-authoring/references/versions.md +13 -0
- package/skills/workflow-patterns/SKILL.md +51 -0
- package/src/accept-workflow-guidance.ts +71 -0
- package/src/adversarial-review.ts +120 -0
- package/src/agent-history.ts +157 -0
- package/src/agent-registry.ts +221 -0
- package/src/agent.ts +929 -0
- package/src/builtin-commands.ts +286 -0
- package/src/builtin-workflows.ts +155 -0
- package/src/code-review.ts +183 -0
- package/src/config.ts +55 -0
- package/src/deep-research.ts +135 -0
- package/src/display.ts +367 -0
- package/src/effort-command.ts +87 -0
- package/src/enums.ts +77 -0
- package/src/errors.ts +199 -0
- package/src/extension-reload.ts +100 -0
- package/src/fs-persistence.ts +124 -0
- package/src/index.ts +176 -0
- package/src/logger.ts +88 -0
- package/src/model-routing.ts +73 -0
- package/src/model-spec.ts +309 -0
- package/src/model-tier-config.ts +296 -0
- package/src/run-persistence.ts +484 -0
- package/src/saved-commands.ts +115 -0
- package/src/shared-store.ts +228 -0
- package/src/structured-output.ts +47 -0
- package/src/task-panel.ts +490 -0
- package/src/usage-limit-scheduler.ts +432 -0
- package/src/web-tools.ts +124 -0
- package/src/workflow-authoring-coverage.ts +486 -0
- package/src/workflow-authoring-reference.ts +186 -0
- package/src/workflow-capability-contract.ts +806 -0
- package/src/workflow-commands.ts +287 -0
- package/src/workflow-comprehension.ts +1673 -0
- package/src/workflow-context-measurement.ts +262 -0
- package/src/workflow-control-tool.ts +238 -0
- package/src/workflow-delivery-choice.ts +69 -0
- package/src/workflow-editor.ts +444 -0
- package/src/workflow-manager.ts +1405 -0
- package/src/workflow-paths.ts +63 -0
- package/src/workflow-release-gate.ts +529 -0
- package/src/workflow-saved.ts +180 -0
- package/src/workflow-settings.ts +194 -0
- package/src/workflow-tool.ts +464 -0
- package/src/workflow-ui.ts +1789 -0
- package/src/workflow.ts +1615 -0
- package/src/workflows-models-command.ts +211 -0
- package/src/worktree.ts +76 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/workflows-models` command handler.
|
|
3
|
+
*
|
|
4
|
+
* Uses Pi's built-in `ctx.ui.select()`, `ctx.ui.confirm()`, and `ctx.ui.notify()`
|
|
5
|
+
* to let users view and manage model tier configuration for workflows.
|
|
6
|
+
*
|
|
7
|
+
* Model selection draws from the host session's shared model registry so users
|
|
8
|
+
* see every provider Pi can reach, including extension-registered providers such
|
|
9
|
+
* as `ollama-cloud`.
|
|
10
|
+
*
|
|
11
|
+
* Each tier holds exactly one model spec string. The string may include Pi
|
|
12
|
+
* CLI-style thinking suffixes, e.g. `openai-codex/gpt-5.5:xhigh`.
|
|
13
|
+
* When editing a tier, users pick a model, then an optional thinking level.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import {
|
|
18
|
+
Container,
|
|
19
|
+
type SelectItem,
|
|
20
|
+
SelectList,
|
|
21
|
+
type SelectListTheme,
|
|
22
|
+
Spacer,
|
|
23
|
+
Text,
|
|
24
|
+
type TUI,
|
|
25
|
+
} from "@earendil-works/pi-tui";
|
|
26
|
+
import { listAvailableModelSpecs, listAvailableModels } from "./agent.js";
|
|
27
|
+
import {
|
|
28
|
+
formatModelSpecWithThinking,
|
|
29
|
+
type ModelThinkingLevel,
|
|
30
|
+
splitModelSpecThinking,
|
|
31
|
+
THINKING_LEVELS,
|
|
32
|
+
} from "./model-spec.js";
|
|
33
|
+
import {
|
|
34
|
+
buildDefaultTierConfig,
|
|
35
|
+
loadModelTierConfig,
|
|
36
|
+
saveModelTierConfig,
|
|
37
|
+
sortedTierNames,
|
|
38
|
+
} from "./model-tier-config.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register the `/workflows-models` command with Pi.
|
|
42
|
+
*/
|
|
43
|
+
export function registerWorkflowModelsCommand(pi: ExtensionAPI): void {
|
|
44
|
+
pi.registerCommand("workflows-models", {
|
|
45
|
+
description: "View and edit model tiers used by workflows (small/medium/big)",
|
|
46
|
+
handler: async (_args, ctx) => {
|
|
47
|
+
await ctx.waitForIdle();
|
|
48
|
+
|
|
49
|
+
// Load the saved config, or build an in-memory default spread across the
|
|
50
|
+
// available models. If the model registry is empty, fall back to the
|
|
51
|
+
// current Pi model so the tiers are still usable. Pass the host session's
|
|
52
|
+
// registry explicitly: since pi 0.80.8 the no-registry fallback inside
|
|
53
|
+
// listAvailableModels() initializes asynchronously and reports [] on the
|
|
54
|
+
// first call, which would rank defaults from an empty model list.
|
|
55
|
+
const currentModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
56
|
+
let config =
|
|
57
|
+
loadModelTierConfig() ?? buildDefaultTierConfig(currentModel, listAvailableModels(ctx.modelRegistry));
|
|
58
|
+
let dirty = false;
|
|
59
|
+
|
|
60
|
+
const ensureFresh = (cfg: typeof config) => {
|
|
61
|
+
config = cfg;
|
|
62
|
+
dirty = true;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// eslint-disable-next-line no-constant-condition
|
|
66
|
+
while (true) {
|
|
67
|
+
const tiers = sortedTierNames(config);
|
|
68
|
+
const menuOptions: string[] = [];
|
|
69
|
+
|
|
70
|
+
menuOptions.push("─".repeat(30));
|
|
71
|
+
for (const name of tiers) {
|
|
72
|
+
const model = config.tiers[name];
|
|
73
|
+
menuOptions.push(`${name} tier → ${model}`);
|
|
74
|
+
}
|
|
75
|
+
menuOptions.push("─".repeat(30));
|
|
76
|
+
|
|
77
|
+
menuOptions.push("Reset to defaults");
|
|
78
|
+
menuOptions.push(dirty ? "Save and exit" : "Exit");
|
|
79
|
+
|
|
80
|
+
const choice = await ctx.ui.select("Model tier configuration", menuOptions);
|
|
81
|
+
|
|
82
|
+
if (!choice) break;
|
|
83
|
+
|
|
84
|
+
// Handle "<tier> → [model]" selections
|
|
85
|
+
for (const name of tiers) {
|
|
86
|
+
if (choice.startsWith(`${name} tier →`)) {
|
|
87
|
+
const updatedTiers = await editSingleTier(ctx, config.tiers, name);
|
|
88
|
+
if (updatedTiers !== null) {
|
|
89
|
+
ensureFresh({ ...config, tiers: updatedTiers });
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (choice === "Reset to defaults") {
|
|
96
|
+
const confirmed = await ctx.ui.confirm(
|
|
97
|
+
"Reset model tiers",
|
|
98
|
+
"This will reset tiers from your available model list. Continue?",
|
|
99
|
+
);
|
|
100
|
+
if (confirmed) {
|
|
101
|
+
ensureFresh(buildDefaultTierConfig(currentModel, listAvailableModels(ctx.modelRegistry)));
|
|
102
|
+
ctx.ui.notify("Tiers reset to defaults. Use 'Save and exit' to persist.", "info");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (choice === "Save and exit" || choice === "Exit") {
|
|
107
|
+
if (choice === "Save and exit") {
|
|
108
|
+
saveModelTierConfig(config);
|
|
109
|
+
ctx.ui.notify("Model tiers saved.", "info");
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const DEFAULT_THINKING_CHOICE = "Default thinking (session setting)";
|
|
119
|
+
const THINKING_CHOICES = [DEFAULT_THINKING_CHOICE, ...THINKING_LEVELS] as const;
|
|
120
|
+
|
|
121
|
+
function fromThinkingChoice(choice: string | undefined): ModelThinkingLevel | undefined {
|
|
122
|
+
return THINKING_LEVELS.find((level) => level === choice);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Interactive editor for a single tier — scrollable model picker plus optional
|
|
127
|
+
* thinking-level picker.
|
|
128
|
+
*
|
|
129
|
+
* Uses `ctx.ui.custom()` with Pi TUI's `SelectList` for proper scrollable list
|
|
130
|
+
* with limited visible rows (like `/advisor`). The currently selected base
|
|
131
|
+
* model is shown in the dialog title. After choosing the model, users can set
|
|
132
|
+
* a Pi CLI-style thinking suffix or keep the session default.
|
|
133
|
+
*
|
|
134
|
+
* Returns the updated tiers object, or null if nothing changed.
|
|
135
|
+
*/
|
|
136
|
+
export async function editSingleTier(
|
|
137
|
+
ctx: ExtensionCommandContext,
|
|
138
|
+
tiers: Record<string, string>,
|
|
139
|
+
tierName: string,
|
|
140
|
+
): Promise<Record<string, string> | null> {
|
|
141
|
+
const available = listAvailableModelSpecs(ctx.modelRegistry);
|
|
142
|
+
const knownSpecs = available.length > 0 ? available : undefined;
|
|
143
|
+
const current = tiers[tierName];
|
|
144
|
+
const currentParts = splitModelSpecThinking(current, knownSpecs);
|
|
145
|
+
|
|
146
|
+
// Build SelectItems: all available models as scrollable list
|
|
147
|
+
const items: SelectItem[] = available.map((m) => ({ value: m, label: m }));
|
|
148
|
+
|
|
149
|
+
const selectedModel = await ctx.ui.custom<string | null>((tui: TUI, theme: Theme, _keybindings, done) => {
|
|
150
|
+
const container = new Container();
|
|
151
|
+
|
|
152
|
+
// Title showing current model
|
|
153
|
+
const titleText = current
|
|
154
|
+
? `Pick a model for "${tierName}" (current: ${current})`
|
|
155
|
+
: `Pick a model for "${tierName}"`;
|
|
156
|
+
container.addChild(new Text(theme.fg("accent", titleText), 1, 0));
|
|
157
|
+
container.addChild(new Spacer(1));
|
|
158
|
+
|
|
159
|
+
// SelectList theme
|
|
160
|
+
const selectTheme: SelectListTheme = {
|
|
161
|
+
selectedPrefix: (t: string) => theme.bg("selectedBg", theme.fg("accent", t)),
|
|
162
|
+
selectedText: (t: string) => theme.bg("selectedBg", theme.bold(t)),
|
|
163
|
+
description: (t: string) => theme.fg("muted", t),
|
|
164
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
165
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const selectList = new SelectList(items, 12, selectTheme);
|
|
169
|
+
|
|
170
|
+
// Preselect the current base model even when the stored tier has :thinking.
|
|
171
|
+
if (currentParts.modelSpec) {
|
|
172
|
+
const idx = items.findIndex((i) => i.value === currentParts.modelSpec);
|
|
173
|
+
if (idx >= 0) selectList.setSelectedIndex(idx);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Wire up callbacks
|
|
177
|
+
selectList.onSelect = (item) => done(item.value);
|
|
178
|
+
selectList.onCancel = () => done(null);
|
|
179
|
+
|
|
180
|
+
container.addChild(selectList);
|
|
181
|
+
container.addChild(new Spacer(1));
|
|
182
|
+
container.addChild(
|
|
183
|
+
new Text(theme.fg("dim", "↑↓ navigate enter select esc cancel · thinking is chosen next"), 1, 0),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
render: (w: number) => container.render(w),
|
|
188
|
+
invalidate: () => container.invalidate(),
|
|
189
|
+
handleInput: (data: string) => {
|
|
190
|
+
selectList.handleInput(data);
|
|
191
|
+
tui.requestRender();
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!selectedModel) return null;
|
|
197
|
+
|
|
198
|
+
const currentThinkingLabel = currentParts.thinkingLevel ?? DEFAULT_THINKING_CHOICE;
|
|
199
|
+
const thinkingChoice = await ctx.ui.select(
|
|
200
|
+
`Thinking for "${tierName}" tier (current: ${currentThinkingLabel})`,
|
|
201
|
+
THINKING_CHOICES.map((choice) => String(choice)),
|
|
202
|
+
);
|
|
203
|
+
if (!thinkingChoice) return null;
|
|
204
|
+
|
|
205
|
+
const thinkingLevel = fromThinkingChoice(thinkingChoice);
|
|
206
|
+
const result = formatModelSpecWithThinking(selectedModel, thinkingLevel);
|
|
207
|
+
if (result === current) return null;
|
|
208
|
+
|
|
209
|
+
ctx.ui.notify(`"${tierName}" tier → ${result}`, "info");
|
|
210
|
+
return { ...tiers, [tierName]: result };
|
|
211
|
+
}
|
package/src/worktree.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-agent git worktree isolation. When an agent requests `isolation: "worktree"`,
|
|
3
|
+
* it runs in a throwaway worktree on its own branch so parallel agents can edit the
|
|
4
|
+
* same files without conflict. Results are NOT auto-merged — the path is surfaced for
|
|
5
|
+
* the caller to inspect. Falls back to a logged no-op when isolation isn't possible.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
|
|
12
|
+
const exec = promisify(execFile);
|
|
13
|
+
|
|
14
|
+
export interface Worktree {
|
|
15
|
+
/** True when a real worktree was created; false means "ran in the shared tree". */
|
|
16
|
+
isolated: boolean;
|
|
17
|
+
/** cwd the agent should run in (worktree path when isolated, else the base cwd). */
|
|
18
|
+
cwd: string;
|
|
19
|
+
branch?: string;
|
|
20
|
+
/** Repo root the worktree was added to (for teardown). */
|
|
21
|
+
repoRoot?: string;
|
|
22
|
+
/** Why isolation was skipped, when isolated === false. */
|
|
23
|
+
reason?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function slug(name: string): string {
|
|
27
|
+
return (
|
|
28
|
+
name
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
31
|
+
.replace(/^-+|-+$/g, "")
|
|
32
|
+
.slice(0, 32) || "agent"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create an isolated worktree under `<repoRoot>/.pi/worktrees/<name>` on branch
|
|
38
|
+
* `pi/wf/<name>`. The `name` must be deterministic (derived from runId + call index,
|
|
39
|
+
* never wall-clock) so resume keys stay stable. Returns a no-op Worktree on any failure.
|
|
40
|
+
*/
|
|
41
|
+
export async function createWorktree(baseCwd: string, name: string): Promise<Worktree> {
|
|
42
|
+
const id = slug(name);
|
|
43
|
+
let repoRoot: string;
|
|
44
|
+
try {
|
|
45
|
+
const { stdout } = await exec("git", ["-C", baseCwd, "rev-parse", "--show-toplevel"]);
|
|
46
|
+
repoRoot = stdout.trim();
|
|
47
|
+
} catch {
|
|
48
|
+
return { isolated: false, cwd: baseCwd, reason: "not a git repository" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const path = join(repoRoot, ".pi", "worktrees", id);
|
|
52
|
+
const branch = `pi/wf/${id}`;
|
|
53
|
+
try {
|
|
54
|
+
await exec("git", ["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
55
|
+
return { isolated: true, cwd: path, branch, repoRoot };
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return { isolated: false, cwd: baseCwd, reason: error instanceof Error ? error.message : String(error) };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Remove a worktree and its branch. Best-effort; safe to call on a no-op Worktree. */
|
|
62
|
+
export async function removeWorktree(wt: Worktree): Promise<void> {
|
|
63
|
+
if (!wt.isolated || !wt.repoRoot) return;
|
|
64
|
+
try {
|
|
65
|
+
await exec("git", ["-C", wt.repoRoot, "worktree", "remove", "--force", wt.cwd]);
|
|
66
|
+
} catch {
|
|
67
|
+
// already gone / locked — fall through
|
|
68
|
+
}
|
|
69
|
+
if (wt.branch) {
|
|
70
|
+
try {
|
|
71
|
+
await exec("git", ["-C", wt.repoRoot, "branch", "-D", wt.branch]);
|
|
72
|
+
} catch {
|
|
73
|
+
// branch already deleted
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|