infinity-harness 2.8.9 → 2.8.11
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 +18 -0
- package/dist/extensions/infinity-harness/index.js +3614 -0
- package/dist/extensions/infinity-harness/index.js.map +1 -0
- package/dist/src/approval.js +154 -0
- package/dist/src/approval.js.map +1 -0
- package/dist/src/core/brief.js +309 -0
- package/dist/src/core/brief.js.map +1 -0
- package/dist/src/core/config.js +539 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/exec.js +106 -0
- package/dist/src/core/exec.js.map +1 -0
- package/dist/src/core/featureList.js +365 -0
- package/dist/src/core/featureList.js.map +1 -0
- package/dist/src/core/fsx.js +123 -0
- package/dist/src/core/fsx.js.map +1 -0
- package/dist/src/core/gates.js +490 -0
- package/dist/src/core/gates.js.map +1 -0
- package/dist/src/core/init.js +480 -0
- package/dist/src/core/init.js.map +1 -0
- package/dist/src/core/lock.js +183 -0
- package/dist/src/core/lock.js.map +1 -0
- package/dist/src/core/modelRouter.js +185 -0
- package/dist/src/core/modelRouter.js.map +1 -0
- package/dist/src/core/paths.js +129 -0
- package/dist/src/core/paths.js.map +1 -0
- package/dist/src/core/phases.js +254 -0
- package/dist/src/core/phases.js.map +1 -0
- package/dist/src/core/plan.js +15 -0
- package/dist/src/core/plan.js.map +1 -0
- package/dist/src/core/runState.js +126 -0
- package/dist/src/core/runState.js.map +1 -0
- package/dist/src/core/settings.js +877 -0
- package/dist/src/core/settings.js.map +1 -0
- package/dist/src/core/skills.js +319 -0
- package/dist/src/core/skills.js.map +1 -0
- package/dist/src/core/skillsAudit.js +194 -0
- package/dist/src/core/skillsAudit.js.map +1 -0
- package/dist/src/core/types.js +77 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/daemon/budget.js +85 -0
- package/dist/src/daemon/budget.js.map +1 -0
- package/dist/src/daemon/guard.js +122 -0
- package/dist/src/daemon/guard.js.map +1 -0
- package/dist/src/daemon/index.js +461 -0
- package/dist/src/daemon/index.js.map +1 -0
- package/dist/src/daemon/isolation.js +84 -0
- package/dist/src/daemon/isolation.js.map +1 -0
- package/dist/src/daemon/preflight.js +131 -0
- package/dist/src/daemon/preflight.js.map +1 -0
- package/dist/src/daemon/server.js +174 -0
- package/dist/src/daemon/server.js.map +1 -0
- package/dist/src/daemon/supervisorState.js +42 -0
- package/dist/src/daemon/supervisorState.js.map +1 -0
- package/dist/src/daemon/worker.js +239 -0
- package/dist/src/daemon/worker.js.map +1 -0
- package/dist/src/daemon/worktree.js +102 -0
- package/dist/src/daemon/worktree.js.map +1 -0
- package/dist/src/escalate.js +312 -0
- package/dist/src/escalate.js.map +1 -0
- package/dist/src/exec/piWorker.js +630 -0
- package/dist/src/exec/piWorker.js.map +1 -0
- package/dist/src/goal.js +317 -0
- package/dist/src/goal.js.map +1 -0
- package/dist/src/goalLoop.js +374 -0
- package/dist/src/goalLoop.js.map +1 -0
- package/dist/src/goalSpec.js +397 -0
- package/dist/src/goalSpec.js.map +1 -0
- package/dist/src/goalState.js +255 -0
- package/dist/src/goalState.js.map +1 -0
- package/dist/src/handoff.js +205 -0
- package/dist/src/handoff.js.map +1 -0
- package/dist/src/intake.js +203 -0
- package/dist/src/intake.js.map +1 -0
- package/dist/src/loop.js +592 -0
- package/dist/src/loop.js.map +1 -0
- package/dist/src/modelRouter.js +216 -0
- package/dist/src/modelRouter.js.map +1 -0
- package/dist/src/remote.js +246 -0
- package/dist/src/remote.js.map +1 -0
- package/dist/src/replan.js +262 -0
- package/dist/src/replan.js.map +1 -0
- package/dist/src/review.js +54 -0
- package/dist/src/review.js.map +1 -0
- package/dist/src/rework.js +241 -0
- package/dist/src/rework.js.map +1 -0
- package/dist/src/runState.js +4 -0
- package/dist/src/runState.js.map +1 -0
- package/dist/src/scheduler.js +270 -0
- package/dist/src/scheduler.js.map +1 -0
- package/dist/src/supervisor.js +787 -0
- package/dist/src/supervisor.js.map +1 -0
- package/dist/src/taskList.js +430 -0
- package/dist/src/taskList.js.map +1 -0
- package/dist/src/ui/config.js +268 -0
- package/dist/src/ui/config.js.map +1 -0
- package/dist/src/ui/dashboard.js +1288 -0
- package/dist/src/ui/dashboard.js.map +1 -0
- package/dist/src/ui/display.js +229 -0
- package/dist/src/ui/display.js.map +1 -0
- package/dist/src/ui/planTree.js +219 -0
- package/dist/src/ui/planTree.js.map +1 -0
- package/dist/src/ui/theme.js +298 -0
- package/dist/src/ui/theme.js.map +1 -0
- package/dist/src/ui/viewState.js +65 -0
- package/dist/src/ui/viewState.js.map +1 -0
- package/dist/src/ui/widget.js +721 -0
- package/dist/src/ui/widget.js.map +1 -0
- package/dist/src/ui/wizard.js +540 -0
- package/dist/src/ui/wizard.js.map +1 -0
- package/dist/src/unstuck.js +210 -0
- package/dist/src/unstuck.js.map +1 -0
- package/dist/src/worker.js +267 -0
- package/dist/src/worker.js.map +1 -0
- package/dist/src/workflow.js +250 -0
- package/dist/src/workflow.js.map +1 -0
- package/dist/tests/approval.test.js +144 -0
- package/dist/tests/approval.test.js.map +1 -0
- package/dist/tests/brief.test.js +271 -0
- package/dist/tests/brief.test.js.map +1 -0
- package/dist/tests/config.test.js +320 -0
- package/dist/tests/config.test.js.map +1 -0
- package/dist/tests/dashboard.test.js +277 -0
- package/dist/tests/dashboard.test.js.map +1 -0
- package/dist/tests/display.test.js +229 -0
- package/dist/tests/display.test.js.map +1 -0
- package/dist/tests/escalate.test.js +272 -0
- package/dist/tests/escalate.test.js.map +1 -0
- package/dist/tests/featureList.test.js +429 -0
- package/dist/tests/featureList.test.js.map +1 -0
- package/dist/tests/gates.test.js +333 -0
- package/dist/tests/gates.test.js.map +1 -0
- package/dist/tests/goal.test.js +229 -0
- package/dist/tests/goal.test.js.map +1 -0
- package/dist/tests/goalLoop.test.js +170 -0
- package/dist/tests/goalLoop.test.js.map +1 -0
- package/dist/tests/goalSpec.test.js +146 -0
- package/dist/tests/goalSpec.test.js.map +1 -0
- package/dist/tests/goalState.test.js +202 -0
- package/dist/tests/goalState.test.js.map +1 -0
- package/dist/tests/handoff.test.js +127 -0
- package/dist/tests/handoff.test.js.map +1 -0
- package/dist/tests/init.test.js +264 -0
- package/dist/tests/init.test.js.map +1 -0
- package/dist/tests/intake.test.js +288 -0
- package/dist/tests/intake.test.js.map +1 -0
- package/dist/tests/loop.test.js +453 -0
- package/dist/tests/loop.test.js.map +1 -0
- package/dist/tests/modelRouter.test.js +131 -0
- package/dist/tests/modelRouter.test.js.map +1 -0
- package/dist/tests/phases.test.js +244 -0
- package/dist/tests/phases.test.js.map +1 -0
- package/dist/tests/planTree.test.js +190 -0
- package/dist/tests/planTree.test.js.map +1 -0
- package/dist/tests/remote.test.js +380 -0
- package/dist/tests/remote.test.js.map +1 -0
- package/dist/tests/replan.test.js +113 -0
- package/dist/tests/replan.test.js.map +1 -0
- package/dist/tests/review.test.js +77 -0
- package/dist/tests/review.test.js.map +1 -0
- package/dist/tests/rework.test.js +109 -0
- package/dist/tests/rework.test.js.map +1 -0
- package/dist/tests/routing-live.test.js +121 -0
- package/dist/tests/routing-live.test.js.map +1 -0
- package/dist/tests/runState.test.js +73 -0
- package/dist/tests/runState.test.js.map +1 -0
- package/dist/tests/settings.test.js +311 -0
- package/dist/tests/settings.test.js.map +1 -0
- package/dist/tests/skills.test.js +387 -0
- package/dist/tests/skills.test.js.map +1 -0
- package/dist/tests/supervisor.test.js +437 -0
- package/dist/tests/supervisor.test.js.map +1 -0
- package/dist/tests/surface.test.js +203 -0
- package/dist/tests/surface.test.js.map +1 -0
- package/dist/tests/taskList.test.js +376 -0
- package/dist/tests/taskList.test.js.map +1 -0
- package/dist/tests/theme.test.js +149 -0
- package/dist/tests/theme.test.js.map +1 -0
- package/dist/tests/unstuck.test.js +268 -0
- package/dist/tests/unstuck.test.js.map +1 -0
- package/dist/tests/widget.test.js +424 -0
- package/dist/tests/widget.test.js.map +1 -0
- package/dist/tests/worker.test.js +306 -0
- package/dist/tests/worker.test.js.map +1 -0
- package/dist/tests/workflow.test.js +187 -0
- package/dist/tests/workflow.test.js.map +1 -0
- package/extensions/infinity-harness/index.ts +39 -12
- package/package.json +5 -2
- package/src/core/modelRouter.ts +23 -1
- package/src/daemon/index.ts +3 -3
- package/src/daemon/worker.ts +28 -5
- package/src/ui/config.ts +25 -1
- package/src/ui/wizard.ts +10 -5
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* infinity-harness — workflows: which phases run, and who signs each one.
|
|
3
|
+
*
|
|
4
|
+
* "copilot" and "autopilot" were a single switch, and a single switch is the
|
|
5
|
+
* wrong shape for the question. What people actually want is per-phase: let
|
|
6
|
+
* the model define and plan on its own but show me the review; or grill me on
|
|
7
|
+
* the definition and then leave me alone until it ships. Two words cannot say
|
|
8
|
+
* that.
|
|
9
|
+
*
|
|
10
|
+
* So the setting is a **mode per phase**, and copilot and autopilot become two
|
|
11
|
+
* named points in that space rather than the only two points in it:
|
|
12
|
+
*
|
|
13
|
+
* copilot you sign RESEARCH, DEFINE and PLAN
|
|
14
|
+
* autopilot you sign nothing
|
|
15
|
+
* custom you decide, phase by phase, and can name and keep it
|
|
16
|
+
*
|
|
17
|
+
* A named workflow is worth as much on the next project as on this one, so
|
|
18
|
+
* saved ones live with the *person* (`~/.pi/agent/infinity-harness/`), not
|
|
19
|
+
* under a project's `harness/`.
|
|
20
|
+
*
|
|
21
|
+
* Built-ins are read-only on purpose. "copilot" has to mean the same thing in
|
|
22
|
+
* every conversation about this tool; someone who wants a different copilot
|
|
23
|
+
* makes a custom workflow and gives it their own name.
|
|
24
|
+
*/
|
|
25
|
+
import { PHASE_ORDER, DEFAULT_ENABLED_PHASES } from "./core/types.js";
|
|
26
|
+
import { userWorkflowsPath } from "./core/paths.js";
|
|
27
|
+
import { readJsonSafe, writeJsonAtomic, ensureDir } from "./core/fsx.js";
|
|
28
|
+
import { dirname } from "node:path";
|
|
29
|
+
/** Phases a human can be asked to sign. Everything except INIT, which is plumbing. */
|
|
30
|
+
export const SIGNABLE_PHASES = PHASE_ORDER.filter((p) => p !== "init");
|
|
31
|
+
const BASE = [...DEFAULT_ENABLED_PHASES];
|
|
32
|
+
const WITH_RESEARCH = PHASE_ORDER.filter((p) => p === "research" || DEFAULT_ENABLED_PHASES.includes(p));
|
|
33
|
+
function modesFrom(phases, copilotPhases) {
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const p of phases)
|
|
36
|
+
out[p] = copilotPhases.includes(p) ? "copilot" : "autopilot";
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The workflows that ship with the package.
|
|
41
|
+
*
|
|
42
|
+
* Four, not one per taste: enough that most people find themselves in the
|
|
43
|
+
* list, few enough that reading the list is faster than building one.
|
|
44
|
+
*/
|
|
45
|
+
export const BUILTIN_WORKFLOWS = [
|
|
46
|
+
{
|
|
47
|
+
id: "copilot",
|
|
48
|
+
name: "copilot",
|
|
49
|
+
description: "You approve the research, the definition and the plan. Then it builds.",
|
|
50
|
+
builtIn: true,
|
|
51
|
+
phases: BASE,
|
|
52
|
+
modes: modesFrom(BASE, ["research", "define", "plan"]),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "autopilot",
|
|
56
|
+
name: "autopilot",
|
|
57
|
+
description: "You approve nothing. Say what you want, walk away, read the result.",
|
|
58
|
+
builtIn: true,
|
|
59
|
+
phases: BASE,
|
|
60
|
+
modes: modesFrom(BASE, []),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "spec-and-ship",
|
|
64
|
+
name: "spec and ship",
|
|
65
|
+
description: "You sign the scope going in and the release coming out. The middle is its own.",
|
|
66
|
+
builtIn: true,
|
|
67
|
+
phases: BASE,
|
|
68
|
+
modes: modesFrom(BASE, ["define", "ship"]),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "research-first",
|
|
72
|
+
name: "research first",
|
|
73
|
+
description: "Adds a RESEARCH phase and stops on all three thinking phases before any code.",
|
|
74
|
+
builtIn: true,
|
|
75
|
+
phases: WITH_RESEARCH,
|
|
76
|
+
modes: modesFrom(WITH_RESEARCH, ["research", "define", "plan"]),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "every-gate",
|
|
80
|
+
name: "every gate",
|
|
81
|
+
description: "It stops at every phase. Slowest, and the one you want on something that matters.",
|
|
82
|
+
builtIn: true,
|
|
83
|
+
phases: BASE,
|
|
84
|
+
modes: modesFrom(BASE, BASE),
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
export function builtInWorkflow(id) {
|
|
88
|
+
return BUILTIN_WORKFLOWS.find((w) => w.id === id) ?? null;
|
|
89
|
+
}
|
|
90
|
+
/** Turn a name into a stable id. Two workflows cannot share one. */
|
|
91
|
+
export function slugify(name) {
|
|
92
|
+
const slug = String(name ?? "")
|
|
93
|
+
.trim()
|
|
94
|
+
.toLowerCase()
|
|
95
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
96
|
+
.replace(/^-+|-+$/g, "")
|
|
97
|
+
.slice(0, 48);
|
|
98
|
+
return slug;
|
|
99
|
+
}
|
|
100
|
+
export function loadSavedWorkflows(env) {
|
|
101
|
+
const store = readJsonSafe(userWorkflowsPath(env), null);
|
|
102
|
+
const list = Array.isArray(store?.workflows) ? store.workflows : [];
|
|
103
|
+
return list
|
|
104
|
+
.filter((w) => typeof w?.id === "string" && typeof w?.name === "string")
|
|
105
|
+
.map((w) => ({
|
|
106
|
+
...w,
|
|
107
|
+
builtIn: false,
|
|
108
|
+
phases: normalizePhases(w.phases),
|
|
109
|
+
modes: normalizeModes(w.modes, normalizePhases(w.phases)),
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
/** Built-ins first, then the person's own. */
|
|
113
|
+
export function listWorkflows(env) {
|
|
114
|
+
return [...BUILTIN_WORKFLOWS, ...loadSavedWorkflows(env)];
|
|
115
|
+
}
|
|
116
|
+
export function findWorkflow(id, env) {
|
|
117
|
+
return listWorkflows(env).find((w) => w.id === id) ?? null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Save a workflow under a name the person chose.
|
|
121
|
+
*
|
|
122
|
+
* Overwriting one of their own is fine — that is editing. Overwriting a
|
|
123
|
+
* built-in is refused: "copilot" has to mean the same thing everywhere, and a
|
|
124
|
+
* shadowed built-in is a support conversation nobody enjoys.
|
|
125
|
+
*/
|
|
126
|
+
export function saveWorkflow(input, env) {
|
|
127
|
+
const name = String(input.name ?? "").trim();
|
|
128
|
+
if (!name)
|
|
129
|
+
return { ok: false, error: "A workflow needs a name." };
|
|
130
|
+
const id = slugify(name);
|
|
131
|
+
if (!id)
|
|
132
|
+
return { ok: false, error: `"${name}" has no letters or digits in it.` };
|
|
133
|
+
if (builtInWorkflow(id)) {
|
|
134
|
+
return { ok: false, error: `"${name}" is a built-in workflow. Pick another name.` };
|
|
135
|
+
}
|
|
136
|
+
const phases = normalizePhases(input.phases);
|
|
137
|
+
const workflow = {
|
|
138
|
+
id,
|
|
139
|
+
name,
|
|
140
|
+
description: (input.description ?? "").trim() || describeModes(phases, normalizeModes(input.modes, phases)),
|
|
141
|
+
builtIn: false,
|
|
142
|
+
phases,
|
|
143
|
+
modes: normalizeModes(input.modes, phases),
|
|
144
|
+
savedAt: new Date().toISOString(),
|
|
145
|
+
};
|
|
146
|
+
const existing = loadSavedWorkflows(env).filter((w) => w.id !== id);
|
|
147
|
+
const path = userWorkflowsPath(env);
|
|
148
|
+
try {
|
|
149
|
+
ensureDir(dirname(path));
|
|
150
|
+
writeJsonAtomic(path, { version: "1", workflows: [...existing, workflow] });
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
154
|
+
}
|
|
155
|
+
return { ok: true, error: null, workflow };
|
|
156
|
+
}
|
|
157
|
+
export function deleteWorkflow(id, env) {
|
|
158
|
+
if (builtInWorkflow(id))
|
|
159
|
+
return { ok: false, error: "Built-in workflows cannot be deleted." };
|
|
160
|
+
const remaining = loadSavedWorkflows(env).filter((w) => w.id !== id);
|
|
161
|
+
const path = userWorkflowsPath(env);
|
|
162
|
+
try {
|
|
163
|
+
ensureDir(dirname(path));
|
|
164
|
+
writeJsonAtomic(path, { version: "1", workflows: remaining });
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
168
|
+
}
|
|
169
|
+
return { ok: true, error: null };
|
|
170
|
+
}
|
|
171
|
+
// ── normalising ─────────────────────────────────────────────────────────────
|
|
172
|
+
export function normalizePhases(requested) {
|
|
173
|
+
if (!Array.isArray(requested) || requested.length === 0)
|
|
174
|
+
return [...DEFAULT_ENABLED_PHASES];
|
|
175
|
+
const wanted = new Set(requested.filter((p) => PHASE_ORDER.includes(p)));
|
|
176
|
+
wanted.delete("init");
|
|
177
|
+
const ordered = PHASE_ORDER.filter((p) => wanted.has(p));
|
|
178
|
+
return ordered.length ? [...ordered] : [...DEFAULT_ENABLED_PHASES];
|
|
179
|
+
}
|
|
180
|
+
/** Every enabled phase gets an explicit mode; anything else is dropped. */
|
|
181
|
+
export function normalizeModes(modes, phases) {
|
|
182
|
+
const out = {};
|
|
183
|
+
for (const p of phases)
|
|
184
|
+
out[p] = modes?.[p] === "copilot" ? "copilot" : "autopilot";
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
// ── applying, and reading back ──────────────────────────────────────────────
|
|
188
|
+
/** Fold a workflow into a config. Returns the config for chaining. */
|
|
189
|
+
export function applyWorkflow(config, workflow) {
|
|
190
|
+
const phases = normalizePhases(workflow.phases);
|
|
191
|
+
config.phases = { ...(config.phases ?? { enabled: phases }), enabled: phases };
|
|
192
|
+
config.phaseModes = normalizeModes(workflow.modes, phases);
|
|
193
|
+
config.workflow = { id: workflow.id, name: workflow.name };
|
|
194
|
+
return config;
|
|
195
|
+
}
|
|
196
|
+
/** The modes a config is actually running, whatever shape it was written in. */
|
|
197
|
+
export function modesOf(config) {
|
|
198
|
+
const phases = normalizePhases(config.phases?.enabled);
|
|
199
|
+
return normalizeModes(config.phaseModes, phases);
|
|
200
|
+
}
|
|
201
|
+
export function modeFor(config, phase) {
|
|
202
|
+
if (!phase)
|
|
203
|
+
return "autopilot";
|
|
204
|
+
return modesOf(config)[phase] === "copilot" ? "copilot" : "autopilot";
|
|
205
|
+
}
|
|
206
|
+
/** The phases this config stops on, in pipeline order. */
|
|
207
|
+
export function signedPhases(config) {
|
|
208
|
+
const modes = modesOf(config);
|
|
209
|
+
return PHASE_ORDER.filter((p) => modes[p] === "copilot");
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Which built-in or saved workflow a config currently matches, if any.
|
|
213
|
+
*
|
|
214
|
+
* Used to tell someone their settings have drifted off the preset they picked
|
|
215
|
+
* — otherwise a config edited one setting at a time still claims to be
|
|
216
|
+
* "copilot", and the word stops meaning anything.
|
|
217
|
+
*/
|
|
218
|
+
export function matchWorkflow(config, env) {
|
|
219
|
+
const phases = normalizePhases(config.phases?.enabled);
|
|
220
|
+
const modes = modesOf(config);
|
|
221
|
+
return (listWorkflows(env).find((w) => normalizePhases(w.phases).join(",") === phases.join(",") &&
|
|
222
|
+
SIGNABLE_PHASES.every((p) => normalizeModes(w.modes, phases)[p] === modes[p])) ?? null);
|
|
223
|
+
}
|
|
224
|
+
// ── describing ──────────────────────────────────────────────────────────────
|
|
225
|
+
export function describeModes(phases, modes) {
|
|
226
|
+
const signed = phases.filter((p) => modes[p] === "copilot");
|
|
227
|
+
if (signed.length === 0)
|
|
228
|
+
return "runs the whole pipeline without stopping";
|
|
229
|
+
if (signed.length === phases.length)
|
|
230
|
+
return "stops at every phase";
|
|
231
|
+
return `stops at ${signed.map((p) => p.toUpperCase()).join(", ")}`;
|
|
232
|
+
}
|
|
233
|
+
/** The pipeline with each phase's mode, for a menu row or a notification. */
|
|
234
|
+
export function renderWorkflow(workflow) {
|
|
235
|
+
const modes = normalizeModes(workflow.modes, normalizePhases(workflow.phases));
|
|
236
|
+
const rail = normalizePhases(workflow.phases)
|
|
237
|
+
.map((p) => (modes[p] === "copilot" ? `[${p}]` : p))
|
|
238
|
+
.join(" → ");
|
|
239
|
+
return `${workflow.name}\n ${workflow.description}\n ${rail}\n (a phase in [brackets] stops for you)`;
|
|
240
|
+
}
|
|
241
|
+
/** One line: `copilot · stops at RESEARCH, DEFINE, PLAN`. */
|
|
242
|
+
export function summarizeWorkflow(config, env) {
|
|
243
|
+
const phases = normalizePhases(config.phases?.enabled);
|
|
244
|
+
const modes = modesOf(config);
|
|
245
|
+
const named = matchWorkflow(config, env);
|
|
246
|
+
const recorded = config.workflow?.name;
|
|
247
|
+
const label = named ? named.name : recorded ? `${recorded} (edited)` : "custom";
|
|
248
|
+
return `${label} · ${describeModes(phases, modes)}`;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../src/workflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BpC,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAY,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC;AAEhF,MAAM,IAAI,GAAY,CAAC,GAAG,sBAAsB,CAAC,CAAC;AAClD,MAAM,aAAa,GAAY,WAAW,CAAC,MAAM,CAC/C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,UAAU,IAAI,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC9D,CAAC;AAEF,SAAS,SAAS,CAAC,MAAe,EAAE,aAAsB;IACxD,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IACrF,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAe;IAC3C;QACE,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,wEAAwE;QACrF,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;KACvD;IACD;QACE,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,qEAAqE;QAClF,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;KAC3B;IACD;QACE,EAAE,EAAE,eAAe;QACnB,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,gFAAgF;QAC7F,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;KAC3C;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,IAAI,EAAE,gBAAgB;QACtB,WAAW,EAAE,+EAA+E;QAC5F,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,aAAa;QACrB,KAAK,EAAE,SAAS,CAAC,aAAa,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;KAChE;IACD;QACE,EAAE,EAAE,YAAY;QAChB,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,mFAAmF;QAChG,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC;KAC7B;CACF,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC;AAC5D,CAAC;AAMD,oEAAoE;AACpE,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;SAC5B,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAuB;IACxD,MAAM,KAAK,GAAG,YAAY,CAAoB,iBAAiB,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,OAAO,IAAI;SACR,MAAM,CAAC,CAAC,CAAC,EAAiB,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,CAAC;SACtF,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,GAAG,CAAC;QACJ,OAAO,EAAE,KAAK;QACd,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC;QACjC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;KAC1D,CAAC,CAAC,CAAC;AACR,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,aAAa,CAAC,GAAuB;IACnD,OAAO,CAAC,GAAG,iBAAiB,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAAU,EAAE,GAAuB;IAC9D,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC;AAC7D,CAAC;AAID;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAiF,EACjF,GAAuB;IAEvB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;IAEnE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,mCAAmC,EAAE,CAAC;IAClF,IAAI,eAAe,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,8CAA8C,EAAE,CAAC;IACtF,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAa;QACzB,EAAE;QACF,IAAI;QACJ,WAAW,EAAE,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,aAAa,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC3G,OAAO,EAAE,KAAK;QACd,MAAM;QACN,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC;QAC1C,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KAClC,CAAC;IAEF,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACzB,eAAe,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,GAAG,QAAQ,EAAE,QAAQ,CAAC,EAAuB,CAAC,CAAC;IACnG,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU,EAAE,GAAuB;IAChE,IAAI,eAAe,CAAC,EAAE,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,uCAAuC,EAAE,CAAC;IAC9F,MAAM,SAAS,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACzB,eAAe,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAuB,CAAC,CAAC;IACrF,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACnC,CAAC;AAED,+EAA+E;AAE/E,MAAM,UAAU,eAAe,CAAC,SAAuC;IACrE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,GAAG,sBAAsB,CAAC,CAAC;IAC5F,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,WAAiC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,sBAAsB,CAAC,CAAC;AACrE,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,KAA6B,EAAE,MAAe;IAC3E,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IACpF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+EAA+E;AAE/E,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,MAAqB,EAAE,QAAkB;IACrE,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC/E,MAAM,CAAC,UAAU,GAAG,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3D,MAAM,CAAC,QAAQ,GAAG,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC3D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,OAAO,CAAC,MAAqB;IAC3C,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,OAA8B,CAAC,CAAC;IAC9E,OAAO,cAAc,CAAC,MAAM,CAAC,UAAoC,EAAE,MAAM,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAqB,EAAE,KAAmB;IAChE,IAAI,CAAC,KAAK;QAAE,OAAO,WAAW,CAAC;IAC/B,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;AACxE,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAqB,EAAE,GAAuB;IAC1E,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,OAA8B,CAAC,CAAC;IAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,CACL,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,CACrB,CAAC,CAAC,EAAE,EAAE,CACJ,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;QACxD,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAChF,IAAI,IAAI,CACV,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E,MAAM,UAAU,aAAa,CAAC,MAAe,EAAE,KAAiB;IAC9D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IAC5D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,0CAA0C,CAAC;IAC3E,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,OAAO,sBAAsB,CAAC;IACnE,OAAO,YAAY,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,cAAc,CAAC,QAAkB;IAC/C,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/E,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACnD,IAAI,CAAC,KAAK,CAAC,CAAC;IACf,OAAO,GAAG,QAAQ,CAAC,IAAI,OAAO,QAAQ,CAAC,WAAW,OAAO,IAAI,2CAA2C,CAAC;AAC3G,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,MAAqB,EAAE,GAAuB;IAC9E,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,OAA8B,CAAC,CAAC;IAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAI,MAAM,CAAC,QAA0C,EAAE,IAAI,CAAC;IAC1E,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC;IAChF,OAAO,GAAG,KAAK,MAAM,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human sign-off on the phases the person wants to see.
|
|
3
|
+
*
|
|
4
|
+
* The gate can prove a feature has acceptance criteria. It cannot prove they
|
|
5
|
+
* are the right criteria, and no amount of determinism will make it able to.
|
|
6
|
+
* These are the phases where the run stops and asks.
|
|
7
|
+
*
|
|
8
|
+
* The subtle half of this module is the *rejection*. A gate is deterministic,
|
|
9
|
+
* so the instant a phase is sent back it passes again and the run would ask
|
|
10
|
+
* the identical question about the identical artefact, forever. A rejection is
|
|
11
|
+
* therefore pinned to what the project looked like when it was made.
|
|
12
|
+
*
|
|
13
|
+
* Which phases stop is a mode per phase now, not a three-phase switch — see
|
|
14
|
+
* `tests/workflow.test.ts` for the workflows those modes come from.
|
|
15
|
+
*/
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { approvalNotes, approvedPhases, describeApproval, isApprovable, loadRejection, needsApproval, rejectionStandsFor, renderApprovalRequest, requestApproval, resolveApproval, } from "../src/approval.js";
|
|
21
|
+
import { defaultConfig, loadConfig, saveConfig } from "../src/core/config.js";
|
|
22
|
+
function project(mutate = () => { }) {
|
|
23
|
+
const dir = mkdtempSync(join(tmpdir(), "approval-test-"));
|
|
24
|
+
mkdirSync(join(dir, "harness"), { recursive: true });
|
|
25
|
+
const config = defaultConfig();
|
|
26
|
+
mutate(config);
|
|
27
|
+
const saved = saveConfig(dir, config);
|
|
28
|
+
assert.equal(saved.ok, true, saved.error ?? "saveConfig failed");
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
// ── which phases can be a checkpoint ───────────────────────────────────────
|
|
32
|
+
{
|
|
33
|
+
// Every phase can stop for a human. Someone may not care about the plan and
|
|
34
|
+
// care very much about the review; the tool should not have an opinion.
|
|
35
|
+
for (const p of ["research", "define", "plan", "build", "verify", "simplify", "review", "ship"]) {
|
|
36
|
+
assert.equal(isApprovable(p), true, `${p} should be signable`);
|
|
37
|
+
}
|
|
38
|
+
// INIT is plumbing — there is nothing there for a human to judge.
|
|
39
|
+
assert.equal(isApprovable("init"), false);
|
|
40
|
+
assert.equal(isApprovable(null), false);
|
|
41
|
+
const config = defaultConfig();
|
|
42
|
+
config.phases = { enabled: ["define", "plan", "build", "verify", "review", "ship"] };
|
|
43
|
+
config.phaseModes = { define: "copilot", plan: "copilot", review: "copilot" };
|
|
44
|
+
assert.equal(needsApproval(config, "define"), true);
|
|
45
|
+
assert.equal(needsApproval(config, "build"), false);
|
|
46
|
+
assert.equal(needsApproval(config, "review"), true, "a late phase is signable like any other");
|
|
47
|
+
assert.deepEqual(approvedPhases(config), ["define", "plan", "review"]);
|
|
48
|
+
// A config written before this feature existed has no `phaseModes` key.
|
|
49
|
+
const legacy = defaultConfig();
|
|
50
|
+
delete legacy.phaseModes;
|
|
51
|
+
assert.equal(needsApproval(legacy, "define"), false, "a missing policy is 'sign nothing', not a crash");
|
|
52
|
+
assert.deepEqual(approvedPhases(legacy), []);
|
|
53
|
+
console.log("✓ every phase but INIT can be a checkpoint, and a missing policy signs nothing");
|
|
54
|
+
}
|
|
55
|
+
// ── what the human is shown ────────────────────────────────────────────────
|
|
56
|
+
{
|
|
57
|
+
for (const phase of [
|
|
58
|
+
"research",
|
|
59
|
+
"define",
|
|
60
|
+
"plan",
|
|
61
|
+
"build",
|
|
62
|
+
"verify",
|
|
63
|
+
"simplify",
|
|
64
|
+
"review",
|
|
65
|
+
"ship",
|
|
66
|
+
]) {
|
|
67
|
+
const request = describeApproval(phase);
|
|
68
|
+
assert.ok(request.prompt.length > 20, `${phase} asks a real question`);
|
|
69
|
+
assert.ok(request.artifacts.length > 0, `${phase} says what to look at`);
|
|
70
|
+
const text = renderApprovalRequest(request);
|
|
71
|
+
assert.match(text, new RegExp(phase.toUpperCase()));
|
|
72
|
+
assert.match(text, /\/infinity:approve/, "and how to answer");
|
|
73
|
+
assert.match(text, /sends it back/, "including how to say no usefully");
|
|
74
|
+
}
|
|
75
|
+
console.log("✓ an approval request says what to look at and how to answer");
|
|
76
|
+
}
|
|
77
|
+
// ── approving, and rejecting with a reason ─────────────────────────────────
|
|
78
|
+
{
|
|
79
|
+
const dir = project((c) => {
|
|
80
|
+
c.currentPhase = "define";
|
|
81
|
+
c.phaseModes = { define: "copilot", plan: "copilot" };
|
|
82
|
+
});
|
|
83
|
+
assert.deepEqual(resolveApproval(dir), { ok: false, error: "Nothing is waiting for approval." });
|
|
84
|
+
assert.equal(requestApproval(dir, "define").ok, true);
|
|
85
|
+
assert.equal(loadConfig(dir).config.awaitingApproval, "define");
|
|
86
|
+
const rejected = resolveApproval(dir, "the criteria say nothing about refunds", "fingerprint-a");
|
|
87
|
+
assert.equal(rejected.ok && rejected.approved, false);
|
|
88
|
+
assert.equal(rejected.ok && !rejected.approved && rejected.note, "the criteria say nothing about refunds");
|
|
89
|
+
const afterReject = loadConfig(dir).config;
|
|
90
|
+
assert.equal(afterReject.awaitingApproval, null, "a rejection un-parks the run so the agent can fix it");
|
|
91
|
+
assert.equal(approvalNotes(afterReject, "define").length, 1, "and the reason is kept for the brief");
|
|
92
|
+
const rejection = loadRejection(afterReject);
|
|
93
|
+
assert.equal(rejection?.phase, "define");
|
|
94
|
+
assert.equal(rejection?.fingerprint, "fingerprint-a");
|
|
95
|
+
// The nag guard: same project, same question. Different project, ask again.
|
|
96
|
+
assert.ok(rejectionStandsFor(afterReject, "define", "fingerprint-a"), "nothing changed — do not re-ask");
|
|
97
|
+
assert.equal(rejectionStandsFor(afterReject, "define", "fingerprint-b"), null, "the agent moved — ask again");
|
|
98
|
+
assert.equal(rejectionStandsFor(afterReject, "plan", "fingerprint-a"), null, "a different phase is a different question");
|
|
99
|
+
requestApproval(dir, "define");
|
|
100
|
+
const approved = resolveApproval(dir);
|
|
101
|
+
assert.equal(approved.ok && approved.approved, true);
|
|
102
|
+
const afterApprove = loadConfig(dir).config;
|
|
103
|
+
assert.equal(afterApprove.awaitingApproval, null);
|
|
104
|
+
assert.equal(loadRejection(afterApprove), null, "approving answers the rejection");
|
|
105
|
+
assert.deepEqual(approvalNotes(afterApprove, "define"), [], "and clears the note — an agent told to fix a complaint that was already resolved goes in circles");
|
|
106
|
+
rmSync(dir, { recursive: true, force: true });
|
|
107
|
+
console.log("✓ a rejection carries a reason, pins itself to the project, and is cleared by approval");
|
|
108
|
+
}
|
|
109
|
+
// ── notes from one phase do not leak into another ──────────────────────────
|
|
110
|
+
{
|
|
111
|
+
const dir = project((c) => {
|
|
112
|
+
c.currentPhase = "define";
|
|
113
|
+
c.phaseModes = { define: "copilot", plan: "copilot" };
|
|
114
|
+
});
|
|
115
|
+
requestApproval(dir, "define");
|
|
116
|
+
resolveApproval(dir, "define is wrong", "fp1");
|
|
117
|
+
requestApproval(dir, "plan");
|
|
118
|
+
resolveApproval(dir, "plan is wrong too", "fp2");
|
|
119
|
+
const config = loadConfig(dir).config;
|
|
120
|
+
assert.equal(approvalNotes(config).length, 2, "both are remembered");
|
|
121
|
+
assert.equal(approvalNotes(config, "define")[0]?.note, "define is wrong");
|
|
122
|
+
assert.equal(approvalNotes(config, "plan")[0]?.note, "plan is wrong too");
|
|
123
|
+
requestApproval(dir, "plan");
|
|
124
|
+
resolveApproval(dir);
|
|
125
|
+
const after = loadConfig(dir).config;
|
|
126
|
+
assert.equal(approvalNotes(after, "plan").length, 0, "approving PLAN clears PLAN's note");
|
|
127
|
+
assert.equal(approvalNotes(after, "define").length, 1, "and leaves DEFINE's alone");
|
|
128
|
+
rmSync(dir, { recursive: true, force: true });
|
|
129
|
+
console.log("✓ approval notes are per-phase and do not leak");
|
|
130
|
+
}
|
|
131
|
+
// ── a broken config is reported, not thrown ────────────────────────────────
|
|
132
|
+
{
|
|
133
|
+
const dir = mkdtempSync(join(tmpdir(), "approval-broken-"));
|
|
134
|
+
mkdirSync(join(dir, "harness"), { recursive: true });
|
|
135
|
+
writeFileSync(join(dir, "harness", "config.json"), "{ not json", "utf-8");
|
|
136
|
+
const requested = requestApproval(dir, "define");
|
|
137
|
+
assert.equal(requested.ok, false);
|
|
138
|
+
assert.ok(requested.error);
|
|
139
|
+
const resolved = resolveApproval(dir, "anything");
|
|
140
|
+
assert.equal(resolved.ok, false);
|
|
141
|
+
rmSync(dir, { recursive: true, force: true });
|
|
142
|
+
console.log("✓ an unreadable config produces an error, never an exception in a lifecycle hook");
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=approval.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.test.js","sourceRoot":"","sources":["../../tests/approval.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EACL,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAE9E,SAAS,OAAO,CAAC,SAAwD,GAAG,EAAE,GAAE,CAAC;IAC/E,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC1D,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,CAAC,MAAM,CAAC,CAAC;IACf,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,IAAI,mBAAmB,CAAC,CAAC;IACjE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8EAA8E;AAC9E,CAAC;IACC,4EAA4E;IAC5E,wEAAwE;IACxE,KAAK,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAU,EAAE,CAAC;QACzG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACjE,CAAC;IACD,kEAAkE;IAClE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;IAExC,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,CAAC,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;IACrF,MAAM,CAAC,UAAU,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC9E,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,yCAAyC,CAAC,CAAC;IAC/F,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEvE,wEAAwE;IACxE,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,OAAQ,MAAkC,CAAC,UAAU,CAAC;IACtD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,iDAAiD,CAAC,CAAC;IACxG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,gFAAgF,CAAC,CAAC;AAChG,CAAC;AAED,8EAA8E;AAC9E,CAAC;IACC,KAAK,MAAM,KAAK,IAAI;QAClB,UAAU;QACV,QAAQ;QACR,MAAM;QACN,OAAO;QACP,QAAQ;QACR,UAAU;QACV,QAAQ;QACR,MAAM;KACE,EAAE,CAAC;QACX,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,GAAG,KAAK,uBAAuB,CAAC,CAAC;QACvE,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,KAAK,uBAAuB,CAAC,CAAC;QACzE,MAAM,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,oBAAoB,EAAE,mBAAmB,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,EAAE,kCAAkC,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;AAC9E,CAAC;AAED,8EAA8E;AAC9E,CAAC;IACC,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACxB,CAAC,CAAC,YAAY,GAAG,QAAQ,CAAC;QAC1B,CAAC,CAAC,UAAU,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kCAAkC,EAAE,CAAC,CAAC;IAEjG,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACtD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IAEhE,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,EAAE,wCAAwC,EAAE,eAAe,CAAC,CAAC;IACjG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,wCAAwC,CAAC,CAAC;IAE3G,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,IAAI,EAAE,sDAAsD,CAAC,CAAC;IACzG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,sCAAsC,CAAC,CAAC;IAErG,MAAM,SAAS,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;IAEtD,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,kBAAkB,CAAC,WAAW,EAAE,QAAQ,EAAE,eAAe,CAAC,EAAE,iCAAiC,CAAC,CAAC;IACzG,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,WAAW,EAAE,QAAQ,EAAE,eAAe,CAAC,EAAE,IAAI,EAAE,6BAA6B,CAAC,CAAC;IAC9G,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,IAAI,EAAE,2CAA2C,CAAC,CAAC;IAE1H,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAErD,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,iCAAiC,CAAC,CAAC;IACnF,MAAM,CAAC,SAAS,CACd,aAAa,CAAC,YAAY,EAAE,QAAQ,CAAC,EACrC,EAAE,EACF,kGAAkG,CACnG,CAAC;IAEF,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,wFAAwF,CAAC,CAAC;AACxG,CAAC;AAED,8EAA8E;AAC9E,CAAC;IACC,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACxB,CAAC,CAAC,YAAY,GAAG,QAAQ,CAAC;QAC1B,CAAC,CAAC,UAAU,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC/B,eAAe,CAAC,GAAG,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAAC;IAC/C,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7B,eAAe,CAAC,GAAG,EAAE,mBAAmB,EAAE,KAAK,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,qBAAqB,CAAC,CAAC;IACrE,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC;IAC1E,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,mBAAmB,CAAC,CAAC;IAE1E,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7B,eAAe,CAAC,GAAG,CAAC,CAAC;IACrB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,mCAAmC,CAAC,CAAC;IAC1F,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAEpF,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;AAChE,CAAC;AAED,8EAA8E;AAC9E,CAAC;IACC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC5D,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAE1E,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAE3B,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAEjC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,kFAAkF,CAAC,CAAC;AAClG,CAAC"}
|