@gajae-code/coding-agent 0.16.3 → 0.16.6

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.
Files changed (277) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/args.d.ts +2 -2
  4. package/dist/types/cli/customize-doctor.d.ts +22 -0
  5. package/dist/types/cli/root-flags.d.ts +4 -2
  6. package/dist/types/cli.d.ts +1 -1
  7. package/dist/types/commands/launch.d.ts +1 -1
  8. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  9. package/dist/types/config/file-lock.d.ts +31 -3
  10. package/dist/types/config/model-registry.d.ts +4 -0
  11. package/dist/types/config/model-resolver.d.ts +9 -0
  12. package/dist/types/config/settings-schema.d.ts +29 -1
  13. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  15. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  16. package/dist/types/discovery/helpers.d.ts +35 -0
  17. package/dist/types/exec/bash-executor.d.ts +17 -1
  18. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  19. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  20. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  21. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  22. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  23. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  24. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  25. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  26. package/dist/types/exec/isolated-shell.d.ts +19 -0
  27. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  28. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  29. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  30. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  31. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  32. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  33. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  34. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  35. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  36. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  37. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  38. package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
  39. package/dist/types/extensibility/shared-events.d.ts +1 -1
  40. package/dist/types/extensibility/skill-management.d.ts +9 -4
  41. package/dist/types/extensibility/skills.d.ts +2 -0
  42. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  43. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  44. package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
  45. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  46. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  47. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  48. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  49. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  50. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  51. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  52. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  53. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  54. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  55. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  56. package/dist/types/modes/interactive-mode.d.ts +3 -0
  57. package/dist/types/modes/types.d.ts +7 -0
  58. package/dist/types/runtime-mcp/client.d.ts +9 -1
  59. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  60. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  61. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  62. package/dist/types/runtime-mcp/types.d.ts +22 -0
  63. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  64. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  65. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  66. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  68. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  69. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  70. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  71. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  72. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  73. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  74. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  75. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  76. package/dist/types/sdk/client/client.d.ts +6 -0
  77. package/dist/types/sdk/host/host.d.ts +3 -1
  78. package/dist/types/sdk/host/session-runtime.d.ts +26 -1
  79. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  80. package/dist/types/sdk/router/session-router.d.ts +10 -2
  81. package/dist/types/sdk/session-api-key.d.ts +26 -0
  82. package/dist/types/sdk/session.d.ts +5 -1
  83. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  84. package/dist/types/session/agent-session.d.ts +11 -0
  85. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  86. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  87. package/dist/types/session/messages.d.ts +1 -0
  88. package/dist/types/session/session-manager.d.ts +3 -1
  89. package/dist/types/setup/paseo/announce.d.ts +112 -0
  90. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  91. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  92. package/dist/types/system-prompt.d.ts +10 -0
  93. package/dist/types/task/executor.d.ts +5 -1
  94. package/dist/types/tools/bash.d.ts +1 -0
  95. package/dist/types/tools/fetch.d.ts +7 -0
  96. package/dist/types/tools/github-cache.d.ts +2 -1
  97. package/dist/types/tools/index.d.ts +10 -0
  98. package/dist/types/tools/path-utils.d.ts +2 -0
  99. package/dist/types/tools/search.d.ts +6 -0
  100. package/package.json +9 -7
  101. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +11 -0
  103. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  104. package/src/capability/index.ts +14 -2
  105. package/src/capability/types.ts +4 -0
  106. package/src/cli/args.ts +20 -9
  107. package/src/cli/customize-doctor.ts +155 -12
  108. package/src/cli/root-flags.ts +5 -3
  109. package/src/cli.ts +22 -0
  110. package/src/commands/sdk.ts +74 -31
  111. package/src/config/autorouting-tier-map.ts +1 -0
  112. package/src/config/file-lock-gc.ts +40 -2
  113. package/src/config/file-lock.ts +379 -31
  114. package/src/config/model-preset-registry.ts +120 -19
  115. package/src/config/model-profiles.ts +40 -0
  116. package/src/config/model-registry.ts +73 -23
  117. package/src/config/model-resolver.ts +48 -0
  118. package/src/config/settings-schema.ts +33 -1
  119. package/src/config/settings.ts +1 -0
  120. package/src/coordinator-mcp/question-state.ts +33 -2
  121. package/src/coordinator-mcp/server.ts +477 -101
  122. package/src/crash/sanitize.ts +1 -1
  123. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  124. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  125. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  126. package/src/discovery/builtin.ts +52 -55
  127. package/src/discovery/claude-plugins.ts +12 -4
  128. package/src/discovery/helpers.ts +307 -34
  129. package/src/exec/bash-executor.ts +330 -50
  130. package/src/exec/bash-shell-guardian.ts +388 -0
  131. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  132. package/src/exec/bash-shell-signals.ts +29 -0
  133. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  134. package/src/exec/bash-shell-supervisor.ts +359 -0
  135. package/src/exec/bash-shell-worker-entry.ts +5 -0
  136. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  137. package/src/exec/bash-shell-worker.ts +116 -0
  138. package/src/exec/isolated-shell.ts +393 -0
  139. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  140. package/src/extensibility/extensions/function-hooks.ts +959 -0
  141. package/src/extensibility/extensions/index.ts +22 -0
  142. package/src/extensibility/extensions/loader.ts +53 -2
  143. package/src/extensibility/extensions/runner.ts +895 -264
  144. package/src/extensibility/extensions/types.ts +42 -0
  145. package/src/extensibility/extensions/wrapper.ts +56 -15
  146. package/src/extensibility/gjc-plugins/compiler.ts +228 -60
  147. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  148. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  149. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  150. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  152. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  153. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  154. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  155. package/src/extensibility/gjc-plugins/types.ts +25 -0
  156. package/src/extensibility/hooks/loader.ts +133 -16
  157. package/src/extensibility/hooks/types.ts +28 -0
  158. package/src/extensibility/runtime-skill-discovery.ts +149 -190
  159. package/src/extensibility/shared-events.ts +1 -1
  160. package/src/extensibility/skill-management.ts +200 -52
  161. package/src/extensibility/skills.ts +73 -25
  162. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  163. package/src/gjc-runtime/gc-runtime.ts +29 -0
  164. package/src/gjc-runtime/linux-proc.ts +14 -0
  165. package/src/gjc-runtime/repository-binding.ts +1 -1
  166. package/src/gjc-runtime/session-state-lock.ts +143 -10
  167. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  168. package/src/gjc-runtime/state-writer.ts +30 -29
  169. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  170. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  171. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  172. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  173. package/src/harness-control-plane/session-lease.ts +2 -2
  174. package/src/internal-urls/docs-index.generated.ts +12 -12
  175. package/src/lsp/config.ts +10 -3
  176. package/src/lsp/index.ts +1 -1
  177. package/src/lsp/path-trust.ts +69 -14
  178. package/src/main.ts +78 -35
  179. package/src/modes/DESIGN.md +54 -0
  180. package/src/modes/acp/acp-agent.ts +16 -3
  181. package/src/modes/components/assistant-message.ts +22 -9
  182. package/src/modes/components/bash-execution.ts +4 -1
  183. package/src/modes/components/execution-shared.ts +4 -1
  184. package/src/modes/components/model-selector.ts +166 -33
  185. package/src/modes/components/session-observer-overlay.ts +1 -1
  186. package/src/modes/components/settings-selector.ts +1075 -235
  187. package/src/modes/components/status-line/command.ts +190 -0
  188. package/src/modes/components/status-line/index.ts +1 -0
  189. package/src/modes/components/status-line/segments.ts +32 -5
  190. package/src/modes/components/status-line/types.ts +5 -0
  191. package/src/modes/components/tool-status-header.ts +396 -22
  192. package/src/modes/controllers/command-controller.ts +3 -0
  193. package/src/modes/controllers/event-controller.ts +151 -7
  194. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  195. package/src/modes/controllers/input-controller.ts +6 -3
  196. package/src/modes/controllers/selector-controller.ts +21 -0
  197. package/src/modes/interactive-mode.ts +53 -6
  198. package/src/modes/print-mode.ts +5 -1
  199. package/src/modes/prompt-action-autocomplete.ts +54 -27
  200. package/src/modes/runtime-init.ts +2 -0
  201. package/src/modes/types.ts +17 -3
  202. package/src/modes/utils/ui-helpers.ts +2 -0
  203. package/src/prompts/system/system-prompt.md +8 -0
  204. package/src/prompts/tools/search.md +2 -0
  205. package/src/prompts/tools/subagent.md +1 -0
  206. package/src/runtime-mcp/client.ts +60 -17
  207. package/src/runtime-mcp/manager.ts +189 -14
  208. package/src/runtime-mcp/pool.test.ts +218 -0
  209. package/src/runtime-mcp/pool.ts +224 -23
  210. package/src/runtime-mcp/transports/stdio.ts +158 -24
  211. package/src/runtime-mcp/types.ts +24 -0
  212. package/src/sdk/broker/broker.ts +124 -32
  213. package/src/sdk/broker/ensure.ts +248 -41
  214. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  215. package/src/sdk/broker/lifecycle.ts +56 -6
  216. package/src/sdk/broker/startup-failure.ts +15 -6
  217. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  218. package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
  219. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  220. package/src/sdk/bus/conversation-store.ts +29 -0
  221. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  222. package/src/sdk/bus/slack-conversation.ts +1 -0
  223. package/src/sdk/bus/slack-daemon.ts +478 -49
  224. package/src/sdk/bus/slack-live-provider.ts +12 -0
  225. package/src/sdk/bus/slack-provider.ts +5 -0
  226. package/src/sdk/bus/telegram-daemon.ts +16 -2
  227. package/src/sdk/bus/topic-registry.ts +19 -0
  228. package/src/sdk/cli/master-cli.ts +1 -1
  229. package/src/sdk/cli/session-cli.ts +11 -8
  230. package/src/sdk/client/client.ts +66 -17
  231. package/src/sdk/host/host.ts +29 -9
  232. package/src/sdk/host/query/revision-store.ts +19 -13
  233. package/src/sdk/host/reverse-leases.ts +7 -4
  234. package/src/sdk/host/session-runtime.test.ts +52 -10
  235. package/src/sdk/host/session-runtime.ts +383 -44
  236. package/src/sdk/lifecycle/service.ts +4 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +77 -0
  238. package/src/sdk/router/session-router.ts +89 -38
  239. package/src/sdk/session-api-key.ts +94 -0
  240. package/src/sdk/session.ts +228 -55
  241. package/src/sdk/transport/relay.ts +35 -13
  242. package/src/sdk/transport/serve-cli.ts +3 -3
  243. package/src/session/agent-session.ts +240 -90
  244. package/src/session/contribution-prep.ts +214 -21
  245. package/src/session/fallback-chain-controller.ts +38 -0
  246. package/src/session/internal/managed-session-storage.ts +19 -5
  247. package/src/session/messages.ts +5 -0
  248. package/src/session/session-manager.ts +29 -5
  249. package/src/session-import/redact.ts +5 -1
  250. package/src/setup/paseo/announce.ts +456 -0
  251. package/src/setup/paseo/setup-deps.ts +15 -3
  252. package/src/skill-state/canonical-skills.ts +12 -0
  253. package/src/skills/skills.test.ts +41 -21
  254. package/src/slash-commands/builtin-registry.ts +1 -0
  255. package/src/system-prompt.ts +29 -3
  256. package/src/task/executor.ts +7 -1
  257. package/src/task/index.ts +21 -4
  258. package/src/tools/bash.ts +63 -19
  259. package/src/tools/cron.ts +1 -1
  260. package/src/tools/fetch.ts +29 -1
  261. package/src/tools/gh.ts +26 -3
  262. package/src/tools/github-cache.ts +68 -16
  263. package/src/tools/index.ts +10 -0
  264. package/src/tools/job.ts +21 -4
  265. package/src/tools/monitor.ts +8 -2
  266. package/src/tools/path-utils.ts +7 -0
  267. package/src/tools/read.ts +14 -2
  268. package/src/tools/render-utils.ts +1 -3
  269. package/src/tools/resource-gc.ts +28 -8
  270. package/src/tools/search.ts +116 -58
  271. package/src/tools/skill-discovery.ts +5 -0
  272. package/src/tools/skill.ts +37 -2
  273. package/src/tools/subagent.ts +6 -3
  274. package/src/tools/tool-catalog.generated.ts +10 -3
  275. package/src/tools/ultragoal-ask-guard.ts +2 -2
  276. package/src/web/insane/bridge.ts +113 -23
  277. package/src/web/search/providers/perplexity.ts +102 -41
@@ -79,6 +79,11 @@ import {
79
79
  type UltragoalChangeSetPath,
80
80
  type UltragoalChangeStatus,
81
81
  } from "./ultragoal-change-set";
82
+ import {
83
+ assertUltragoalAdoptionPublished,
84
+ assertUltragoalGoalNotFenced,
85
+ runUltragoalSuccessionCommand,
86
+ } from "./ultragoal-succession";
82
87
  import {
83
88
  resolveUltragoalValidationApplicability,
84
89
  type UltragoalValidationApplicability,
@@ -560,7 +565,13 @@ export async function recordUltragoalNudgeIfBudgetRemaining(input: {
560
565
  );
561
566
  }
562
567
 
563
- export async function writePlan(cwd: string, plan: UltragoalPlan, sessionId?: string | null): Promise<void> {
568
+ export async function writePlan(
569
+ cwd: string,
570
+ plan: UltragoalPlan,
571
+ sessionId?: string | null,
572
+ /** Set by callers already inside {@link withUltragoalPlanOwnership} for this plan. */
573
+ options: { lockHeld?: boolean } = {},
574
+ ): Promise<void> {
564
575
  const resolvedSessionId =
565
576
  sessionId?.trim() || resolveGjcSessionForWrite(cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
566
577
  const paths = getUltragoalPaths(cwd, resolvedSessionId);
@@ -571,12 +582,47 @@ export async function writePlan(cwd: string, plan: UltragoalPlan, sessionId?: st
571
582
  await writeGuardedJsonAtomic(paths.goalsPath, plan, {
572
583
  cwd,
573
584
  policy: "source",
585
+ lockHeld: options.lockHeld === true,
574
586
  expectedRevision: typeof plan.state_revision === "number" ? persistedStateRevision(plan) : undefined,
575
587
  audit: { category: "state", verb: "write", owner: "gjc-runtime", sessionId: resolvedSessionId },
576
588
  });
577
589
  await writeSessionActivityMarker(cwd, resolvedSessionId, { writer: "ultragoal-runtime", path: paths.goalsPath });
578
590
  }
579
591
 
592
+ /**
593
+ * The single exclusion that decides who owns an Ultragoal run (#5353).
594
+ *
595
+ * Establishing ownership is a read-decide-write sequence, not a write: a start
596
+ * reads the plan, checks the outgoing succession fence, then commits `active`.
597
+ * Checking quiescence at a point in time and writing afterwards is not enough —
598
+ * an offer can observe `pending` in the window between an in-flight start's
599
+ * check and its commit, and both runs then believe they own the goal.
600
+ *
601
+ * Every caller that decides ownership — source admission (`start`/`checkpoint`),
602
+ * succession offer/fence issuance, and successor publication — runs its whole
603
+ * decision inside this lock, keyed on the plan it is deciding about, and
604
+ * re-reads the plan under it. Writers inside pass `lockHeld` because
605
+ * `withFileLock` intentionally serializes same-process callers as well.
606
+ *
607
+ * Lock ordering: this lock is always taken BEFORE any ledger lock. The existing
608
+ * ledger-scoped critical sections (nudge budget, critic verdict, critic gate
609
+ * override) never write the plan, so no cycle exists.
610
+ */
611
+ export async function withUltragoalPlanOwnership<T>(cwd: string, sessionId: string, fn: () => Promise<T>): Promise<T> {
612
+ return withWorkflowStateLock(getUltragoalPaths(cwd, sessionId).goalsPath, fn, { cwd });
613
+ }
614
+
615
+ /**
616
+ * Resolve the session whose plan ownership is being decided, without changing
617
+ * the resolution behaviour callers already depend on.
618
+ */
619
+ export async function resolveUltragoalOwnershipSession(cwd: string, sessionId?: string | null): Promise<string> {
620
+ return (
621
+ sessionId?.trim() ||
622
+ (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId
623
+ );
624
+ }
625
+
580
626
  function chooseReceiptKind(
581
627
  plan: UltragoalPlan,
582
628
  ledger: readonly UltragoalLedgerEvent[],
@@ -1315,40 +1361,53 @@ export async function startNextUltragoalGoal(input: {
1315
1361
  allComplete: boolean;
1316
1362
  nextAction: UltragoalCompleteNextAction;
1317
1363
  }> {
1318
- const plan = await readUltragoalPlan(input.cwd, input.sessionId);
1319
- if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
1320
- // Fail closed: delegated execution requires stamped repository authority (#2901).
1321
- if (!plan.repositoryBinding) {
1322
- throw new Error(
1323
- "Ultragoal plan is missing repositoryBinding; recreate goals so the plan is bound to an authoritative repository identity.",
1324
- );
1325
- }
1326
- await assertCwdMatchesRepositoryBinding(input.cwd, plan.repositoryBinding);
1327
- const retryFailed = input.retryFailed === true;
1328
- const goal = chooseNextGoal(plan, retryFailed);
1329
- if (!goal) {
1330
- const state = getUltragoalRunCompletionState(plan, { retryFailed });
1364
+ const sessionId = await resolveUltragoalOwnershipSession(input.cwd, input.sessionId);
1365
+ // Claiming a goal is a read-decide-write sequence. It runs entirely inside the
1366
+ // plan-ownership exclusion so a succession offer cannot observe this run as
1367
+ // idle between the fence check and the commit of `active` (#5353).
1368
+ return withUltragoalPlanOwnership(input.cwd, sessionId, async () => {
1369
+ const plan = await readUltragoalPlan(input.cwd, sessionId);
1370
+ if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
1371
+ // Fail closed: delegated execution requires stamped repository authority (#2901).
1372
+ if (!plan.repositoryBinding) {
1373
+ throw new Error(
1374
+ "Ultragoal plan is missing repositoryBinding; recreate goals so the plan is bound to an authoritative repository identity.",
1375
+ );
1376
+ }
1377
+ await assertCwdMatchesRepositoryBinding(input.cwd, plan.repositoryBinding);
1378
+ // Fail closed before any mutation: a run whose cross-repository adoption never
1379
+ // finished publishing does not own its goals yet, however visible goals.json is
1380
+ // (#5353).
1381
+ await assertUltragoalAdoptionPublished(input.cwd, sessionId);
1382
+ const retryFailed = input.retryFailed === true;
1383
+ const goal = chooseNextGoal(plan, retryFailed);
1384
+ // Re-checked under the exclusion, not before it: an offer that won the lock
1385
+ // first has already published its fence by the time we get here.
1386
+ if (goal) await assertUltragoalGoalNotFenced(input.cwd, sessionId, goal.id);
1387
+ if (!goal) {
1388
+ const state = getUltragoalRunCompletionState(plan, { retryFailed });
1389
+ return {
1390
+ plan,
1391
+ allComplete: state.allComplete,
1392
+ nextAction: resolveUltragoalCompleteNextAction(plan, { retryFailed }),
1393
+ };
1394
+ }
1395
+ if (goal.status !== "active") {
1396
+ const now = new Date().toISOString();
1397
+ goal.status = "active";
1398
+ goal.startedAt = goal.startedAt ?? now;
1399
+ goal.updatedAt = now;
1400
+ plan.updatedAt = now;
1401
+ await writePlan(input.cwd, plan, sessionId, { lockHeld: true });
1402
+ await appendLedger(input.cwd, { event: "goal_started", goalId: goal.id }, sessionId);
1403
+ }
1331
1404
  return {
1332
1405
  plan,
1333
- allComplete: state.allComplete,
1334
- nextAction: resolveUltragoalCompleteNextAction(plan, { retryFailed }),
1406
+ goal,
1407
+ allComplete: false,
1408
+ nextAction: { kind: "execute-goal", goal },
1335
1409
  };
1336
- }
1337
- if (goal.status !== "active") {
1338
- const now = new Date().toISOString();
1339
- goal.status = "active";
1340
- goal.startedAt = goal.startedAt ?? now;
1341
- goal.updatedAt = now;
1342
- plan.updatedAt = now;
1343
- await writePlan(input.cwd, plan, input.sessionId);
1344
- await appendLedger(input.cwd, { event: "goal_started", goalId: goal.id }, input.sessionId);
1345
- }
1346
- return {
1347
- plan,
1348
- goal,
1349
- allComplete: false,
1350
- nextAction: { kind: "execute-goal", goal },
1351
- };
1410
+ });
1352
1411
  }
1353
1412
 
1354
1413
  async function readStructuredValue(cwd: string, value: string): Promise<unknown> {
@@ -3496,17 +3555,41 @@ function validateCompleteCheckpointTargetGoal(goal: UltragoalGoal): void {
3496
3555
  );
3497
3556
  }
3498
3557
 
3499
- export async function checkpointUltragoalGoal(input: {
3558
+ export interface UltragoalCheckpointInput {
3500
3559
  cwd: string;
3501
3560
  goalId: string;
3502
3561
  status: UltragoalGoalStatus;
3503
3562
  evidence: string;
3504
3563
  qualityGateJson?: string;
3505
- }): Promise<UltragoalPlan> {
3506
- const plan = await readUltragoalPlan(input.cwd);
3564
+ }
3565
+
3566
+ /**
3567
+ * Checkpointing shares its plan with a succession offer, but only its *commit*
3568
+ * is fenced against one (#5353). Completion validation must observe the same
3569
+ * repository basis `quality-gate source-hash` did, and the ownership lock's own
3570
+ * on-disk artifact lives under `.gjc/` — a path repositories are not required to
3571
+ * ignore — so capturing the basis while holding it would make the runtime digest
3572
+ * disagree with the gate the caller was told to produce.
3573
+ */
3574
+ export async function checkpointUltragoalGoal(input: UltragoalCheckpointInput): Promise<UltragoalPlan> {
3575
+ const sessionId = await resolveUltragoalOwnershipSession(input.cwd, null);
3576
+ return checkpointUltragoalGoalForSession(input, sessionId);
3577
+ }
3578
+
3579
+ async function checkpointUltragoalGoalForSession(
3580
+ input: UltragoalCheckpointInput,
3581
+ sessionId: string,
3582
+ ): Promise<UltragoalPlan> {
3583
+ const plan = await readUltragoalPlan(input.cwd, sessionId);
3507
3584
  if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
3508
3585
  const goal = plan.goals.find(item => item.id === input.goalId);
3509
3586
  if (!goal) throw new Error(`No ultragoal goal found for ${input.goalId}.`);
3587
+ // A handed-off goal is owned by the successor run; the source must not record
3588
+ // further checkpoints against it (#5353). A run whose own adoption is still
3589
+ // unpublished may not record checkpoints at all — a completion receipt must
3590
+ // never rest on a transaction that never committed.
3591
+ await assertUltragoalAdoptionPublished(input.cwd, sessionId);
3592
+ await assertUltragoalGoalNotFenced(input.cwd, sessionId, goal.id);
3510
3593
  const evidence = input.evidence.trim();
3511
3594
  if (!evidence) throw new Error("checkpoint evidence is required");
3512
3595
  const ledgerBefore = await readUltragoalLedger(input.cwd);
@@ -3683,17 +3766,27 @@ export async function checkpointUltragoalGoal(input: {
3683
3766
  goal.updatedAt = now;
3684
3767
  if (input.status === "complete") goal.completedAt = now;
3685
3768
  plan.updatedAt = now;
3686
- await writePlan(input.cwd, plan);
3687
- const persistedPlan = await readUltragoalPlan(input.cwd);
3688
- if (persistedPlan?.state_revision !== undefined) plan.state_revision = persistedPlan.state_revision;
3689
- await appendLedger(input.cwd, {
3690
- eventId: pendingCheckpointEventId,
3691
- event: "goal_checkpointed",
3692
- goalId: goal.id,
3693
- status: input.status,
3694
- evidence,
3695
- qualityGateJson,
3696
- completionVerification: goal.completionVerification,
3769
+ // Commit inside the exclusion an offer holds while it decides quiescence and
3770
+ // installs its fence. The earlier assertions ran before validation, which is
3771
+ // slow, and an offer may fence a non-active goal in that window; because an
3772
+ // offer writes no source bytes, the plan's compare-and-swap cannot observe it.
3773
+ // Re-asserting here is what makes "not handed off" true at the instant of the
3774
+ // write rather than merely true earlier.
3775
+ await withUltragoalPlanOwnership(input.cwd, sessionId, async () => {
3776
+ await assertUltragoalAdoptionPublished(input.cwd, sessionId);
3777
+ await assertUltragoalGoalNotFenced(input.cwd, sessionId, goal.id);
3778
+ await writePlan(input.cwd, plan, sessionId, { lockHeld: true });
3779
+ const persistedPlan = await readUltragoalPlan(input.cwd, sessionId);
3780
+ if (persistedPlan?.state_revision !== undefined) plan.state_revision = persistedPlan.state_revision;
3781
+ await appendLedger(input.cwd, {
3782
+ eventId: pendingCheckpointEventId,
3783
+ event: "goal_checkpointed",
3784
+ goalId: goal.id,
3785
+ status: input.status,
3786
+ evidence,
3787
+ qualityGateJson,
3788
+ completionVerification: goal.completionVerification,
3789
+ });
3697
3790
  });
3698
3791
  return plan;
3699
3792
  }
@@ -4732,6 +4825,10 @@ const FLAGS_WITH_VALUES = new Set([
4732
4825
  "--validation-batch-json",
4733
4826
  "--out",
4734
4827
  "--surface",
4828
+ "--target-repo",
4829
+ "--authorize",
4830
+ "--authorized-by",
4831
+ "--offer",
4735
4832
  ]);
4736
4833
 
4737
4834
  function isHelpArg(arg: string): boolean {
@@ -4861,6 +4958,46 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
4861
4958
  ].join("\n");
4862
4959
  }
4863
4960
 
4961
+ if (subject === "succession") {
4962
+ return [
4963
+ "Run native GJC Ultragoal workflow commands",
4964
+ "",
4965
+ "USAGE",
4966
+ " $ gjc ultragoal succession offer --target-repo <path> --goal-id <id> [--goal-id <id> ...] --authorize <statement> --authorized-by <identity> [--json]",
4967
+ " $ gjc ultragoal succession adopt --offer <path> [--gjc-goal-mode aggregate|per-story] [--json]",
4968
+ " $ gjc ultragoal succession status [--json]",
4969
+ "",
4970
+ "FLAGS",
4971
+ " --target-repo=<value> Destination repository for the successor run; never the source repository",
4972
+ " --goal-id=<value> Unfinished source goal to hand off (repeatable; no implicit or wildcard selection)",
4973
+ " --authorize=<value> Required. Explicit bounded authorization statement for this source/target/selection",
4974
+ " --authorized-by=<value> Required. Identity that authorized the handoff",
4975
+ " --offer=<value> Offer document recorded by `succession offer`, read from inside the source worktree",
4976
+ " --gjc-goal-mode=<value> Successor validation mode (default aggregate); the source mode is provenance only",
4977
+ " --json Output a machine-readable receipt",
4978
+ "",
4979
+ "OWNERSHIP",
4980
+ " `offer` records a durable outgoing ownership fence, so the source run stops scheduling and checkpointing the",
4981
+ " selected goals immediately — not when the target happens to adopt. Between offer and adoption no run owns them.",
4982
+ " The source brief, goals and ledger are never written. Goals the offer did not select stay schedulable, so",
4983
+ " admission compares a snapshot of the offered work rather than whole-file digests.",
4984
+ " There is deliberately no `revoke` verb: any read-then-delete of the fence races a concurrent adoption and could",
4985
+ " revive the source while the target is starting, so an unadopted offer keeps its goals fenced.",
4986
+ "",
4987
+ "AUTHORITY",
4988
+ " Adoption starts a fresh run bound to the target repository, pinned to the exact worktree the offer named. Source",
4989
+ " statuses, completion receipts, quality gates, reviews and approvals are carried as provenance only and never",
4990
+ " authorize anything in the target. Adoption fails closed on changed offered work, an altered or unfenced offer, an",
4991
+ " unnamed or occupied target, and any duplicate or divergent adoption in the target repository; a retry reconciles",
4992
+ " the exact recorded operation or fails closed. Until publication completes, the target refuses to execute.",
4993
+ "",
4994
+ "EXAMPLES",
4995
+ ' $ gjc ultragoal succession offer --target-repo ../payments-api --goal-id G002 --goal-id G003 --authorize "leader approved moving the refund stories" --authorized-by human:release-lead --json',
4996
+ " $ gjc ultragoal succession adopt --offer ../plan-repo/.gjc/_session-abc/ultragoal/succession/offer-<id>.json --json",
4997
+ "",
4998
+ ].join("\n");
4999
+ }
5000
+
4864
5001
  if (subject === "quality-gate") {
4865
5002
  return [
4866
5003
  "Run native GJC Ultragoal workflow commands",
@@ -4906,9 +5043,12 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
4906
5043
  " quality-gate source-hash",
4907
5044
  " quality-gate lane-selection",
4908
5045
  " quality-gate validate",
5046
+ " succession offer",
5047
+ " succession adopt",
5048
+ " succession status",
4909
5049
 
4910
5050
  "",
4911
- "Run `gjc ultragoal checkpoint --help`, `gjc ultragoal review --help`, `gjc ultragoal classify-blocker --help`, `gjc ultragoal record-critic-verdict --help`, or `gjc ultragoal record-critic-gate-override --help`, or `gjc ultragoal quality-gate --help` for command-specific requirements.",
5051
+ "Run `gjc ultragoal checkpoint --help`, `gjc ultragoal review --help`, `gjc ultragoal classify-blocker --help`, `gjc ultragoal record-critic-verdict --help`, or `gjc ultragoal record-critic-gate-override --help`, or `gjc ultragoal quality-gate --help`, or `gjc ultragoal succession --help` for command-specific requirements.",
4912
5052
  "",
4913
5053
  ].join("\n");
4914
5054
  }
@@ -5390,6 +5530,8 @@ async function dispatchUltragoalCommand(
5390
5530
  .join("\n")}\n`,
5391
5531
  };
5392
5532
  }
5533
+ case "succession":
5534
+ return await runUltragoalSuccessionCommand(args, cwd);
5393
5535
  case "review": {
5394
5536
  const result = await runUltragoalReview(cwd, args);
5395
5537
  return {