@rallycry/conveyor-agent 10.13.72 → 11.0.0

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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _project_shared from '@project/shared';
2
- import { RunnerMode, AgentSessionServiceMethods, AgentMode, PtyChatEventPayload, WorkspaceDiscoveredPort, AgentQuestion, AgentRunnerStatus } from '@project/shared';
2
+ import { RunnerMode, AgentSessionServiceMethods, AgentMode, PtyChatEventPayload, WorkspaceDiscoveredPort, AgentQuestion, PackExecution, AgentRunnerStatus } from '@project/shared';
3
3
  export * from '@project/shared';
4
4
  import { ChildProcess } from 'node:child_process';
5
5
 
@@ -56,6 +56,31 @@ interface SpawnReviewData {
56
56
  prNumber?: number | null;
57
57
  checkoutRef?: string | null;
58
58
  }
59
+ /**
60
+ * Server push asking a PLANNER pod to spawn the Builder child: a second
61
+ * conveyor-agent process bound to a fresh `mode:"build"` WorkspaceSession. The
62
+ * lifecycle handoff — a human pressing Build, or a Yolomatic plan landing —
63
+ * without cold-booting a second pod, and without flipping the planner's own
64
+ * session into a builder in place.
65
+ *
66
+ * Same wire discipline as SpawnReviewData: no taskId (the child inherits the
67
+ * parent's CONVEYOR_TASK_ID and the server derives it from the JWT), and the
68
+ * runner mode rides the JWT claim rather than this field — `mode` here is what
69
+ * the supervisor exports as CONVEYOR_MODE for the child process.
70
+ */
71
+ interface SpawnBuilderData {
72
+ sessionId: string;
73
+ sessionJwt: string;
74
+ /**
75
+ * Runner mode for the child — `pack` when the card has children, so the
76
+ * coordinator gets the single-pod pack prompt and toolset. Optional so a pod
77
+ * running against an older server (which sends none) still boots as `task`.
78
+ */
79
+ mode?: "task" | "pack";
80
+ branch?: string | null;
81
+ /** The agent whose model/settings/instructions the Builder runs under. */
82
+ agentName?: string | null;
83
+ }
59
84
  /**
60
85
  * Server push asking the builder pod to spawn an extra interactive child on
61
86
  * this pod: a second Claude TUI (mode "adhoc") or a raw login shell (mode
@@ -98,6 +123,8 @@ declare class AgentConnection {
98
123
  private earlyPullBranches;
99
124
  private spawnReviewCallback;
100
125
  private earlySpawnReviews;
126
+ private spawnBuilderCallback;
127
+ private earlySpawnBuilders;
101
128
  private spawnTuiCallback;
102
129
  private earlySpawnTuis;
103
130
  private probeUsageCallback;
@@ -168,6 +195,9 @@ declare class AgentConnection {
168
195
  branch: string;
169
196
  }) => void): void;
170
197
  onSpawnReview(callback: (data: SpawnReviewData) => void): void;
198
+ /** Mirror of onSpawnReview for the Builder handoff. Drains the early buffer
199
+ * so a Build pressed during boot is not lost. */
200
+ onSpawnBuilder(callback: (data: SpawnBuilderData) => void): void;
171
201
  /**
172
202
  * Report that a same-pod review child failed to spawn (fire-and-forget).
173
203
  * The server Ends the orphaned review session and falls back to a dedicated
@@ -175,6 +205,15 @@ declare class AgentConnection {
175
205
  * it; the review session is identified separately.
176
206
  */
177
207
  reportReviewSpawnFailure(reviewSessionId: string, error?: string): void;
208
+ /**
209
+ * Report that this (planner) pod could not spawn the Builder child, so the
210
+ * server can End the orphaned build session and reopen the card instead of
211
+ * leaving it InProgress with a Builder tab that never appears.
212
+ *
213
+ * sessionId is OUR (planner) session — the task-identity guard runs on it;
214
+ * the build session is identified separately.
215
+ */
216
+ reportBuilderSpawnFailure(buildSessionId: string, error?: string): void;
178
217
  /**
179
218
  * Report that this pod's git credential is dead and refreshing did not fix
180
219
  * it (fire-and-forget).
@@ -412,14 +451,66 @@ declare class ModeController {
412
451
  private _pendingModeRestart;
413
452
  private _runnerMode;
414
453
  private _isAuto;
454
+ /**
455
+ * Which planner this is. Only read when `_runnerMode` is `plan`; see
456
+ * `effectiveMode` for the full reasoning, and `latchPlanFlavor` for why this
457
+ * latches toward `chat` and never away from it.
458
+ */
459
+ private _planFlavor;
415
460
  constructor(initialMode: AgentMode, runnerMode?: RunnerMode, isAuto?: boolean);
461
+ /**
462
+ * Promote this planner to the chat flavor, once, and never demote it.
463
+ *
464
+ * It cannot be a constructor-only assignment, because the constructor usually
465
+ * has nothing to go on. A GKE pod's bootstrap bundle carries no agentMode, so
466
+ * `CONVEYOR_AGENT_MODE` is unset and `initialMode` falls back to `"building"`
467
+ * for EVERY pod — which is the whole reason `applyServerMode` exists. Pinning
468
+ * the flavor at construction alone would therefore make every chat card in the
469
+ * cloud a discovery planner and silently re-open the defect this replaced.
470
+ *
471
+ * One-way, and both directions matter:
472
+ *
473
+ * - Only the literal card mode `chat` promotes. The server reports `chat` only
474
+ * for a card whose agentMode IS `chat`, so a discovery card can never be
475
+ * talked into the build-capable flavor. The original hazard — `auto` stamped
476
+ * onto a plan-less auto card's planner — is untouched, because `auto` is not
477
+ * `chat`.
478
+ * - Nothing demotes. The Build press overwrites `Task.agentMode` to
479
+ * `auto`/`building` before the server sees it, so a later stamp would
480
+ * otherwise strip a live conversation's prompt and file deliverables
481
+ * mid-turn.
482
+ *
483
+ * The asymmetry with `auto` is principled rather than convenient: an auto
484
+ * card's mode and its planner phase legitimately differ, while a chat card's
485
+ * mode IS what its session is, for the session's whole life.
486
+ */
487
+ private latchPlanFlavor;
416
488
  get mode(): AgentMode;
417
489
  get isAuto(): boolean;
418
490
  get hasExitedPlanMode(): boolean;
419
491
  set hasExitedPlanMode(val: boolean);
420
492
  get pendingModeRestart(): boolean;
421
493
  set pendingModeRestart(val: boolean);
422
- /** Effective mode accounting for PM/task defaults */
494
+ /**
495
+ * Effective mode accounting for PM/task defaults.
496
+ *
497
+ * A `plan` runner is pinned to ONE mode for its whole life, whatever the
498
+ * card's own agentMode later says. That pin is what makes the planner
499
+ * read-only, non-build-capable, and prompted for planning — all three derive
500
+ * from here — and it has to be immune to `applyServerMode`, which runs AFTER
501
+ * boot and would otherwise stamp `auto` onto the planner of a plan-less auto
502
+ * card, handing it the build prompt and `--dangerously-skip-permissions`
503
+ * inside a session whose whole purpose is not to have them.
504
+ *
505
+ * There are TWO planner flavors, and which one this session is was decided at
506
+ * boot (`_planFlavor`) — never re-read from the mutable `_mode`. A chat card
507
+ * boots a plan session too (it is a Planner-session flavor, not a builder in
508
+ * disguise), and it must keep `buildChatPrompt`, its file deliverables, and
509
+ * the build-capable tool handler. Reading the flavor from `_mode` would put
510
+ * that choice back under server control and re-open the exact hole the
511
+ * paragraph above closes, in the other direction: a stamp of `chat` onto a
512
+ * discovery planner would hand it build capability.
513
+ */
423
514
  get effectiveMode(): AgentMode;
424
515
  get isReadOnly(): boolean;
425
516
  get isAutoPlanning(): boolean;
@@ -507,7 +598,13 @@ declare class Lifecycle {
507
598
  stopGitFlush(): void;
508
599
  startUsageSample(): void;
509
600
  stopUsageSample(): void;
510
- startIdleTimer(): void;
601
+ /** Start (or restart) the idle timer.
602
+ * @param overrideMs Optional custom delay in ms, mirroring
603
+ * `startDormantTimer`. SessionRunner passes a short delay when it DEFERS a
604
+ * shutdown because a spawned child is still working: the pod must re-check
605
+ * soon after that child exits, rather than granting itself a fresh full idle
606
+ * window every time it defers. */
607
+ startIdleTimer(overrideMs?: number): void;
511
608
  cancelIdleTimer(): void;
512
609
  /** Start (or restart) the dormant timer.
513
610
  * @param overrideMs Optional custom delay in ms. When provided, the timer
@@ -522,10 +619,43 @@ declare class Lifecycle {
522
619
  private clearIdleTimers;
523
620
  }
524
621
 
622
+ /**
623
+ * Who else is working on this pod?
624
+ *
625
+ * The Planner is the pod's MAIN process, so its idle/dormant timeouts end the
626
+ * whole pod: `stopped = true` → `shutdown("finished")` → `process.exit(0)` →
627
+ * the supervisor logs "agent exited cleanly, shutting down pod". Nothing used
628
+ * to ask whether a spawned child was still working. A handed-off build that
629
+ * outlived the Planner's idle window therefore died mid-run — the work survived
630
+ * only as far as the last `conveyor-wip` flush, the janitor parked the
631
+ * workspace, and the card reverted to Open.
632
+ *
633
+ * This is the seam that lets `SessionRunner` ask without importing the
634
+ * supervisors: `cli.ts` owns them and hands the runner a probe. Keeping the
635
+ * dependency pointing that way also keeps the runner testable with a fake.
636
+ *
637
+ * Ordering is deliberate — `builder` first — so the deferral log names the
638
+ * session a human is most likely looking for when they ask why a pod is still
639
+ * up.
640
+ */
641
+ /** A child supervisor, reduced to the one question the shutdown path asks. */
642
+ interface ChildSessionSource {
643
+ /**
644
+ * Names the child kind in the deferral log. `tui` rather than `session` —
645
+ * these come from `session:spawnTui`, but "session session <id>" stutters in
646
+ * the one place this string is read.
647
+ */
648
+ readonly kind: "builder" | "review" | "tui";
649
+ /** Sessions this supervisor currently hosts. Empty when it hosts none. */
650
+ activeSessionIds(): string[];
651
+ }
652
+
525
653
  interface SessionRunnerConfig {
526
654
  connection: AgentConnectionConfig;
527
655
  agentMode?: AgentMode;
528
656
  runnerMode?: RunnerMode;
657
+ /** Only read when `runnerMode` is "pack". Defaults to single-pod. */
658
+ packExecution?: PackExecution;
529
659
  isAuto?: boolean;
530
660
  workspaceDir: string;
531
661
  model?: string;
@@ -601,6 +731,33 @@ declare class SessionRunner {
601
731
  private agentLiveReported;
602
732
  constructor(config: SessionRunnerConfig, callbacks: SessionRunnerCallbacks, deps?: SessionRunnerDependencies);
603
733
  get state(): AgentRunnerStatus;
734
+ /**
735
+ * Supervisors whose live children keep this pod alive. Empty until `cli.ts`
736
+ * wires them, which is deliberate: the supervisors are constructed AFTER the
737
+ * runner, and an empty list simply means "no children to protect" — the
738
+ * pre-existing shutdown behavior.
739
+ */
740
+ private liveChildSources;
741
+ /** Called by `cli.ts` once the child supervisors exist. */
742
+ setLiveChildSources(sources: readonly ChildSessionSource[]): void;
743
+ /**
744
+ * Suppress an idle/dormant shutdown while a spawned child is still working.
745
+ *
746
+ * The Planner is the pod's main process, so its timeouts take the pod — and
747
+ * the Builder's in-flight work — down with it. Returns true when the shutdown
748
+ * was deferred and the caller must not proceed.
749
+ *
750
+ * Re-arms with a SHORT delay rather than a fresh full window: the pod should
751
+ * converge on shutdown soon after the last child exits, not one more idle
752
+ * window later. Bounded by the configured timeout so a test with a 50ms idle
753
+ * window re-checks in 50ms rather than a minute.
754
+ *
755
+ * Fail-open by construction — if the probe throws, or no sources are wired,
756
+ * the shutdown proceeds exactly as before. The opposite bias (a pod that
757
+ * cannot die) is the more expensive mistake here only in money; killing a
758
+ * live build costs work.
759
+ */
760
+ private deferShutdownForLiveChild;
604
761
  get sessionId(): string;
605
762
  get isStopped(): boolean;
606
763
  /** Wire the boot supervisor handle post-construction — cli.ts constructs it
@@ -815,6 +972,16 @@ interface AgentRunnerConfig {
815
972
  mode?: _project_shared.RunnerMode;
816
973
  isAuto?: boolean;
817
974
  agentSettings?: _project_shared.AgentSettings;
975
+ /**
976
+ * How a `mode: "pack"` session executes its children.
977
+ *
978
+ * "single-pod" (the default): this session implements every child itself,
979
+ * serially. "fan-out": the legacy coordinator — it fires one pod per child
980
+ * and writes no code. Carried as a sub-flag rather than a new `RunnerMode`
981
+ * so `resolveModeEnv`, the JWT mode claim, and every `mode === "pack"`
982
+ * reader keep working unchanged.
983
+ */
984
+ packExecution?: _project_shared.PackExecution;
818
985
  }
819
986
  interface AgentRunnerCallbacks {
820
987
  onEvent: (event: Record<string, unknown>) => void | Promise<void>;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SessionRunner,
3
3
  unshallowRepo
4
- } from "./chunk-2SN32LM6.js";
4
+ } from "./chunk-PEEGCZAR.js";
5
5
  import "./chunk-XORJ6SII.js";
6
6
  import {
7
7
  AgentConnection,
@@ -16,8 +16,8 @@ import {
16
16
  stageAndCommit,
17
17
  updateRemoteToken,
18
18
  workspacePathExists
19
- } from "./chunk-LSZ2KLJY.js";
20
- import "./chunk-WMMBAKPE.js";
19
+ } from "./chunk-N4WSUTGV.js";
20
+ import "./chunk-GL2DIQEQ.js";
21
21
  import "./chunk-IA45XHOA.js";
22
22
  import {
23
23
  getWorkbenchClient
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  WorkspaceCommandSupervisor
3
- } from "./chunk-2L5THOWD.js";
3
+ } from "./chunk-JQVAWRVL.js";
4
4
  import {
5
5
  AgentConnection,
6
6
  CodespacePortVisibility,
@@ -11,8 +11,8 @@ import {
11
11
  createServiceLogger,
12
12
  ensureOnTaskBranch,
13
13
  loadConveyorConfig
14
- } from "./chunk-LSZ2KLJY.js";
15
- import "./chunk-WMMBAKPE.js";
14
+ } from "./chunk-N4WSUTGV.js";
15
+ import "./chunk-GL2DIQEQ.js";
16
16
  import "./chunk-IA45XHOA.js";
17
17
  import "./chunk-EXQ6AHOY.js";
18
18
  import "./chunk-KMB3BU4S.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rallycry/conveyor-agent",
3
- "version": "10.13.72",
3
+ "version": "11.0.0",
4
4
  "description": "Conveyor Agent Runner v10 - PTY harness for the task chat (SDK harness for audit/project-chat). Agent-as-User architecture with BaseService patterns. Works locally too.",
5
5
  "keywords": [
6
6
  "agent",
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "files": [
17
17
  "dist",
18
- "runtime"
18
+ "runtime",
19
+ "skills"
19
20
  ],
20
21
  "type": "module",
21
22
  "main": "./dist/index.js",
@@ -36,8 +37,8 @@
36
37
  "typecheck": "tsgo --noEmit"
37
38
  },
38
39
  "dependencies": {
39
- "@anthropic-ai/claude-agent-sdk": "^0.3.219",
40
- "@modelcontextprotocol/sdk": "^1.12.1",
40
+ "@anthropic-ai/claude-agent-sdk": "^0.3.246",
41
+ "@modelcontextprotocol/sdk": "^1.30.0",
41
42
  "node-pty": "^1.0.0",
42
43
  "socket.io-client": "^4.8.3",
43
44
  "tar": "^7.5.21",
@@ -0,0 +1,262 @@
1
+ ---
2
+ name: conveyor-build
3
+ description: Execute a planned Conveyor card to a pull request — one task or a whole feature-branch pack — then stay wake-able for review feedback and CI. Use when the user says "/conveyor-build <card>", "build this card", "implement this card", "run this pack", or when a claudespace session is told to follow its card's plan. Works identically in a cloud pod and in a local checkout; the card is the spec and task chat is the log. For producing the plan in the first place use conveyor-plan; to work a whole queue of Open cards use conveyor-local-loop.
4
+ ---
5
+
6
+ # Conveyor Build
7
+
8
+ Take a card that already has a plan and drive it to a pull request, then stay
9
+ reachable for review feedback and CI. The card IS the spec, task chat is the
10
+ log, and the host repo's CLAUDE.md governs gates, verification, and PR
11
+ mechanics. This skill adds routing, claiming, and the pack chain.
12
+
13
+ **One skill, two environments.** Everything below applies in a cloud pod and in
14
+ a local checkout alike. Where they genuinely differ, the difference is called
15
+ out in an **Environment** note — never assume; check which one you are in.
16
+
17
+ ## Ground rules
18
+
19
+ - **Conveyor is the state store.** A build spans hours or days and the session
20
+ gets compacted. Re-derive state from `mcp__conveyor__get_task` +
21
+ `mcp__conveyor__list_subtasks` at every decision point, never from
22
+ conversation memory.
23
+ - **All Conveyor tools fully-qualified** — `mcp__conveyor__get_task`, not
24
+ `get_task`. Bare names fail.
25
+ - **The card must stand alone.** If you find yourself relying on session
26
+ memory or on this conversation, stop and re-read the card. A plan that fails
27
+ the context-free-reader bar is a blocker, not something to wing: post what is
28
+ missing to chat and stop.
29
+ - **Never approve or merge your own PR into `dev`.** Finish line = the card in
30
+ ReviewPR with CI started. The exception is narrow and lives in the pack path:
31
+ you ARE the reviewer of record for a pack's CHILD PRs into the pack branch,
32
+ because the automated reviewer skips those.
33
+ - **Never boot another environment for work you are doing.** Locally that
34
+ means never `mcp__conveyor__start_task` (it exists only on the local/MCP
35
+ surface, and it spawns a cloud pod that duplicates you). In a pod driving a
36
+ pack it means never `mcp__conveyor__start_child_cloud_build` /
37
+ `mcp__conveyor__stop_child_build` — you implement the children yourself,
38
+ serially. Parallel fan-out is a deliberate choice the user makes by pressing
39
+ Build on the parent, not something a build session opts into.
40
+
41
+ ## Environment
42
+
43
+ Resolve which one you are in before touching git or the filesystem.
44
+ `mcp__conveyor__get_connection_context` answers it: a pod session reports a
45
+ task binding and no user account.
46
+
47
+ **In a pod:**
48
+
49
+ - Uncommitted work is auto-snapshotted to `conveyor-wip/<branch>` after every
50
+ turn, so a sleep or crash does not lose the tree. Commit and push anyway as
51
+ soon as a chunk is coherent — committed work never depends on that mechanism.
52
+ - `mcp__conveyor__create_pull_request` stages, commits, and pushes for you.
53
+ - Capture UI evidence with the repo's own harness (in this repo,
54
+ `scripts/agent/shot.ts`) and attach with `mcp__conveyor__upload_attachment`
55
+ before opening the PR.
56
+ - The database and dev server belong to this pod alone — experiment freely.
57
+
58
+ **Locally:**
59
+
60
+ - There is NO WIP autosync. Committed-and-pushed is the only durable state:
61
+ push the branch (`-u origin`) the moment it exists, and always before a long
62
+ background job or a wait.
63
+ - `mcp__conveyor__create_pull_request` opens a PR for a branch you have ALREADY
64
+ pushed. Push first.
65
+ - Work in the main checkout, **never a git worktree** — the provisioned
66
+ workspace (installed dependencies, env/direnv auth wiring, a running dev
67
+ stack) is the entire value of local execution, and worktrees have none of it.
68
+ A dirty `git status` before any branch switch is a hard stop: report and let
69
+ the user commit or stash. Do not solve it with a second checkout.
70
+ - The dev database and dev-server ports are shared with the user's own
71
+ sessions. No destructive experiments, never reset the dev DB, and reuse a
72
+ running dev stack rather than fighting over ports.
73
+
74
+ ## Resolve and route
75
+
76
+ 1. Resolve the card from the argument — slug, id, or URL. **None given → ask
77
+ which card.** Never infer one from the branch you happen to be on.
78
+ 2. `mcp__conveyor__get_task` for the full plan and
79
+ `mcp__conveyor__read_task_chat` for addenda and user answers. Consult
80
+ `mcp__conveyor__get_tag` on the card's tags before diving in — a tag's
81
+ overview and linked files are the fast path into the subsystem.
82
+ 3. Route on the card's shape:
83
+
84
+ | Card shape | Path |
85
+ | --- | --- |
86
+ | No children | **Task path** — [references/task-path.md](references/task-path.md) |
87
+ | Has children, children PR into a **feature branch** | **Pack path** — [references/pack-path.md](references/pack-path.md) |
88
+ | Has children that PR straight into `dev` | Not a pack chain. Build the children one at a time via the task path; leave the parent alone. |
89
+ | Has a `parentTaskId` | Task path, but **only while the parent is parked** (neither InProgress nor ReviewPR). The child's base is the parent's feature branch, never `dev`. |
90
+
91
+ > **Environment — a pod's pack runs one branch, not one branch per child.** The
92
+ > pod's `create_pull_request` takes no task argument: it always opens the PR for
93
+ > the card the session is bound to. A pod bound to the pack PARENT therefore
94
+ > cannot open a CHILD's pull request, and must not try. It implements each child
95
+ > directly on the pack branch, commits per child, moves the child's status with
96
+ > `mcp__conveyor__update_task` (`task_id` + `status`), and opens exactly ONE pull
97
+ > request at the end — the parent's, into `dev`.
98
+ >
99
+ > **Locally** each child gets its own branch and its own PR into the pack
100
+ > branch, which is what makes them individually reviewable. Read the pack path
101
+ > with that substitution in mind: everywhere it says "the child's PR", a pod
102
+ > means "the child's commits on the pack branch", and the merge step becomes a
103
+ > status write rather than a merge.
104
+
105
+ **The parked-parent rule is load-bearing, not bookkeeping.** An actively
106
+ orchestrating parent reads a headless InProgress child as a dead agent
107
+ environment and "recovers" it onto a cloud pod — duplicate implementation of
108
+ the same child. If the parent is InProgress or ReviewPR, decline the child and
109
+ say why.
110
+
111
+ ## Claim
112
+
113
+ Re-confirm the card is still claimable, then `mcp__conveyor__update_task` →
114
+ `status: "InProgress"`, then `mcp__conveyor__post_to_chat` with a claim marker
115
+ naming where you are running. Status changed under you → someone else took it;
116
+ stop.
117
+
118
+ **A pack parent is claimed differently** — see the pack path. The parent stays
119
+ parked and the chat marker IS the claim.
120
+
121
+ ## Work the plan
122
+
123
+ Post to chat at real milestones only — claim, a blocking discovery, gates
124
+ green, PR — never play-by-play. Your turn output is not visible to the team;
125
+ chat is.
126
+
127
+ **Saving the plan and card fields:** `mcp__conveyor__update_task` handles
128
+ title, description, plan, status, and `githubBranch` on both surfaces. Story
129
+ points and risk are the one split: the local `update_task` carries them, the
130
+ pod's does not — in a pod they go through
131
+ `mcp__conveyor__update_task_properties`. Correct them when the work turns out
132
+ materially bigger or smaller than the estimate, in either direction; a stale
133
+ estimate is worse than a corrected one. Never re-size a card to make a gate, a
134
+ transition, or a review pass.
135
+
136
+ > **Environment — exiting plan mode in a pod.** A pod in discovery mode cannot
137
+ > leave plan mode until the card has a plan, a title, story points, AND a risk
138
+ > level. `update_task` carries neither of the last two, so that step needs
139
+ > `mcp__conveyor__update_task_properties`. A pod exposes it in `discovery`,
140
+ > `auto`, `building` and `chat` modes — but NOT in `review`, and not to a pack
141
+ > parent. If you hold neither tool, you are not the session that is supposed to
142
+ > be sizing the card: say so in chat rather than trying to force the transition.
143
+
144
+ ## Verify
145
+
146
+ Follow the host repo's CLAUDE.md verification policy — scope the gates to your
147
+ diff rather than running everything. Never open a PR with known-failing gates,
148
+ and never silence a failure with a filter, a `.skip`, or a "known flake" note:
149
+ either your diff broke it, or the test is broken or environment-dependent and
150
+ you fix the test.
151
+
152
+ **A UI-visible change needs visual proof before the PR**, in both environments:
153
+ capture a screenshot (static) or a short recording (interaction) with the host
154
+ repo's own tooling and attach it with `mcp__conveyor__upload_attachment`, then
155
+ embed the returned URL in the PR body. A UI PR without it is incomplete.
156
+
157
+ **On a deep pack the gate surface grows, and that is expected.**
158
+ `test:affected` diffs against `origin/dev`, so by the fourth child it also
159
+ covers the three already merged into the pack branch. That is correct
160
+ behavior — do not go looking for why "unrelated" tests are running.
161
+
162
+ > **Repo config, not an environment difference — check it, don't assume it.**
163
+ > Where CI is configured to run only on PRs into `dev`/`main` (this is the
164
+ > Conveyor repo's config), a child PR into a pack branch gets **no automated
165
+ > verification at all**, and your local gate pass is the ONLY gate before that
166
+ > child merges — mandatory, never skippable. A consumer repo may trigger CI
167
+ > more broadly; if it does, let CI finish before merging. Confirm which case
168
+ > you are in rather than assuming, since the two lead to opposite behavior.
169
+
170
+ ## Not every task ends in a PR
171
+
172
+ `create_pull_request` is for work that changes code. Plenty of cards don't:
173
+ support requests, config or credential help, answering a question,
174
+ investigations, research whose deliverable is an answer or a file.
175
+
176
+ If you finish with **no code changes** — an empty `git diff <base>..HEAD` —
177
+ do NOT open a PR. An empty or throwaway PR exists only so a human can close it.
178
+ Instead: post the answer, config, or findings with
179
+ `mcp__conveyor__post_to_chat`, attach anything the user should keep with
180
+ `mcp__conveyor__upload_attachment` (any file type, up to 25MB), and complete
181
+ the card directly with `force_update_task_status("Complete")` — there is no PR
182
+ or review step for a no-code task. Never publish a deliverable as an off-card
183
+ link; the card is where it belongs.
184
+
185
+ When unsure, check the diff: a real diff means open a PR, no diff means finish
186
+ in chat and mark it Complete.
187
+
188
+ ## Open the PR
189
+
190
+ Refresh against the card's base first, then gate, then open — in that order, so
191
+ nothing invalidates the verification you just did:
192
+
193
+ 1. `git fetch origin <base> && git merge origin/<base> --no-edit`
194
+ 2. One verification pass, scoped to the diff.
195
+ 3. `mcp__conveyor__create_pull_request`, naming the base branch **explicitly**.
196
+ **The two surfaces spell these differently and unknown keys are silently
197
+ dropped, not rejected** — locally it is `head:` / `base:`; in a pod it is
198
+ `branch:` / `baseBranch:`. Passing the local names in a pod does not error,
199
+ it just leaves the base unset, and the server then falls back to the base
200
+ recorded on the card. That fallback is usually right and is exactly why the
201
+ mistake is invisible — until the card's recorded branch has drifted, and the
202
+ PR opens against `dev`.
203
+ 4. **Publish the PR guide** with `mcp__conveyor__publish_review_guide`, right
204
+ after `create_pull_request` succeeds — it is part of opening a PR, not a
205
+ follow-up. `sections` is a top-level array argument, not prose stuffed into
206
+ `overview`; keep `overview` a short intro and order the sections with core
207
+ behavior first, tests and generated files later. Reference only files this
208
+ diff actually changed, never context you merely read. Anchors are optional
209
+ and strict — `{"path": "..."}` alone is the safe form. Republish for the new
210
+ head SHA after any later push, or the card's Guide tab shows stale. It is
211
+ best-effort: if it still fails after one corrected retry, carry on — it
212
+ never blocks opening or updating the PR.
213
+ 5. Post a chat summary: what shipped, how it was verified, what a reviewer
214
+ should look at, and anything you did NOT do.
215
+
216
+ Then confirm CI actually started (read-only `gh pr checks`). Do not wait on it.
217
+
218
+ **Do not re-merge the base and do not re-run a gate that already passed.** If
219
+ the base moved while the gates ran, open the PR anyway — CI validates against
220
+ the merged base.
221
+
222
+ ## Aftercare
223
+
224
+ The card is in ReviewPR. What happens next depends on who is driving:
225
+
226
+ > **Environment — a pod is woken; a standalone local run is not.** In a **pod**
227
+ > you stay wake-able: the system wakes you on CI results and review comments,
228
+ > so go idle rather than polling. In a **standalone local run** there is no
229
+ > wake mechanism — report the card and PR state to the user and STOP. No
230
+ > pacing, no loop, no babysitting; a follow-up CI fix is a fresh ask. Under
231
+ > `conveyor-local-loop` the loop's own Babysit tier owns aftercare instead, and
232
+ > its pacing rules govern.
233
+
234
+ When you are the one handling it:
235
+
236
+ - **Red CI** → fix it now, on the same branch. Read the failure before
237
+ concluding it is yours: a suite that fails on a file your diff never touched
238
+ is usually environment-dependent, and the honest fix is to the test, in the
239
+ same PR or a dedicated one.
240
+ - **Review comments or requested changes** → address them, then reply saying
241
+ what changed.
242
+ - **Green and quiet** → leave it alone. Do not poll.
243
+
244
+ ## Blocked
245
+
246
+ After two genuinely different failed approaches, or on a decision only the user
247
+ can make: post the reason AND the specific question to chat, set the card back
248
+ to `"Open"`, restore the tree (locally, `git checkout dev` — leaving a shared
249
+ checkout parked on an abandoned feature branch is how the next session starts
250
+ from the wrong base), and stop. A vague "this is hard" is not a handoff; the
251
+ question is what makes it one.
252
+
253
+ **A thin plan is a different case, and it does not stop a pack.** A card whose
254
+ plan fails the context-free-reader bar: post what is missing to its chat and
255
+ stop — but inside a pack, skip that child and take the next ready one instead
256
+ of halting the whole chain.
257
+
258
+ ## Improve This Skill
259
+
260
+ If this skill was insufficient or slowed the work down, file it with
261
+ `mcp__conveyor__create_suggestion` on the Conveyor project: the issue,
262
+ evidence, and proposed fix.