@linimin/pi-letscook 0.1.74 → 0.1.76

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
@@ -1,11 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.76
4
+
5
+ ### Fixed
6
+
7
+ - taught the completion protocol and core role prompts to auto-preserve routine unrelated tracked worktree dirt with a reversible stash-plus-note flow instead of asking the user to choose between stash/cleanup/background continuation for every dirty-worktree checkpoint
8
+ - refined sticky `/cook` continuation detection so clear workflow-follow-up turns stay inside the active workflow while unrelated ordinary chat stays outside it, and aligned smoke/release regressions with that split
9
+
10
+ ## 0.1.75
11
+
12
+ ### Fixed
13
+
14
+ - 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
15
+ - 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
16
+ - taught the completion protocol and core role prompts to auto-preserve routine unrelated tracked worktree dirt with a reversible stash-plus-note flow instead of asking the user to choose between stash/cleanup/background continuation for every dirty-worktree checkpoint
17
+
3
18
  ## 0.1.74
4
19
 
5
20
  ### Fixed
6
21
 
7
- - 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
22
+ - made active `/cook` workflows sticky across routine continuation turns, exact await-user-input replies, and mandatory completion-role dispatch so long-running workflows keep moving without repeated manual `/cook` re-entry while unrelated ordinary chat stays outside workflow mode
8
23
  - updated smoke, canonical-evidence, release-check, and completion-role gating regressions to enforce the new sticky active-workflow self-healing behavior
24
+ - 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
9
25
 
10
26
  ## 0.1.73
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` also fails closed instead of silently treating that capsule as planning support or canonical workflow state.
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**. Stale, planning-only, or non-startable handoffs still fail closed. |
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
 
@@ -190,6 +190,8 @@ Deterministic active-slice contract regression now lives in `bash scripts/active
190
190
 
191
191
  Deterministic verification for this packaged contract also lives in `npm run rubric-contract-test`, which now exercises reviewer, auditor, and stop-judge transcription paths while the bootstrap/refocus/context regressions plus control-plane verifier fail closed when required canonical signaling is missing.
192
192
 
193
+ Active `/cook` workflows now also auto-reconcile routine unrelated tracked worktree dirt instead of bouncing that decision back to the user. When the dirty tracked files are outside the latest slice or current reconciliation surfaces and can be isolated safely, the workflow should preserve them with a reversible mechanism such as a named git stash plus a `.agent/tmp/dirty-worktree-autostash.json` note, continue the mandatory step on a clean worktree, and restore them before handing control back. Only overlapping changes, ownership ambiguity, or stash/restore conflicts should force a user-facing decision.
194
+
193
195
  ## Canonical files
194
196
 
195
197
  This package stores canonical workflow state under:
@@ -64,13 +64,14 @@ These lines are for workflow observability, not hidden reasoning. Keep them brie
64
64
  3. Confirm the canonical slice ID, goal, acceptance criteria, contract IDs, priority, why_now, implementation_surfaces, verification_commands, locked notes, must-fix findings, basis_commit, and before-slice counters in `.agent/active-slice.json` match canonical `.agent/plan.json`. If they do not match, stop and report the mismatch instead of guessing.
65
65
  4. Make truthful `.agent/state.json` and `.agent/active-slice.json` updates before implementation if needed.
66
66
  5. If implementation reveals roadmap-level drift — for example a missing prerequisite slice, invalid slice boundary, dependency reorder, or blocker that changes the current slice contract — do not silently redesign the plan. Report the discrepancy explicitly, make only the minimal truthful local state updates needed for the current slice, and hand control back for canonical re-grounding by `completion-regrounder`.
67
- 6. Make the smallest correct tracked-file change.
68
- 7. Add or strengthen tests or deterministic proof.
69
- 8. Run focused verification first, then broader verification if shared surfaces changed.
70
- 9. If the chosen slice changes top-level validation entrypoints or is explicitly about verifier freshness, refresh `.agent/verify_completion_stop.sh` so it remains a truthful repo-level baseline verifier.
71
- 10. Create a new commit.
72
- 11. Make truthful `.agent/state.json`, `.agent/active-slice.json`, and `.agent/plan.json` updates after the commit, including `current_phase = post_commit_review`, `continuation_policy = continue`, `continuation_reason`, and `next_mandatory_role = completion-reviewer`.
73
- 12. Append exactly one `implemented` record to `.agent/slice-history.jsonl`.
67
+ 6. If unrelated tracked worktree changes are present and would otherwise block the mandatory dirty-worktree reconciliation or the current slice commit, auto-preserve them yourself with a reversible mechanism such as a named git stash plus a `.agent/tmp/dirty-worktree-autostash.json` note, continue the current slice on a clean worktree, and restore them before handing control back. Ask the user only when overlap, ownership ambiguity, or stash/restore conflicts make automatic isolation unsafe.
68
+ 7. Make the smallest correct tracked-file change.
69
+ 8. Add or strengthen tests or deterministic proof.
70
+ 9. Run focused verification first, then broader verification if shared surfaces changed.
71
+ 10. If the chosen slice changes top-level validation entrypoints or is explicitly about verifier freshness, refresh `.agent/verify_completion_stop.sh` so it remains a truthful repo-level baseline verifier.
72
+ 11. Create a new commit.
73
+ 12. Make truthful `.agent/state.json`, `.agent/active-slice.json`, and `.agent/plan.json` updates after the commit, including `current_phase = post_commit_review`, `continuation_policy = continue`, `continuation_reason`, and `next_mandatory_role = completion-reviewer`.
74
+ 13. Append exactly one `implemented` record to `.agent/slice-history.jsonl`.
74
75
 
75
76
  Do not stop after editing or verification if the slice changes remain uncommitted.
76
77
 
@@ -41,14 +41,16 @@ These lines are for workflow observability, not hidden reasoning. Keep them brie
41
41
  5. Reopen any previously `done` slice whose acceptance criteria no longer hold.
42
42
  6. Keep `.agent/state.json` and `.agent/active-slice.json` truthful, including `current_phase`, `continuation_policy`, `continuation_reason`, `next_mandatory_role`, and any exact implementer handoff snapshot fields.
43
43
  7. Reconcile canonical state after review, audit, and final stop verification waves when required.
44
- 8. If the latest committed slice leaves the tracked and unignored worktree dirty, treat that dirty state as a blocker, reopen or continue that latest slice for reconciliation, set `Next role to invoke` to `completion-implementer`, and do not select or hand off any different next slice until it is reconciled.
45
- 9. When reconciling after review, audit, or dirty-worktree follow-up for the latest committed slice, emit an explicit reconciliation record decision:
44
+ 8. If the latest committed slice leaves the tracked and unignored worktree dirty, first classify the dirty tracked files against the latest slice's `implementation_surfaces` and the tracked reconciliation surfaces you need to touch now.
45
+ 9. If the dirty tracked files are unrelated and can be isolated safely, auto-preserve them yourself with a reversible mechanism such as a named git stash plus a `.agent/tmp/dirty-worktree-autostash.json` note, continue the mandatory reconciliation on a clean worktree, and restore them before handing control back. Do not ask the user for this routine unrelated-dirty-worktree case.
46
+ 10. If overlap, ownership ambiguity, or stash/restore conflicts make automatic isolation unsafe, treat that dirty state as a blocker, reopen or continue the latest slice for reconciliation, set `Next role to invoke` to `completion-implementer`, and do not select or hand off any different next slice until it is reconciled.
47
+ 11. When reconciling after review, audit, or dirty-worktree follow-up for the latest committed slice, emit an explicit reconciliation record decision:
46
48
  - `accepted` only when the latest committed slice is truthfully accepted as-is
47
49
  - `reopened` only when the latest committed slice must be reopened for follow-up work
48
50
  - `none` when this re-ground was not a post-commit reconciliation decision
49
- 10. If you emit `accepted` or `reopened`, also emit the exact reconciled slice id in the report.
50
- 11. If a slice is already selected, ensure `.agent/active-slice.json` contains the exact implementer handoff snapshot and return that exact handoff payload for `completion-implementer` instead of implementing it yourself.
51
- 12. If no slice is selected, return the exact next recommended slice and why.
51
+ 12. If you emit `accepted` or `reopened`, also emit the exact reconciled slice id in the report.
52
+ 13. If a slice is already selected, ensure `.agent/active-slice.json` contains the exact implementer handoff snapshot and return that exact handoff payload for `completion-implementer` instead of implementing it yourself.
53
+ 14. If no slice is selected, return the exact next recommended slice and why.
52
54
 
53
55
  Output format:
54
56
 
@@ -257,8 +257,26 @@ function isCompletionDriverPromptTurn(snapshot: CompletionStateSnapshot | undefi
257
257
  return true;
258
258
  }
259
259
 
260
- function isCompletionWorkflowSessionTurn(snapshot: CompletionStateSnapshot | undefined, _ctx: { sessionManager?: any }): boolean {
261
- return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);
260
+ function workflowContinuationIntentText(text: string | undefined): string {
261
+ return (text ?? "").trim().toLowerCase();
262
+ }
263
+
264
+ function isLikelyWorkflowContinuationTurn(
265
+ snapshot: CompletionStateSnapshot | undefined,
266
+ ctx: { sessionManager?: any },
267
+ ): boolean {
268
+ if (!hasActiveWorkflowEntry(snapshot)) return false;
269
+ const latest = workflowContinuationIntentText(latestUserOrCustomTurnText(ctx));
270
+ if (!latest) return false;
271
+ if (isCookCommandTurn(ctx) || isCompletionDriverPromptTurn(snapshot, ctx)) return true;
272
+ if (asString(snapshot?.state?.continuation_policy) === "await_user_input") return true;
273
+ return /(\b(continue|resume|proceed|go ahead|keep going|next|finish|fix|repair|reconcile|commit|stash|audit|review|reground|implement|phase|slice|batch)\b|\.agent\b|\bworktree\b|\bworkflow\b|\bdirty\b|繼續|继续|開始|开始|先做|先把|修好|修復|修复|清理|處理|处理|提交|下一步|接著|继续做|做完|完成)/iu.test(latest);
274
+ }
275
+
276
+ function isCompletionWorkflowSessionTurn(snapshot: CompletionStateSnapshot | undefined, ctx: { sessionManager?: any }): boolean {
277
+ if (hasCompletionRoutingActivation(snapshot)) return true;
278
+ if (!hasActiveWorkflowEntry(snapshot)) return false;
279
+ return isCookCommandTurn(ctx) || isCompletionDriverPromptTurn(snapshot, ctx) || isLikelyWorkflowContinuationTurn(snapshot, ctx);
262
280
  }
263
281
 
264
282
  function shouldInjectCompletionWorkflowContext(snapshot: CompletionStateSnapshot | undefined, ctx: { sessionManager?: any }): boolean {
@@ -430,7 +448,7 @@ async function deriveCookContextProposal(
430
448
  projectName: string,
431
449
  ): Promise<CookContextProposalResult> {
432
450
  const explicit = await deriveCookStartupProposal(ctx, projectName);
433
- if (explicit.proposal || explicit.blockedFailureMessage) return explicit;
451
+ if (explicit.proposal) return explicit;
434
452
  const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
435
453
  const recentEntries = recentMessages
436
454
  .filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "assistant" || entry.role === "custom" || entry.role === "summary"))
@@ -450,6 +468,10 @@ async function deriveCookContextProposal(
450
468
  `verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
451
469
  ]
452
470
  : [];
471
+ if (explicit.blockedFailureMessage) {
472
+ workflowContextLines.push(`fresh explicit handoff required tightening before startup: ${explicit.blockedFailureMessage}`);
473
+ 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.");
474
+ }
453
475
  const raw = await generateCookHandoffWithAgent({
454
476
  ctx,
455
477
  projectName,
@@ -476,6 +498,7 @@ async function deriveCookContextProposal(
476
498
  });
477
499
  if (generated.status === "startable") return { proposal: generated.proposal };
478
500
  if (generated.status === "fresh_but_not_startable") return { blockedFailureMessage: generated.message };
501
+ if (explicit.blockedFailureMessage) return { blockedFailureMessage: explicit.blockedFailureMessage };
479
502
  return {};
480
503
  }
481
504
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linimin/pi-letscook",
3
- "version": "0.1.74",
3
+ "version": "0.1.76",
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,
@@ -42,6 +42,7 @@
42
42
  "context-proposal-test": "bash ./scripts/context-proposal-test.sh",
43
43
  "observability-status-test": "bash ./scripts/observability-status-test.sh",
44
44
  "completion-role-gating-test": "bash ./scripts/completion-role-gating-test.sh",
45
+ "dirty-worktree-policy-test": "bash ./scripts/dirty-worktree-policy-test.sh",
45
46
  "evaluator-calibration-test": "bash ./scripts/evaluator-calibration-test.sh",
46
47
  "rubric-contract-test": "bash ./scripts/rubric-contract-test.sh",
47
48
  "release-check": "bash ./scripts/release-check.sh"
@@ -510,10 +510,7 @@ import sys
510
510
  from pathlib import Path
511
511
 
512
512
  reminder = Path(sys.argv[1])
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'
513
+ assert not reminder.exists(), 'ordinary non-/cook turn should not inject completion reminder solely from selected-slice canonical state'
517
514
  PY
518
515
 
519
516
  python3 - <<'PY'
@@ -21,26 +21,24 @@ const assertNotIncludes = (file, snippet) => {
21
21
  }
22
22
  };
23
23
 
24
+ assertIncludes('extensions/completion/index.ts', 'function isLikelyWorkflowContinuationTurn(');
24
25
  assertIncludes('extensions/completion/index.ts', 'function isCompletionWorkflowSessionTurn(');
25
- assertIncludes('extensions/completion/index.ts', 'return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);');
26
+ assertIncludes('extensions/completion/index.ts', 'return isCookCommandTurn(ctx) || isCompletionDriverPromptTurn(snapshot, ctx) || isLikelyWorkflowContinuationTurn(snapshot, ctx);');
26
27
  assertIncludes('extensions/completion/index.ts', 'const completionRoleDispatchAllowed = Boolean(role) || isCompletionWorkflowSessionTurn(snapshot, ctx);');
27
28
  assertIncludes('extensions/completion/policy-guards.ts', 'return "completion_role may only be used from an active /cook workflow session.";');
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
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');
30
30
 
31
- assertNotIncludes('extensions/completion/index.ts', 'function isOrdinaryMainChatTurnDuringActiveWorkflow(');
32
- assertNotIncludes('extensions/completion/index.ts', 'function isCompletionRoleDispatchAllowedTurn(');
33
- assertNotIncludes('extensions/completion/index.ts', 'function isAwaitingUserInputWorkflowReplyTurn(');
31
+ assertNotIncludes('extensions/completion/index.ts', 'return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);');
34
32
 
35
33
  const indexText = read('extensions/completion/index.ts');
36
- const sessionTurnIndex = indexText.indexOf('function isCompletionWorkflowSessionTurn(');
37
- const stickyReturnIndex = indexText.indexOf('return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);');
34
+ const continuationIntentIndex = indexText.indexOf('function isLikelyWorkflowContinuationTurn(');
35
+ const stickyReturnIndex = indexText.indexOf('return isCookCommandTurn(ctx) || isCompletionDriverPromptTurn(snapshot, ctx) || isLikelyWorkflowContinuationTurn(snapshot, ctx);');
38
36
  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.');
37
+ if (continuationIntentIndex === -1 || stickyReturnIndex === -1 || toolGateIndex === -1) {
38
+ throw new Error('extensions/completion/index.ts must gate workflow continuation through explicit workflow turns or likely continuation turns before dispatching completion_role.');
41
39
  }
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.');
40
+ if (!(continuationIntentIndex < stickyReturnIndex && stickyReturnIndex < toolGateIndex)) {
41
+ throw new Error('extensions/completion/index.ts should define continuation-turn detection before reusing it for completion_role dispatch.');
44
42
  }
45
43
  NODE
46
44
 
@@ -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 still fail closed
568
- # without rewriting canonical state.
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'] == 'blocked', 'fresh non-startable explicit handoff should fail closed for active bare /cook'
661
- assert routing['reason'] == 'fresh_explicit_handoff_not_startable', 'fresh non-startable explicit handoff should keep the dedicated explicit-handoff fail-closed reason'
662
- assert 'fresh explicit primary-agent handoff exists' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should surface the dedicated fail-closed message'
663
- 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'
664
- assert not resume_path.exists(), 'fresh non-startable explicit handoff should not queue a resume prompt'
665
- assert not chooser_path.exists(), 'fresh non-startable explicit handoff should not open the replacement chooser'
666
- assert not proposal_path.exists(), 'fresh non-startable explicit handoff should not open final proposal confirmation'
667
- assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable explicit handoff should explain that the explicit capsule blocked active-workflow replacement'
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 fail closed instead of falling back
1315
- # to a broad recent-discussion startup brief when the explicit capsule is still too vague.
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 not snapshot.exists(), 'fresh non-startable handoff should not emit a startup proposal snapshot'
1381
- assert not Path('.agent').exists(), 'fresh non-startable handoff should fail closed without writing canonical state'
1382
- assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable handoff should explain that the explicit capsule blocked startup'
1383
- assert 'acceptance is not anchored to concrete repo changes or verification' in output, 'fresh non-startable handoff should explain the workflow-only acceptance failure'
1384
- assert 'implementation_surfaces is empty' in output, 'fresh non-startable handoff should explain the missing implementation_surfaces requirement'
1385
- assert 'verification_commands is empty' in output, 'fresh non-startable handoff should explain the missing verification_commands requirement'
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 fail closed
1389
- # with the dedicated explicit-handoff message instead of bootstrapping canonical state.
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 not snapshot.exists(), 'fresh explicit handoff with vague acceptance should not emit a startup proposal snapshot'
1461
- assert not Path('.agent').exists(), 'fresh explicit handoff with vague acceptance should fail closed without writing canonical state'
1462
- assert 'fresh explicit primary-agent handoff exists' in output, 'fresh explicit handoff with vague acceptance should use the dedicated explicit-handoff fail-closed message'
1463
- assert 'acceptance is not anchored to concrete repo changes or verification' in output, 'fresh explicit handoff with vague acceptance should explain the vague acceptance failure'
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"
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ cd "$ROOT"
6
+
7
+ node <<'NODE'
8
+ const fs = require('node:fs');
9
+
10
+ const read = (file) => fs.readFileSync(file, 'utf8');
11
+ const assertIncludes = (file, snippet) => {
12
+ const text = read(file);
13
+ if (!text.includes(snippet)) {
14
+ throw new Error(`${file} is missing required dirty-worktree policy text: ${snippet}`);
15
+ }
16
+ };
17
+
18
+ assertIncludes('skills/completion-protocol/SKILL.md', 'auto-preserve them with a reversible mechanism such as a named git stash plus a `.agent/tmp/dirty-worktree-autostash.json` note');
19
+ assertIncludes('skills/completion-protocol/SKILL.md', 'Ask the user only when overlap, ownership ambiguity, or stash/restore conflicts make automatic isolation unsafe.');
20
+ assertIncludes('skills/completion-protocol/references/completion.md', 'Dirty-worktree auto-reconcile. If tracked worktree dirt is unrelated to the latest slice or current reconciliation surfaces and can be isolated safely');
21
+ assertIncludes('agents/completion-regrounder.md', 'Do not ask the user for this routine unrelated-dirty-worktree case.');
22
+ assertIncludes('agents/completion-implementer.md', 'auto-preserve them yourself with a reversible mechanism such as a named git stash plus a `.agent/tmp/dirty-worktree-autostash.json` note');
23
+ assertIncludes('README.md', 'Active `/cook` workflows now also auto-reconcile routine unrelated tracked worktree dirt instead of bouncing that decision back to the user.');
24
+ assertIncludes('CHANGELOG.md', 'auto-preserve routine unrelated tracked worktree dirt with a reversible stash-plus-note flow');
25
+ NODE
26
+
27
+ echo "dirty-worktree policy test passed"
@@ -5,7 +5,7 @@ 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, explicit-/cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, completion-role gating, legacy cleanup, evaluator calibration, and rubric contract coverage"
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, completion-role gating, dirty-worktree policy, 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
 
@@ -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 isCompletionWorkflowSessionTurn(',
38
- 'return hasCompletionRoutingActivation(snapshot) || hasActiveWorkflowEntry(snapshot);',
37
+ 'function isLikelyWorkflowContinuationTurn(',
38
+ 'return isCookCommandTurn(ctx) || isCompletionDriverPromptTurn(snapshot, ctx) || isLikelyWorkflowContinuationTurn(snapshot, ctx);',
39
39
  ],
40
40
  "extensions/completion/policy-guards.ts": [
41
41
  'return "completion_role may only be used from an active /cook workflow session.";',
@@ -84,6 +84,7 @@ bash ./scripts/canonical-evidence-artifact-test.sh
84
84
  bash ./scripts/active-slice-contract-test.sh
85
85
  npm run observability-status-test
86
86
  npm run completion-role-gating-test
87
+ npm run dirty-worktree-policy-test
87
88
  bash ./scripts/legacy-cleanup-test.sh
88
89
  npm run evaluator-calibration-test
89
90
  npm run rubric-contract-test
@@ -273,16 +273,20 @@ reminder = Path(sys.argv[3])
273
273
  handoff = Path(sys.argv[4])
274
274
  auto_resume = Path(sys.argv[5])
275
275
 
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
- assert auto_resume.exists(), 'active workflow should queue an auto-resume prompt from canonical state without requiring another /cook'
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'
284
- assert 'Resume the completion workflow from canonical state.' in auto_resume_text, 'auto-resume prompt should resume canonical workflow state'
285
- assert 'Skipped completion workflow auto-resume prompt (test mode)' in output, 'active workflow should attempt auto-resume in test mode'
276
+ assert not reminder.exists(), 'ordinary non-/cook turn should not inject completion reminder solely from canonical state'
277
+ assert handoff.exists(), 'ordinary non-/cook turn should inject the /cook handoff boundary reminder'
278
+ handoff_text = handoff.read_text()
279
+ assert 'ordinary main chat unless the user explicitly runs /cook' in handoff_text, 'ordinary handoff reminder should preserve explicit /cook workflow entry'
280
+ assert 'directly implement requested repo changes, including multi-file work' in handoff_text, 'ordinary handoff reminder should allow direct ordinary-chat implementation'
281
+ assert 'Do not proactively tell the user to run /cook' in handoff_text, 'ordinary handoff reminder should keep ordinary chat neutral until explicit /cook entry'
282
+ assert '/cook is optional workflow mode' in handoff_text, 'ordinary handoff reminder should position /cook as optional workflow mode'
283
+ assert 'In ordinary chat, do not load or follow completion-protocol, and do not call completion_role.' in handoff_text, 'ordinary handoff reminder should forbid workflow-role routing before explicit /cook'
284
+ assert 'If the user wants direct implementation now, stay in ordinary chat and help directly instead of blocking on /cook.' in handoff_text, 'ordinary handoff reminder should avoid blocking implementation on /cook'
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'
286
290
  PY
287
291
 
288
292
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
@@ -299,11 +303,12 @@ from pathlib import Path
299
303
 
300
304
  expected_task_type = 'completion-workflow'
301
305
  expected_eval_profile = 'completion-rubric-v1'
302
- resume = Path(sys.argv[1]).read_text()
306
+ resume_path = Path(sys.argv[1])
303
307
  routing = json.loads(Path(sys.argv[2]).read_text())
304
308
  chooser_path = Path(sys.argv[3])
305
309
  state = json.loads(Path('.agent/state.json').read_text())
306
310
 
311
+ resume = resume_path.read_text()
307
312
  assert 'Canonical routing profile:' in resume, 'resume prompt should expose canonical routing profile'
308
313
  assert f'- task_type: {expected_task_type}' in resume, 'resume prompt missing canonical task_type'
309
314
  assert f'- evaluation_profile: {expected_eval_profile}' in resume, 'resume prompt missing canonical evaluation_profile'
@@ -32,6 +32,7 @@ This skill defines shared protocol facts only. Role-specific behavior belongs in
32
32
  - Run exactly one implementation slice at a time.
33
33
  - A slice is not complete unless it lands as a new commit.
34
34
  - Before selecting or advancing to the next slice after a committed slice, the tracked and unignored worktree must be clean. If it is not clean, treat that dirty state as a blocker to next-slice progression and reopen or continue the latest slice for reconciliation.
35
+ - When that dirty tracked worktree contains changes unrelated to the latest slice or current reconciliation surfaces and those changes can be isolated safely, auto-preserve them with a reversible mechanism such as a named git stash plus a `.agent/tmp/dirty-worktree-autostash.json` note, continue the mandatory workflow step, and restore them before handing control back. Ask the user only when overlap, ownership ambiguity, or stash/restore conflicts make automatic isolation unsafe.
35
36
  - Docs, config, and runbooks must stay truthful to shipped behavior.
36
37
  - `.agent/verify_completion_stop.sh` is a generated repo-level baseline verifier. Onboarding should create a working version from current repo truth rather than an unconditional failing placeholder.
37
38
  - The packaged default stop policy is `required_stop_judges: 2` plus `stop_aggregation_policy: "unanimous-current-head-v1"` in `.agent/profile.json`.
@@ -168,6 +168,7 @@ Rules:
168
168
  3. Done requires all satisfied. A slice may only transition to `done` when every acceptance criterion is satisfied and `evidence` contains the proof for each one.
169
169
  4. Re-ground validation. During re-ground, the current slice backlog must be revalidated against repo truth. A slice previously marked `done` whose criteria no longer hold must be reopened.
170
170
  5. Clean handoff before next slice. After a committed slice is reviewed and audited, the tracked and unignored worktree must be clean before the next slice is selected.
171
+ 6. Dirty-worktree auto-reconcile. If tracked worktree dirt is unrelated to the latest slice or current reconciliation surfaces and can be isolated safely, the workflow should auto-preserve it with a reversible mechanism such as a named git stash plus a `.agent/tmp/dirty-worktree-autostash.json` note, continue the mandatory workflow step, and restore it before handing control back. Ask the user only when overlap, ownership ambiguity, or stash/restore conflicts make automatic isolation unsafe.
171
172
 
172
173
  `active-slice.json` carries one current slice cursor.
173
174