@linimin/pi-letscook 0.1.41 → 0.1.43
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 +12 -0
- package/README.md +11 -10
- package/extensions/completion/index.ts +57 -27
- package/package.json +1 -1
- package/scripts/canonical-evidence-artifact-test.sh +44 -0
- package/scripts/context-proposal-test.sh +19 -22
- package/scripts/refocus-test.sh +6 -6
- package/scripts/release-check.sh +7 -21
- package/scripts/smoke-test.sh +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.43
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- stopped injecting completion-workflow reminder and compaction-resume context into ordinary primary-agent turns after canonical `continuation_policy` reaches `done`, so users must rerun `/cook` before the workflow protocol reactivates
|
|
10
|
+
|
|
11
|
+
## 0.1.42
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- allowed `/cook <hint>` as an analyst-only high-priority prompt that focuses proposal derivation without bypassing the existing approval-only Start/Cancel confirmation gate or canonical fail-closed routing
|
|
16
|
+
|
|
5
17
|
## 0.1.41
|
|
6
18
|
|
|
7
19
|
### Changed
|
package/README.md
CHANGED
|
@@ -32,32 +32,33 @@ Then run `/reload` in Pi.
|
|
|
32
32
|
|
|
33
33
|
## Quick start
|
|
34
34
|
|
|
35
|
-
Primary
|
|
35
|
+
Primary entrypoints:
|
|
36
36
|
|
|
37
37
|
```text
|
|
38
38
|
/cook
|
|
39
|
+
/cook <hint>
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
Use
|
|
42
|
+
Use `/cook` after you discuss the mission in the main chat. If the recent context is fuzzy, `/cook <hint>` can provide a short high-priority user hint for the proposal analyst while still relying on recent discussion to fill in scope, constraints, and acceptance. The same command can:
|
|
42
43
|
|
|
43
44
|
- start a brand-new workflow from recent discussion
|
|
44
45
|
- continue the current workflow when recent discussion still matches it, or when discussion is too weak or ambiguous to justify a refocus
|
|
45
46
|
- surface a conservative refocus chooser when recent discussion clearly points to a different workflow
|
|
46
47
|
- start the next workflow round after the previous one is `done`
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
`/cook` expects recent main-chat discussion to describe concrete repo changes. README/CHANGELOG updates still count as concrete repo changes, but assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts do not. An inline `/cook <hint>` only guides proposal derivation; it does not bypass confirmation or become canonical state on its own.
|
|
49
50
|
|
|
50
|
-
On startup and next-round flows, if recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes,
|
|
51
|
+
On startup and next-round flows, if recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning `/cook`. If an inline hint conflicts with the recent discussion, `/cook` also fails closed instead of guessing.
|
|
51
52
|
|
|
52
53
|
## How `/cook` works
|
|
53
54
|
|
|
54
|
-
Bare `/cook`
|
|
55
|
+
Bare `/cook` and `/cook <hint>` are the supported workflow entrypoints.
|
|
55
56
|
|
|
56
|
-
| Repo state |
|
|
57
|
+
| Repo state | `/cook` behavior |
|
|
57
58
|
|---|---|
|
|
58
|
-
| No workflow yet | Summarizes recent main-chat discussion into a startup proposal, then asks for approval with **Start** or **Cancel**. If the discussion is weak, ambiguous, assistant-produced, or only a plan/spec/design-doc/proposal artifact instead of concrete repo changes, `/cook` fails closed without writing `.agent/**` state and tells you to clarify the mission in the main chat before rerunning
|
|
59
|
-
| Active workflow exists | Reads the current mission plus recent non-command main-chat discussion. Matching or unclear discussion resumes from canonical `.agent/**` state. Clear replacement discussion about different concrete repo changes opens a chooser first, then only rewrites canonical state after the follow-on **Start** confirmation. Assistant/summary artifacts or plan/spec/design-doc/proposal-only context do not refocus the workflow. |
|
|
60
|
-
| Previous workflow is `done` | Starts the next round from recent main-chat discussion, then asks for approval with **Start** or **Cancel**. Weak, ambiguous, assistant-produced, or planning-artifact-only discussion fails closed without rewriting canonical state and tells you to clarify the mission in the main chat before rerunning
|
|
59
|
+
| No workflow yet | Summarizes recent main-chat discussion, optionally focused by `/cook <hint>`, into a startup proposal, then asks for approval with **Start** or **Cancel**. If the discussion is weak, ambiguous, assistant-produced, or only a plan/spec/design-doc/proposal artifact instead of concrete repo changes, `/cook` fails closed without writing `.agent/**` state and tells you to clarify the mission in the main chat before rerunning `/cook`. |
|
|
60
|
+
| Active workflow exists | Reads the current mission plus recent non-command main-chat discussion, with any inline hint treated as extra proposal-analysis guidance only. Matching or unclear discussion resumes from canonical `.agent/**` state. Clear replacement discussion about different concrete repo changes opens a chooser first, then only rewrites canonical state after the follow-on **Start** confirmation. Assistant/summary artifacts or plan/spec/design-doc/proposal-only context do not refocus the workflow. |
|
|
61
|
+
| Previous workflow is `done` | Starts the next round from recent main-chat discussion, optionally focused by `/cook <hint>`, then asks for approval with **Start** or **Cancel**. Weak, ambiguous, assistant-produced, or planning-artifact-only discussion fails closed without rewriting canonical state and tells you to clarify the mission in the main chat before rerunning `/cook`. |
|
|
61
62
|
|
|
62
63
|
## Approval-only confirmation and fail-closed behavior
|
|
63
64
|
|
|
@@ -67,7 +68,7 @@ All startup, next-round, and replacement proposals are **approval-only**:
|
|
|
67
68
|
- actions are only **Start** and **Cancel**
|
|
68
69
|
- **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
|
|
69
70
|
|
|
70
|
-
When
|
|
71
|
+
When `/cook` cannot derive a clear startup, next-round, or replacement proposal for concrete repo changes from recent main-chat discussion plus any optional inline hint, it fails closed instead of guessing. That means no canonical `.agent/**` state is created or rewritten until the discussion is clarified in the main chat and you rerun `/cook`. Tracked docs-only work such as README/CHANGELOG updates is still execution-ready, but assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts are not enough to start or refocus a workflow on their own.
|
|
71
72
|
|
|
72
73
|
When an active workflow already exists and recent discussion clearly suggests a different workflow, `/cook` shows a separate chooser first:
|
|
73
74
|
|
|
@@ -539,10 +539,8 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
542
|
-
const COOK_INLINE_ARG_REJECTION_MESSAGE =
|
|
543
|
-
"Inline /cook arguments are no longer supported. Clarify the mission in the main chat and rerun bare /cook.";
|
|
544
542
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
545
|
-
"
|
|
543
|
+
"/cook failed closed because recent discussion and any optional inline /cook hint did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook.";
|
|
546
544
|
|
|
547
545
|
function buildCookCancellationMessage(prefix: string): string {
|
|
548
546
|
return `${prefix}. ${COOK_MAIN_CHAT_RERUN_GUIDANCE}`;
|
|
@@ -564,6 +562,10 @@ function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean
|
|
|
564
562
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
565
563
|
}
|
|
566
564
|
|
|
565
|
+
function shouldInjectCompletionWorkflowContext(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
566
|
+
return Boolean(snapshot) && !isWorkflowDone(snapshot);
|
|
567
|
+
}
|
|
568
|
+
|
|
567
569
|
function extractTextFromMessageContent(content: unknown): string {
|
|
568
570
|
if (typeof content === "string") return content.trim();
|
|
569
571
|
if (!Array.isArray(content)) return "";
|
|
@@ -1114,9 +1116,18 @@ function contextProposalAnalystModelArg(model: unknown): string | undefined {
|
|
|
1114
1116
|
return provider && id ? `${provider}/${id}` : undefined;
|
|
1115
1117
|
}
|
|
1116
1118
|
|
|
1117
|
-
function buildContextProposalAnalystPrompt(projectName: string, recentEntries: RecentDiscussionEntry[]): string {
|
|
1119
|
+
function buildContextProposalAnalystPrompt(projectName: string, recentEntries: RecentDiscussionEntry[], hintText?: string): string {
|
|
1118
1120
|
const discussion = serializeRecentDiscussionEntries(recentEntries);
|
|
1119
|
-
|
|
1121
|
+
const lines = [
|
|
1122
|
+
`Project: ${projectName}`,
|
|
1123
|
+
"Infer the current mission from the discussion.",
|
|
1124
|
+
"If an inline /cook hint is present, treat it as a high-priority user hint that may focus the mission, but do not ignore conflicting discussion or skip missing details.",
|
|
1125
|
+
];
|
|
1126
|
+
if (hintText) {
|
|
1127
|
+
lines.push("", "Inline /cook hint:", hintText);
|
|
1128
|
+
}
|
|
1129
|
+
lines.push("", "Recent discussion:", discussion || "(none)");
|
|
1130
|
+
return lines.join("\n");
|
|
1120
1131
|
}
|
|
1121
1132
|
|
|
1122
1133
|
function contextProposalAnalystProgressLines(activity: LiveRoleActivity): string[] {
|
|
@@ -1145,20 +1156,23 @@ async function runContextProposalAnalystSubprocess(
|
|
|
1145
1156
|
ctx: { cwd: string; hasUI: boolean; ui: any; model?: any },
|
|
1146
1157
|
projectName: string,
|
|
1147
1158
|
recentEntries: RecentDiscussionEntry[],
|
|
1159
|
+
hintText?: string,
|
|
1148
1160
|
): Promise<string | undefined> {
|
|
1149
1161
|
const modelArg = contextProposalAnalystModelArg(ctx.model);
|
|
1150
1162
|
if (!modelArg) return undefined;
|
|
1151
1163
|
const cwd = getCtxCwd(ctx);
|
|
1152
1164
|
const runCwd = findCompletionRoot(cwd) ?? findRepoRoot(cwd) ?? cwd;
|
|
1153
1165
|
const rootKey = completionRootKey(undefined, cwd);
|
|
1154
|
-
const prompt = buildContextProposalAnalystPrompt(projectName, recentEntries);
|
|
1166
|
+
const prompt = buildContextProposalAnalystPrompt(projectName, recentEntries, hintText);
|
|
1155
1167
|
const systemPromptTemp = await writeTempFile("pi-cook-proposal-analyst-", CONTEXT_PROPOSAL_ANALYST_SYSTEM_PROMPT);
|
|
1156
1168
|
const analystRole = "cook-proposal-analyst";
|
|
1157
1169
|
const args: string[] = ["--mode", "json", "-p", "--no-session", "--append-system-prompt", systemPromptTemp.filePath, "--model", modelArg, prompt];
|
|
1158
1170
|
const invocation = getPiInvocation(args);
|
|
1159
1171
|
const liveActivity = createLiveRoleActivity(analystRole);
|
|
1160
|
-
liveActivity.progress = "Analyzing recent discussion";
|
|
1161
|
-
liveActivity.currentAction =
|
|
1172
|
+
liveActivity.progress = hintText ? "Analyzing recent discussion and inline hint" : "Analyzing recent discussion";
|
|
1173
|
+
liveActivity.currentAction = hintText
|
|
1174
|
+
? "Reading recent discussion plus the inline /cook hint and preparing a startup proposal"
|
|
1175
|
+
: "Reading recent discussion and preparing a startup proposal";
|
|
1162
1176
|
liveActivity.assistantSummary = liveActivity.progress;
|
|
1163
1177
|
liveActivity.recentActivity = pushRecentActivity(liveActivity.recentActivity, `assistant: ${liveActivity.progress}`);
|
|
1164
1178
|
const messages: RoleMessage[] = [];
|
|
@@ -1188,6 +1202,17 @@ async function runContextProposalAnalystSubprocess(
|
|
|
1188
1202
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1189
1203
|
shell: false,
|
|
1190
1204
|
});
|
|
1205
|
+
let settled = false;
|
|
1206
|
+
const resolveOnce = (value: string | undefined) => {
|
|
1207
|
+
if (settled) return;
|
|
1208
|
+
settled = true;
|
|
1209
|
+
resolve(value);
|
|
1210
|
+
};
|
|
1211
|
+
const abort = () => {
|
|
1212
|
+
proc.kill("SIGTERM");
|
|
1213
|
+
resolveOnce(undefined);
|
|
1214
|
+
};
|
|
1215
|
+
const handleSigint = () => abort();
|
|
1191
1216
|
let buffer = "";
|
|
1192
1217
|
const processLine = (line: string) => {
|
|
1193
1218
|
if (!line.trim()) return;
|
|
@@ -1208,14 +1233,19 @@ async function runContextProposalAnalystSubprocess(
|
|
|
1208
1233
|
stderr += chunk.toString();
|
|
1209
1234
|
});
|
|
1210
1235
|
proc.on("close", (code) => {
|
|
1236
|
+
process.off("SIGINT", handleSigint);
|
|
1211
1237
|
if (buffer.trim()) processLine(buffer);
|
|
1212
|
-
|
|
1238
|
+
resolveOnce(code === 0 ? liveActivity.lastAssistantText?.trim() || undefined : undefined);
|
|
1239
|
+
});
|
|
1240
|
+
proc.on("error", () => {
|
|
1241
|
+
process.off("SIGINT", handleSigint);
|
|
1242
|
+
resolveOnce(undefined);
|
|
1213
1243
|
});
|
|
1214
|
-
|
|
1244
|
+
process.once("SIGINT", handleSigint);
|
|
1215
1245
|
if (overlay) {
|
|
1216
1246
|
overlay.onAbort = () => {
|
|
1217
|
-
|
|
1218
|
-
|
|
1247
|
+
process.off("SIGINT", handleSigint);
|
|
1248
|
+
abort();
|
|
1219
1249
|
};
|
|
1220
1250
|
}
|
|
1221
1251
|
});
|
|
@@ -1253,15 +1283,16 @@ async function analyzeContextProposalWithAgent(
|
|
|
1253
1283
|
ctx: { cwd: string; hasUI: boolean; ui: any; model?: any; modelRegistry?: any },
|
|
1254
1284
|
projectName: string,
|
|
1255
1285
|
recentEntries: RecentDiscussionEntry[],
|
|
1286
|
+
hintText?: string,
|
|
1256
1287
|
): Promise<ContextProposal | undefined> {
|
|
1257
1288
|
if (shouldDisableContextProposalAnalyst()) return undefined;
|
|
1258
1289
|
const testOutput = completionTestContextProposalAnalystOutput();
|
|
1259
1290
|
if (testOutput) {
|
|
1260
1291
|
return parseContextProposalAnalystOutput(testOutput, projectName);
|
|
1261
1292
|
}
|
|
1262
|
-
if (recentEntries.length === 0) return undefined;
|
|
1293
|
+
if (recentEntries.length === 0 && !hintText?.trim()) return undefined;
|
|
1263
1294
|
try {
|
|
1264
|
-
const raw = await runContextProposalAnalystSubprocess(ctx, projectName, recentEntries);
|
|
1295
|
+
const raw = await runContextProposalAnalystSubprocess(ctx, projectName, recentEntries, hintText);
|
|
1265
1296
|
if (!raw) return undefined;
|
|
1266
1297
|
return parseContextProposalAnalystOutput(raw, projectName);
|
|
1267
1298
|
} catch (error) {
|
|
@@ -1719,17 +1750,19 @@ function extractContextProposalFromStructuredSession(
|
|
|
1719
1750
|
async function extractContextProposalFromSession(
|
|
1720
1751
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
1721
1752
|
projectName: string,
|
|
1753
|
+
hintText?: string,
|
|
1722
1754
|
): Promise<ContextProposal | undefined> {
|
|
1723
1755
|
const recentEntries = collectRecentDiscussionEntries(ctx);
|
|
1724
|
-
return (await analyzeContextProposalWithAgent(ctx, projectName, recentEntries)) ??
|
|
1756
|
+
return (await analyzeContextProposalWithAgent(ctx, projectName, recentEntries, hintText)) ??
|
|
1725
1757
|
extractContextProposalFromStructuredSession(recentEntries, projectName);
|
|
1726
1758
|
}
|
|
1727
1759
|
|
|
1728
1760
|
async function deriveCookContextProposal(
|
|
1729
1761
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
1730
1762
|
projectName: string,
|
|
1763
|
+
hintText?: string,
|
|
1731
1764
|
): Promise<ContextProposal | undefined> {
|
|
1732
|
-
return await extractContextProposalFromSession(ctx, projectName);
|
|
1765
|
+
return await extractContextProposalFromSession(ctx, projectName, hintText);
|
|
1733
1766
|
}
|
|
1734
1767
|
|
|
1735
1768
|
async function confirmContextProposal(
|
|
@@ -2005,10 +2038,11 @@ function buildEvaluationRoleReminderText(snapshot: CompletionStateSnapshot, role
|
|
|
2005
2038
|
async function assessActiveWorkflowProposalRouting(
|
|
2006
2039
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
2007
2040
|
snapshot: CompletionStateSnapshot,
|
|
2041
|
+
hintText?: string,
|
|
2008
2042
|
): Promise<ActiveWorkflowProposalAssessment> {
|
|
2009
2043
|
const currentMission = currentMissionAnchor(snapshot);
|
|
2010
2044
|
const projectName = path.basename(snapshot.files.root);
|
|
2011
|
-
const proposal = await deriveCookContextProposal(ctx, projectName);
|
|
2045
|
+
const proposal = await deriveCookContextProposal(ctx, projectName, hintText);
|
|
2012
2046
|
if (!proposal) {
|
|
2013
2047
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
2014
2048
|
action: "unclear",
|
|
@@ -3703,7 +3737,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
3703
3737
|
const fingerprint = completionContinuationFingerprint(loaded.snapshot);
|
|
3704
3738
|
if (fingerprint) markQueuedDriverPromptInFlight(rootKey, fingerprint);
|
|
3705
3739
|
}
|
|
3706
|
-
if (!loaded) return;
|
|
3740
|
+
if (!loaded || !shouldInjectCompletionWorkflowContext(loaded.snapshot)) return;
|
|
3707
3741
|
const markerText = await readText(loaded.snapshot.files.compactionMarkerPath);
|
|
3708
3742
|
let marker: JsonRecord | undefined;
|
|
3709
3743
|
if (markerText) {
|
|
@@ -3726,7 +3760,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
3726
3760
|
|
|
3727
3761
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
3728
3762
|
const loaded = await loadCompletionDataForReminder(getCtxCwd(ctx));
|
|
3729
|
-
if (!loaded) return;
|
|
3763
|
+
if (!loaded || !shouldInjectCompletionWorkflowContext(loaded.snapshot)) return;
|
|
3730
3764
|
const { preparation } = event;
|
|
3731
3765
|
const summary = buildResumeCapsule(loaded.snapshot, loaded.sliceHistory, loaded.stopHistory);
|
|
3732
3766
|
await fsp.mkdir(loaded.snapshot.files.tmpDir, { recursive: true });
|
|
@@ -4073,11 +4107,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
4073
4107
|
pi.registerCommand("cook", {
|
|
4074
4108
|
description: "Discussion-driven /cook workflow: start, continue, refocus, or start the next round",
|
|
4075
4109
|
handler: async (args, ctx) => {
|
|
4076
|
-
const
|
|
4077
|
-
if (inlineArgs) {
|
|
4078
|
-
emitCommandText(ctx, COOK_INLINE_ARG_REJECTION_MESSAGE, "info");
|
|
4079
|
-
return;
|
|
4080
|
-
}
|
|
4110
|
+
const inlineHint = args.trim() || undefined;
|
|
4081
4111
|
let goal: string | undefined;
|
|
4082
4112
|
const cwd = getCtxCwd(ctx);
|
|
4083
4113
|
let snapshot = await loadCompletionSnapshot(cwd);
|
|
@@ -4089,7 +4119,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
4089
4119
|
if (!snapshot) {
|
|
4090
4120
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
4091
4121
|
const projectName = path.basename(root);
|
|
4092
|
-
const proposal = await deriveCookContextProposal(ctx, projectName);
|
|
4122
|
+
const proposal = await deriveCookContextProposal(ctx, projectName, inlineHint);
|
|
4093
4123
|
if (!proposal) {
|
|
4094
4124
|
emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(), "info");
|
|
4095
4125
|
return;
|
|
@@ -4127,7 +4157,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
4127
4157
|
if (!goal) {
|
|
4128
4158
|
if (workflowDone) {
|
|
4129
4159
|
const projectName = path.basename(snapshot.files.root);
|
|
4130
|
-
const proposal = await deriveCookContextProposal(ctx, projectName);
|
|
4160
|
+
const proposal = await deriveCookContextProposal(ctx, projectName, inlineHint);
|
|
4131
4161
|
if (!proposal) {
|
|
4132
4162
|
emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage("The previous completion workflow is already done."), "info");
|
|
4133
4163
|
return;
|
|
@@ -4146,7 +4176,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
4146
4176
|
snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
|
|
4147
4177
|
emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
|
|
4148
4178
|
} else {
|
|
4149
|
-
const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot);
|
|
4179
|
+
const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, inlineHint);
|
|
4150
4180
|
if (assessment.action !== "refocus" || !assessment.proposal) {
|
|
4151
4181
|
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot);
|
|
4152
4182
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"description": "Pi package for long-running completion workflows with canonical .agent state, role-based subagents, continuity, and verification helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -392,4 +392,48 @@ assert 'Verification evidence summary:' in text, text
|
|
|
392
392
|
assert 'selected_slice' in text, text
|
|
393
393
|
PY
|
|
394
394
|
|
|
395
|
+
python3 - <<'PY'
|
|
396
|
+
import json
|
|
397
|
+
from pathlib import Path
|
|
398
|
+
|
|
399
|
+
state_path = Path('.agent/state.json')
|
|
400
|
+
plan_path = Path('.agent/plan.json')
|
|
401
|
+
active_path = Path('.agent/active-slice.json')
|
|
402
|
+
|
|
403
|
+
state = json.loads(state_path.read_text())
|
|
404
|
+
state.update({
|
|
405
|
+
'current_phase': 'done',
|
|
406
|
+
'continuation_policy': 'done',
|
|
407
|
+
'continuation_reason': 'Fixture is complete; ordinary primary-agent turns should stay outside completion until /cook runs again.',
|
|
408
|
+
'project_done': True,
|
|
409
|
+
'remaining_high_value_gaps': 0,
|
|
410
|
+
'unsatisfied_contract_ids': [],
|
|
411
|
+
'next_mandatory_action': None,
|
|
412
|
+
'next_mandatory_role': None,
|
|
413
|
+
'remaining_stop_judges': 0,
|
|
414
|
+
'contract_status': 'done',
|
|
415
|
+
})
|
|
416
|
+
state_path.write_text(json.dumps(state, indent=2) + '\n')
|
|
417
|
+
|
|
418
|
+
plan = json.loads(plan_path.read_text())
|
|
419
|
+
for slice_data in plan.get('candidate_slices', []):
|
|
420
|
+
if slice_data.get('slice_id') == 'evidence-fixture':
|
|
421
|
+
slice_data['status'] = 'done'
|
|
422
|
+
plan_path.write_text(json.dumps(plan, indent=2) + '\n')
|
|
423
|
+
|
|
424
|
+
active = json.loads(active_path.read_text())
|
|
425
|
+
active['status'] = 'done'
|
|
426
|
+
active_path.write_text(json.dumps(active, indent=2) + '\n')
|
|
427
|
+
PY
|
|
428
|
+
|
|
429
|
+
rm -f "$SYSTEM_REMINDER"
|
|
430
|
+
PI_COMPLETION_TEST_SYSTEM_REMINDER_PATH="$SYSTEM_REMINDER" \
|
|
431
|
+
pi -e "$PKG_ROOT" -p "Summarize the latest release briefly." \
|
|
432
|
+
>"$TMPDIR/pi-canonical-evidence-done-reminder.out" 2>"$TMPDIR/pi-canonical-evidence-done-reminder.err"
|
|
433
|
+
|
|
434
|
+
[[ ! -f "$SYSTEM_REMINDER" ]] || {
|
|
435
|
+
echo "expected no completion reminder snapshot when continuation_policy is done" >&2
|
|
436
|
+
exit 1
|
|
437
|
+
}
|
|
438
|
+
|
|
395
439
|
echo "canonical evidence artifact test passed: $TMPDIR"
|
|
@@ -178,7 +178,7 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
178
178
|
snapshot = Path(sys.argv[3])
|
|
179
179
|
assert not Path('.agent').exists(), 'missing-section structured discussion should fail closed without writing canonical state'
|
|
180
180
|
assert not snapshot.exists(), 'missing-section structured discussion should not emit a proposal snapshot when bare /cook fails closed'
|
|
181
|
-
assert '
|
|
181
|
+
assert '/cook failed closed' in output, 'missing-section structured discussion should explain the fail-closed startup outcome'
|
|
182
182
|
assert 'Mission/Scope/Constraints/Acceptance' in output, 'missing-section structured discussion should explain the strict fallback requirement'
|
|
183
183
|
PY
|
|
184
184
|
|
|
@@ -198,7 +198,7 @@ from pathlib import Path
|
|
|
198
198
|
|
|
199
199
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
200
200
|
assert not Path('.agent').exists(), 'ambiguous structured discussion should fail closed without writing canonical state'
|
|
201
|
-
assert '
|
|
201
|
+
assert '/cook failed closed' in output, 'ambiguous structured discussion should explain the fail-closed startup outcome'
|
|
202
202
|
assert 'Mission/Scope/Constraints/Acceptance' in output, 'ambiguous structured discussion should explain the strict fallback requirement'
|
|
203
203
|
PY
|
|
204
204
|
|
|
@@ -289,7 +289,7 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
289
289
|
snapshot = Path(sys.argv[3])
|
|
290
290
|
assert not Path('.agent').exists(), 'planning-only startup should fail closed without writing canonical state'
|
|
291
291
|
assert not snapshot.exists(), 'planning-only startup should not emit a proposal snapshot when bare /cook fails closed'
|
|
292
|
-
assert '
|
|
292
|
+
assert '/cook failed closed' in output, 'planning-only startup should explain the fail-closed startup outcome'
|
|
293
293
|
assert 'Mission/Scope/Constraints/Acceptance' in output, 'planning-only startup should still explain the structured discussion requirement'
|
|
294
294
|
assert 'concrete repo changes' in output, 'planning-only startup should explain that bare /cook now expects execution-ready repo changes'
|
|
295
295
|
PY
|
|
@@ -495,7 +495,7 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
495
495
|
snapshot = Path(sys.argv[3])
|
|
496
496
|
assert not Path('.agent').exists(), 'assistant-only startup summary should fail closed without writing canonical state'
|
|
497
497
|
assert not snapshot.exists(), 'assistant-only startup summary should not emit a proposal snapshot when bare /cook fails closed'
|
|
498
|
-
assert '
|
|
498
|
+
assert '/cook failed closed' in output, 'assistant-only startup summary should explain the fail-closed startup outcome'
|
|
499
499
|
assert 'concrete repo changes' in output, 'assistant-only startup summary should explain that bare /cook expects execution-ready repo changes from main-chat discussion'
|
|
500
500
|
PY
|
|
501
501
|
|
|
@@ -523,7 +523,7 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
523
523
|
snapshot = Path(sys.argv[3])
|
|
524
524
|
assert not Path('.agent').exists(), 'analyst-derived ambiguous generic discussion should fail closed without writing canonical state'
|
|
525
525
|
assert not snapshot.exists(), 'analyst-derived ambiguous generic discussion should not emit a proposal snapshot when bare /cook fails closed'
|
|
526
|
-
assert '
|
|
526
|
+
assert '/cook failed closed' in output, 'analyst-derived ambiguous generic discussion should explain the fail-closed startup outcome'
|
|
527
527
|
PY
|
|
528
528
|
|
|
529
529
|
# No workflow yet: /cook with no goal should infer from recent discussion through analyst output.
|
|
@@ -836,8 +836,8 @@ assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_re
|
|
|
836
836
|
assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
|
|
837
837
|
PY
|
|
838
838
|
|
|
839
|
-
# Active workflow: inline /cook
|
|
840
|
-
# and
|
|
839
|
+
# Active workflow: inline /cook hint should flow through routing plus final proposal confirmation
|
|
840
|
+
# and still leave canonical state unchanged when the final proposal is cancelled.
|
|
841
841
|
ACTIVE_INLINE_REJECTION_ROUTING="$TMPDIR/context-proposal-active-inline-arg-routing.json"
|
|
842
842
|
ACTIVE_INLINE_REJECTION_PROPOSAL="$TMPDIR/context-proposal-active-inline-arg-proposal.json"
|
|
843
843
|
ACTIVE_INLINE_REJECTION_CHOOSER="$TMPDIR/context-proposal-active-inline-arg-chooser.json"
|
|
@@ -859,8 +859,8 @@ Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in
|
|
|
859
859
|
PY
|
|
860
860
|
|
|
861
861
|
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
862
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=
|
|
863
|
-
|
|
862
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
|
|
863
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT='{"mission":"Replacement mission for the active workflow.","scope":["Review the replacement through the existing workflow chooser first."],"constraints":["Do not rewrite canonical state before the final Start confirmation."],"acceptance":["Show the final replacement proposal only after the chooser selects refocus."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.9}' \
|
|
864
864
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$ACTIVE_INLINE_REJECTION_PROPOSAL" \
|
|
865
865
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ACTIVE_INLINE_REJECTION_ROUTING" \
|
|
866
866
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$ACTIVE_INLINE_REJECTION_CHOOSER" \
|
|
@@ -886,16 +886,15 @@ tracked = [
|
|
|
886
886
|
Path('.agent/verification-evidence.json'),
|
|
887
887
|
]
|
|
888
888
|
|
|
889
|
-
assert
|
|
890
|
-
assert
|
|
891
|
-
assert
|
|
892
|
-
assert not proposal.exists(), 'active inline /cook args should not open proposal confirmation'
|
|
893
|
-
assert not chooser.exists(), 'active inline /cook args should not open the existing-workflow chooser'
|
|
889
|
+
assert routing.exists(), 'active inline /cook hint should run active-workflow routing'
|
|
890
|
+
assert proposal.exists(), 'active inline /cook hint should open final proposal confirmation after refocus is chosen'
|
|
891
|
+
assert chooser.exists(), 'active inline /cook hint should open the existing-workflow chooser'
|
|
894
892
|
after = {path.name: path.read_text() for path in tracked}
|
|
895
|
-
assert before == after, 'active inline /cook
|
|
893
|
+
assert before == after, 'active inline /cook hint should leave canonical files unchanged when confirmation is cancelled'
|
|
896
894
|
PY
|
|
897
895
|
|
|
898
|
-
# Completed workflow: inline /cook
|
|
896
|
+
# Completed workflow: inline /cook hint should also drive the next-round proposal and leave
|
|
897
|
+
# canonical state unchanged when the proposal is cancelled.
|
|
899
898
|
mark_done
|
|
900
899
|
|
|
901
900
|
DONE_INLINE_REJECTION_ROUTING="$TMPDIR/context-proposal-done-inline-arg-routing.json"
|
|
@@ -918,8 +917,8 @@ tracked = [
|
|
|
918
917
|
Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
|
|
919
918
|
PY
|
|
920
919
|
|
|
921
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=
|
|
922
|
-
|
|
920
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
|
|
921
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT='{"mission":"Update README guidance for the next workflow round.","scope":["Refresh README guidance for /cook hint-driven startup behavior."],"constraints":["Do not rewrite canonical state before Start is accepted."],"acceptance":["Keep the next-round proposal scoped to README updates only."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.9}' \
|
|
923
922
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DONE_INLINE_REJECTION_PROPOSAL" \
|
|
924
923
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$DONE_INLINE_REJECTION_ROUTING" \
|
|
925
924
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$DONE_INLINE_REJECTION_CHOOSER" \
|
|
@@ -947,13 +946,11 @@ tracked = [
|
|
|
947
946
|
state_before = json.loads(before['state.json'])
|
|
948
947
|
assert state_before['current_phase'] == 'done', 'done inline /cook rejection should start from a completed workflow'
|
|
949
948
|
assert state_before['project_done'] is True, 'done inline /cook rejection should start from project_done=true'
|
|
950
|
-
assert 'Inline /cook arguments are no longer supported.' in output, 'done inline /cook args should explain the hard rejection'
|
|
951
|
-
assert 'Clarify the mission in the main chat and rerun bare /cook.' in output, 'done inline /cook args should redirect users back to main chat plus bare /cook'
|
|
952
949
|
assert not routing.exists(), 'done inline /cook args should not run active/done workflow routing'
|
|
953
|
-
assert
|
|
950
|
+
assert proposal.exists(), 'done inline /cook hint should open next-round proposal confirmation'
|
|
954
951
|
assert not chooser.exists(), 'done inline /cook args should not open the chooser flow'
|
|
955
952
|
after = {path.name: path.read_text() for path in tracked}
|
|
956
|
-
assert before == after, 'done inline /cook
|
|
953
|
+
assert before == after, 'done inline /cook hint cancel should leave canonical files unchanged'
|
|
957
954
|
PY
|
|
958
955
|
|
|
959
956
|
# Completed workflow again: /cook with no goal should be able to use model-assisted
|
package/scripts/refocus-test.sh
CHANGED
|
@@ -103,6 +103,7 @@ Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in
|
|
|
103
103
|
PY
|
|
104
104
|
|
|
105
105
|
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
|
|
106
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT='{"mission":"Replacement mission that should stay in the main chat.","scope":["Replace the current workflow only after explicit approval."],"constraints":["Do not rewrite canonical state before the final Start confirmation."],"acceptance":["Surface the chooser before any replacement workflow rewrite."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.9}' \
|
|
106
107
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INLINE_REJECTION_ROUTING" \
|
|
107
108
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INLINE_REJECTION_PROPOSAL" \
|
|
108
109
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$INLINE_REJECTION_CHOOSER" \
|
|
@@ -131,13 +132,12 @@ tracked = [
|
|
|
131
132
|
]
|
|
132
133
|
current_state = json.loads(before['state.json'])
|
|
133
134
|
assert current_state['mission_anchor'] == initial_mission, 'active inline /cook args should start from the current mission anchor'
|
|
134
|
-
assert '
|
|
135
|
-
assert
|
|
136
|
-
assert not
|
|
137
|
-
assert
|
|
138
|
-
assert not chooser.exists(), 'active inline /cook args should not open the existing-workflow chooser'
|
|
135
|
+
assert 'Cancelled existing workflow confirmation.' in output, 'active inline /cook hint cancel should surface chooser cancellation'
|
|
136
|
+
assert routing.exists(), 'active inline /cook hint should run active-workflow routing'
|
|
137
|
+
assert not proposal.exists(), 'active inline /cook hint cancel at chooser should not open final proposal confirmation'
|
|
138
|
+
assert chooser.exists(), 'active inline /cook hint should open the existing-workflow chooser'
|
|
139
139
|
after = {path.name: path.read_text() for path in tracked}
|
|
140
|
-
assert before == after, 'active inline /cook
|
|
140
|
+
assert before == after, 'active inline /cook hint cancel should leave canonical files unchanged'
|
|
141
141
|
PY
|
|
142
142
|
|
|
143
143
|
SESSION_INITIAL_REFOCUS="$TMPDIR/session-initial-bare-refocus.jsonl"
|
package/scripts/release-check.sh
CHANGED
|
@@ -7,15 +7,16 @@ cd "$ROOT"
|
|
|
7
7
|
echo "[release-check] running control-plane validation, bare /cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, evaluator calibration, and rubric contract coverage"
|
|
8
8
|
bash .agent/verify_completion_control_plane.sh
|
|
9
9
|
|
|
10
|
-
echo "[release-check] verifying public
|
|
10
|
+
echo "[release-check] verifying public /cook parity"
|
|
11
11
|
python3 - <<'PY'
|
|
12
12
|
import re
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
checks = {
|
|
16
16
|
"README.md": [
|
|
17
|
-
"Bare `/cook`
|
|
18
|
-
"
|
|
17
|
+
"Bare `/cook` and `/cook <hint>` are the supported workflow entrypoints.",
|
|
18
|
+
"If the recent context is fuzzy, `/cook <hint>` can provide a short high-priority user hint for the proposal analyst",
|
|
19
|
+
"clarify the mission in the main chat before rerunning `/cook`",
|
|
19
20
|
"Matching or unclear discussion resumes from canonical `.agent/**` state.",
|
|
20
21
|
"approval-only Start/Cancel gate",
|
|
21
22
|
"Start new workflow from recent discussion",
|
|
@@ -35,8 +36,8 @@ checks = {
|
|
|
35
36
|
],
|
|
36
37
|
"extensions/completion/index.ts": [
|
|
37
38
|
'description: "Discussion-driven /cook workflow: start, continue, refocus, or start the next round"',
|
|
38
|
-
"
|
|
39
|
-
"
|
|
39
|
+
"If an inline /cook hint is present, treat it as a high-priority user hint that may focus the mission, but do not ignore conflicting discussion or skip missing details.",
|
|
40
|
+
"/cook failed closed because recent discussion and any optional inline /cook hint did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook.",
|
|
40
41
|
],
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -46,32 +47,17 @@ forbidden = {
|
|
|
46
47
|
"extensions/completion/index.ts": ["temporary" + " compatibility" + " shim, pass /cook"],
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
public_doc_paths = [
|
|
50
|
-
Path("README.md"),
|
|
51
|
-
Path("CHANGELOG.md"),
|
|
52
|
-
Path("PUBLISHING.md"),
|
|
53
|
-
]
|
|
54
|
-
inline_cook_pattern = re.compile(r"/cook\s*<[^>]+>")
|
|
55
|
-
|
|
56
50
|
for path, needles in checks.items():
|
|
57
51
|
text = Path(path).read_text()
|
|
58
52
|
for needle in needles:
|
|
59
53
|
if needle not in text:
|
|
60
|
-
raise SystemExit(f"[release-check] missing expected
|
|
54
|
+
raise SystemExit(f"[release-check] missing expected /cook parity text in {path}: {needle}")
|
|
61
55
|
|
|
62
56
|
for path, needles in forbidden.items():
|
|
63
57
|
text = Path(path).read_text()
|
|
64
58
|
for needle in needles:
|
|
65
59
|
if needle in text:
|
|
66
60
|
raise SystemExit(f"[release-check] found stale compatibility wording in {path}: {needle}")
|
|
67
|
-
|
|
68
|
-
for path in public_doc_paths:
|
|
69
|
-
text = path.read_text()
|
|
70
|
-
match = inline_cook_pattern.search(text)
|
|
71
|
-
if match:
|
|
72
|
-
raise SystemExit(
|
|
73
|
-
f"[release-check] found unsupported inline /cook syntax in {path}: {match.group(0)}"
|
|
74
|
-
)
|
|
75
61
|
PY
|
|
76
62
|
|
|
77
63
|
npm run smoke-test
|
package/scripts/smoke-test.sh
CHANGED
|
@@ -57,12 +57,15 @@ INLINE_REJECTION_ROUTING_SNAPSHOT="$TMPDIR/inline-arg-routing.json"
|
|
|
57
57
|
INLINE_REJECTION_PROPOSAL_SNAPSHOT="$TMPDIR/inline-arg-proposal.json"
|
|
58
58
|
INLINE_REJECTION_CHOOSER_SNAPSHOT="$TMPDIR/inline-arg-chooser.json"
|
|
59
59
|
BOOTSTRAP_SESSION="$TMPDIR/session-smoke-bootstrap.jsonl"
|
|
60
|
-
BOOTSTRAP_DISCUSSION=$'Mission: Exercise smoke-test bootstrap.\nScope:\n- Materialize the canonical completion control-plane files.\n- Keep the smoke test on supported
|
|
60
|
+
BOOTSTRAP_DISCUSSION=$'Mission: Exercise smoke-test bootstrap.\nScope:\n- Materialize the canonical completion control-plane files.\n- Keep the smoke test on supported /cook startup behavior.\nConstraints:\n- Keep startup proposal confirmation approval-only.\nAcceptance:\n- Scaffold canonical files and kickoff prompts for the smoke fixture.'
|
|
61
|
+
INLINE_HINT_ANALYST_OUTPUT='{"mission":"Smoke-test inline hint startup mission.","scope":["Materialize the canonical completion control-plane files."],"constraints":["Keep startup proposal confirmation approval-only."],"acceptance":["Scaffold canonical files and kickoff prompts for the smoke fixture."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.91}'
|
|
61
62
|
|
|
62
63
|
mkdir -p "$ROOT"
|
|
63
64
|
cd "$ROOT"
|
|
64
65
|
git init -q
|
|
65
66
|
|
|
67
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
|
|
68
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$INLINE_HINT_ANALYST_OUTPUT" \
|
|
66
69
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
67
70
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INLINE_REJECTION_ROUTING_SNAPSHOT" \
|
|
68
71
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INLINE_REJECTION_PROPOSAL_SNAPSHOT" \
|
|
@@ -79,12 +82,11 @@ routing = Path(sys.argv[3])
|
|
|
79
82
|
proposal = Path(sys.argv[4])
|
|
80
83
|
chooser = Path(sys.argv[5])
|
|
81
84
|
|
|
82
|
-
assert not Path('.agent').exists(), 'startup inline /cook
|
|
83
|
-
assert not routing.exists(), 'startup inline /cook
|
|
84
|
-
assert
|
|
85
|
-
assert not chooser.exists(), 'startup inline /cook
|
|
86
|
-
assert '
|
|
87
|
-
assert 'Clarify the mission in the main chat and rerun bare /cook.' in output, 'startup inline /cook args should redirect users back to main chat plus bare /cook'
|
|
85
|
+
assert not Path('.agent').exists(), 'startup inline /cook hint cancel should leave canonical state untouched'
|
|
86
|
+
assert not routing.exists(), 'startup inline /cook hint should not open active-workflow routing'
|
|
87
|
+
assert proposal.exists(), 'startup inline /cook hint should still prepare a proposal for confirmation'
|
|
88
|
+
assert not chooser.exists(), 'startup inline /cook hint should not open the chooser flow during startup'
|
|
89
|
+
assert 'Cancelled recent-discussion workflow proposal.' in output, 'startup inline /cook hint cancel should surface the proposal cancellation message'
|
|
88
90
|
PY
|
|
89
91
|
|
|
90
92
|
write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
|