agent-conveyor 0.1.4 → 0.1.6
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 +22 -0
- package/dist/cli/typescript-runtime.js +317 -7
- package/dist/cli/typescript-runtime.js.map +1 -1
- package/dist/runtime/manager-config.d.ts +1 -0
- package/dist/runtime/manager-config.js +2 -1
- package/dist/runtime/manager-config.js.map +1 -1
- package/dist/state/database.js +2 -2
- package/dist/state/schema-v23.d.ts +1 -0
- package/dist/state/{schema-v22.js → schema-v23.js} +3 -2
- package/dist/state/{schema-v22.js.map → schema-v23.js.map} +1 -1
- package/dist/state/sqlite-contract.d.ts +1 -1
- package/dist/state/sqlite-contract.js +1 -1
- package/docs/landing-page.html +702 -0
- package/docs/manager-recipes.md +303 -0
- package/package.json +5 -1
- package/scripts/serve-landing-page.mjs +39 -0
- package/skills/manage-codex-workers/SKILL.md +33 -0
- package/dist/state/schema-v22.d.ts +0 -1
package/README.md
CHANGED
|
@@ -132,6 +132,19 @@ command exits 0 and the JSON result reports `"ok": true`.
|
|
|
132
132
|
Before publishing `agent-conveyor` to npm, use
|
|
133
133
|
[`docs/package-release.md`](docs/package-release.md).
|
|
134
134
|
|
|
135
|
+
For common manager setups, start with
|
|
136
|
+
[`docs/manager-recipes.md`](docs/manager-recipes.md). It maps natural-language
|
|
137
|
+
requests such as GoalBuddy conveyor runs, test coverage loops, UX polish loops,
|
|
138
|
+
what-next nudging, and PR/CI/merge Ralph loops to concrete `manager-config`
|
|
139
|
+
settings, permissions, evidence gates, cleanup behavior, and example
|
|
140
|
+
manager/Dispatch/worker interactions. Use `conveyor manager-recipes --list`
|
|
141
|
+
or `conveyor manager-recipes --show goalbuddy-conveyor --json` for a
|
|
142
|
+
machine-readable setup preview.
|
|
143
|
+
For a package-facing overview of these modes, open
|
|
144
|
+
[`docs/landing-page.html`](docs/landing-page.html) locally or host it as a
|
|
145
|
+
static landing page. From the repo, `npm run docs:landing` serves it at
|
|
146
|
+
`http://127.0.0.1:8765/`.
|
|
147
|
+
|
|
135
148
|
After install, the intended Codex app entry point is natural language. Open a
|
|
136
149
|
new Codex app session in the target repo and say:
|
|
137
150
|
|
|
@@ -163,6 +176,10 @@ Use `conveyor qa-plan adversarial-triggers` to verify natural-language
|
|
|
163
176
|
manager prompts activate Ralph-loop adversarial gates.
|
|
164
177
|
Use `conveyor qa-plan goalbuddy-conveyor` when a broad request should become
|
|
165
178
|
sequential GoalBuddy child boards with PR/CI/merge receipts.
|
|
179
|
+
Before cutting a manager loose, have it resolve the freeform setup request to a
|
|
180
|
+
named recipe from `docs/manager-recipes.md` or an explicit `custom` setup, then
|
|
181
|
+
show the saved mode, permissions, evidence gates, cleanup policy, and disallowed
|
|
182
|
+
actions.
|
|
166
183
|
For manual QA, launch the dashboard with Dispatch enforcement so the page can
|
|
167
184
|
show live proof:
|
|
168
185
|
|
|
@@ -411,6 +428,11 @@ tmux attach -t codex-live-test
|
|
|
411
428
|
Use `--require` when a manager command should fail closed. Use
|
|
412
429
|
`--require-handoff` before worker compact/clear style instructions so visible
|
|
413
430
|
context is persisted first.
|
|
431
|
+
- `manager-recipes --list|--show RECIPE [--json]` — List or show built-in
|
|
432
|
+
manager setup recipes. Recipe JSON includes the supervision mode,
|
|
433
|
+
permissions, expected tools, epilogues, evidence gates, cleanup behavior,
|
|
434
|
+
disallowed actions, locked setup summary template, and suggested
|
|
435
|
+
`manager-config` command. Use this before cutting a manager loose.
|
|
414
436
|
- `worker-ack <task> --from-stdin|--json [--correlation-id ID]` /
|
|
415
437
|
`manager-ack <task> --from-stdin|--json [--correlation-id ID]` — Persist or
|
|
416
438
|
read the latest structured acknowledgement from the worker or manager. Acks
|
|
@@ -109,6 +109,9 @@ export function runTypescriptRuntimeCommand(options) {
|
|
|
109
109
|
if (parsed.command === "ralph-loop-presets") {
|
|
110
110
|
return runRalphLoopPresetsCommand(parsed, options);
|
|
111
111
|
}
|
|
112
|
+
if (parsed.command === "manager-recipes") {
|
|
113
|
+
return runManagerRecipesCommand(parsed);
|
|
114
|
+
}
|
|
112
115
|
if (parsed.command === "loop-triggers") {
|
|
113
116
|
return runLoopTriggersCommand(parsed, options);
|
|
114
117
|
}
|
|
@@ -499,6 +502,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
499
502
|
managerObjective: null,
|
|
500
503
|
managerPermissionsJson: null,
|
|
501
504
|
managerPermit: [],
|
|
505
|
+
managerRecipe: null,
|
|
502
506
|
managerReference: [],
|
|
503
507
|
managerRequireAcks: false,
|
|
504
508
|
managerTool: [],
|
|
@@ -593,6 +597,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
593
597
|
&& command !== "runs"
|
|
594
598
|
&& command !== "loop-templates"
|
|
595
599
|
&& command !== "ralph-loop-presets"
|
|
600
|
+
&& command !== "manager-recipes"
|
|
596
601
|
&& command !== "loop-triggers"
|
|
597
602
|
&& command !== "manager-permission"
|
|
598
603
|
&& command !== "continuation"
|
|
@@ -946,7 +951,7 @@ function parseRuntimeArgs(args, env) {
|
|
|
946
951
|
index += 1;
|
|
947
952
|
}
|
|
948
953
|
else if (arg === "--show") {
|
|
949
|
-
if (command !== "runs" && command !== "loop-templates" && command !== "ralph-loop-presets") {
|
|
954
|
+
if (command !== "runs" && command !== "loop-templates" && command !== "ralph-loop-presets" && command !== "manager-recipes") {
|
|
950
955
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --show", explicit, flags, task };
|
|
951
956
|
}
|
|
952
957
|
const value = valueAfter(queue, index, arg);
|
|
@@ -1417,6 +1422,17 @@ function parseRuntimeArgs(args, env) {
|
|
|
1417
1422
|
flags.managerMode = value.value;
|
|
1418
1423
|
index += 1;
|
|
1419
1424
|
}
|
|
1425
|
+
else if (arg === "--manager-recipe") {
|
|
1426
|
+
if (command !== "pair") {
|
|
1427
|
+
return { command, enabled, error: "Unsupported TypeScript runtime option: --manager-recipe", explicit, flags, task };
|
|
1428
|
+
}
|
|
1429
|
+
const value = valueAfter(queue, index, arg);
|
|
1430
|
+
if (value.error) {
|
|
1431
|
+
return { command, enabled, error: value.error, explicit, flags, task };
|
|
1432
|
+
}
|
|
1433
|
+
flags.managerRecipe = value.value;
|
|
1434
|
+
index += 1;
|
|
1435
|
+
}
|
|
1420
1436
|
else if (arg === "--manager-objective") {
|
|
1421
1437
|
if (command !== "pair") {
|
|
1422
1438
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --manager-objective", explicit, flags, task };
|
|
@@ -1551,6 +1567,17 @@ function parseRuntimeArgs(args, env) {
|
|
|
1551
1567
|
flags.managerObjective = value.value;
|
|
1552
1568
|
index += 1;
|
|
1553
1569
|
}
|
|
1570
|
+
else if (arg === "--recipe") {
|
|
1571
|
+
if (command !== "manager-config") {
|
|
1572
|
+
return { command, enabled, error: "Unsupported TypeScript runtime option: --recipe", explicit, flags, task };
|
|
1573
|
+
}
|
|
1574
|
+
const value = valueAfter(queue, index, arg);
|
|
1575
|
+
if (value.error) {
|
|
1576
|
+
return { command, enabled, error: value.error, explicit, flags, task };
|
|
1577
|
+
}
|
|
1578
|
+
flags.managerRecipe = value.value;
|
|
1579
|
+
index += 1;
|
|
1580
|
+
}
|
|
1554
1581
|
else if (arg === "--guideline") {
|
|
1555
1582
|
if (command !== "manager-config") {
|
|
1556
1583
|
return { command, enabled, error: "Unsupported TypeScript runtime option: --guideline", explicit, flags, task };
|
|
@@ -3110,6 +3137,43 @@ function runRalphLoopPresetsCommand(parsed, options) {
|
|
|
3110
3137
|
database.close();
|
|
3111
3138
|
}
|
|
3112
3139
|
}
|
|
3140
|
+
function runManagerRecipesCommand(parsed) {
|
|
3141
|
+
const unsupportedOptions = unsupportedLoopCommandOptions(parsed, {
|
|
3142
|
+
allowedFlags: new Set(["json", "list", "show"]),
|
|
3143
|
+
commandName: "manager-recipes",
|
|
3144
|
+
});
|
|
3145
|
+
if (unsupportedOptions) {
|
|
3146
|
+
return unsupportedRuntimeResult(parsed, unsupportedOptions);
|
|
3147
|
+
}
|
|
3148
|
+
const actionCount = [parsed.flags.list, parsed.flags.show !== null].filter(Boolean).length;
|
|
3149
|
+
if (actionCount !== 1) {
|
|
3150
|
+
return errorResult("Choose one of --list or --show");
|
|
3151
|
+
}
|
|
3152
|
+
if (parsed.flags.list) {
|
|
3153
|
+
const recipes = listManagerRecipes();
|
|
3154
|
+
if (parsed.flags.json) {
|
|
3155
|
+
return jsonResult({ recipes });
|
|
3156
|
+
}
|
|
3157
|
+
return textResult(recipes.map((recipe) => {
|
|
3158
|
+
const loop = recipe.loop_template ? ` loop=${recipe.loop_template}` : "";
|
|
3159
|
+
return `${recipe.name}\tmode=${recipe.mode}${loop}\t${recipe.description}`;
|
|
3160
|
+
}));
|
|
3161
|
+
}
|
|
3162
|
+
const recipe = managerRecipeSummary(parsed.flags.show ?? "");
|
|
3163
|
+
if (parsed.flags.json) {
|
|
3164
|
+
return jsonResult({ recipe });
|
|
3165
|
+
}
|
|
3166
|
+
const lines = [
|
|
3167
|
+
String(recipe.locked_summary_template),
|
|
3168
|
+
"",
|
|
3169
|
+
"manager config command:",
|
|
3170
|
+
` ${recipe.manager_config_command.map(shellQuote).join(" ")}`,
|
|
3171
|
+
];
|
|
3172
|
+
if (recipe.loop_template) {
|
|
3173
|
+
lines.push("", `loop template: ${recipe.loop_template}`);
|
|
3174
|
+
}
|
|
3175
|
+
return textResult(lines);
|
|
3176
|
+
}
|
|
3113
3177
|
function runLoopTriggersCommand(parsed, _options) {
|
|
3114
3178
|
const unsupported = unsupportedMigratedProofCliOptions(parsed);
|
|
3115
3179
|
if (unsupported) {
|
|
@@ -5733,6 +5797,7 @@ function runPairCommand(parsed, options) {
|
|
|
5733
5797
|
managerObjective: parsed.flags.managerObjective,
|
|
5734
5798
|
managerPermissionsJson: parsed.flags.managerPermissionsJson,
|
|
5735
5799
|
managerPermit: parsed.flags.managerPermit,
|
|
5800
|
+
managerRecipe: parsed.flags.managerRecipe,
|
|
5736
5801
|
managerReference: parsed.flags.managerReference,
|
|
5737
5802
|
managerRequireAcks: parsed.flags.managerRequireAcks,
|
|
5738
5803
|
managerTool: parsed.flags.managerTool,
|
|
@@ -6012,6 +6077,7 @@ function emitPairTelemetry(database, options) {
|
|
|
6012
6077
|
function ensurePairManagerConfig(database, options) {
|
|
6013
6078
|
const existing = managerConfigSync(database, options.taskId);
|
|
6014
6079
|
const requested = options.managerMode !== null
|
|
6080
|
+
|| options.managerRecipe !== null
|
|
6015
6081
|
|| options.managerObjective !== null
|
|
6016
6082
|
|| options.managerGuideline.length > 0
|
|
6017
6083
|
|| options.managerAcceptance.length > 0
|
|
@@ -6032,6 +6098,7 @@ function ensurePairManagerConfig(database, options) {
|
|
|
6032
6098
|
if (supervisionMode !== "light" && supervisionMode !== "guided" && supervisionMode !== "strict") {
|
|
6033
6099
|
throw new Error("manager_mode must be light, guided, or strict");
|
|
6034
6100
|
}
|
|
6101
|
+
const recipeName = cleanManagerRecipeName(options.managerRecipe, existing?.recipe_name ?? (existing === null ? "custom" : null));
|
|
6035
6102
|
const objective = options.managerObjective !== null ? options.managerObjective : existing?.objective ?? null;
|
|
6036
6103
|
const guidelines = options.managerGuideline.length > 0 ? options.managerGuideline : existing?.guidelines ?? [];
|
|
6037
6104
|
const acceptanceCriteria = options.managerAcceptance.length > 0
|
|
@@ -6052,13 +6119,14 @@ function ensurePairManagerConfig(database, options) {
|
|
|
6052
6119
|
const requireAcks = options.managerRequireAcks || (existing?.require_acks ?? false);
|
|
6053
6120
|
database.prepare(`
|
|
6054
6121
|
insert into manager_configs(
|
|
6055
|
-
task_id, supervision_mode, objective, guidelines_json,
|
|
6122
|
+
task_id, recipe_name, supervision_mode, objective, guidelines_json,
|
|
6056
6123
|
acceptance_criteria_json, reference_paths_json, permissions_json,
|
|
6057
6124
|
tools_json, epilogues_json, nudge_on_completion, require_acks,
|
|
6058
6125
|
revision, created_at, updated_at
|
|
6059
6126
|
)
|
|
6060
|
-
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
|
|
6127
|
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
|
|
6061
6128
|
on conflict(task_id) do update set
|
|
6129
|
+
recipe_name = excluded.recipe_name,
|
|
6062
6130
|
supervision_mode = excluded.supervision_mode,
|
|
6063
6131
|
objective = excluded.objective,
|
|
6064
6132
|
guidelines_json = excluded.guidelines_json,
|
|
@@ -6070,6 +6138,7 @@ function ensurePairManagerConfig(database, options) {
|
|
|
6070
6138
|
nudge_on_completion = excluded.nudge_on_completion,
|
|
6071
6139
|
require_acks = excluded.require_acks,
|
|
6072
6140
|
revision = case when
|
|
6141
|
+
manager_configs.recipe_name is not excluded.recipe_name or
|
|
6073
6142
|
manager_configs.supervision_mode is not excluded.supervision_mode or
|
|
6074
6143
|
manager_configs.objective is not excluded.objective or
|
|
6075
6144
|
manager_configs.guidelines_json is not excluded.guidelines_json or
|
|
@@ -6082,7 +6151,7 @@ function ensurePairManagerConfig(database, options) {
|
|
|
6082
6151
|
manager_configs.require_acks is not excluded.require_acks
|
|
6083
6152
|
then manager_configs.revision + 1 else manager_configs.revision end,
|
|
6084
6153
|
updated_at = excluded.updated_at
|
|
6085
|
-
`).run(options.taskId, supervisionMode, objective, stableJson(guidelines), stableJson(acceptanceCriteria), stableJson(referencePaths), stableJson(permissions), stableJson(tools), stableJson(epilogues), nudgeOnCompletion, requireAcks ? 1 : 0, options.timestamp, options.timestamp);
|
|
6154
|
+
`).run(options.taskId, recipeName, supervisionMode, objective, stableJson(guidelines), stableJson(acceptanceCriteria), stableJson(referencePaths), stableJson(permissions), stableJson(tools), stableJson(epilogues), nudgeOnCompletion, requireAcks ? 1 : 0, options.timestamp, options.timestamp);
|
|
6086
6155
|
const config = managerConfigSync(database, options.taskId);
|
|
6087
6156
|
if (config === null) {
|
|
6088
6157
|
throw new Error(`manager config was not recorded for task ${options.taskId}`);
|
|
@@ -11801,6 +11870,7 @@ function insertCompatEventSync(database, options) {
|
|
|
11801
11870
|
}
|
|
11802
11871
|
function managerConfigMutationRequested(parsed) {
|
|
11803
11872
|
return parsed.flags.managerMode !== null
|
|
11873
|
+
|| parsed.flags.managerRecipe !== null
|
|
11804
11874
|
|| parsed.flags.managerObjective !== null
|
|
11805
11875
|
|| parsed.flags.managerGuideline.length > 0
|
|
11806
11876
|
|| parsed.flags.managerAcceptance.length > 0
|
|
@@ -11819,6 +11889,7 @@ function upsertManagerConfigFromParsed(database, options) {
|
|
|
11819
11889
|
const parsed = options.parsed;
|
|
11820
11890
|
const existing = options.existing;
|
|
11821
11891
|
const supervisionMode = parsed.flags.managerMode ?? existing?.supervision_mode ?? "guided";
|
|
11892
|
+
const recipeName = cleanManagerRecipeName(parsed.flags.managerRecipe, existing?.recipe_name ?? null);
|
|
11822
11893
|
const objective = parsed.flags.managerObjective !== null ? parsed.flags.managerObjective : existing?.objective ?? null;
|
|
11823
11894
|
const guidelines = parsed.flags.managerGuideline.length > 0 ? parsed.flags.managerGuideline : existing?.guidelines ?? [];
|
|
11824
11895
|
const acceptanceCriteria = parsed.flags.managerAcceptance.length > 0
|
|
@@ -11839,13 +11910,14 @@ function upsertManagerConfigFromParsed(database, options) {
|
|
|
11839
11910
|
const requireAcks = parsed.flags.managerRequireAcks || (existing?.require_acks ?? false);
|
|
11840
11911
|
database.prepare(`
|
|
11841
11912
|
insert into manager_configs(
|
|
11842
|
-
task_id, supervision_mode, objective, guidelines_json,
|
|
11913
|
+
task_id, recipe_name, supervision_mode, objective, guidelines_json,
|
|
11843
11914
|
acceptance_criteria_json, reference_paths_json, permissions_json,
|
|
11844
11915
|
tools_json, epilogues_json, nudge_on_completion, require_acks,
|
|
11845
11916
|
revision, created_at, updated_at
|
|
11846
11917
|
)
|
|
11847
|
-
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
|
|
11918
|
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
|
|
11848
11919
|
on conflict(task_id) do update set
|
|
11920
|
+
recipe_name = excluded.recipe_name,
|
|
11849
11921
|
supervision_mode = excluded.supervision_mode,
|
|
11850
11922
|
objective = excluded.objective,
|
|
11851
11923
|
guidelines_json = excluded.guidelines_json,
|
|
@@ -11857,6 +11929,7 @@ function upsertManagerConfigFromParsed(database, options) {
|
|
|
11857
11929
|
nudge_on_completion = excluded.nudge_on_completion,
|
|
11858
11930
|
require_acks = excluded.require_acks,
|
|
11859
11931
|
revision = case when
|
|
11932
|
+
manager_configs.recipe_name is not excluded.recipe_name or
|
|
11860
11933
|
manager_configs.supervision_mode is not excluded.supervision_mode or
|
|
11861
11934
|
manager_configs.objective is not excluded.objective or
|
|
11862
11935
|
manager_configs.guidelines_json is not excluded.guidelines_json or
|
|
@@ -11869,7 +11942,7 @@ function upsertManagerConfigFromParsed(database, options) {
|
|
|
11869
11942
|
manager_configs.require_acks is not excluded.require_acks
|
|
11870
11943
|
then manager_configs.revision + 1 else manager_configs.revision end,
|
|
11871
11944
|
updated_at = excluded.updated_at
|
|
11872
|
-
`).run(options.taskId, supervisionMode, objective, stableJson(guidelines), stableJson(acceptanceCriteria), stableJson(referencePaths), stableJson(permissions), stableJson(tools), stableJson(epilogues), nudgeOnCompletion, requireAcks ? 1 : 0, options.timestamp, options.timestamp);
|
|
11945
|
+
`).run(options.taskId, recipeName, supervisionMode, objective, stableJson(guidelines), stableJson(acceptanceCriteria), stableJson(referencePaths), stableJson(permissions), stableJson(tools), stableJson(epilogues), nudgeOnCompletion, requireAcks ? 1 : 0, options.timestamp, options.timestamp);
|
|
11873
11946
|
const config = managerConfigSync(database, options.taskId);
|
|
11874
11947
|
if (config === null) {
|
|
11875
11948
|
throw new Error(`manager config was not recorded for task ${options.taskId}`);
|
|
@@ -11892,6 +11965,16 @@ function cleanManagerNudgeOnCompletion(value) {
|
|
|
11892
11965
|
}
|
|
11893
11966
|
return value;
|
|
11894
11967
|
}
|
|
11968
|
+
function cleanManagerRecipeName(value, existing) {
|
|
11969
|
+
if (value === null) {
|
|
11970
|
+
return existing;
|
|
11971
|
+
}
|
|
11972
|
+
const normalized = normalizeManagerRecipeName(value);
|
|
11973
|
+
if (normalized === "custom") {
|
|
11974
|
+
return null;
|
|
11975
|
+
}
|
|
11976
|
+
return managerRecipeDefinition(normalized).name;
|
|
11977
|
+
}
|
|
11895
11978
|
function managerPermissionWarnings(permissions) {
|
|
11896
11979
|
const warnings = [];
|
|
11897
11980
|
for (const [key, value] of Object.entries(permissions ?? {})) {
|
|
@@ -13418,6 +13501,7 @@ function isDefaultRuntimeCommand(command) {
|
|
|
13418
13501
|
|| command === "loop-templates"
|
|
13419
13502
|
|| command === "loop-triggers"
|
|
13420
13503
|
|| command === "ralph-loop-presets"
|
|
13504
|
+
|| command === "manager-recipes"
|
|
13421
13505
|
|| command === "qa-plan"
|
|
13422
13506
|
|| command === "qa-run"
|
|
13423
13507
|
|| command === "start"
|
|
@@ -15170,6 +15254,232 @@ function ralphLoopPresetMetadata(name, options) {
|
|
|
15170
15254
|
ralphLoopPreset(name);
|
|
15171
15255
|
return loopTemplateMetadata(name, options);
|
|
15172
15256
|
}
|
|
15257
|
+
const MANAGER_RECIPES = {
|
|
15258
|
+
"goalbuddy-conveyor": {
|
|
15259
|
+
acceptance: [
|
|
15260
|
+
"Every child board has PR/CI/merge, satisfied_on_main, or blocker proof.",
|
|
15261
|
+
"Parent state records final status for every child.",
|
|
15262
|
+
],
|
|
15263
|
+
cleanup: "compact between child boards after saved handoff",
|
|
15264
|
+
description: "Run broad work as one parent GoalBuddy board with one active child board at a time.",
|
|
15265
|
+
disallowedActions: [
|
|
15266
|
+
"Do not run two child boards at once.",
|
|
15267
|
+
"Do not merge without green CI.",
|
|
15268
|
+
"Do not compact or clear before a saved handoff.",
|
|
15269
|
+
],
|
|
15270
|
+
displayName: "GoalBuddy Conveyor",
|
|
15271
|
+
epilogues: ["draft-pr", "record-handoff"],
|
|
15272
|
+
evidenceGates: [
|
|
15273
|
+
"child receipt with focused verification",
|
|
15274
|
+
"adversarial review",
|
|
15275
|
+
"PR/CI/merge or satisfied_on_main proof",
|
|
15276
|
+
"parent receipt update before the next child",
|
|
15277
|
+
],
|
|
15278
|
+
guidelines: [
|
|
15279
|
+
"Keep exactly one child board active at a time.",
|
|
15280
|
+
"Before activating the next child, update the parent receipt.",
|
|
15281
|
+
],
|
|
15282
|
+
loopTemplate: null,
|
|
15283
|
+
mode: "strict",
|
|
15284
|
+
name: "goalbuddy-conveyor",
|
|
15285
|
+
objective: "Run a one-child-at-a-time GoalBuddy conveyor until every child is merged, proven satisfied, or blocked with evidence.",
|
|
15286
|
+
permissions: ["repo.open_pr", "repo.merge_green_pr", "worker_session.compact", "worker_session.clear"],
|
|
15287
|
+
supportPatterns: ["Inbox / No-Tmux App Loop", "Recovery / Resume / Handoff"],
|
|
15288
|
+
tools: ["verification.run_tests", "context.fetch_prs"],
|
|
15289
|
+
},
|
|
15290
|
+
"nudge-whats-next": {
|
|
15291
|
+
acceptance: [
|
|
15292
|
+
"Accepted criteria are satisfied or explicitly deferred.",
|
|
15293
|
+
"The final summary names commands run, changed files, and residual risk.",
|
|
15294
|
+
],
|
|
15295
|
+
cleanup: "off by default",
|
|
15296
|
+
description: "Observe, ask useful status questions, negotiate criteria, and keep permissions minimal.",
|
|
15297
|
+
disallowedActions: ["Do not grant repo or worker-session mutation permissions by default."],
|
|
15298
|
+
displayName: "Nudge / What's Next Manager",
|
|
15299
|
+
epilogues: [],
|
|
15300
|
+
evidenceGates: ["manager decision", "worker receipt", "accepted criteria closure"],
|
|
15301
|
+
guidelines: [
|
|
15302
|
+
"Prefer wait over nudge while the worker is active.",
|
|
15303
|
+
"Ask for must-have current-task criteria versus follow-ups when scope changes.",
|
|
15304
|
+
],
|
|
15305
|
+
loopTemplate: null,
|
|
15306
|
+
mode: "guided",
|
|
15307
|
+
name: "nudge-whats-next",
|
|
15308
|
+
objective: "Observe the worker, ask useful status and next-step questions, and finish only with evidence.",
|
|
15309
|
+
permissions: [],
|
|
15310
|
+
supportPatterns: ["Inbox / No-Tmux App Loop", "Recovery / Resume / Handoff"],
|
|
15311
|
+
tools: [],
|
|
15312
|
+
},
|
|
15313
|
+
"pr-ci-merge-ralph-loop": {
|
|
15314
|
+
acceptance: [
|
|
15315
|
+
"PR URL, green CI, merge receipt, and adversarial proof are recorded.",
|
|
15316
|
+
"Worker handoff exists before compact or clear.",
|
|
15317
|
+
],
|
|
15318
|
+
cleanup: "clear after saved handoff",
|
|
15319
|
+
description: "Drive delivery through PR readiness, CI, merge, handoff, and worker clear receipts.",
|
|
15320
|
+
disallowedActions: [
|
|
15321
|
+
"Do not open PRs before repo.open_pr is permitted.",
|
|
15322
|
+
"Do not merge before repo.merge_green_pr is permitted and CI is green.",
|
|
15323
|
+
"Do not clear before a saved handoff.",
|
|
15324
|
+
],
|
|
15325
|
+
displayName: "PR/CI/Merge Ralph Loop",
|
|
15326
|
+
epilogues: ["draft-pr", "record-handoff"],
|
|
15327
|
+
evidenceGates: ["pr_url", "ci_green", "merge", "adversarial_check"],
|
|
15328
|
+
guidelines: ["Merge only after green CI and recorded manager decision evidence."],
|
|
15329
|
+
loopTemplate: "pr_ci_merge_loop",
|
|
15330
|
+
mode: "strict",
|
|
15331
|
+
name: "pr-ci-merge-ralph-loop",
|
|
15332
|
+
objective: "Drive the worker through PR readiness, CI, merge, handoff, and clear receipts.",
|
|
15333
|
+
permissions: ["repo.open_pr", "repo.merge_green_pr", "worker_session.compact", "worker_session.clear"],
|
|
15334
|
+
supportPatterns: ["Inbox / No-Tmux App Loop", "Recovery / Resume / Handoff"],
|
|
15335
|
+
tools: ["verification.run_tests", "context.fetch_prs"],
|
|
15336
|
+
},
|
|
15337
|
+
"test-coverage-loop": {
|
|
15338
|
+
acceptance: [
|
|
15339
|
+
"Coverage or targeted test evidence is recorded before another worker pass.",
|
|
15340
|
+
"Structured adversarial proof names the strongest realistic failure mode.",
|
|
15341
|
+
],
|
|
15342
|
+
cleanup: "clear by default",
|
|
15343
|
+
description: "Improve or prove test confidence with coverage evidence before another pass.",
|
|
15344
|
+
disallowedActions: ["Do not continue after only generic tests-passed text."],
|
|
15345
|
+
displayName: "Test Coverage Loop",
|
|
15346
|
+
epilogues: [],
|
|
15347
|
+
evidenceGates: ["test_coverage", "adversarial_check"],
|
|
15348
|
+
guidelines: ["Record coverage evidence before asking for another worker pass."],
|
|
15349
|
+
loopTemplate: "test_coverage_loop",
|
|
15350
|
+
mode: "strict",
|
|
15351
|
+
name: "test-coverage-loop",
|
|
15352
|
+
objective: "Improve or prove test coverage for the requested behavior.",
|
|
15353
|
+
permissions: ["worker_session.compact", "worker_session.clear"],
|
|
15354
|
+
supportPatterns: ["Inbox / No-Tmux App Loop", "Recovery / Resume / Handoff"],
|
|
15355
|
+
tools: ["verification.run_tests"],
|
|
15356
|
+
},
|
|
15357
|
+
"ux-polish-loop": {
|
|
15358
|
+
acceptance: [
|
|
15359
|
+
"Reference artifact, candidate screenshot, visual diff report, and below-threshold evidence are recorded.",
|
|
15360
|
+
"Structured adversarial proof is recorded before another visual pass.",
|
|
15361
|
+
],
|
|
15362
|
+
cleanup: "compact by default",
|
|
15363
|
+
description: "Iterate on visible UI quality using browser, screenshot, and visual-diff evidence.",
|
|
15364
|
+
disallowedActions: ["Do not approve a visual pass without screenshot or browser evidence."],
|
|
15365
|
+
displayName: "UX Polish Loop",
|
|
15366
|
+
epilogues: [],
|
|
15367
|
+
evidenceGates: [
|
|
15368
|
+
"reference_artifact",
|
|
15369
|
+
"candidate_screenshot",
|
|
15370
|
+
"visual_diff_report",
|
|
15371
|
+
"diff_below_threshold",
|
|
15372
|
+
"adversarial_check",
|
|
15373
|
+
],
|
|
15374
|
+
guidelines: ["Compare visible output against references before requesting another pass."],
|
|
15375
|
+
loopTemplate: "visual_diff_loop",
|
|
15376
|
+
mode: "guided",
|
|
15377
|
+
name: "ux-polish-loop",
|
|
15378
|
+
objective: "Iterate on visible UI quality using browser and screenshot evidence.",
|
|
15379
|
+
permissions: ["worker_session.compact", "worker_session.clear"],
|
|
15380
|
+
supportPatterns: ["Inbox / No-Tmux App Loop", "Recovery / Resume / Handoff"],
|
|
15381
|
+
tools: ["verification.run_playwright"],
|
|
15382
|
+
},
|
|
15383
|
+
};
|
|
15384
|
+
const MANAGER_RECIPE_ALIASES = {
|
|
15385
|
+
"goalbuddy conveyor": "goalbuddy-conveyor",
|
|
15386
|
+
goalbuddy: "goalbuddy-conveyor",
|
|
15387
|
+
"nudge / what's next manager": "nudge-whats-next",
|
|
15388
|
+
"nudge whats next": "nudge-whats-next",
|
|
15389
|
+
"pr ci merge ralph loop": "pr-ci-merge-ralph-loop",
|
|
15390
|
+
"pr/ci/merge ralph loop": "pr-ci-merge-ralph-loop",
|
|
15391
|
+
"ralph loop": "pr-ci-merge-ralph-loop",
|
|
15392
|
+
"test coverage": "test-coverage-loop",
|
|
15393
|
+
"test coverage loop": "test-coverage-loop",
|
|
15394
|
+
"ux polish": "ux-polish-loop",
|
|
15395
|
+
"ux polish loop": "ux-polish-loop",
|
|
15396
|
+
"visual polish": "ux-polish-loop",
|
|
15397
|
+
"what's next": "nudge-whats-next",
|
|
15398
|
+
"whats next": "nudge-whats-next",
|
|
15399
|
+
};
|
|
15400
|
+
function listManagerRecipes() {
|
|
15401
|
+
return Object.keys(MANAGER_RECIPES).sort().map((name) => managerRecipeSummary(name));
|
|
15402
|
+
}
|
|
15403
|
+
function managerRecipeDefinition(name) {
|
|
15404
|
+
const key = normalizeManagerRecipeName(name);
|
|
15405
|
+
const recipe = MANAGER_RECIPES[key];
|
|
15406
|
+
if (!recipe) {
|
|
15407
|
+
throw new Error(`Unknown manager recipe: ${name}; expected one of: ${Object.keys(MANAGER_RECIPES).sort().join(", ")}`);
|
|
15408
|
+
}
|
|
15409
|
+
return recipe;
|
|
15410
|
+
}
|
|
15411
|
+
function normalizeManagerRecipeName(name) {
|
|
15412
|
+
const normalized = name.trim().toLowerCase().split(/\s+/).join(" ");
|
|
15413
|
+
return MANAGER_RECIPE_ALIASES[normalized] ?? normalized.replace(/_/g, "-").replace(/ /g, "-");
|
|
15414
|
+
}
|
|
15415
|
+
function managerRecipeSummary(name) {
|
|
15416
|
+
const recipe = managerRecipeDefinition(name);
|
|
15417
|
+
return {
|
|
15418
|
+
acceptance: [...recipe.acceptance],
|
|
15419
|
+
cleanup: recipe.cleanup,
|
|
15420
|
+
description: recipe.description,
|
|
15421
|
+
disallowed_actions: [...recipe.disallowedActions],
|
|
15422
|
+
display_name: recipe.displayName,
|
|
15423
|
+
epilogues: [...recipe.epilogues],
|
|
15424
|
+
evidence_gates: [...recipe.evidenceGates],
|
|
15425
|
+
guidelines: [...recipe.guidelines],
|
|
15426
|
+
locked_summary_template: lockedManagerRecipeSummary(recipe),
|
|
15427
|
+
loop_template: recipe.loopTemplate,
|
|
15428
|
+
manager_config_command: managerRecipeConfigCommand(recipe),
|
|
15429
|
+
mode: recipe.mode,
|
|
15430
|
+
name: recipe.name,
|
|
15431
|
+
objective: recipe.objective,
|
|
15432
|
+
permissions: [...recipe.permissions],
|
|
15433
|
+
support_patterns: [...recipe.supportPatterns],
|
|
15434
|
+
tools: [...recipe.tools],
|
|
15435
|
+
};
|
|
15436
|
+
}
|
|
15437
|
+
function managerRecipeConfigCommand(recipe, taskPlaceholder = "<task>") {
|
|
15438
|
+
const command = ["conveyor", "manager-config", taskPlaceholder, "--mode", recipe.mode, "--objective", recipe.objective];
|
|
15439
|
+
for (const guideline of recipe.guidelines) {
|
|
15440
|
+
command.push("--guideline", guideline);
|
|
15441
|
+
}
|
|
15442
|
+
for (const acceptance of recipe.acceptance) {
|
|
15443
|
+
command.push("--acceptance", acceptance);
|
|
15444
|
+
}
|
|
15445
|
+
const permissions = new Set(recipe.permissions);
|
|
15446
|
+
if (permissions.has("worker_session.compact") && permissions.has("worker_session.clear")) {
|
|
15447
|
+
command.push("--allow-worker-compact-clear");
|
|
15448
|
+
permissions.delete("worker_session.compact");
|
|
15449
|
+
permissions.delete("worker_session.clear");
|
|
15450
|
+
}
|
|
15451
|
+
if (permissions.has("repo.open_pr")) {
|
|
15452
|
+
command.push("--allow-pr");
|
|
15453
|
+
permissions.delete("repo.open_pr");
|
|
15454
|
+
}
|
|
15455
|
+
if (permissions.has("repo.merge_green_pr")) {
|
|
15456
|
+
command.push("--allow-merge-green");
|
|
15457
|
+
permissions.delete("repo.merge_green_pr");
|
|
15458
|
+
}
|
|
15459
|
+
for (const permission of [...permissions].sort()) {
|
|
15460
|
+
command.push("--permit", permission);
|
|
15461
|
+
}
|
|
15462
|
+
for (const tool of recipe.tools) {
|
|
15463
|
+
command.push("--tool", tool);
|
|
15464
|
+
}
|
|
15465
|
+
for (const epilogue of recipe.epilogues) {
|
|
15466
|
+
command.push("--epilogue", epilogue);
|
|
15467
|
+
}
|
|
15468
|
+
return command;
|
|
15469
|
+
}
|
|
15470
|
+
function lockedManagerRecipeSummary(recipe) {
|
|
15471
|
+
return [
|
|
15472
|
+
`Selected recipe: ${recipe.displayName}`,
|
|
15473
|
+
`Mode: ${recipe.mode}`,
|
|
15474
|
+
`Permissions: ${recipe.permissions.length > 0 ? recipe.permissions.join(", ") : "none"}`,
|
|
15475
|
+
`Tools: ${recipe.tools.length > 0 ? recipe.tools.join(", ") : "none"}`,
|
|
15476
|
+
`Epilogues: ${recipe.epilogues.length > 0 ? recipe.epilogues.join(", ") : "none"}`,
|
|
15477
|
+
`Cleanup: ${recipe.cleanup}`,
|
|
15478
|
+
`Evidence gates: ${recipe.evidenceGates.length > 0 ? recipe.evidenceGates.join(", ") : "manager-reviewed evidence"}`,
|
|
15479
|
+
`Not allowed: ${recipe.disallowedActions.length > 0 ? recipe.disallowedActions.join("; ") : "unconfirmed custom actions"}`,
|
|
15480
|
+
"User confirmed: <yes|no>",
|
|
15481
|
+
].join("\n");
|
|
15482
|
+
}
|
|
15173
15483
|
const LOOP_TRIGGERS = [
|
|
15174
15484
|
{
|
|
15175
15485
|
acceptance: "Create or reuse a loop policy whose required_before_continue includes adversarial_check.",
|