@linimin/pi-letscook 0.1.41 → 0.1.42

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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.42
6
+
7
+ ### Changed
8
+
9
+ - 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
10
+
5
11
  ## 0.1.41
6
12
 
7
13
  ### Changed
package/README.md CHANGED
@@ -32,32 +32,33 @@ Then run `/reload` in Pi.
32
32
 
33
33
  ## Quick start
34
34
 
35
- Primary entrypoint:
35
+ Primary entrypoints:
36
36
 
37
37
  ```text
38
38
  /cook
39
+ /cook <hint>
39
40
  ```
40
41
 
41
- Use bare `/cook` after you discuss the mission in the main chat. The same command can:
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
- Bare `/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.
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, bare `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning bare `/cook`.
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` is the only supported workflow entrypoint.
55
+ Bare `/cook` and `/cook <hint>` are the supported workflow entrypoints.
55
56
 
56
- | Repo state | Bare `/cook` |
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 bare `/cook`. |
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 bare `/cook`. |
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 bare `/cook` cannot derive a clear startup, next-round, or replacement proposal for concrete repo changes from recent main-chat discussion, 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 bare `/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
+ 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
- "Bare /cook failed closed because recent discussion did not contain a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun bare /cook.";
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}`;
@@ -1114,9 +1112,18 @@ function contextProposalAnalystModelArg(model: unknown): string | undefined {
1114
1112
  return provider && id ? `${provider}/${id}` : undefined;
1115
1113
  }
1116
1114
 
1117
- function buildContextProposalAnalystPrompt(projectName: string, recentEntries: RecentDiscussionEntry[]): string {
1115
+ function buildContextProposalAnalystPrompt(projectName: string, recentEntries: RecentDiscussionEntry[], hintText?: string): string {
1118
1116
  const discussion = serializeRecentDiscussionEntries(recentEntries);
1119
- return [`Project: ${projectName}`, "Infer the current mission from the discussion.", "", "Recent discussion:", discussion].join("\n");
1117
+ const lines = [
1118
+ `Project: ${projectName}`,
1119
+ "Infer the current mission from the discussion.",
1120
+ "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.",
1121
+ ];
1122
+ if (hintText) {
1123
+ lines.push("", "Inline /cook hint:", hintText);
1124
+ }
1125
+ lines.push("", "Recent discussion:", discussion || "(none)");
1126
+ return lines.join("\n");
1120
1127
  }
1121
1128
 
1122
1129
  function contextProposalAnalystProgressLines(activity: LiveRoleActivity): string[] {
@@ -1145,20 +1152,23 @@ async function runContextProposalAnalystSubprocess(
1145
1152
  ctx: { cwd: string; hasUI: boolean; ui: any; model?: any },
1146
1153
  projectName: string,
1147
1154
  recentEntries: RecentDiscussionEntry[],
1155
+ hintText?: string,
1148
1156
  ): Promise<string | undefined> {
1149
1157
  const modelArg = contextProposalAnalystModelArg(ctx.model);
1150
1158
  if (!modelArg) return undefined;
1151
1159
  const cwd = getCtxCwd(ctx);
1152
1160
  const runCwd = findCompletionRoot(cwd) ?? findRepoRoot(cwd) ?? cwd;
1153
1161
  const rootKey = completionRootKey(undefined, cwd);
1154
- const prompt = buildContextProposalAnalystPrompt(projectName, recentEntries);
1162
+ const prompt = buildContextProposalAnalystPrompt(projectName, recentEntries, hintText);
1155
1163
  const systemPromptTemp = await writeTempFile("pi-cook-proposal-analyst-", CONTEXT_PROPOSAL_ANALYST_SYSTEM_PROMPT);
1156
1164
  const analystRole = "cook-proposal-analyst";
1157
1165
  const args: string[] = ["--mode", "json", "-p", "--no-session", "--append-system-prompt", systemPromptTemp.filePath, "--model", modelArg, prompt];
1158
1166
  const invocation = getPiInvocation(args);
1159
1167
  const liveActivity = createLiveRoleActivity(analystRole);
1160
- liveActivity.progress = "Analyzing recent discussion";
1161
- liveActivity.currentAction = "Reading recent discussion and preparing a startup proposal";
1168
+ liveActivity.progress = hintText ? "Analyzing recent discussion and inline hint" : "Analyzing recent discussion";
1169
+ liveActivity.currentAction = hintText
1170
+ ? "Reading recent discussion plus the inline /cook hint and preparing a startup proposal"
1171
+ : "Reading recent discussion and preparing a startup proposal";
1162
1172
  liveActivity.assistantSummary = liveActivity.progress;
1163
1173
  liveActivity.recentActivity = pushRecentActivity(liveActivity.recentActivity, `assistant: ${liveActivity.progress}`);
1164
1174
  const messages: RoleMessage[] = [];
@@ -1188,6 +1198,17 @@ async function runContextProposalAnalystSubprocess(
1188
1198
  stdio: ["ignore", "pipe", "pipe"],
1189
1199
  shell: false,
1190
1200
  });
1201
+ let settled = false;
1202
+ const resolveOnce = (value: string | undefined) => {
1203
+ if (settled) return;
1204
+ settled = true;
1205
+ resolve(value);
1206
+ };
1207
+ const abort = () => {
1208
+ proc.kill("SIGTERM");
1209
+ resolveOnce(undefined);
1210
+ };
1211
+ const handleSigint = () => abort();
1191
1212
  let buffer = "";
1192
1213
  const processLine = (line: string) => {
1193
1214
  if (!line.trim()) return;
@@ -1208,14 +1229,19 @@ async function runContextProposalAnalystSubprocess(
1208
1229
  stderr += chunk.toString();
1209
1230
  });
1210
1231
  proc.on("close", (code) => {
1232
+ process.off("SIGINT", handleSigint);
1211
1233
  if (buffer.trim()) processLine(buffer);
1212
- resolve(code === 0 ? liveActivity.lastAssistantText?.trim() || undefined : undefined);
1234
+ resolveOnce(code === 0 ? liveActivity.lastAssistantText?.trim() || undefined : undefined);
1235
+ });
1236
+ proc.on("error", () => {
1237
+ process.off("SIGINT", handleSigint);
1238
+ resolveOnce(undefined);
1213
1239
  });
1214
- proc.on("error", () => resolve(undefined));
1240
+ process.once("SIGINT", handleSigint);
1215
1241
  if (overlay) {
1216
1242
  overlay.onAbort = () => {
1217
- proc.kill("SIGTERM");
1218
- resolve(undefined);
1243
+ process.off("SIGINT", handleSigint);
1244
+ abort();
1219
1245
  };
1220
1246
  }
1221
1247
  });
@@ -1253,15 +1279,16 @@ async function analyzeContextProposalWithAgent(
1253
1279
  ctx: { cwd: string; hasUI: boolean; ui: any; model?: any; modelRegistry?: any },
1254
1280
  projectName: string,
1255
1281
  recentEntries: RecentDiscussionEntry[],
1282
+ hintText?: string,
1256
1283
  ): Promise<ContextProposal | undefined> {
1257
1284
  if (shouldDisableContextProposalAnalyst()) return undefined;
1258
1285
  const testOutput = completionTestContextProposalAnalystOutput();
1259
1286
  if (testOutput) {
1260
1287
  return parseContextProposalAnalystOutput(testOutput, projectName);
1261
1288
  }
1262
- if (recentEntries.length === 0) return undefined;
1289
+ if (recentEntries.length === 0 && !hintText?.trim()) return undefined;
1263
1290
  try {
1264
- const raw = await runContextProposalAnalystSubprocess(ctx, projectName, recentEntries);
1291
+ const raw = await runContextProposalAnalystSubprocess(ctx, projectName, recentEntries, hintText);
1265
1292
  if (!raw) return undefined;
1266
1293
  return parseContextProposalAnalystOutput(raw, projectName);
1267
1294
  } catch (error) {
@@ -1719,17 +1746,19 @@ function extractContextProposalFromStructuredSession(
1719
1746
  async function extractContextProposalFromSession(
1720
1747
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
1721
1748
  projectName: string,
1749
+ hintText?: string,
1722
1750
  ): Promise<ContextProposal | undefined> {
1723
1751
  const recentEntries = collectRecentDiscussionEntries(ctx);
1724
- return (await analyzeContextProposalWithAgent(ctx, projectName, recentEntries)) ??
1752
+ return (await analyzeContextProposalWithAgent(ctx, projectName, recentEntries, hintText)) ??
1725
1753
  extractContextProposalFromStructuredSession(recentEntries, projectName);
1726
1754
  }
1727
1755
 
1728
1756
  async function deriveCookContextProposal(
1729
1757
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
1730
1758
  projectName: string,
1759
+ hintText?: string,
1731
1760
  ): Promise<ContextProposal | undefined> {
1732
- return await extractContextProposalFromSession(ctx, projectName);
1761
+ return await extractContextProposalFromSession(ctx, projectName, hintText);
1733
1762
  }
1734
1763
 
1735
1764
  async function confirmContextProposal(
@@ -2005,10 +2034,11 @@ function buildEvaluationRoleReminderText(snapshot: CompletionStateSnapshot, role
2005
2034
  async function assessActiveWorkflowProposalRouting(
2006
2035
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
2007
2036
  snapshot: CompletionStateSnapshot,
2037
+ hintText?: string,
2008
2038
  ): Promise<ActiveWorkflowProposalAssessment> {
2009
2039
  const currentMission = currentMissionAnchor(snapshot);
2010
2040
  const projectName = path.basename(snapshot.files.root);
2011
- const proposal = await deriveCookContextProposal(ctx, projectName);
2041
+ const proposal = await deriveCookContextProposal(ctx, projectName, hintText);
2012
2042
  if (!proposal) {
2013
2043
  const assessment: ActiveWorkflowProposalAssessment = {
2014
2044
  action: "unclear",
@@ -4073,11 +4103,7 @@ export default function completionExtension(pi: ExtensionAPI) {
4073
4103
  pi.registerCommand("cook", {
4074
4104
  description: "Discussion-driven /cook workflow: start, continue, refocus, or start the next round",
4075
4105
  handler: async (args, ctx) => {
4076
- const inlineArgs = args.trim();
4077
- if (inlineArgs) {
4078
- emitCommandText(ctx, COOK_INLINE_ARG_REJECTION_MESSAGE, "info");
4079
- return;
4080
- }
4106
+ const inlineHint = args.trim() || undefined;
4081
4107
  let goal: string | undefined;
4082
4108
  const cwd = getCtxCwd(ctx);
4083
4109
  let snapshot = await loadCompletionSnapshot(cwd);
@@ -4089,7 +4115,7 @@ export default function completionExtension(pi: ExtensionAPI) {
4089
4115
  if (!snapshot) {
4090
4116
  const root = findRepoRoot(cwd) ?? cwd;
4091
4117
  const projectName = path.basename(root);
4092
- const proposal = await deriveCookContextProposal(ctx, projectName);
4118
+ const proposal = await deriveCookContextProposal(ctx, projectName, inlineHint);
4093
4119
  if (!proposal) {
4094
4120
  emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(), "info");
4095
4121
  return;
@@ -4127,7 +4153,7 @@ export default function completionExtension(pi: ExtensionAPI) {
4127
4153
  if (!goal) {
4128
4154
  if (workflowDone) {
4129
4155
  const projectName = path.basename(snapshot.files.root);
4130
- const proposal = await deriveCookContextProposal(ctx, projectName);
4156
+ const proposal = await deriveCookContextProposal(ctx, projectName, inlineHint);
4131
4157
  if (!proposal) {
4132
4158
  emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage("The previous completion workflow is already done."), "info");
4133
4159
  return;
@@ -4146,7 +4172,7 @@ export default function completionExtension(pi: ExtensionAPI) {
4146
4172
  snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
4147
4173
  emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
4148
4174
  } else {
4149
- const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot);
4175
+ const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, inlineHint);
4150
4176
  if (assessment.action !== "refocus" || !assessment.proposal) {
4151
4177
  await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot);
4152
4178
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linimin/pi-letscook",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
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,
@@ -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 'Bare /cook failed closed' in output, 'missing-section structured discussion should explain the fail-closed startup outcome'
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 'Bare /cook failed closed' in output, 'ambiguous structured discussion should explain the fail-closed startup outcome'
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 'Bare /cook failed closed' in output, 'planning-only startup should explain the fail-closed startup outcome'
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 'Bare /cook failed closed' in output, 'assistant-only startup summary should explain the fail-closed startup outcome'
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 'Bare /cook failed closed' in output, 'analyst-derived ambiguous generic discussion should explain the fail-closed startup outcome'
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 args should be rejected before proposal/routing helpers run
840
- # and should leave canonical state unchanged.
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=accept \
863
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
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 'Inline /cook arguments are no longer supported.' in output, 'active inline /cook args should explain the hard rejection'
890
- assert 'Clarify the mission in the main chat and rerun bare /cook.' in output, 'active inline /cook args should redirect users back to main chat plus bare /cook'
891
- assert not routing.exists(), 'active inline /cook args should not run active-workflow routing'
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 args should leave canonical files unchanged'
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 args should also fail closed without starting the next round.
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=accept \
922
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
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 not proposal.exists(), 'done inline /cook args should not open next-round proposal confirmation'
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 args should leave canonical files unchanged'
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
@@ -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 'Inline /cook arguments are no longer supported.' in output, 'active inline /cook args should explain the hard rejection'
135
- assert 'Clarify the mission in the main chat and rerun bare /cook.' in output, 'active inline /cook args should redirect users back to main chat plus bare /cook'
136
- assert not routing.exists(), 'active inline /cook args should not run active-workflow routing'
137
- assert not proposal.exists(), 'active inline /cook args should not open proposal confirmation'
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 args should leave canonical files unchanged'
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"
@@ -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 bare /cook parity"
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` is the only supported workflow entrypoint.",
18
- "clarify the mission in the main chat before rerunning bare `/cook`",
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
- "Inline /cook arguments are no longer supported. Clarify the mission in the main chat and rerun bare /cook.",
39
- "Bare /cook failed closed because recent discussion did not contain a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun bare /cook.",
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 bare /cook parity text in {path}: {needle}")
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
@@ -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 bare /cook behavior.\nConstraints:\n- Do not rely on inline /cook arguments.\nAcceptance:\n- Scaffold canonical files and kickoff prompts for the smoke fixture.'
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 args should fail closed without creating canonical state'
83
- assert not routing.exists(), 'startup inline /cook args should not open active-workflow routing'
84
- assert not proposal.exists(), 'startup inline /cook args should not open the proposal confirmation flow'
85
- assert not chooser.exists(), 'startup inline /cook args should not open the chooser flow'
86
- assert 'Inline /cook arguments are no longer supported.' in output, 'startup inline /cook args should explain the hard rejection'
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"