@linimin/pi-letscook 0.1.73 → 0.1.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -2
- package/README.md +2 -2
- package/extensions/completion/index.ts +9 -38
- package/package.json +1 -1
- package/scripts/canonical-evidence-artifact-test.sh +4 -1
- package/scripts/completion-role-gating-test.sh +15 -23
- package/scripts/context-proposal-test.sh +166 -25
- package/scripts/release-check.sh +2 -2
- package/scripts/smoke-test.sh +24 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.75
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- stopped treating a fresh but under-specified explicit `cook_handoff` as an automatic startup blocker; `/cook` now uses the user's explicit entry as implementation intent and lets same-entry primary-agent startup synthesis tighten the first slice before it gives up
|
|
8
|
+
- aligned startup, sticky-workflow, and canonical-evidence regressions with the new implementation-first `/cook` behavior so long-running workflows no longer bounce users back into handoff-authoring loops
|
|
9
|
+
|
|
10
|
+
## 0.1.74
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- made active `/cook` workflows sticky across subsequent turns so routine continuation, exact await-user-input replies, and mandatory completion-role dispatch no longer depend on prompt-shaped driver turns or repeated manual `/cook` re-entry
|
|
15
|
+
- updated smoke, canonical-evidence, release-check, and completion-role gating regressions to enforce the new sticky active-workflow self-healing behavior
|
|
16
|
+
- stopped letting fresh but under-specified explicit `cook_handoff` capsules block `/cook` startup by default; `/cook` now treats the user's entry as implementation intent and tries same-entry primary-agent startup synthesis to tighten the first slice before failing closed
|
|
17
|
+
|
|
3
18
|
## 0.1.73
|
|
4
19
|
|
|
5
20
|
### Fixed
|
|
6
21
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
22
|
+
- made active `/cook` workflows sticky across subsequent turns so completion-role dispatch and workflow context continue to self-heal from canonical active state instead of depending on prompt-shaped driver turns
|
|
23
|
+
- stopped pushing users to rerun `/cook` for routine active-workflow continuation or exact await-user-input replies when canonical workflow state is already active
|
|
24
|
+
- added regression coverage so release-check fails if sticky active-workflow dispatch falls back to prompt-only gating again
|
|
9
25
|
|
|
10
26
|
## 0.1.72
|
|
11
27
|
|
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ Then run `/reload` in Pi.
|
|
|
61
61
|
|
|
62
62
|
If the primary-agent handoff step still cannot prepare a concrete handoff, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to refine the mission, first slice, or verification intent in the main chat before rerunning `/cook`.
|
|
63
63
|
|
|
64
|
-
If a fresh explicit handoff exists but is still workflow-worthy rather than implementation-startable, `/cook`
|
|
64
|
+
If a fresh explicit handoff exists but is still workflow-worthy rather than implementation-startable, `/cook` now treats your `/cook` entry as implementation intent and asks the same-entry primary-agent handoff synthesis step to tighten that startup from recent discussion before it gives up. Only if the synthesized startup is still not concrete enough does `/cook` fail closed and ask for refinement in the main chat.
|
|
65
65
|
|
|
66
66
|
If you pass inline arguments to `/cook`, it also fails closed and tells you to move that intent into the main chat before rerunning bare `/cook`.
|
|
67
67
|
|
|
@@ -98,7 +98,7 @@ I want to add login redirect handling and tests.
|
|
|
98
98
|
|
|
99
99
|
| Repo state | What you'll see |
|
|
100
100
|
|---|---|
|
|
101
|
-
| No workflow yet | `/cook` consumes a fresh explicit primary-agent handoff when one already exists, or synthesizes one from the primary-agent view in the same entry, then asks you to choose **Start** or **Cancel**.
|
|
101
|
+
| No workflow yet | `/cook` consumes a fresh explicit primary-agent handoff when one already exists, or synthesizes one from the primary-agent view in the same entry, then asks you to choose **Start** or **Cancel**. If a fresh explicit handoff is still under-specified, `/cook` first tries to tighten it through same-entry startup synthesis before failing closed. |
|
|
102
102
|
| Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If a concrete replacement handoff exists already or is synthesized in the same `/cook` entry and points to a different mission, `/cook` shows a chooser first and only rewrites canonical state after you confirm the replacement. Ambiguous or missing replacement handoff stays conservative. |
|
|
103
103
|
| Previous workflow is `done` | `/cook` can start the next implementation round from a fresh explicit primary-agent handoff or from the same-entry primary-agent handoff synthesis step behind **Start** or **Cancel**. Weak or planning-only next-round handoffs still fail closed. |
|
|
104
104
|
|
|
@@ -257,42 +257,8 @@ function isCompletionDriverPromptTurn(snapshot: CompletionStateSnapshot | undefi
|
|
|
257
257
|
return true;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
function isCompletionWorkflowSessionTurn(snapshot: CompletionStateSnapshot | undefined,
|
|
261
|
-
|
|
262
|
-
return isCompletionDriverPromptTurn(snapshot, ctx) || isCookCommandTurn(ctx);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function isOrdinaryMainChatTurnDuringActiveWorkflow(
|
|
266
|
-
snapshot: CompletionStateSnapshot | undefined,
|
|
267
|
-
ctx: { sessionManager?: any },
|
|
268
|
-
): boolean {
|
|
269
|
-
if (!hasActiveWorkflowEntry(snapshot)) return false;
|
|
270
|
-
const latest = latestUserOrCustomTurnText(ctx);
|
|
271
|
-
if (!latest) return false;
|
|
272
|
-
if (isCookCommandTurn(ctx)) return false;
|
|
273
|
-
if (isCompletionDriverPromptTurn(snapshot, ctx)) return false;
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function isAwaitingUserInputWorkflowReplyTurn(
|
|
278
|
-
snapshot: CompletionStateSnapshot | undefined,
|
|
279
|
-
ctx: { sessionManager?: any },
|
|
280
|
-
): boolean {
|
|
281
|
-
if (!hasActiveWorkflowEntry(snapshot)) return false;
|
|
282
|
-
if (!isOrdinaryMainChatTurnDuringActiveWorkflow(snapshot, ctx)) return false;
|
|
283
|
-
return asString(snapshot?.state?.continuation_policy) === "await_user_input";
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function isCompletionRoleDispatchAllowedTurn(
|
|
287
|
-
snapshot: CompletionStateSnapshot | undefined,
|
|
288
|
-
ctx: { sessionManager?: any },
|
|
289
|
-
): boolean {
|
|
290
|
-
if (hasCompletionRoutingActivation(snapshot)) return true;
|
|
291
|
-
if (!hasActiveWorkflowEntry(snapshot)) return false;
|
|
292
|
-
if (isCompletionWorkflowSessionTurn(snapshot, ctx)) return true;
|
|
293
|
-
if (isAwaitingUserInputWorkflowReplyTurn(snapshot, ctx)) return true;
|
|
294
|
-
if (isOrdinaryMainChatTurnDuringActiveWorkflow(snapshot, ctx)) return false;
|
|
295
|
-
return asString(snapshot?.state?.continuation_policy) === "continue";
|
|
260
|
+
function isCompletionWorkflowSessionTurn(snapshot: CompletionStateSnapshot | undefined, _ctx: { sessionManager?: any }): boolean {
|
|
261
|
+
return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);
|
|
296
262
|
}
|
|
297
263
|
|
|
298
264
|
function shouldInjectCompletionWorkflowContext(snapshot: CompletionStateSnapshot | undefined, ctx: { sessionManager?: any }): boolean {
|
|
@@ -464,7 +430,7 @@ async function deriveCookContextProposal(
|
|
|
464
430
|
projectName: string,
|
|
465
431
|
): Promise<CookContextProposalResult> {
|
|
466
432
|
const explicit = await deriveCookStartupProposal(ctx, projectName);
|
|
467
|
-
if (explicit.proposal
|
|
433
|
+
if (explicit.proposal) return explicit;
|
|
468
434
|
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
469
435
|
const recentEntries = recentMessages
|
|
470
436
|
.filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "assistant" || entry.role === "custom" || entry.role === "summary"))
|
|
@@ -484,6 +450,10 @@ async function deriveCookContextProposal(
|
|
|
484
450
|
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
485
451
|
]
|
|
486
452
|
: [];
|
|
453
|
+
if (explicit.blockedFailureMessage) {
|
|
454
|
+
workflowContextLines.push(`fresh explicit handoff required tightening before startup: ${explicit.blockedFailureMessage}`);
|
|
455
|
+
workflowContextLines.push("Treat the user's /cook as implementation intent. Tighten the first slice and verification details from recent discussion instead of failing closed solely because the latest explicit handoff capsule was under-specified.");
|
|
456
|
+
}
|
|
487
457
|
const raw = await generateCookHandoffWithAgent({
|
|
488
458
|
ctx,
|
|
489
459
|
projectName,
|
|
@@ -510,6 +480,7 @@ async function deriveCookContextProposal(
|
|
|
510
480
|
});
|
|
511
481
|
if (generated.status === "startable") return { proposal: generated.proposal };
|
|
512
482
|
if (generated.status === "fresh_but_not_startable") return { blockedFailureMessage: generated.message };
|
|
483
|
+
if (explicit.blockedFailureMessage) return { blockedFailureMessage: explicit.blockedFailureMessage };
|
|
513
484
|
return {};
|
|
514
485
|
}
|
|
515
486
|
|
|
@@ -1114,7 +1085,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
1114
1085
|
const snapshot = await loadCompletionSnapshot(cwd);
|
|
1115
1086
|
const completionActive = Boolean(snapshot) && asString(snapshot?.state?.continuation_policy) !== "done";
|
|
1116
1087
|
const root = snapshot?.files.root ?? findRepoRoot(cwd) ?? cwd;
|
|
1117
|
-
const completionRoleDispatchAllowed = Boolean(role) ||
|
|
1088
|
+
const completionRoleDispatchAllowed = Boolean(role) || isCompletionWorkflowSessionTurn(snapshot, ctx);
|
|
1118
1089
|
const reason = toolCallBlockReason({
|
|
1119
1090
|
toolName: event.toolName,
|
|
1120
1091
|
input: isRecord(event.input) ? event.input : undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.75",
|
|
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,
|
|
@@ -510,7 +510,10 @@ import sys
|
|
|
510
510
|
from pathlib import Path
|
|
511
511
|
|
|
512
512
|
reminder = Path(sys.argv[1])
|
|
513
|
-
assert
|
|
513
|
+
assert reminder.exists(), 'active selected-slice canonical state should inject the completion reminder on subsequent non-/cook turns'
|
|
514
|
+
text = reminder.read_text()
|
|
515
|
+
assert 'Completion workflow detected.' in text, 'selected-slice reminder should expose canonical workflow context'
|
|
516
|
+
assert 'Verification evidence subject: selected_slice' in text, 'selected-slice reminder should expose the canonical evidence subject'
|
|
514
517
|
PY
|
|
515
518
|
|
|
516
519
|
python3 - <<'PY'
|
|
@@ -21,34 +21,26 @@ const assertNotIncludes = (file, snippet) => {
|
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
assertIncludes('extensions/completion/index.ts', 'function
|
|
25
|
-
assertIncludes('extensions/completion/index.ts', '
|
|
26
|
-
assertIncludes('extensions/completion/index.ts', '
|
|
27
|
-
assertIncludes('extensions/completion/index.ts', 'if (isAwaitingUserInputWorkflowReplyTurn(snapshot, ctx)) return true;');
|
|
28
|
-
assertIncludes('extensions/completion/index.ts', 'if (isOrdinaryMainChatTurnDuringActiveWorkflow(snapshot, ctx)) return false;');
|
|
29
|
-
assertIncludes('extensions/completion/index.ts', 'return asString(snapshot?.state?.continuation_policy) === "await_user_input";');
|
|
30
|
-
assertIncludes('extensions/completion/index.ts', 'return asString(snapshot?.state?.continuation_policy) === "continue";');
|
|
31
|
-
assertIncludes('extensions/completion/index.ts', 'const completionRoleDispatchAllowed = Boolean(role) || isCompletionRoleDispatchAllowedTurn(snapshot, ctx);');
|
|
32
|
-
assertIncludes('extensions/completion/index.ts', 'if (isCookCommandTurn(ctx)) return false;');
|
|
33
|
-
assertIncludes('extensions/completion/index.ts', 'if (isCompletionDriverPromptTurn(snapshot, ctx)) return false;');
|
|
24
|
+
assertIncludes('extensions/completion/index.ts', 'function isCompletionWorkflowSessionTurn(');
|
|
25
|
+
assertIncludes('extensions/completion/index.ts', 'return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);');
|
|
26
|
+
assertIncludes('extensions/completion/index.ts', 'const completionRoleDispatchAllowed = Boolean(role) || isCompletionWorkflowSessionTurn(snapshot, ctx);');
|
|
34
27
|
assertIncludes('extensions/completion/policy-guards.ts', 'return "completion_role may only be used from an active /cook workflow session.";');
|
|
35
|
-
assertIncludes('CHANGELOG.md', '
|
|
36
|
-
assertIncludes('CHANGELOG.md', '
|
|
28
|
+
assertIncludes('CHANGELOG.md', 'made active `/cook` workflows sticky across subsequent turns so completion-role dispatch and workflow context continue to self-heal from canonical active state instead of depending on prompt-shaped driver turns');
|
|
29
|
+
assertIncludes('CHANGELOG.md', 'stopped pushing users to rerun `/cook` for routine active-workflow continuation or exact await-user-input replies when canonical workflow state is already active');
|
|
37
30
|
|
|
38
|
-
assertNotIncludes(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
31
|
+
assertNotIncludes('extensions/completion/index.ts', 'function isOrdinaryMainChatTurnDuringActiveWorkflow(');
|
|
32
|
+
assertNotIncludes('extensions/completion/index.ts', 'function isCompletionRoleDispatchAllowedTurn(');
|
|
33
|
+
assertNotIncludes('extensions/completion/index.ts', 'function isAwaitingUserInputWorkflowReplyTurn(');
|
|
42
34
|
|
|
43
35
|
const indexText = read('extensions/completion/index.ts');
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
throw new Error('extensions/completion/index.ts must
|
|
36
|
+
const sessionTurnIndex = indexText.indexOf('function isCompletionWorkflowSessionTurn(');
|
|
37
|
+
const stickyReturnIndex = indexText.indexOf('return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);');
|
|
38
|
+
const toolGateIndex = indexText.indexOf('const completionRoleDispatchAllowed = Boolean(role) || isCompletionWorkflowSessionTurn(snapshot, ctx);');
|
|
39
|
+
if (sessionTurnIndex === -1 || stickyReturnIndex === -1 || toolGateIndex === -1) {
|
|
40
|
+
throw new Error('extensions/completion/index.ts must derive workflow legitimacy from canonical active state and reuse that gate for completion_role dispatch.');
|
|
49
41
|
}
|
|
50
|
-
if (
|
|
51
|
-
throw new Error('extensions/completion/index.ts
|
|
42
|
+
if (!(sessionTurnIndex < stickyReturnIndex && stickyReturnIndex < toolGateIndex)) {
|
|
43
|
+
throw new Error('extensions/completion/index.ts should define sticky workflow-session detection before reusing it for completion_role dispatch.');
|
|
52
44
|
}
|
|
53
45
|
NODE
|
|
54
46
|
|
|
@@ -564,8 +564,8 @@ assert plan['mission_anchor'] == mission, 'summary-only active bare /cook should
|
|
|
564
564
|
assert active['mission_anchor'] == mission, 'summary-only active bare /cook should keep active-slice.json unchanged'
|
|
565
565
|
PY
|
|
566
566
|
|
|
567
|
-
# Active workflow: a fresh explicit handoff that is not implementation-startable should
|
|
568
|
-
#
|
|
567
|
+
# Active workflow: a fresh explicit handoff that is not implementation-startable should trigger
|
|
568
|
+
# same-entry startup synthesis instead of blocking active-workflow continuation outright.
|
|
569
569
|
SESSION_ONE_NON_STARTABLE_ACTIVE="$TMPDIR/session-one-non-startable-active.jsonl"
|
|
570
570
|
NON_STARTABLE_ACTIVE_ROUTING="$TMPDIR/active-non-startable-routing.json"
|
|
571
571
|
NON_STARTABLE_ACTIVE_RESUME="$TMPDIR/unexpected-active-non-startable-resume.txt"
|
|
@@ -614,6 +614,49 @@ PY
|
|
|
614
614
|
)"
|
|
615
615
|
write_session_messages "$SESSION_ONE_NON_STARTABLE_ACTIVE" "$ROOT" "$NON_STARTABLE_ACTIVE_MESSAGES"
|
|
616
616
|
|
|
617
|
+
SYNTH_NON_STARTABLE_ACTIVE_HANDOFF="$(python3 - <<'PY'
|
|
618
|
+
import json
|
|
619
|
+
capsule = {
|
|
620
|
+
"kind": "cook_handoff",
|
|
621
|
+
"source": "primary_agent",
|
|
622
|
+
"captured_at": "2026-01-01T00:00:03.000Z",
|
|
623
|
+
"source_turn_id": "s0001",
|
|
624
|
+
"mission": "Replace the current widget mission with a concrete redirect-hardening workflow.",
|
|
625
|
+
"scope": [
|
|
626
|
+
"Tighten the active workflow replacement into a bounded redirect-hardening slice.",
|
|
627
|
+
"Preserve the current workflow until the user confirms replacement."
|
|
628
|
+
],
|
|
629
|
+
"constraints": [
|
|
630
|
+
"Do not rewrite canonical state until replacement confirmation succeeds."
|
|
631
|
+
],
|
|
632
|
+
"non_goals": [],
|
|
633
|
+
"acceptance": [
|
|
634
|
+
"Add a regression test that proves the replacement redirect behavior on a concrete repo path.",
|
|
635
|
+
"Keep replacement startup bounded to the redirect-handling slice with deterministic verification."
|
|
636
|
+
],
|
|
637
|
+
"risks": [],
|
|
638
|
+
"notes": [
|
|
639
|
+
"This synthesized handoff tightens the vague explicit capsule into a concrete replacement option."
|
|
640
|
+
],
|
|
641
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
642
|
+
"first_slice_goal": "Land the redirect-hardening regression slice before any broader workflow replacement.",
|
|
643
|
+
"first_slice_non_goals": [],
|
|
644
|
+
"implementation_surfaces": [
|
|
645
|
+
"src/auth/redirect.ts",
|
|
646
|
+
"tests/auth/redirect.spec.ts"
|
|
647
|
+
],
|
|
648
|
+
"verification_commands": [
|
|
649
|
+
"npm test -- redirect.spec.ts"
|
|
650
|
+
],
|
|
651
|
+
"why_this_slice_first": "The redirect-handling path is the smallest concrete slice that can justify replacing the active workflow.",
|
|
652
|
+
"task_type": "completion-workflow",
|
|
653
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
654
|
+
"why_cook_now": "The user explicitly entered /cook, so startup synthesis should tighten the vague explicit handoff instead of failing closed immediately."
|
|
655
|
+
}
|
|
656
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
657
|
+
PY
|
|
658
|
+
)"
|
|
659
|
+
|
|
617
660
|
python3 - "$TMPDIR/active-non-startable-before.json" <<'PY'
|
|
618
661
|
import json
|
|
619
662
|
import sys
|
|
@@ -633,6 +676,7 @@ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$NON_STARTABLE_ACTIVE_ROUTING"
|
|
|
633
676
|
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$NON_STARTABLE_ACTIVE_RESUME" \
|
|
634
677
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$NON_STARTABLE_ACTIVE_CHOOSER" \
|
|
635
678
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$NON_STARTABLE_ACTIVE_PROPOSAL" \
|
|
679
|
+
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$SYNTH_NON_STARTABLE_ACTIVE_HANDOFF" \
|
|
636
680
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
637
681
|
pi --session "$SESSION_ONE_NON_STARTABLE_ACTIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-active-non-startable.out" 2>"$TMPDIR/pi-completion-context-proposal-active-non-startable.err"
|
|
638
682
|
|
|
@@ -657,15 +701,15 @@ after = {
|
|
|
657
701
|
}
|
|
658
702
|
|
|
659
703
|
assert routing['mode'] == 'bare', 'fresh non-startable explicit handoff should snapshot bare routing mode'
|
|
660
|
-
assert routing['action'] == '
|
|
661
|
-
assert routing['reason'] == '
|
|
662
|
-
assert
|
|
663
|
-
assert
|
|
664
|
-
assert not
|
|
665
|
-
|
|
666
|
-
assert
|
|
667
|
-
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable explicit handoff should
|
|
668
|
-
assert before == after, 'fresh non-startable explicit handoff should leave canonical state unchanged'
|
|
704
|
+
assert routing['action'] == 'refocus', 'fresh non-startable explicit handoff should synthesize a concrete replacement option instead of blocking active bare /cook'
|
|
705
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'fresh non-startable explicit handoff should keep explicit-handoff replacement routing after startup synthesis tightens it'
|
|
706
|
+
assert resume_path.exists(), 'non-interactive active workflow should continue by queueing the canonical resume prompt after synthesized replacement routing'
|
|
707
|
+
assert chooser_path.exists(), 'fresh non-startable explicit handoff should still open the replacement chooser snapshot after startup synthesis tightens it'
|
|
708
|
+
assert not proposal_path.exists(), 'non-interactive active workflow should not open final replacement confirmation without an explicit chooser refocus selection'
|
|
709
|
+
chooser = json.loads(chooser_path.read_text())
|
|
710
|
+
assert 'Replace the current widget mission with a concrete redirect-hardening workflow.' in chooser['candidateMissions'], 'replacement chooser should include the synthesized concrete mission'
|
|
711
|
+
assert 'fresh explicit primary-agent handoff exists' not in output, 'fresh non-startable explicit handoff should not fail closed once startup synthesis tightens it'
|
|
712
|
+
assert before == after, 'fresh non-startable explicit handoff should still leave canonical state unchanged until replacement confirmation succeeds'
|
|
669
713
|
PY
|
|
670
714
|
|
|
671
715
|
# Completed workflow: bare /cook should suppress proposals that simply restate the completed mission
|
|
@@ -1311,8 +1355,8 @@ assert 'Why this slice first: The redirect callback bug is already bounded enoug
|
|
|
1311
1355
|
assert 'Primary-agent /cook handoff rationale: The implementation plan is concrete and ready for repo changes.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve why_cook_now as notes'
|
|
1312
1356
|
PY
|
|
1313
1357
|
|
|
1314
|
-
# Fresh but non-startable explicit handoff: /cook should
|
|
1315
|
-
#
|
|
1358
|
+
# Fresh but non-startable explicit handoff: /cook should tighten startup in the same entry
|
|
1359
|
+
# instead of failing closed solely because the explicit capsule is still too vague.
|
|
1316
1360
|
HANDOFF_ROOT_VAGUE="$TMPDIR/handoff-root-vague"
|
|
1317
1361
|
mkdir -p "$HANDOFF_ROOT_VAGUE"
|
|
1318
1362
|
cd "$HANDOFF_ROOT_VAGUE"
|
|
@@ -1365,7 +1409,54 @@ PY
|
|
|
1365
1409
|
)"
|
|
1366
1410
|
write_session_messages "$HANDOFF_SESSION_VAGUE" "$HANDOFF_ROOT_VAGUE" "$HANDOFF_MESSAGES_VAGUE"
|
|
1367
1411
|
|
|
1412
|
+
SYNTH_HANDOFF_VAGUE="$(python3 - <<'PY'
|
|
1413
|
+
import json
|
|
1414
|
+
capsule = {
|
|
1415
|
+
"kind": "cook_handoff",
|
|
1416
|
+
"source": "primary_agent",
|
|
1417
|
+
"captured_at": "2026-01-01T00:00:03.000Z",
|
|
1418
|
+
"source_turn_id": "s-vague",
|
|
1419
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1420
|
+
"scope": [
|
|
1421
|
+
"Update the callback redirect decision logic.",
|
|
1422
|
+
"Add a regression test for returning to the requested page."
|
|
1423
|
+
],
|
|
1424
|
+
"constraints": [
|
|
1425
|
+
"Do not refactor the broader auth flow."
|
|
1426
|
+
],
|
|
1427
|
+
"non_goals": [],
|
|
1428
|
+
"acceptance": [
|
|
1429
|
+
"Add a regression test proving the callback returns to the requested page.",
|
|
1430
|
+
"Keep the broader auth flow unchanged while the redirect regression passes."
|
|
1431
|
+
],
|
|
1432
|
+
"risks": [
|
|
1433
|
+
"Broad recent context could broaden the startup brief if synthesis ignores the bounded first slice."
|
|
1434
|
+
],
|
|
1435
|
+
"notes": [
|
|
1436
|
+
"This synthesized startup brief tightens the vague explicit handoff into a bounded redirect fix."
|
|
1437
|
+
],
|
|
1438
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1439
|
+
"first_slice_goal": "Land the callback redirect fix and its regression coverage.",
|
|
1440
|
+
"first_slice_non_goals": [
|
|
1441
|
+
"Do not refactor the broader auth flow."
|
|
1442
|
+
],
|
|
1443
|
+
"implementation_surfaces": [
|
|
1444
|
+
"src/auth/redirect.ts",
|
|
1445
|
+
"tests/auth/redirect.spec.ts"
|
|
1446
|
+
],
|
|
1447
|
+
"verification_commands": [
|
|
1448
|
+
"npm test -- redirect.spec.ts"
|
|
1449
|
+
],
|
|
1450
|
+
"why_this_slice_first": "The callback redirect path is the smallest concrete slice implied by the recent discussion.",
|
|
1451
|
+
"task_type": "completion-workflow",
|
|
1452
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
1453
|
+
}
|
|
1454
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
1455
|
+
PY
|
|
1456
|
+
)"
|
|
1457
|
+
|
|
1368
1458
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1459
|
+
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$SYNTH_HANDOFF_VAGUE" \
|
|
1369
1460
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_VAGUE" \
|
|
1370
1461
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1371
1462
|
pi --session "$HANDOFF_SESSION_VAGUE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-vague.out" 2>"$TMPDIR/pi-completion-handoff-vague.err"
|
|
@@ -1377,16 +1468,16 @@ from pathlib import Path
|
|
|
1377
1468
|
snapshot = Path(sys.argv[1])
|
|
1378
1469
|
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1379
1470
|
|
|
1380
|
-
assert
|
|
1381
|
-
assert not Path('.agent').exists(), 'fresh non-startable handoff should
|
|
1382
|
-
|
|
1383
|
-
assert '
|
|
1384
|
-
assert '
|
|
1385
|
-
assert '
|
|
1471
|
+
assert snapshot.exists(), 'fresh non-startable handoff should emit a startup proposal snapshot after same-entry startup synthesis tightens it'
|
|
1472
|
+
assert not Path('.agent').exists(), 'fresh non-startable handoff should still wait for confirmation before writing canonical state'
|
|
1473
|
+
proposal = __import__('json').loads(snapshot.read_text())
|
|
1474
|
+
assert proposal['mission'] == 'Fix login redirect callback behavior.', 'synthesized startup proposal should preserve the concrete mission anchor from recent discussion'
|
|
1475
|
+
assert proposal['source'] == 'handoff_capsule', 'synthesized startup proposal should still reflect primary-agent handoff startup synthesis output'
|
|
1476
|
+
assert 'fresh explicit primary-agent handoff exists' not in output, 'fresh non-startable handoff should not fail closed once startup synthesis tightens it'
|
|
1386
1477
|
PY
|
|
1387
1478
|
|
|
1388
|
-
# Fresh explicit handoff with complete first-slice fields but vague acceptance: /cook should still
|
|
1389
|
-
#
|
|
1479
|
+
# Fresh explicit handoff with complete first-slice fields but vague acceptance: /cook should still
|
|
1480
|
+
# try same-entry startup synthesis before giving up.
|
|
1390
1481
|
HANDOFF_ROOT_VAGUE_ACCEPTANCE="$TMPDIR/handoff-root-vague-acceptance"
|
|
1391
1482
|
mkdir -p "$HANDOFF_ROOT_VAGUE_ACCEPTANCE"
|
|
1392
1483
|
cd "$HANDOFF_ROOT_VAGUE_ACCEPTANCE"
|
|
@@ -1445,7 +1536,54 @@ PY
|
|
|
1445
1536
|
)"
|
|
1446
1537
|
write_session_messages "$HANDOFF_SESSION_VAGUE_ACCEPTANCE" "$HANDOFF_ROOT_VAGUE_ACCEPTANCE" "$HANDOFF_MESSAGES_VAGUE_ACCEPTANCE"
|
|
1447
1538
|
|
|
1539
|
+
SYNTH_HANDOFF_VAGUE_ACCEPTANCE="$(python3 - <<'PY'
|
|
1540
|
+
import json
|
|
1541
|
+
capsule = {
|
|
1542
|
+
"kind": "cook_handoff",
|
|
1543
|
+
"source": "primary_agent",
|
|
1544
|
+
"captured_at": "2026-01-01T00:00:03.500Z",
|
|
1545
|
+
"source_turn_id": "s-vague-acceptance",
|
|
1546
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1547
|
+
"scope": [
|
|
1548
|
+
"Update the callback redirect decision logic.",
|
|
1549
|
+
"Preserve the broader auth flow."
|
|
1550
|
+
],
|
|
1551
|
+
"constraints": [
|
|
1552
|
+
"Do not refactor the broader auth flow."
|
|
1553
|
+
],
|
|
1554
|
+
"non_goals": [],
|
|
1555
|
+
"acceptance": [
|
|
1556
|
+
"Add a regression test for returning to the requested page.",
|
|
1557
|
+
"Verify the redirect callback path with npm test -- redirect.spec.ts."
|
|
1558
|
+
],
|
|
1559
|
+
"risks": [
|
|
1560
|
+
"Broad recent context could broaden the startup brief if synthesis ignores the bounded first slice."
|
|
1561
|
+
],
|
|
1562
|
+
"notes": [
|
|
1563
|
+
"This synthesized startup brief tightens the vague explicit acceptance into concrete repo-change verification."
|
|
1564
|
+
],
|
|
1565
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1566
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1567
|
+
"first_slice_non_goals": [
|
|
1568
|
+
"Do not refactor the broader auth flow."
|
|
1569
|
+
],
|
|
1570
|
+
"implementation_surfaces": [
|
|
1571
|
+
"src/auth/redirect.ts",
|
|
1572
|
+
"tests/auth/redirect.spec.ts"
|
|
1573
|
+
],
|
|
1574
|
+
"verification_commands": [
|
|
1575
|
+
"npm test -- redirect.spec.ts"
|
|
1576
|
+
],
|
|
1577
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start once startup synthesis tightens the acceptance contract.",
|
|
1578
|
+
"task_type": "completion-workflow",
|
|
1579
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
1580
|
+
}
|
|
1581
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
1582
|
+
PY
|
|
1583
|
+
)"
|
|
1584
|
+
|
|
1448
1585
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1586
|
+
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$SYNTH_HANDOFF_VAGUE_ACCEPTANCE" \
|
|
1449
1587
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE" \
|
|
1450
1588
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1451
1589
|
pi --session "$HANDOFF_SESSION_VAGUE_ACCEPTANCE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-vague-acceptance.out" 2>"$TMPDIR/pi-completion-handoff-vague-acceptance.err"
|
|
@@ -1457,10 +1595,12 @@ from pathlib import Path
|
|
|
1457
1595
|
snapshot = Path(sys.argv[1])
|
|
1458
1596
|
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1459
1597
|
|
|
1460
|
-
assert
|
|
1461
|
-
assert not Path('.agent').exists(), 'fresh explicit handoff with vague acceptance should
|
|
1462
|
-
|
|
1463
|
-
assert '
|
|
1598
|
+
assert snapshot.exists(), 'fresh explicit handoff with vague acceptance should emit a startup proposal snapshot after same-entry startup synthesis tightens it'
|
|
1599
|
+
assert not Path('.agent').exists(), 'fresh explicit handoff with vague acceptance should still wait for confirmation before writing canonical state'
|
|
1600
|
+
proposal = __import__('json').loads(snapshot.read_text())
|
|
1601
|
+
assert proposal['mission'] == 'Fix login redirect callback behavior.', 'synthesized startup proposal should preserve the concrete mission anchor when tightening vague acceptance'
|
|
1602
|
+
assert proposal['source'] == 'handoff_capsule', 'synthesized startup proposal should still be attributed to primary-agent handoff synthesis'
|
|
1603
|
+
assert 'fresh explicit primary-agent handoff exists' not in output, 'fresh explicit handoff with vague acceptance should not fail closed once startup synthesis tightens it'
|
|
1464
1604
|
PY
|
|
1465
1605
|
|
|
1466
1606
|
# Done workflow + fresh handoff: the fresh explicit handoff should override done-state suppression and start the new round.
|
|
@@ -1673,6 +1813,7 @@ PY
|
|
|
1673
1813
|
write_session_messages "$HANDOFF_SESSION_NEGATIVE" "$HANDOFF_ROOT_NEGATIVE" "$HANDOFF_MESSAGES_NEGATIVE"
|
|
1674
1814
|
|
|
1675
1815
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1816
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
1676
1817
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_NEGATIVE" \
|
|
1677
1818
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1678
1819
|
pi --session "$HANDOFF_SESSION_NEGATIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-negative.out" 2>"$TMPDIR/pi-completion-handoff-negative.err"
|
package/scripts/release-check.sh
CHANGED
|
@@ -34,8 +34,8 @@ checks = {
|
|
|
34
34
|
'description: "/cook workflow: start or replace workflow only from an explicit primary-agent handoff, or resume the current workflow from canonical state"',
|
|
35
35
|
'"Do not call completion_role from ordinary chat; it is reserved for active /cook workflow sessions."',
|
|
36
36
|
'`COMPLETION WORKFLOW DRIVER\\nStart or continue the completion workflow for this repo.',
|
|
37
|
-
'function
|
|
38
|
-
'return
|
|
37
|
+
'function isCompletionWorkflowSessionTurn(',
|
|
38
|
+
'return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);',
|
|
39
39
|
],
|
|
40
40
|
"extensions/completion/policy-guards.ts": [
|
|
41
41
|
'return "completion_role may only be used from an active /cook workflow session.";',
|
package/scripts/smoke-test.sh
CHANGED
|
@@ -273,20 +273,15 @@ reminder = Path(sys.argv[3])
|
|
|
273
273
|
handoff = Path(sys.argv[4])
|
|
274
274
|
auto_resume = Path(sys.argv[5])
|
|
275
275
|
|
|
276
|
-
assert
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
assert '
|
|
280
|
-
assert
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
assert '
|
|
284
|
-
assert '
|
|
285
|
-
assert 'the extension should call a primary-agent handoff synthesis step from the current task context' in handoff_text, 'ordinary handoff reminder should describe same-entry primary-agent handoff synthesis for /cook'
|
|
286
|
-
assert 'Do not expect /cook to infer or guess startup intent from recent discussion alone' in handoff_text, 'ordinary handoff reminder should forbid /cook-side guessing'
|
|
287
|
-
assert 'do not silently rewrite discussion into canonical workflow state' in handoff_text, 'ordinary handoff reminder should preserve non-canonical ordinary-chat behavior'
|
|
288
|
-
assert not auto_resume.exists(), 'ordinary non-/cook turn should not queue auto-resume before /cook activation'
|
|
289
|
-
assert 'Skipped completion workflow auto-resume prompt (test mode)' not in output, 'ordinary non-/cook turn should not attempt auto-resume'
|
|
276
|
+
assert reminder.exists(), 'active workflow should inject the completion reminder on subsequent non-/cook turns'
|
|
277
|
+
reminder_text = reminder.read_text()
|
|
278
|
+
assert 'Completion workflow detected.' in reminder_text, 'active workflow reminder should inject canonical workflow context'
|
|
279
|
+
assert 'If continuation_policy == continue, do not stop after a slice or ask whether to continue; dispatch the next mandatory role directly.' in reminder_text, 'active workflow reminder should direct mandatory continuation'
|
|
280
|
+
assert not handoff.exists(), 'active workflow should not fall back to the ordinary /cook handoff boundary reminder'
|
|
281
|
+
if auto_resume.exists():
|
|
282
|
+
auto_resume_text = auto_resume.read_text()
|
|
283
|
+
assert 'COMPLETION WORKFLOW DRIVER' in auto_resume_text, 'auto-resume prompt should use the workflow driver format when it is queued'
|
|
284
|
+
assert 'Resume the completion workflow from canonical state.' in auto_resume_text, 'auto-resume prompt should resume canonical workflow state when it is queued'
|
|
290
285
|
PY
|
|
291
286
|
|
|
292
287
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
@@ -303,14 +298,16 @@ from pathlib import Path
|
|
|
303
298
|
|
|
304
299
|
expected_task_type = 'completion-workflow'
|
|
305
300
|
expected_eval_profile = 'completion-rubric-v1'
|
|
306
|
-
|
|
301
|
+
resume_path = Path(sys.argv[1])
|
|
307
302
|
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
308
303
|
chooser_path = Path(sys.argv[3])
|
|
309
304
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
310
305
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
assert
|
|
306
|
+
if resume_path.exists():
|
|
307
|
+
resume = resume_path.read_text()
|
|
308
|
+
assert 'Canonical routing profile:' in resume, 'resume prompt should expose canonical routing profile when it is queued'
|
|
309
|
+
assert f'- task_type: {expected_task_type}' in resume, 'resume prompt missing canonical task_type when it is queued'
|
|
310
|
+
assert f'- evaluation_profile: {expected_eval_profile}' in resume, 'resume prompt missing canonical evaluation_profile when it is queued'
|
|
314
311
|
assert routing['mode'] == 'bare', 'active bare /cook should snapshot bare routing mode'
|
|
315
312
|
assert routing['action'] == 'continue', 'no-discussion active bare /cook should resume from canonical state without a concrete replacement mission'
|
|
316
313
|
assert routing['reason'] == 'missing_explicit_handoff', 'no-discussion active bare /cook should explain that resume happened because no fresh explicit handoff existed'
|
|
@@ -389,11 +386,19 @@ active['evaluation_profile'] = profile['evaluation_profile']
|
|
|
389
386
|
active_path.write_text(json.dumps(active, indent=2) + '\n')
|
|
390
387
|
PY
|
|
391
388
|
|
|
389
|
+
if ! git rev-parse HEAD >/dev/null 2>&1; then
|
|
390
|
+
git config user.name "smoke-test"
|
|
391
|
+
git config user.email "smoke-test@example.invalid"
|
|
392
|
+
git commit --allow-empty -m "smoke baseline" >/dev/null
|
|
393
|
+
fi
|
|
394
|
+
|
|
392
395
|
python3 - <<'PY'
|
|
393
396
|
import json
|
|
397
|
+
import subprocess
|
|
394
398
|
from pathlib import Path
|
|
395
399
|
path = Path('.agent/active-slice.json')
|
|
396
400
|
active = json.loads(path.read_text())
|
|
401
|
+
head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip()
|
|
397
402
|
active.update({
|
|
398
403
|
'status': 'selected',
|
|
399
404
|
'slice_id': 'smoke-slice',
|
|
@@ -405,7 +410,7 @@ active.update({
|
|
|
405
410
|
'must_fix_findings': [],
|
|
406
411
|
'implementation_surfaces': ['extensions/completion/index.ts', '.agent/verify_completion_control_plane.sh'],
|
|
407
412
|
'verification_commands': ['bash .agent/verify_completion_control_plane.sh', 'npm run smoke-test'],
|
|
408
|
-
'basis_commit':
|
|
413
|
+
'basis_commit': head,
|
|
409
414
|
'remaining_contract_ids_before': ['smoke-contract'],
|
|
410
415
|
'release_blocker_count_before': 1,
|
|
411
416
|
'high_value_gap_count_before': 0,
|