@linimin/pi-letscook 0.1.60 → 0.1.61
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 +5 -8
- package/README.md +28 -32
- package/extensions/completion/driver.ts +41 -28
- package/extensions/completion/index.ts +76 -63
- package/extensions/completion/prompt-surfaces.ts +15 -13
- package/extensions/completion/proposal.ts +28 -1
- package/package.json +1 -1
- package/scripts/context-proposal-test.sh +49 -154
- package/scripts/refocus-test.sh +4 -4
- package/scripts/release-check.sh +24 -31
- package/scripts/smoke-test.sh +64 -24
- package/skills/cook-handoff-boundary/SKILL.md +54 -26
|
@@ -27,7 +27,7 @@ export type ContextProposalAlternate = {
|
|
|
27
27
|
analysis: ContextProposalAnalysis;
|
|
28
28
|
goalText: string;
|
|
29
29
|
basisPreview: string;
|
|
30
|
-
source: "session" | "analyst" | "handoff_capsule";
|
|
30
|
+
source: "session" | "analyst" | "handoff_capsule" | "deferred_primary_agent_handoff";
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export type ContextProposal = ContextProposalAlternate & {
|
|
@@ -1246,6 +1246,7 @@ export function extractContextProposalFromStructuredSession(
|
|
|
1246
1246
|
|
|
1247
1247
|
const COOK_HANDOFF_BLOCK_REGEX = /```cook_handoff\s*([\s\S]*?)```/giu;
|
|
1248
1248
|
const COOK_HANDOFF_MAX_AGE_MS = 45 * 60 * 1000;
|
|
1249
|
+
const COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES = 2;
|
|
1249
1250
|
const COOK_HANDOFF_NEGATIVE_MISSION_REGEX =
|
|
1250
1251
|
/(?:\b(?:do not|don't|dont|not|never|avoid|skip|refuse|recognize that|suppress|ignore|block|prevent)\b|(?:不要|別|别|勿|禁止|避免|忽略|阻止))/iu;
|
|
1251
1252
|
const COOK_HANDOFF_WORKFLOW_ONLY_ACCEPTANCE_REGEX =
|
|
@@ -1388,6 +1389,19 @@ function isStartableCookHandoffCapsule(
|
|
|
1388
1389
|
return cookHandoffStartabilityFailures(capsule, deps).length === 0;
|
|
1389
1390
|
}
|
|
1390
1391
|
|
|
1392
|
+
function laterMessagesInvalidateCookHandoff(
|
|
1393
|
+
laterMessages: RecentSessionMessage[],
|
|
1394
|
+
deps: Pick<ProposalParseDeps, "stripCodeBlocks">,
|
|
1395
|
+
): boolean {
|
|
1396
|
+
const laterNonCommandMessages = laterMessages.filter((entry) => !entry.isCommand);
|
|
1397
|
+
if (laterNonCommandMessages.length > COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES) return true;
|
|
1398
|
+
return laterNonCommandMessages.some((entry) => {
|
|
1399
|
+
if (entry.role === "summary") return false;
|
|
1400
|
+
if (!hasRecentDiscussionImplementationIntent(entry.text, deps.stripCodeBlocks)) return false;
|
|
1401
|
+
return true;
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1391
1405
|
function cookHandoffIsFreshEnough(capsule: CookHandoffCapsule, laterMessages: RecentSessionMessage[]): boolean {
|
|
1392
1406
|
const capturedAtMs = Date.parse(capsule.captured_at);
|
|
1393
1407
|
if (!Number.isFinite(capturedAtMs)) return false;
|
|
@@ -1470,6 +1484,7 @@ export function assessLatestCookHandoffProposal(
|
|
|
1470
1484
|
const capsule = capsules[capsuleIndex];
|
|
1471
1485
|
const laterMessages = recentMessages.slice(0, index);
|
|
1472
1486
|
if (!cookHandoffIsFreshEnough(capsule, laterMessages)) continue;
|
|
1487
|
+
if (laterMessagesInvalidateCookHandoff(laterMessages, deps)) continue;
|
|
1473
1488
|
const failures = cookHandoffStartabilityFailures(capsule, deps);
|
|
1474
1489
|
if (failures.length > 0) {
|
|
1475
1490
|
return {
|
|
@@ -1515,6 +1530,18 @@ export async function deriveCookContextProposalFromRecentDiscussion(
|
|
|
1515
1530
|
return undefined;
|
|
1516
1531
|
}
|
|
1517
1532
|
|
|
1533
|
+
export function retagContextProposalSource(
|
|
1534
|
+
proposal: ContextProposal | undefined,
|
|
1535
|
+
source: ContextProposalAlternate["source"],
|
|
1536
|
+
): ContextProposal | undefined {
|
|
1537
|
+
if (!proposal) return undefined;
|
|
1538
|
+
return {
|
|
1539
|
+
...proposal,
|
|
1540
|
+
source,
|
|
1541
|
+
alternateProposals: proposal.alternateProposals.map((alternate) => ({ ...alternate, source })),
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1518
1545
|
export function resolveContextProposalConfirmationAction(
|
|
1519
1546
|
proposal: ContextProposal,
|
|
1520
1547
|
action: ContextProposalConfirmAction,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
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,
|
|
@@ -147,15 +147,14 @@ mkdir -p "$ROOT"
|
|
|
147
147
|
cd "$ROOT"
|
|
148
148
|
git init -q
|
|
149
149
|
|
|
150
|
-
# No workflow yet: bare /cook should synthesize a startup brief from recent discussion,
|
|
151
|
-
#
|
|
150
|
+
# No workflow yet: bare /cook should synthesize a deferred startup brief from recent discussion,
|
|
151
|
+
# even when no explicit ordinary-chat handoff capsule exists.
|
|
152
152
|
SESSION_ZERO="$TMPDIR/session-zero.jsonl"
|
|
153
153
|
DISCUSSION_ZERO=$'Mission: Remove the completion status line while keeping the completion widget.\nScope:\n- Keep the non-running completion widget.\n- Suppress the widget while a completion role is active.\nConstraints:\n- Do not reintroduce any other completion status surface.\nAcceptance:\n- Update README to match the shipped behavior.\n- Keep observability regression coverage truthful.'
|
|
154
154
|
DISCUSSION_SNAPSHOT_ZERO="$TMPDIR/context-proposal-structured-fallback.json"
|
|
155
155
|
write_session "$SESSION_ZERO" "$ROOT" "$DISCUSSION_ZERO"
|
|
156
156
|
|
|
157
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=
|
|
158
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
157
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
159
158
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO" \
|
|
160
159
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
161
160
|
pi --session "$SESSION_ZERO" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-structured-fallback.out" 2>"$TMPDIR/pi-completion-context-proposal-structured-fallback.err"
|
|
@@ -167,14 +166,17 @@ from pathlib import Path
|
|
|
167
166
|
|
|
168
167
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
169
168
|
snapshot = Path(sys.argv[3])
|
|
169
|
+
assert Path('.agent').exists(), 'bare /cook should scaffold canonical state from structured recent discussion'
|
|
170
|
+
assert snapshot.exists(), 'bare /cook should emit a startup proposal snapshot when recent discussion is concrete enough'
|
|
170
171
|
proposal = json.loads(snapshot.read_text())
|
|
171
|
-
|
|
172
|
-
assert
|
|
173
|
-
assert
|
|
174
|
-
assert
|
|
175
|
-
assert 'Cancelled recent-discussion workflow proposal' in output, 'recent-discussion Cancel should report that canonical state was left unchanged'
|
|
172
|
+
brief = json.loads(Path('.agent/state.json').read_text())['advisory_startup_brief']
|
|
173
|
+
assert proposal['source'] == 'deferred_primary_agent_handoff', 'structured startup should snapshot the deferred primary-agent handoff source'
|
|
174
|
+
assert brief['source'] == 'deferred_primary_agent_handoff', 'structured startup should record the deferred primary-agent handoff source in advisory intake'
|
|
175
|
+
assert 'Initialized completion control plane' in output, 'structured startup should initialize canonical workflow state'
|
|
176
176
|
PY
|
|
177
177
|
|
|
178
|
+
rm -rf .agent
|
|
179
|
+
|
|
178
180
|
# No workflow yet: user-authored faux handoffs must not bootstrap canonical workflow state.
|
|
179
181
|
SESSION_ZERO_USER_AUTHORED="$TMPDIR/session-zero-user-authored.jsonl"
|
|
180
182
|
USER_AUTHORED_SNAPSHOT_ZERO="$TMPDIR/context-proposal-user-authored-handoff.json"
|
|
@@ -206,7 +208,6 @@ PY
|
|
|
206
208
|
)"
|
|
207
209
|
write_session_messages "$SESSION_ZERO_USER_AUTHORED" "$ROOT" "$USER_AUTHORED_MESSAGES_ZERO"
|
|
208
210
|
|
|
209
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
210
211
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$USER_AUTHORED_SNAPSHOT_ZERO" \
|
|
211
212
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
212
213
|
pi --session "$SESSION_ZERO_USER_AUTHORED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-user-authored.out" 2>"$TMPDIR/pi-completion-context-proposal-user-authored.err"
|
|
@@ -217,9 +218,9 @@ from pathlib import Path
|
|
|
217
218
|
|
|
218
219
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
219
220
|
snapshot = Path(sys.argv[3])
|
|
220
|
-
assert not Path('.agent').exists(), 'user-authored faux handoff should fail closed without writing canonical state'
|
|
221
|
+
assert not Path('.agent').exists(), 'user-authored faux handoff without supporting discussion should still fail closed without writing canonical state'
|
|
221
222
|
assert not snapshot.exists(), 'user-authored faux handoff should not emit a startup proposal snapshot'
|
|
222
|
-
assert '
|
|
223
|
+
assert 'could not derive a concrete startup brief from recent discussion' in output, 'user-authored faux handoff should fall back to the deferred-synthesis fail-closed message'
|
|
223
224
|
PY
|
|
224
225
|
|
|
225
226
|
# No workflow yet: malformed or invalid assistant handoff capsules must also fail closed.
|
|
@@ -228,7 +229,6 @@ INVALID_SNAPSHOT_ZERO="$TMPDIR/context-proposal-invalid-handoff.json"
|
|
|
228
229
|
INVALID_MESSAGES_ZERO='[{"role":"assistant","content":"This is not a valid startup capsule.\n\n```cook_handoff\n{\"kind\":\"cook_handoff\",\"source\":\"primary_agent\",\"mission\":\"Broken JSON handoff\"\n```"}]'
|
|
229
230
|
write_session_messages "$SESSION_ZERO_INVALID" "$ROOT" "$INVALID_MESSAGES_ZERO"
|
|
230
231
|
|
|
231
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
232
232
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INVALID_SNAPSHOT_ZERO" \
|
|
233
233
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
234
234
|
pi --session "$SESSION_ZERO_INVALID" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-invalid-handoff.out" 2>"$TMPDIR/pi-completion-context-proposal-invalid-handoff.err"
|
|
@@ -239,9 +239,9 @@ from pathlib import Path
|
|
|
239
239
|
|
|
240
240
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
241
241
|
snapshot = Path(sys.argv[3])
|
|
242
|
-
assert not Path('.agent').exists(), 'invalid assistant handoff should fail closed without writing canonical state'
|
|
242
|
+
assert not Path('.agent').exists(), 'invalid assistant handoff without supporting discussion should fail closed without writing canonical state'
|
|
243
243
|
assert not snapshot.exists(), 'invalid assistant handoff should not emit a startup proposal snapshot'
|
|
244
|
-
assert '
|
|
244
|
+
assert 'could not derive a concrete startup brief from recent discussion' in output, 'invalid assistant handoff should explain the deferred-synthesis fail-closed contract'
|
|
245
245
|
PY
|
|
246
246
|
|
|
247
247
|
# No workflow yet: a fresh explicit primary-agent handoff should still bootstrap canonical startup state.
|
|
@@ -389,7 +389,7 @@ assert routing['mode'] == 'bare', 'active bare /cook resume regression should sn
|
|
|
389
389
|
assert 'explicitGoal' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
390
390
|
assert 'explicitGoalProvided' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
391
391
|
assert routing['action'] == 'continue', 'active bare /cook should resume when no fresh explicit handoff exists'
|
|
392
|
-
assert routing['reason'] == '
|
|
392
|
+
assert routing['reason'] == 'no_replacement_proposal', 'active bare /cook should explain that resume happened because no replacement mission was derived'
|
|
393
393
|
assert routing['currentMissionAnchor'] == mission, 'resume routing should preserve the current mission anchor'
|
|
394
394
|
assert routing['proposedMissionAnchor'] is None, 'resume routing should not derive a replacement mission from recent discussion'
|
|
395
395
|
assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook resume should still use the canonical resume prompt'
|
|
@@ -434,7 +434,7 @@ active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
|
434
434
|
|
|
435
435
|
assert routing['mode'] == 'bare', 'discussion-driven refocus removal should snapshot bare routing mode'
|
|
436
436
|
assert routing['action'] == 'continue', 'bare /cook should resume instead of deriving a replacement workflow from recent discussion'
|
|
437
|
-
assert routing['reason'] == '
|
|
437
|
+
assert routing['reason'] == 'no_replacement_proposal', 'discussion-driven refocus removal should explain that no replacement mission was derived'
|
|
438
438
|
assert routing['currentMissionAnchor'] == mission, 'discussion-driven refocus removal should preserve the current mission anchor'
|
|
439
439
|
assert routing['proposedMissionAnchor'] is None, 'discussion-driven refocus removal should not preserve a replacement mission from recent discussion'
|
|
440
440
|
assert 'Resume the completion workflow from canonical state.' in resume, 'discussion-driven refocus removal should still queue the canonical resume prompt'
|
|
@@ -508,7 +508,7 @@ active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
|
508
508
|
|
|
509
509
|
assert routing['mode'] == 'bare', 'summary-only active bare /cook regression should snapshot bare routing mode'
|
|
510
510
|
assert routing['action'] == 'continue', 'summary-only active bare /cook should resume rather than derive replacement startup'
|
|
511
|
-
assert routing['reason'] == '
|
|
511
|
+
assert routing['reason'] == 'no_replacement_proposal', 'summary-only active bare /cook should explain that no replacement mission was derived'
|
|
512
512
|
assert routing['currentMissionAnchor'] == mission, 'summary-only active bare /cook should preserve the current mission anchor'
|
|
513
513
|
assert routing['proposedMissionAnchor'] is None, 'summary-only active bare /cook should not derive a replacement mission from summary artifacts alone'
|
|
514
514
|
assert 'Resume the completion workflow from canonical state.' in resume, 'summary-only active bare /cook should still resume the canonical workflow'
|
|
@@ -613,7 +613,7 @@ after = {
|
|
|
613
613
|
|
|
614
614
|
assert routing['mode'] == 'bare', 'fresh non-startable explicit handoff should snapshot bare routing mode'
|
|
615
615
|
assert routing['action'] == 'blocked', 'fresh non-startable explicit handoff should fail closed for active bare /cook'
|
|
616
|
-
assert routing['reason'] == '
|
|
616
|
+
assert routing['reason'] == 'replacement_not_startable', 'fresh non-startable explicit handoff should keep the dedicated replacement fail-closed reason'
|
|
617
617
|
assert 'fresh explicit primary-agent handoff exists' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should surface the dedicated fail-closed message'
|
|
618
618
|
assert 'acceptance is not anchored to concrete repo changes or verification' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should explain why the capsule is not startable'
|
|
619
619
|
assert not resume_path.exists(), 'fresh non-startable explicit handoff should not queue a resume prompt'
|
|
@@ -708,57 +708,34 @@ assert not snapshot.exists(), 'verification-evidence overlap suppression should
|
|
|
708
708
|
assert '/cook failed closed' in output, 'verification-evidence overlap suppression should fail closed when the latest discussion only repeats verified work'
|
|
709
709
|
PY
|
|
710
710
|
|
|
711
|
-
# Completed workflow: bare /cook should synthesize the next
|
|
711
|
+
# Completed workflow: bare /cook should synthesize the next round from discussion-only startup too,
|
|
712
|
+
# even when no explicit ordinary-chat handoff capsule exists.
|
|
712
713
|
SESSION_TWO_NORMALIZED="$TMPDIR/session-two-normalized.jsonl"
|
|
713
|
-
DISCUSSION_TWO_NORMALIZED=$'Mission:
|
|
714
|
+
DISCUSSION_TWO_NORMALIZED=$'Mission: 開始實作這個方案\nScope:\n- Normalize bare /cook planning phrasing for the next workflow round.\n- Reset canonical state for the new implementation mission.\nConstraints:\n- Do not resume the completed workflow when the new round is clearly different.\nAcceptance:\n- Start a new round with the normalized mission anchor.'
|
|
714
715
|
DISCUSSION_SNAPSHOT_TWO_NORMALIZED="$TMPDIR/context-proposal-next-round-normalized.json"
|
|
715
716
|
write_session "$SESSION_TWO_NORMALIZED" "$ROOT" "$DISCUSSION_TWO_NORMALIZED"
|
|
716
717
|
|
|
717
718
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
718
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
719
719
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" \
|
|
720
720
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
721
721
|
pi --session "$SESSION_TWO_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-normalized.err"
|
|
722
722
|
|
|
723
|
-
python3 - "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" <<'PY'
|
|
723
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" "$TMPDIR/pi-completion-context-proposal-next-round-normalized.err" "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" "$CURRENT_DONE_MISSION" <<'PY'
|
|
724
724
|
import json
|
|
725
725
|
import sys
|
|
726
726
|
from pathlib import Path
|
|
727
727
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
mission_text = Path('.agent/mission.md').read_text()
|
|
732
|
-
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
728
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
729
|
+
snapshot = Path(sys.argv[3])
|
|
730
|
+
previous = sys.argv[4]
|
|
733
731
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
734
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
735
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
736
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
737
732
|
|
|
738
|
-
assert
|
|
739
|
-
|
|
740
|
-
assert
|
|
741
|
-
assert state['
|
|
742
|
-
assert
|
|
743
|
-
assert
|
|
744
|
-
assert state['advisory_startup_brief']['mission'] == mission, 'recent-discussion next round should preserve the confirmed startup brief as advisory intake'
|
|
745
|
-
assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'recent-discussion next round should preserve the advisory source'
|
|
746
|
-
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after starting the next workflow round from recent discussion'
|
|
747
|
-
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after starting the next workflow round from recent discussion'
|
|
748
|
-
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after starting the next workflow round from recent discussion'
|
|
749
|
-
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after starting the next workflow round from recent discussion'
|
|
750
|
-
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after starting the next workflow round from recent discussion'
|
|
751
|
-
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after starting the next workflow round from recent discussion'
|
|
752
|
-
assert proposal['mission'] == mission, 'recent-discussion next-round proposal snapshot should preserve the synthesized mission anchor'
|
|
753
|
-
assert proposal['source'] == 'session', 'recent-discussion next-round proposal snapshot should record the structured-session source'
|
|
754
|
-
assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground for the recent-discussion next workflow round'
|
|
755
|
-
assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue for the recent-discussion next workflow round'
|
|
756
|
-
assert state['requires_reground'] is True, 'requires_reground should reset to true for the recent-discussion next workflow round'
|
|
757
|
-
assert state['project_done'] is False, 'project_done should reset to false for the recent-discussion next workflow round'
|
|
758
|
-
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder for the recent-discussion next workflow round'
|
|
759
|
-
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the recent-discussion next-round refocus'
|
|
760
|
-
assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_refocus for the recent-discussion next workflow round'
|
|
761
|
-
assert active['status'] == 'idle', 'active-slice should reset to idle for the recent-discussion next workflow round'
|
|
733
|
+
assert snapshot.exists(), 'done-workflow discussion-only startup should emit a proposal snapshot from deferred synthesis'
|
|
734
|
+
proposal = json.loads(snapshot.read_text())
|
|
735
|
+
assert state['mission_anchor'] != previous, 'done-workflow discussion-only startup should advance to the new mission anchor'
|
|
736
|
+
assert state['continuation_policy'] == 'continue', 'done-workflow discussion-only startup should reopen workflow state'
|
|
737
|
+
assert proposal['source'] == 'deferred_primary_agent_handoff', 'done-workflow discussion-only startup should snapshot the deferred handoff source'
|
|
738
|
+
assert 'Started a new completion workflow round from deferred primary-agent handoff' in output, 'done-workflow discussion-only startup should report deferred next-round startup'
|
|
762
739
|
PY
|
|
763
740
|
|
|
764
741
|
# Completed workflow: a fresh explicit primary-agent handoff should still start the next round.
|
|
@@ -977,7 +954,8 @@ after = {path.name: path.read_text() for path in tracked}
|
|
|
977
954
|
assert before == after, 'done /cook inline-args rejection should leave canonical files unchanged'
|
|
978
955
|
PY
|
|
979
956
|
|
|
980
|
-
# Completed workflow again: model-assisted discussion analysis should
|
|
957
|
+
# Completed workflow again: model-assisted discussion analysis alone should be able to
|
|
958
|
+
# synthesize the next round from explicit /cook entry.
|
|
981
959
|
mark_done
|
|
982
960
|
|
|
983
961
|
SESSION_FIVE="$TMPDIR/session-five.jsonl"
|
|
@@ -992,20 +970,18 @@ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_FIVE" \
|
|
|
992
970
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
993
971
|
pi --session "$SESSION_FIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-analyst.out" 2>"$TMPDIR/pi-completion-context-proposal-analyst.err"
|
|
994
972
|
|
|
995
|
-
python3 - "$DISCUSSION_SNAPSHOT_FIVE" <<'PY'
|
|
973
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-analyst.out" "$TMPDIR/pi-completion-context-proposal-analyst.err" "$DISCUSSION_SNAPSHOT_FIVE" <<'PY'
|
|
996
974
|
import json
|
|
997
975
|
import sys
|
|
998
976
|
from pathlib import Path
|
|
999
977
|
|
|
1000
|
-
|
|
978
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
979
|
+
snapshot = Path(sys.argv[3])
|
|
1001
980
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
1002
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
1003
981
|
|
|
1004
|
-
assert
|
|
1005
|
-
assert
|
|
1006
|
-
assert
|
|
1007
|
-
assert state['continuation_policy'] == 'continue', 'analyst-driven restart should reopen the workflow after Start'
|
|
1008
|
-
assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'analyst-driven restart should still record recent-discussion advisory intake'
|
|
982
|
+
assert snapshot.exists(), 'done-workflow analyst-only restart should emit a startup proposal snapshot'
|
|
983
|
+
assert state['continuation_policy'] == 'continue', 'done-workflow analyst-only restart should reopen the workflow'
|
|
984
|
+
assert 'deferred primary-agent handoff' in output, 'done-workflow analyst-only restart should report deferred startup'
|
|
1009
985
|
PY
|
|
1010
986
|
|
|
1011
987
|
# Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.
|
|
@@ -1231,85 +1207,6 @@ assert 'Why this slice first: The redirect callback bug is already bounded enoug
|
|
|
1231
1207
|
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'
|
|
1232
1208
|
PY
|
|
1233
1209
|
|
|
1234
|
-
# Fresh explicit handoff: later ordinary-chat follow-up before /cook should not invalidate startup.
|
|
1235
|
-
HANDOFF_ROOT_FOLLOWUP="$TMPDIR/handoff-root-followup"
|
|
1236
|
-
mkdir -p "$HANDOFF_ROOT_FOLLOWUP"
|
|
1237
|
-
cd "$HANDOFF_ROOT_FOLLOWUP"
|
|
1238
|
-
git init -q
|
|
1239
|
-
|
|
1240
|
-
HANDOFF_SESSION_FOLLOWUP="$TMPDIR/handoff-session-followup.jsonl"
|
|
1241
|
-
HANDOFF_SNAPSHOT_FOLLOWUP="$TMPDIR/handoff-proposal-followup.json"
|
|
1242
|
-
HANDOFF_MESSAGES_FOLLOWUP="$(python3 - <<'PY'
|
|
1243
|
-
import json
|
|
1244
|
-
capsule = {
|
|
1245
|
-
"kind": "cook_handoff",
|
|
1246
|
-
"source": "primary_agent",
|
|
1247
|
-
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1248
|
-
"source_turn_id": "m0002",
|
|
1249
|
-
"mission": "Fix login redirect callback behavior.",
|
|
1250
|
-
"scope": [
|
|
1251
|
-
"Update the callback redirect decision logic.",
|
|
1252
|
-
"Preserve the broader auth flow."
|
|
1253
|
-
],
|
|
1254
|
-
"constraints": [
|
|
1255
|
-
"Do not refactor the broader auth flow."
|
|
1256
|
-
],
|
|
1257
|
-
"acceptance": [
|
|
1258
|
-
"Add a regression test for returning to the requested page."
|
|
1259
|
-
],
|
|
1260
|
-
"risks": [
|
|
1261
|
-
"Late ordinary-chat clarifications should not force the user into a fresh handoff-only retry loop."
|
|
1262
|
-
],
|
|
1263
|
-
"notes": [
|
|
1264
|
-
"Keep the startup brief anchored to the explicit primary-agent handoff until a later assistant reply replaces it."
|
|
1265
|
-
],
|
|
1266
|
-
"handoff_kind": "implementation_workflow_handoff",
|
|
1267
|
-
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1268
|
-
"first_slice_non_goals": [
|
|
1269
|
-
"Do not refactor the broader auth flow."
|
|
1270
|
-
],
|
|
1271
|
-
"implementation_surfaces": [
|
|
1272
|
-
"src/auth/redirect.ts",
|
|
1273
|
-
"tests/auth/redirect.spec.ts"
|
|
1274
|
-
],
|
|
1275
|
-
"verification_commands": [
|
|
1276
|
-
"npm test -- redirect.spec.ts"
|
|
1277
|
-
],
|
|
1278
|
-
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely.",
|
|
1279
|
-
"task_type": "completion-workflow",
|
|
1280
|
-
"evaluation_profile": "completion-rubric-v1",
|
|
1281
|
-
"why_cook_now": "The implementation plan is concrete and ready for repo changes."
|
|
1282
|
-
}
|
|
1283
|
-
messages = [
|
|
1284
|
-
{"role": "user", "content": "Please think through the login redirect fix and tell me when it is ready for /cook."},
|
|
1285
|
-
{"role": "assistant", "content": "This task is now ready for /cook whenever you want to start implementation.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1286
|
-
{"role": "user", "content": "Before I run /cook, one more clarification: keep the broader auth flow unchanged and keep the later verification focused on the redirect regression."},
|
|
1287
|
-
]
|
|
1288
|
-
print(json.dumps(messages, ensure_ascii=False))
|
|
1289
|
-
PY
|
|
1290
|
-
)"
|
|
1291
|
-
write_session_messages "$HANDOFF_SESSION_FOLLOWUP" "$HANDOFF_ROOT_FOLLOWUP" "$HANDOFF_MESSAGES_FOLLOWUP"
|
|
1292
|
-
|
|
1293
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1294
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_FOLLOWUP" \
|
|
1295
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1296
|
-
pi --session "$HANDOFF_SESSION_FOLLOWUP" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-followup.out" 2>"$TMPDIR/pi-completion-handoff-followup.err"
|
|
1297
|
-
|
|
1298
|
-
python3 - "$HANDOFF_SNAPSHOT_FOLLOWUP" <<'PY'
|
|
1299
|
-
import json
|
|
1300
|
-
import sys
|
|
1301
|
-
from pathlib import Path
|
|
1302
|
-
|
|
1303
|
-
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
1304
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
1305
|
-
|
|
1306
|
-
assert snapshot['source'] == 'handoff_capsule', 'later ordinary-chat follow-up should still use the explicit handoff capsule as startup source'
|
|
1307
|
-
assert snapshot['mission'] == 'Fix login redirect callback behavior.', 'later ordinary-chat follow-up should preserve the handoff mission'
|
|
1308
|
-
assert state['mission_anchor'] == 'Fix login redirect callback behavior.', 'later ordinary-chat follow-up should still start from the explicit handoff mission'
|
|
1309
|
-
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'later ordinary-chat follow-up should keep the advisory intake source as the explicit handoff'
|
|
1310
|
-
assert 'Verification commands: npm test -- redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'later ordinary-chat follow-up should preserve the handoff verification commands'
|
|
1311
|
-
PY
|
|
1312
|
-
|
|
1313
1210
|
# Fresh but non-startable explicit handoff: /cook should fail closed instead of falling back
|
|
1314
1211
|
# to a broad recent-discussion startup brief when the explicit capsule is still too vague.
|
|
1315
1212
|
HANDOFF_ROOT_VAGUE="$TMPDIR/handoff-root-vague"
|
|
@@ -1576,7 +1473,7 @@ assert 'First slice goal: Patch the callback edge case and cover it with a focus
|
|
|
1576
1473
|
assert 'Verification commands: npm test -- redirect-edge.spec.ts' in state['advisory_startup_brief']['notes'], 'done-workflow handoff should preserve verification_commands in advisory notes'
|
|
1577
1474
|
PY
|
|
1578
1475
|
|
|
1579
|
-
# Stale handoff:
|
|
1476
|
+
# Stale handoff: later discussion should invalidate the older handoff capsule and fail closed instead of falling back to newer discussion.
|
|
1580
1477
|
HANDOFF_ROOT_STALE="$TMPDIR/handoff-root-stale"
|
|
1581
1478
|
mkdir -p "$HANDOFF_ROOT_STALE"
|
|
1582
1479
|
cd "$HANDOFF_ROOT_STALE"
|
|
@@ -1589,7 +1486,7 @@ import json
|
|
|
1589
1486
|
capsule = {
|
|
1590
1487
|
"kind": "cook_handoff",
|
|
1591
1488
|
"source": "primary_agent",
|
|
1592
|
-
"captured_at": "
|
|
1489
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1593
1490
|
"source_turn_id": "m0002",
|
|
1594
1491
|
"mission": "Fix the original login redirect callback behavior.",
|
|
1595
1492
|
"scope": ["Update the original callback redirect logic."],
|
|
@@ -1602,7 +1499,7 @@ capsule = {
|
|
|
1602
1499
|
"first_slice_non_goals": ["Do not refactor the auth stack."],
|
|
1603
1500
|
"implementation_surfaces": ["src/auth/login-redirect.ts"],
|
|
1604
1501
|
"verification_commands": ["npm test -- login-redirect.spec.ts"],
|
|
1605
|
-
"why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before
|
|
1502
|
+
"why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before later discussion replaced it."
|
|
1606
1503
|
}
|
|
1607
1504
|
newer_discussion = "Mission: Ship logout redirect consistency instead.\nScope:\n- Update the logout redirect path.\nConstraints:\n- Leave the login callback flow unchanged.\nAcceptance:\n- Add a logout redirect regression test."
|
|
1608
1505
|
messages = [
|
|
@@ -1621,18 +1518,16 @@ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_STALE" \
|
|
|
1621
1518
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1622
1519
|
pi --session "$HANDOFF_SESSION_STALE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-stale.out" 2>"$TMPDIR/pi-completion-handoff-stale.err"
|
|
1623
1520
|
|
|
1624
|
-
python3 - "$HANDOFF_SNAPSHOT_STALE" <<'PY'
|
|
1625
|
-
import json
|
|
1521
|
+
python3 - "$HANDOFF_SNAPSHOT_STALE" "$TMPDIR/pi-completion-handoff-stale.out" "$TMPDIR/pi-completion-handoff-stale.err" <<'PY'
|
|
1626
1522
|
import sys
|
|
1627
1523
|
from pathlib import Path
|
|
1628
1524
|
|
|
1629
|
-
snapshot =
|
|
1630
|
-
|
|
1525
|
+
snapshot = Path(sys.argv[1])
|
|
1526
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1631
1527
|
|
|
1632
|
-
assert snapshot
|
|
1633
|
-
assert
|
|
1634
|
-
assert
|
|
1635
|
-
assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'aged-out handoff fallback should keep the advisory startup source non-canonical'
|
|
1528
|
+
assert not snapshot.exists(), 'stale handoff should not emit a startup proposal snapshot'
|
|
1529
|
+
assert not Path('.agent').exists(), 'stale handoff should fail closed without writing canonical state'
|
|
1530
|
+
assert 'could not derive a concrete startup brief from recent discussion' in output, 'stale handoff should explain that deferred startup synthesis failed closed'
|
|
1636
1531
|
PY
|
|
1637
1532
|
|
|
1638
1533
|
# Negative handoff rationale: a non-startable capsule must not become the startup mission.
|
package/scripts/refocus-test.sh
CHANGED
|
@@ -313,7 +313,7 @@ assert routing['mode'] == 'bare', 'supported refocus should use bare active-work
|
|
|
313
313
|
assert 'explicitGoal' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
|
|
314
314
|
assert 'explicitGoalProvided' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
|
|
315
315
|
assert routing['action'] == 'refocus', 'supported bare /cook should classify as refocus when a fresh explicit handoff proposes a different mission'
|
|
316
|
-
assert routing['reason'] == '
|
|
316
|
+
assert routing['reason'] == 'explicit_handoff_replacement', 'supported bare /cook should record the explicit-handoff replacement reason'
|
|
317
317
|
assert routing['proposedMissionAnchor'] == new_anchor, 'explicit handoff routing snapshot should expose the replacement mission anchor'
|
|
318
318
|
assert routing['proposalSource'] == 'handoff_capsule', 'explicit handoff routing snapshot should preserve the handoff source'
|
|
319
319
|
PY
|
|
@@ -411,7 +411,7 @@ assert routing['mode'] == 'bare', 'bare /cook should snapshot bare active-workfl
|
|
|
411
411
|
assert 'explicitGoal' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
|
|
412
412
|
assert 'explicitGoalProvided' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
|
|
413
413
|
assert routing['action'] == 'refocus', 'fresh explicit replacement handoff should classify active bare /cook as refocus'
|
|
414
|
-
assert routing['reason'] == '
|
|
414
|
+
assert routing['reason'] == 'explicit_handoff_replacement', 'fresh explicit replacement handoff should record the explicit-handoff reason'
|
|
415
415
|
assert routing['currentMissionAnchor'] == updated_mission, 'explicit-handoff routing should keep the current mission anchor until the user approves replacement'
|
|
416
416
|
assert routing['proposedMissionAnchor'] == replacement_mission, 'explicit-handoff routing should expose the proposed replacement mission'
|
|
417
417
|
assert routing['proposalSource'] == 'handoff_capsule', 'explicit-handoff routing should preserve the handoff source'
|
|
@@ -454,7 +454,7 @@ assert state['mission_anchor'] == updated_mission, 'final Start/Cancel cancel sh
|
|
|
454
454
|
assert plan['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep plan.json unchanged'
|
|
455
455
|
assert active['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep active-slice.json unchanged'
|
|
456
456
|
assert routing['action'] == 'refocus', 'final Start/Cancel cancel should still come from an explicit-handoff refocus classification'
|
|
457
|
-
assert routing['reason'] == '
|
|
457
|
+
assert routing['reason'] == 'explicit_handoff_replacement', 'final Start/Cancel cancel should preserve the explicit-handoff reason'
|
|
458
458
|
assert routing['currentMissionAnchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor until the user approves replacement'
|
|
459
459
|
assert proposal['mission'] == replacement_mission, 'final Start/Cancel cancel should still prepare the replacement proposal before rewriting state'
|
|
460
460
|
assert proposal['source'] == 'handoff_capsule', 'final Start/Cancel cancel should preserve the explicit-handoff proposal source'
|
|
@@ -495,7 +495,7 @@ assert routing['mode'] == 'bare', 'accepted bare refocus should keep bare routin
|
|
|
495
495
|
assert 'explicitGoal' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
|
|
496
496
|
assert 'explicitGoalProvided' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
|
|
497
497
|
assert routing['action'] == 'refocus', 'accepted bare refocus should keep the explicit-handoff refocus classification'
|
|
498
|
-
assert routing['reason'] == '
|
|
498
|
+
assert routing['reason'] == 'explicit_handoff_replacement', 'accepted bare refocus should keep the explicit-handoff reason'
|
|
499
499
|
assert routing['currentMissionAnchor'] == 'Remove completion status line, keep widget.', 'accepted bare refocus should expose the original mission until Start is accepted'
|
|
500
500
|
assert routing['proposalSource'] == 'handoff_capsule', 'accepted bare refocus should preserve the explicit-handoff source'
|
|
501
501
|
assert new_anchor in mission_text, '.agent/mission.md did not update to the bare refocus mission anchor'
|
package/scripts/release-check.sh
CHANGED
|
@@ -5,58 +5,51 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
5
5
|
cd "$ROOT"
|
|
6
6
|
export PI_COMPLETION_RUNNING_RELEASE_CHECK=1
|
|
7
7
|
|
|
8
|
-
echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity,
|
|
8
|
+
echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity, explicit-/cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, legacy cleanup, evaluator calibration, and rubric contract coverage"
|
|
9
9
|
bash .agent/verify_completion_control_plane.sh
|
|
10
10
|
git ls-files --error-unmatch .agent/README.md .agent/mission.md .agent/profile.json .agent/verify_completion_stop.sh .agent/verify_completion_control_plane.sh >/dev/null
|
|
11
11
|
|
|
12
|
-
echo "[release-check] verifying public /cook
|
|
12
|
+
echo "[release-check] verifying public /cook parity and explicit-entry docs/help"
|
|
13
13
|
python3 - <<'PY'
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
checks = {
|
|
17
17
|
"README.md": [
|
|
18
18
|
"`/cook` is the explicit workflow boundary for starting, continuing, refocusing, or beginning the next round of long-running repo work.",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"-
|
|
23
|
-
"-
|
|
24
|
-
"
|
|
25
|
-
"| No workflow yet | `/cook` synthesizes a startup brief from recent discussion and shows **Start** / **Cancel**.",
|
|
26
|
-
"| Previous workflow is `done` | `/cook` synthesizes the next implementation round from recent discussion behind **Start** / **Cancel**.",
|
|
27
|
-
"any pre-`/cook` preview or capsule is advisory only and never writes canonical workflow state by itself",
|
|
19
|
+
"Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.",
|
|
20
|
+
"Ordinary chat remains advisory until you explicitly run `/cook`. At that point `/cook` synthesizes a startup brief from recent discussion using primary-agent-style context, then asks you to **Start** or **Cancel** before rewriting canonical workflow state.",
|
|
21
|
+
"- startup and next-round entry stay confirm-first, but they now derive startup from explicit user `/cook` entry plus recent discussion when needed",
|
|
22
|
+
"- active workflows resume from canonical `.agent/**` state unless `/cook` synthesizes or receives a concrete replacement mission",
|
|
23
|
+
"`/cook` first checks for a fresh explicit primary-agent handoff capsule as a compatibility intake path. If none is present, `/cook` synthesizes a startup brief from recent discussion using primary-agent-style context.",
|
|
24
|
+
"when a concrete replacement mission suggests replacing an active workflow, `/cook` shows a chooser before any canonical state rewrite",
|
|
28
25
|
],
|
|
29
26
|
"CHANGELOG.md": [
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"updated public parity and
|
|
27
|
+
"removed proactive primary-agent `/cook` prompting and default ordinary-chat `cook_handoff` emission so main chat stays advisory until the user explicitly runs `/cook`",
|
|
28
|
+
"changed bare `/cook` startup and done-workflow next-round entry to synthesize a deferred primary-agent startup brief from recent discussion instead of requiring a pre-authored explicit handoff capsule",
|
|
29
|
+
"updated public parity and shipped package contents so the tracked `.agent` contract files are included in package tarballs and packaged smoke/release verification can scaffold canonical state truthfully",
|
|
33
30
|
],
|
|
34
31
|
"extensions/completion/prompt-surfaces.ts": [
|
|
35
|
-
'"
|
|
36
|
-
'"
|
|
37
|
-
'"
|
|
38
|
-
'"Any preview capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract."',
|
|
32
|
+
'"/cook is the only explicit entrypoint into long-running completion workflow."',
|
|
33
|
+
'"Do not proactively tell the user to run /cook just because a task looks workflow-worthy, and do not emit a ```cook_handoff``` capsule by default in ordinary chat."',
|
|
34
|
+
'"Only provide a preview startup brief or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior."',
|
|
39
35
|
],
|
|
40
36
|
"extensions/completion/index.ts": [
|
|
41
|
-
'"/cook failed closed because
|
|
42
|
-
'description: "/cook workflow: synthesize
|
|
37
|
+
'"/cook failed closed because it could not derive a concrete startup brief from recent discussion. Clarify the mission, first slice, or verification intent in the main chat, then rerun /cook."',
|
|
38
|
+
'description: "/cook workflow: synthesize a startup brief when the user explicitly enters /cook, resume the current workflow from canonical state, or confirm a replacement mission from explicit /cook entry"',
|
|
43
39
|
],
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
forbidden = {
|
|
47
43
|
"README.md": [
|
|
48
|
-
"wait for a fresh primary-agent handoff
|
|
49
|
-
"
|
|
50
|
-
"- startup and next-round entry stay confirm-first and require a fresh valid explicit primary-agent handoff",
|
|
51
|
-
"`/cook` first looks for a fresh explicit primary-agent handoff capsule from recent ordinary-chat discussion.",
|
|
52
|
-
"Without one, `/cook` fails closed instead of deriving the next round from recent discussion.",
|
|
44
|
+
"wait for a fresh primary-agent handoff",
|
|
45
|
+
"require a fresh valid explicit primary-agent handoff",
|
|
53
46
|
],
|
|
54
|
-
"
|
|
55
|
-
"
|
|
47
|
+
"extensions/completion/prompt-surfaces.ts": [
|
|
48
|
+
'"When handing off, explain that /cook can start a new workflow or next round only from a fresh valid explicit primary-agent handoff capsule; otherwise it fails closed, while already-active workflows resume from canonical .agent state unless a fresh valid explicit handoff proposes replacement."',
|
|
56
49
|
],
|
|
57
50
|
"extensions/completion/index.ts": [
|
|
58
|
-
'description: "/cook workflow:
|
|
59
|
-
'"/cook failed closed because
|
|
51
|
+
'description: "/cook workflow: start a new workflow or next round only from a fresh explicit primary-agent handoff, resume the current workflow from canonical state, or confirm an explicit replacement from the explicit /cook command"',
|
|
52
|
+
'"/cook failed closed because new-workflow startup now requires a fresh valid explicit primary-agent handoff from the immediately preceding ordinary-chat turn; recent discussion alone no longer starts a workflow. Ask the primary agent to hand off explicitly in the main chat, then rerun /cook."',
|
|
60
53
|
],
|
|
61
54
|
}
|
|
62
55
|
|
|
@@ -64,13 +57,13 @@ for path, needles in checks.items():
|
|
|
64
57
|
text = Path(path).read_text()
|
|
65
58
|
for needle in needles:
|
|
66
59
|
if needle not in text:
|
|
67
|
-
raise SystemExit(f"[release-check] missing expected /cook
|
|
60
|
+
raise SystemExit(f"[release-check] missing expected /cook parity text in {path}: {needle}")
|
|
68
61
|
|
|
69
62
|
for path, needles in forbidden.items():
|
|
70
63
|
text = Path(path).read_text()
|
|
71
64
|
for needle in needles:
|
|
72
65
|
if needle in text:
|
|
73
|
-
raise SystemExit(f"[release-check] found stale /cook
|
|
66
|
+
raise SystemExit(f"[release-check] found stale /cook parity text in {path}: {needle}")
|
|
74
67
|
PY
|
|
75
68
|
|
|
76
69
|
npm run smoke-test
|