@gajae-code/coding-agent 0.6.4 → 0.7.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.
Files changed (231) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/types/async/job-manager.d.ts +3 -1
  3. package/dist/types/cli/daemon-cli.d.ts +25 -0
  4. package/dist/types/cli/migrate-cli.d.ts +20 -0
  5. package/dist/types/cli/notify-cli.d.ts +23 -0
  6. package/dist/types/cli/setup-cli.d.ts +20 -1
  7. package/dist/types/commands/daemon.d.ts +41 -0
  8. package/dist/types/commands/migrate.d.ts +33 -0
  9. package/dist/types/commands/notify.d.ts +41 -0
  10. package/dist/types/config/keybindings.d.ts +4 -0
  11. package/dist/types/config/model-profile-activation.d.ts +12 -0
  12. package/dist/types/config/model-profiles.d.ts +2 -1
  13. package/dist/types/config/model-registry.d.ts +3 -3
  14. package/dist/types/config/models-config-schema.d.ts +5 -0
  15. package/dist/types/config/settings-schema.d.ts +38 -0
  16. package/dist/types/coordinator/contract.d.ts +1 -1
  17. package/dist/types/daemon/builtin.d.ts +20 -0
  18. package/dist/types/daemon/control-types.d.ts +57 -0
  19. package/dist/types/daemon/runtime.d.ts +25 -0
  20. package/dist/types/extensibility/extensions/types.d.ts +8 -0
  21. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
  22. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
  23. package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
  24. package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
  25. package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
  26. package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
  27. package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
  28. package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
  29. package/dist/types/gjc-runtime/state-writer.d.ts +38 -7
  30. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +15 -0
  31. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +21 -4
  32. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
  33. package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
  34. package/dist/types/harness-control-plane/storage.d.ts +2 -1
  35. package/dist/types/hooks/skill-state.d.ts +12 -4
  36. package/dist/types/migrate/action-planner.d.ts +11 -0
  37. package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
  38. package/dist/types/migrate/adapters/codex.d.ts +5 -0
  39. package/dist/types/migrate/adapters/index.d.ts +45 -0
  40. package/dist/types/migrate/adapters/opencode.d.ts +2 -0
  41. package/dist/types/migrate/executor.d.ts +2 -0
  42. package/dist/types/migrate/mcp-mapper.d.ts +20 -0
  43. package/dist/types/migrate/report.d.ts +18 -0
  44. package/dist/types/migrate/skill-normalizer.d.ts +27 -0
  45. package/dist/types/migrate/types.d.ts +126 -0
  46. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  47. package/dist/types/modes/components/oauth-selector.d.ts +2 -0
  48. package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
  49. package/dist/types/modes/interactive-mode.d.ts +1 -1
  50. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
  51. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  52. package/dist/types/modes/types.d.ts +7 -1
  53. package/dist/types/notifications/config-commands.d.ts +26 -0
  54. package/dist/types/notifications/config.d.ts +61 -0
  55. package/dist/types/notifications/helpers.d.ts +55 -0
  56. package/dist/types/notifications/html-format.d.ts +62 -0
  57. package/dist/types/notifications/index.d.ts +28 -0
  58. package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
  59. package/dist/types/notifications/telegram-cli.d.ts +19 -0
  60. package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
  61. package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
  62. package/dist/types/notifications/telegram-daemon.d.ts +276 -0
  63. package/dist/types/notifications/telegram-reference.d.ts +111 -0
  64. package/dist/types/notifications/threaded-inbound.d.ts +58 -0
  65. package/dist/types/notifications/threaded-render.d.ts +66 -0
  66. package/dist/types/notifications/topic-registry.d.ts +67 -0
  67. package/dist/types/research-plan/index.d.ts +1 -0
  68. package/dist/types/research-plan/ledger.d.ts +33 -0
  69. package/dist/types/rlm/artifacts.d.ts +1 -1
  70. package/dist/types/rlm/index.d.ts +12 -0
  71. package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
  72. package/dist/types/session/agent-session.d.ts +39 -2
  73. package/dist/types/session/auth-storage.d.ts +1 -1
  74. package/dist/types/setup/credential-auto-import.d.ts +63 -0
  75. package/dist/types/setup/credential-import.d.ts +3 -0
  76. package/dist/types/setup/host-plugin-setup.d.ts +39 -0
  77. package/dist/types/skill-state/active-state.d.ts +6 -11
  78. package/dist/types/skill-state/canonical-skills.d.ts +3 -0
  79. package/dist/types/skill-state/workflow-hud.d.ts +2 -0
  80. package/dist/types/task/spawn-gate.d.ts +1 -10
  81. package/dist/types/tools/ask-answer-registry.d.ts +13 -0
  82. package/dist/types/tools/index.d.ts +18 -0
  83. package/dist/types/tools/subagent.d.ts +3 -0
  84. package/package.json +7 -7
  85. package/scripts/build-binary.ts +3 -0
  86. package/src/async/job-manager.ts +5 -1
  87. package/src/cli/daemon-cli.ts +122 -0
  88. package/src/cli/migrate-cli.ts +106 -0
  89. package/src/cli/notify-cli.ts +274 -0
  90. package/src/cli/setup-cli.ts +173 -84
  91. package/src/cli.ts +3 -0
  92. package/src/commands/daemon.ts +47 -0
  93. package/src/commands/deep-interview.ts +2 -2
  94. package/src/commands/migrate.ts +46 -0
  95. package/src/commands/notify.ts +61 -0
  96. package/src/commands/setup.ts +11 -1
  97. package/src/commands/state.ts +2 -1
  98. package/src/commands/team.ts +7 -3
  99. package/src/config/model-profile-activation.ts +74 -5
  100. package/src/config/model-profiles.ts +7 -4
  101. package/src/config/model-registry.ts +6 -3
  102. package/src/config/models-config-schema.ts +1 -1
  103. package/src/config/settings-schema.ts +29 -0
  104. package/src/coordinator/contract.ts +3 -0
  105. package/src/coordinator-mcp/policy.ts +10 -2
  106. package/src/coordinator-mcp/server.ts +270 -1
  107. package/src/daemon/builtin.ts +46 -0
  108. package/src/daemon/control-types.ts +65 -0
  109. package/src/daemon/runtime.ts +51 -0
  110. package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
  111. package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
  112. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
  113. package/src/defaults/gjc/skills/team/SKILL.md +51 -47
  114. package/src/defaults/gjc/skills/ultragoal/SKILL.md +33 -13
  115. package/src/extensibility/custom-commands/loader.ts +0 -7
  116. package/src/extensibility/extensions/runner.ts +4 -0
  117. package/src/extensibility/extensions/types.ts +8 -0
  118. package/src/extensibility/gjc-plugins/injection.ts +23 -4
  119. package/src/extensibility/gjc-plugins/state.ts +16 -1
  120. package/src/gjc-runtime/deep-interview-recorder.ts +51 -18
  121. package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
  122. package/src/gjc-runtime/goal-mode-request.ts +26 -11
  123. package/src/gjc-runtime/launch-tmux.ts +6 -1
  124. package/src/gjc-runtime/ralplan-runtime.ts +79 -50
  125. package/src/gjc-runtime/session-layout.ts +180 -0
  126. package/src/gjc-runtime/session-resolution.ts +217 -0
  127. package/src/gjc-runtime/state-graph.ts +1 -2
  128. package/src/gjc-runtime/state-migrations.ts +1 -0
  129. package/src/gjc-runtime/state-runtime.ts +247 -124
  130. package/src/gjc-runtime/state-schema.ts +2 -0
  131. package/src/gjc-runtime/state-writer.ts +289 -41
  132. package/src/gjc-runtime/team-runtime.ts +43 -19
  133. package/src/gjc-runtime/tmux-sessions.ts +7 -1
  134. package/src/gjc-runtime/ultragoal-guard.ts +102 -4
  135. package/src/gjc-runtime/ultragoal-runtime.ts +226 -60
  136. package/src/gjc-runtime/workflow-command-ref.ts +1 -2
  137. package/src/gjc-runtime/workflow-manifest.generated.json +27 -2
  138. package/src/gjc-runtime/workflow-manifest.ts +12 -3
  139. package/src/goals/tools/goal-tool.ts +11 -2
  140. package/src/harness-control-plane/storage.ts +14 -4
  141. package/src/hooks/native-skill-hook.ts +38 -12
  142. package/src/hooks/skill-state.ts +178 -83
  143. package/src/internal-urls/docs-index.generated.ts +9 -6
  144. package/src/main.ts +30 -0
  145. package/src/migrate/action-planner.ts +318 -0
  146. package/src/migrate/adapters/claude-code.ts +39 -0
  147. package/src/migrate/adapters/codex.ts +70 -0
  148. package/src/migrate/adapters/index.ts +277 -0
  149. package/src/migrate/adapters/opencode.ts +52 -0
  150. package/src/migrate/executor.ts +81 -0
  151. package/src/migrate/mcp-mapper.ts +152 -0
  152. package/src/migrate/report.ts +104 -0
  153. package/src/migrate/skill-normalizer.ts +80 -0
  154. package/src/migrate/types.ts +163 -0
  155. package/src/modes/acp/acp-event-mapper.ts +1 -0
  156. package/src/modes/bridge/bridge-mode.ts +2 -2
  157. package/src/modes/components/custom-editor.ts +30 -20
  158. package/src/modes/components/hook-editor.ts +7 -2
  159. package/src/modes/components/oauth-selector.ts +19 -0
  160. package/src/modes/controllers/event-controller.ts +20 -0
  161. package/src/modes/controllers/selector-controller.ts +80 -17
  162. package/src/modes/interactive-mode.ts +6 -2
  163. package/src/modes/rpc/rpc-mode.ts +2 -2
  164. package/src/modes/runtime-init.ts +1 -0
  165. package/src/modes/shared/agent-wire/event-contract.ts +1 -0
  166. package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
  167. package/src/modes/shared/agent-wire/event-observation.ts +16 -0
  168. package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
  169. package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
  170. package/src/modes/types.ts +7 -1
  171. package/src/modes/utils/ui-helpers.ts +23 -0
  172. package/src/notifications/config-commands.ts +50 -0
  173. package/src/notifications/config.ts +107 -0
  174. package/src/notifications/helpers.ts +135 -0
  175. package/src/notifications/html-format.ts +389 -0
  176. package/src/notifications/index.ts +663 -0
  177. package/src/notifications/rate-limit-pool.ts +179 -0
  178. package/src/notifications/telegram-cli.ts +194 -0
  179. package/src/notifications/telegram-daemon-cli.ts +74 -0
  180. package/src/notifications/telegram-daemon-control.ts +370 -0
  181. package/src/notifications/telegram-daemon.ts +1370 -0
  182. package/src/notifications/telegram-reference.ts +335 -0
  183. package/src/notifications/threaded-inbound.ts +80 -0
  184. package/src/notifications/threaded-render.ts +155 -0
  185. package/src/notifications/topic-registry.ts +133 -0
  186. package/src/prompts/agents/init.md +1 -1
  187. package/src/prompts/system/plan-mode-active.md +1 -1
  188. package/src/prompts/tools/ast-grep.md +1 -1
  189. package/src/prompts/tools/search.md +1 -1
  190. package/src/prompts/tools/task.md +1 -2
  191. package/src/research-plan/index.ts +1 -0
  192. package/src/research-plan/ledger.ts +177 -0
  193. package/src/rlm/artifacts.ts +12 -3
  194. package/src/rlm/index.ts +26 -0
  195. package/src/runtime-mcp/config-writer.ts +46 -0
  196. package/src/sdk.ts +16 -0
  197. package/src/session/agent-session.ts +128 -24
  198. package/src/session/auth-storage.ts +3 -0
  199. package/src/session/session-dump-format.ts +43 -2
  200. package/src/session/session-manager.ts +39 -5
  201. package/src/setup/credential-auto-import.ts +258 -0
  202. package/src/setup/credential-import.ts +17 -0
  203. package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
  204. package/src/setup/hermes-setup.ts +1 -1
  205. package/src/setup/host-plugin-setup.ts +142 -0
  206. package/src/skill-state/active-state.ts +72 -108
  207. package/src/skill-state/canonical-skills.ts +4 -0
  208. package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
  209. package/src/skill-state/workflow-hud.ts +4 -2
  210. package/src/skill-state/workflow-state-contract.ts +3 -3
  211. package/src/slash-commands/builtin-registry.ts +4 -1
  212. package/src/task/agents.ts +1 -22
  213. package/src/task/executor.ts +5 -1
  214. package/src/task/index.ts +1 -41
  215. package/src/task/spawn-gate.ts +1 -38
  216. package/src/task/types.ts +1 -1
  217. package/src/tools/ask-answer-registry.ts +25 -0
  218. package/src/tools/ask.ts +108 -16
  219. package/src/tools/computer.ts +58 -4
  220. package/src/tools/image-gen.ts +5 -8
  221. package/src/tools/index.ts +19 -0
  222. package/src/tools/inspect-image.ts +16 -11
  223. package/src/tools/subagent-render.ts +7 -0
  224. package/src/tools/subagent.ts +38 -7
  225. package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
  226. package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
  227. package/src/prompts/agents/explore.md +0 -58
  228. package/src/prompts/agents/plan.md +0 -49
  229. package/src/prompts/agents/reviewer.md +0 -141
  230. package/src/prompts/agents/task.md +0 -16
  231. package/src/prompts/review-request.md +0 -70
@@ -6,10 +6,11 @@ import type { WorkflowHudSummary } from "../skill-state/active-state";
6
6
  import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
7
7
  import { renderCliWriteReceipt } from "./cli-write-receipt";
8
8
  import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
9
- import { latestUltragoalLedgerEventFromText } from "./ledger-event-renderer";
9
+ import { gjcRoot, sessionUltragoalDir } from "./session-layout";
10
+ import { resolveGjcSessionForRead, resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
10
11
  import { renderUltragoalStatusMarkdown } from "./state-renderer";
11
12
  import { reconcileWorkflowSkillState } from "./state-runtime";
12
- import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
13
+ import { appendJsonl, persistedStateRevision, writeArtifact, writeGuardedJsonAtomic } from "./state-writer";
13
14
 
14
15
  export type UltragoalGjcGoalMode = "aggregate" | "per-story";
15
16
  export type UltragoalGoalStatus =
@@ -44,6 +45,7 @@ export interface UltragoalPlan {
44
45
  goals: UltragoalGoal[];
45
46
  createdAt: string;
46
47
  updatedAt: string;
48
+ [key: string]: unknown;
47
49
  }
48
50
 
49
51
  export type UltragoalReceiptKind = "per-goal" | "final-aggregate";
@@ -107,6 +109,10 @@ interface JsonObject {
107
109
  [key: string]: unknown;
108
110
  }
109
111
 
112
+ function currentUltragoalSessionId(cwd: string): string {
113
+ return resolveGjcSessionForWrite(cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
114
+ }
115
+
110
116
  const TERMINAL_OR_SKIPPED_STATUSES = new Set<UltragoalGoalStatus>(["complete", "superseded"]);
111
117
  const CLEAN_ARCHITECT_STATUS = "CLEAR";
112
118
  const APPROVE_RECOMMENDATION = "APPROVE";
@@ -162,8 +168,9 @@ export function hashStructuredValue(value: unknown): string {
162
168
  .digest("hex");
163
169
  }
164
170
 
165
- export function getUltragoalPaths(cwd: string): UltragoalPaths {
166
- const dir = path.join(cwd, ".gjc", "ultragoal");
171
+ export function getUltragoalPaths(cwd: string, sessionId?: string | null): UltragoalPaths {
172
+ const explicitSessionId = sessionId?.trim() || process.env.GJC_SESSION_ID?.trim();
173
+ const dir = explicitSessionId ? sessionUltragoalDir(cwd, explicitSessionId) : path.join(gjcRoot(cwd), "ultragoal");
167
174
  return {
168
175
  dir,
169
176
  briefPath: path.join(dir, "brief.md"),
@@ -178,8 +185,10 @@ function isEnoent(error: unknown): boolean {
178
185
  );
179
186
  }
180
187
 
181
- async function appendLedger(cwd: string, event: JsonObject): Promise<UltragoalLedgerEvent> {
182
- const paths = getUltragoalPaths(cwd);
188
+ async function appendLedger(cwd: string, event: JsonObject, sessionId?: string | null): Promise<UltragoalLedgerEvent> {
189
+ const resolvedSessionId =
190
+ sessionId?.trim() || resolveGjcSessionForWrite(cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
191
+ const paths = getUltragoalPaths(cwd, resolvedSessionId);
183
192
  const entry: UltragoalLedgerEvent = {
184
193
  eventId: typeof event.eventId === "string" ? event.eventId : crypto.randomUUID(),
185
194
  ...event,
@@ -187,14 +196,18 @@ async function appendLedger(cwd: string, event: JsonObject): Promise<UltragoalLe
187
196
  };
188
197
  await appendJsonl(paths.ledgerPath, entry, {
189
198
  cwd,
190
- audit: { category: "ledger", verb: "append", owner: "gjc-runtime" },
199
+ audit: { category: "ledger", verb: "append", owner: "gjc-runtime", sessionId: resolvedSessionId },
191
200
  });
201
+ await writeSessionActivityMarker(cwd, resolvedSessionId, { writer: "ultragoal-runtime", path: paths.ledgerPath });
192
202
  return entry;
193
203
  }
194
204
 
195
- export async function readUltragoalLedger(cwd: string): Promise<UltragoalLedgerEvent[]> {
205
+ export async function readUltragoalLedger(cwd: string, sessionId?: string | null): Promise<UltragoalLedgerEvent[]> {
206
+ const resolvedSessionId =
207
+ sessionId?.trim() ||
208
+ (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
196
209
  try {
197
- const raw = await Bun.file(getUltragoalPaths(cwd).ledgerPath).text();
210
+ const raw = await Bun.file(getUltragoalPaths(cwd, resolvedSessionId).ledgerPath).text();
198
211
  return raw
199
212
  .split(/\r?\n/)
200
213
  .map(line => line.trim())
@@ -206,16 +219,21 @@ export async function readUltragoalLedger(cwd: string): Promise<UltragoalLedgerE
206
219
  }
207
220
  }
208
221
 
209
- async function writePlan(cwd: string, plan: UltragoalPlan): Promise<void> {
210
- const paths = getUltragoalPaths(cwd);
222
+ async function writePlan(cwd: string, plan: UltragoalPlan, sessionId?: string | null): Promise<void> {
223
+ const resolvedSessionId =
224
+ sessionId?.trim() || resolveGjcSessionForWrite(cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
225
+ const paths = getUltragoalPaths(cwd, resolvedSessionId);
211
226
  await writeArtifact(paths.briefPath, `${plan.brief.trim()}\n`, {
212
227
  cwd,
213
- audit: { category: "artifact", verb: "write", owner: "gjc-runtime" },
228
+ audit: { category: "artifact", verb: "write", owner: "gjc-runtime", sessionId: resolvedSessionId },
214
229
  });
215
- await writeJsonAtomic(paths.goalsPath, plan, {
230
+ await writeGuardedJsonAtomic(paths.goalsPath, plan, {
216
231
  cwd,
217
- audit: { category: "state", verb: "write", owner: "gjc-runtime" },
232
+ policy: "source",
233
+ expectedRevision: typeof plan.state_revision === "number" ? persistedStateRevision(plan) : undefined,
234
+ audit: { category: "state", verb: "write", owner: "gjc-runtime", sessionId: resolvedSessionId },
218
235
  });
236
+ await writeSessionActivityMarker(cwd, resolvedSessionId, { writer: "ultragoal-runtime", path: paths.goalsPath });
219
237
  }
220
238
 
221
239
  function requiredUltragoalGoals(plan: UltragoalPlan): UltragoalGoal[] {
@@ -426,6 +444,7 @@ function normalizePlan(raw: unknown): UltragoalPlan {
426
444
  const objective = nonEmptyString(goalRecord.objective) ?? title;
427
445
  const goalCreatedAt = nonEmptyString(goalRecord.createdAt) ?? createdAt;
428
446
  return {
447
+ ...goalRecord,
429
448
  id,
430
449
  title,
431
450
  objective,
@@ -459,12 +478,18 @@ function normalizePlan(raw: unknown): UltragoalPlan {
459
478
  goals,
460
479
  createdAt,
461
480
  updatedAt,
481
+ ...(typeof record.state_revision === "number" && Number.isFinite(record.state_revision)
482
+ ? { state_revision: record.state_revision }
483
+ : {}),
462
484
  };
463
485
  }
464
486
 
465
- export async function readUltragoalPlan(cwd: string): Promise<UltragoalPlan | null> {
487
+ export async function readUltragoalPlan(cwd: string, sessionId?: string | null): Promise<UltragoalPlan | null> {
488
+ const resolvedSessionId =
489
+ sessionId?.trim() ||
490
+ (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
466
491
  try {
467
- return normalizePlan(await Bun.file(getUltragoalPaths(cwd).goalsPath).json());
492
+ return normalizePlan(await Bun.file(getUltragoalPaths(cwd, resolvedSessionId).goalsPath).json());
468
493
  } catch (error) {
469
494
  if (isEnoent(error)) return null;
470
495
  throw error;
@@ -483,9 +508,12 @@ function emptyCounts(): Record<UltragoalGoalStatus, number> {
483
508
  };
484
509
  }
485
510
 
486
- export async function getUltragoalStatus(cwd: string): Promise<UltragoalStatusSummary> {
487
- const paths = getUltragoalPaths(cwd);
488
- const plan = await readUltragoalPlan(cwd);
511
+ export async function getUltragoalStatus(cwd: string, sessionId?: string | null): Promise<UltragoalStatusSummary> {
512
+ const resolvedSessionId =
513
+ sessionId?.trim() ||
514
+ (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
515
+ const paths = getUltragoalPaths(cwd, resolvedSessionId);
516
+ const plan = await readUltragoalPlan(cwd, resolvedSessionId);
489
517
  const counts = emptyCounts();
490
518
  if (!plan) return { exists: false, status: "missing", paths, counts, goals: [] };
491
519
  for (const goal of plan.goals) counts[goal.status] += 1;
@@ -577,6 +605,7 @@ export async function createUltragoalPlan(input: {
577
605
  cwd: string;
578
606
  brief: string;
579
607
  gjcGoalMode?: UltragoalGjcGoalMode;
608
+ sessionId?: string | null;
580
609
  }): Promise<UltragoalPlan> {
581
610
  const brief = input.brief.trim();
582
611
  if (!brief) throw new Error("ultragoal brief is required");
@@ -601,8 +630,8 @@ export async function createUltragoalPlan(input: {
601
630
  createdAt: now,
602
631
  updatedAt: now,
603
632
  };
604
- await writePlan(input.cwd, plan);
605
- await appendLedger(input.cwd, { event: "plan_created", goalIds: plan.goals.map(goal => goal.id) });
633
+ await writePlan(input.cwd, plan, input.sessionId);
634
+ await appendLedger(input.cwd, { event: "plan_created", goalIds: plan.goals.map(goal => goal.id) }, input.sessionId);
606
635
  return plan;
607
636
  }
608
637
 
@@ -639,12 +668,16 @@ export function getUltragoalRunCompletionState(
639
668
  };
640
669
  }
641
670
 
642
- export async function startNextUltragoalGoal(input: { cwd: string; retryFailed?: boolean }): Promise<{
671
+ export async function startNextUltragoalGoal(input: {
672
+ cwd: string;
673
+ retryFailed?: boolean;
674
+ sessionId?: string | null;
675
+ }): Promise<{
643
676
  plan: UltragoalPlan;
644
677
  goal?: UltragoalGoal;
645
678
  allComplete: boolean;
646
679
  }> {
647
- const plan = await readUltragoalPlan(input.cwd);
680
+ const plan = await readUltragoalPlan(input.cwd, input.sessionId);
648
681
  if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
649
682
  const goal = chooseNextGoal(plan, input.retryFailed === true);
650
683
  if (!goal) return { plan, allComplete: getUltragoalRunCompletionState(plan).allComplete };
@@ -654,8 +687,8 @@ export async function startNextUltragoalGoal(input: { cwd: string; retryFailed?:
654
687
  goal.startedAt = goal.startedAt ?? now;
655
688
  goal.updatedAt = now;
656
689
  plan.updatedAt = now;
657
- await writePlan(input.cwd, plan);
658
- await appendLedger(input.cwd, { event: "goal_started", goalId: goal.id });
690
+ await writePlan(input.cwd, plan, input.sessionId);
691
+ await appendLedger(input.cwd, { event: "goal_started", goalId: goal.id }, input.sessionId);
659
692
  }
660
693
  return { plan, goal, allComplete: false };
661
694
  }
@@ -865,32 +898,29 @@ function categorizeComputerChangePath(value: string): UltragoalChangeCategory {
865
898
  return "other";
866
899
  }
867
900
 
868
- function isComputerChangePath(row: UltragoalChangeSetPath): boolean {
901
+ function isComputerControlSurfaceCategory(category: UltragoalChangeCategory): boolean {
869
902
  return (
870
- categorizeComputerChangePath(row.path) !== "other" ||
871
- (row.oldPath ? categorizeComputerChangePath(row.oldPath) !== "other" : false)
903
+ category === "code" || category === "generated-binding" || category === "tool" || category === "settings-registry"
872
904
  );
873
905
  }
874
906
 
875
- function isDocsOnlyStaticComputerChangeSet(changeSet: UltragoalChangeSet | undefined): boolean {
876
- if (!changeSet || changeSet.paths.length === 0) return false;
877
- return changeSet.paths.every(row => {
878
- const category = row.category ?? categorizeComputerChangePath(row.path);
879
- const oldCategory = row.oldPath ? categorizeComputerChangePath(row.oldPath) : category;
880
- return category === "docs-static" && oldCategory === "docs-static";
881
- });
907
+ function isComputerControlSurfaceChangePath(row: UltragoalChangeSetPath): boolean {
908
+ const category = row.category ?? categorizeComputerChangePath(row.path);
909
+ const oldCategory = row.oldPath ? categorizeComputerChangePath(row.oldPath) : category;
910
+ return isComputerControlSurfaceCategory(category) || isComputerControlSurfaceCategory(oldCategory);
882
911
  }
883
912
 
884
913
  function trustedChangeSetRequiresComputerSuite(changeSet: UltragoalChangeSet | undefined): boolean {
885
914
  if (!changeSet?.trusted) return false;
886
- if (isDocsOnlyStaticComputerChangeSet(changeSet)) return false;
887
- return changeSet.paths.some(isComputerChangePath);
915
+ return changeSet.paths.some(isComputerControlSurfaceChangePath);
888
916
  }
889
917
 
890
918
  function requiresComputerRedTeamSuite(executorQa: JsonObject, changeSet: UltragoalChangeSet | undefined): boolean {
891
919
  if (trustedChangeSetRequiresComputerSuite(changeSet)) return true;
892
920
  const declaredPaths = Array.isArray(executorQa.changedPaths) ? executorQa.changedPaths : [];
893
- return declaredPaths.some(value => typeof value === "string" && categorizeComputerChangePath(value) !== "other");
921
+ return declaredPaths.some(
922
+ value => typeof value === "string" && isComputerControlSurfaceCategory(categorizeComputerChangePath(value)),
923
+ );
894
924
  }
895
925
 
896
926
  function normalizeAdversarialCaseId(value: string): string {
@@ -2350,6 +2380,8 @@ export async function checkpointUltragoalGoal(input: {
2350
2380
  if (input.status === "complete") goal.completedAt = now;
2351
2381
  plan.updatedAt = now;
2352
2382
  await writePlan(input.cwd, plan);
2383
+ const persistedPlan = await readUltragoalPlan(input.cwd);
2384
+ if (persistedPlan?.state_revision !== undefined) plan.state_revision = persistedPlan.state_revision;
2353
2385
  await appendLedger(input.cwd, {
2354
2386
  eventId: pendingCheckpointEventId,
2355
2387
  event: "goal_checkpointed",
@@ -2799,6 +2831,8 @@ export async function recordUltragoalReviewBlockers(input: {
2799
2831
  evidence: input.evidence,
2800
2832
  gjcGoalJson: input.gjcGoalJson,
2801
2833
  });
2834
+ const persistedPlan = await readUltragoalPlan(input.cwd);
2835
+ if (persistedPlan?.state_revision !== undefined) plan.state_revision = persistedPlan.state_revision;
2802
2836
  const now = new Date().toISOString();
2803
2837
  const nextId = `G${String(plan.goals.length + 1).padStart(3, "0")}`;
2804
2838
  plan.goals.push({
@@ -2816,6 +2850,33 @@ export async function recordUltragoalReviewBlockers(input: {
2816
2850
  return plan;
2817
2851
  }
2818
2852
 
2853
+ export type UltragoalBlockerClassification = "human_blocked" | "resolvable";
2854
+
2855
+ /**
2856
+ * Record an audited blocker triage classification in the durable ledger. A
2857
+ * `human_blocked` classification is the only thing that authorizes
2858
+ * `goal({"op":"pause"})` while an Ultragoal run is active; `resolvable` is an
2859
+ * audit note and never unblocks pause.
2860
+ */
2861
+ export async function recordUltragoalBlockerClassification(input: {
2862
+ cwd: string;
2863
+ classification: UltragoalBlockerClassification;
2864
+ evidence: string;
2865
+ goalId?: string;
2866
+ }): Promise<UltragoalLedgerEvent> {
2867
+ const evidence = input.evidence.trim();
2868
+ if (!evidence) throw new Error("classify-blocker --evidence is required");
2869
+ if (input.classification !== "human_blocked" && input.classification !== "resolvable") {
2870
+ throw new Error('classify-blocker --classification must be "human_blocked" or "resolvable"');
2871
+ }
2872
+ return appendLedger(input.cwd, {
2873
+ event: "blocker_classified",
2874
+ classification: input.classification,
2875
+ ...(input.goalId?.trim() ? { goalId: input.goalId.trim() } : {}),
2876
+ evidence,
2877
+ });
2878
+ }
2879
+
2819
2880
  type UltragoalReviewMode = "review-only" | "review-start";
2820
2881
  type UltragoalReviewContractStrength = "strong" | "thin-derived";
2821
2882
 
@@ -2883,11 +2944,33 @@ async function spawnText(
2883
2944
  }
2884
2945
  }
2885
2946
 
2886
- async function resolveGitBase(cwd: string, branch?: string): Promise<string> {
2887
- const candidates = branch ? [branch] : ["origin/main", "origin/master", "main", "master"];
2888
- for (const candidate of candidates) {
2889
- const exists = await spawnText(["git", "rev-parse", "--verify", candidate], { cwd, timeoutMs: 3000 });
2890
- if (exists.ok) return candidate;
2947
+ export async function resolveGitBase(cwd: string, branch?: string): Promise<string> {
2948
+ if (branch) {
2949
+ const exists = await spawnText(["git", "rev-parse", "--verify", branch], { cwd, timeoutMs: 3000 });
2950
+ if (exists.ok) return branch;
2951
+ } else {
2952
+ // Prefer the NEAREST integration base (the branch this work actually forks
2953
+ // from) rather than always `main`. A branch opened against `dev` must be
2954
+ // scoped to `dev`; using a stale `main` sweeps in unrelated trunk history
2955
+ // and mis-attributes other people's changes to this story (e.g. falsely
2956
+ // tripping change-scoped gates). Among existing candidates, pick the one
2957
+ // whose merge-base with HEAD is closest to HEAD (fewest commits ahead).
2958
+ const candidates = ["origin/dev", "dev", "origin/main", "origin/master", "main", "master"];
2959
+ let best: { ref: string; ahead: number } | undefined;
2960
+ for (const candidate of candidates) {
2961
+ const exists = await spawnText(["git", "rev-parse", "--verify", candidate], { cwd, timeoutMs: 3000 });
2962
+ if (!exists.ok) continue;
2963
+ const mergeBase = await spawnText(["git", "merge-base", "HEAD", candidate], { cwd, timeoutMs: 3000 });
2964
+ if (!mergeBase.ok || !mergeBase.stdout.trim()) continue;
2965
+ const count = await spawnText(["git", "rev-list", "--count", `${mergeBase.stdout.trim()}..HEAD`], {
2966
+ cwd,
2967
+ timeoutMs: 3000,
2968
+ });
2969
+ const ahead = Number.parseInt(count.stdout.trim(), 10);
2970
+ if (!Number.isFinite(ahead)) continue;
2971
+ if (!best || ahead < best.ahead) best = { ref: candidate, ahead };
2972
+ }
2973
+ if (best) return best.ref;
2891
2974
  }
2892
2975
  const mergeBase = await spawnText(["git", "merge-base", "HEAD", "origin/main"], { cwd, timeoutMs: 3000 });
2893
2976
  if (mergeBase.ok && mergeBase.stdout.trim()) return mergeBase.stdout.trim();
@@ -3208,6 +3291,7 @@ const FLAGS_WITH_VALUES = new Set([
3208
3291
  "--rationale",
3209
3292
  "--replacements-json",
3210
3293
  "--order-json",
3294
+ "--classification",
3211
3295
  ]);
3212
3296
 
3213
3297
  function isHelpArg(arg: string): boolean {
@@ -3282,6 +3366,25 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
3282
3366
  "",
3283
3367
  ].join("\n");
3284
3368
  }
3369
+ if (subject === "classify-blocker") {
3370
+ return [
3371
+ "Run native GJC Ultragoal workflow commands",
3372
+ "",
3373
+ "USAGE",
3374
+ " $ gjc ultragoal classify-blocker --classification <human_blocked|resolvable> --evidence <text> [FLAGS]",
3375
+ "",
3376
+ "FLAGS",
3377
+ " --classification=<value> Required. human_blocked authorizes pause only as the latest ledger event; resolvable never authorizes pause",
3378
+ " --evidence=<value> Required. Specific blocker evidence; must name the human-only dependency for human_blocked",
3379
+ " --goal-id=<value> Optional durable .gjc/ultragoal goal id, e.g. G001",
3380
+ " --json Output a machine-readable receipt",
3381
+ "",
3382
+ "EXAMPLES",
3383
+ ' $ gjc ultragoal classify-blocker --classification resolvable --evidence "failing test can be fixed autonomously"',
3384
+ ' $ gjc ultragoal classify-blocker --classification human_blocked --evidence "user must provide production API credentials" --goal-id G001',
3385
+ "",
3386
+ ].join("\n");
3387
+ }
3285
3388
  return [
3286
3389
  "Run native GJC Ultragoal workflow commands",
3287
3390
  "",
@@ -3296,8 +3399,9 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
3296
3399
  " review",
3297
3400
  " steer",
3298
3401
  " record-review-blockers",
3402
+ " classify-blocker",
3299
3403
  "",
3300
- "Run `gjc ultragoal checkpoint --help` or `gjc ultragoal review --help` for command-specific requirements.",
3404
+ "Run `gjc ultragoal checkpoint --help`, `gjc ultragoal review --help`, or `gjc ultragoal classify-blocker --help` for command-specific requirements.",
3301
3405
  "",
3302
3406
  ].join("\n");
3303
3407
  }
@@ -3329,7 +3433,7 @@ function renderCompleteHandoff(
3329
3433
  goal_id: result.goal?.id,
3330
3434
  goal_status: result.goal?.status,
3331
3435
  gjc_objective: result.plan.gjcObjective,
3332
- goals_path: getUltragoalPaths(cwd).goalsPath,
3436
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3333
3437
  });
3334
3438
  }
3335
3439
  if (result.allComplete) return "ultragoal complete all=true\n";
@@ -3353,7 +3457,7 @@ function renderCheckpointContinuation(
3353
3457
  ok: true,
3354
3458
  goal_id: result.checkpointedGoal.id,
3355
3459
  status,
3356
- goals_path: getUltragoalPaths(cwd).goalsPath,
3460
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3357
3461
  completion_receipt_kind: result.checkpointedGoal.completionVerification?.receiptKind,
3358
3462
  quality_gate_hash: result.checkpointedGoal.completionVerification?.qualityGateHash,
3359
3463
  all_complete: result.allComplete,
@@ -3407,7 +3511,12 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
3407
3511
  return {
3408
3512
  kind,
3409
3513
  message: "Accepted add_subgoal steering.\n",
3410
- receipt: { ok: true, kind, goal_id: result.goalId, goals_path: getUltragoalPaths(cwd).goalsPath },
3514
+ receipt: {
3515
+ ok: true,
3516
+ kind,
3517
+ goal_id: result.goalId,
3518
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3519
+ },
3411
3520
  };
3412
3521
  }
3413
3522
  case "split_subgoal": {
@@ -3427,7 +3536,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
3427
3536
  kind,
3428
3537
  goal_id: result.goalId,
3429
3538
  replacement_goal_ids: result.replacementGoalIds,
3430
- goals_path: getUltragoalPaths(cwd).goalsPath,
3539
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3431
3540
  },
3432
3541
  };
3433
3542
  }
@@ -3446,7 +3555,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
3446
3555
  ok: true,
3447
3556
  kind,
3448
3557
  pending_goal_ids: result.pendingGoalIds,
3449
- goals_path: getUltragoalPaths(cwd).goalsPath,
3558
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3450
3559
  },
3451
3560
  };
3452
3561
  }
@@ -3468,7 +3577,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
3468
3577
  kind,
3469
3578
  goal_id: result.goalId,
3470
3579
  changed_fields: result.changedFields,
3471
- goals_path: getUltragoalPaths(cwd).goalsPath,
3580
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3472
3581
  },
3473
3582
  };
3474
3583
  }
@@ -3477,7 +3586,11 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
3477
3586
  return {
3478
3587
  kind,
3479
3588
  message: "Accepted annotate_ledger steering.\n",
3480
- receipt: { ok: true, kind, ledger_path: getUltragoalPaths(cwd).ledgerPath },
3589
+ receipt: {
3590
+ ok: true,
3591
+ kind,
3592
+ ledger_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).ledgerPath,
3593
+ },
3481
3594
  };
3482
3595
  }
3483
3596
  case "mark_blocked_superseded": {
@@ -3496,7 +3609,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
3496
3609
  kind,
3497
3610
  goal_id: result.goalId,
3498
3611
  no_replacement_required: true,
3499
- goals_path: getUltragoalPaths(cwd).goalsPath,
3612
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3500
3613
  },
3501
3614
  };
3502
3615
  }
@@ -3517,6 +3630,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
3517
3630
  }
3518
3631
 
3519
3632
  async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<UltragoalCommandResult> {
3633
+ const sessionId = currentUltragoalSessionId(cwd);
3520
3634
  const help = renderUltragoalHelp(args);
3521
3635
  if (help) return { status: 0, stdout: help };
3522
3636
  try {
@@ -3524,7 +3638,7 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
3524
3638
  const json = hasFlag(args, "--json");
3525
3639
  switch (command) {
3526
3640
  case "status":
3527
- return { status: 0, stdout: renderStatus(await getUltragoalStatus(cwd), json) };
3641
+ return { status: 0, stdout: renderStatus(await getUltragoalStatus(cwd, sessionId), json) };
3528
3642
  case "create":
3529
3643
  case "create-goals": {
3530
3644
  const mode = flagValue(args, "--gjc-goal-mode") === "per-story" ? "per-story" : "aggregate";
@@ -3537,9 +3651,9 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
3537
3651
  ok: true,
3538
3652
  goals_count: plan.goals.length,
3539
3653
  goal_ids: plan.goals.map(goal => goal.id),
3540
- goals_path: getUltragoalPaths(cwd).goalsPath,
3654
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3541
3655
  })
3542
- : `Created ultragoal plan with ${plan.goals.length} goal${plan.goals.length === 1 ? "" : "s"} at ${getUltragoalPaths(cwd).goalsPath}.\n`,
3656
+ : `Created ultragoal plan with ${plan.goals.length} goal${plan.goals.length === 1 ? "" : "s"} at ${getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath}.\n`,
3543
3657
  };
3544
3658
  }
3545
3659
  case "complete-goals":
@@ -3598,10 +3712,32 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
3598
3712
  return {
3599
3713
  status: 0,
3600
3714
  stdout: json
3601
- ? renderCliWriteReceipt({ ok: true, goal_id: goal?.id, goals_path: getUltragoalPaths(cwd).goalsPath })
3715
+ ? renderCliWriteReceipt({
3716
+ ok: true,
3717
+ goal_id: goal?.id,
3718
+ goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
3719
+ })
3602
3720
  : "Recorded review blockers.\n",
3603
3721
  };
3604
3722
  }
3723
+ case "classify-blocker": {
3724
+ const event = await recordUltragoalBlockerClassification({
3725
+ cwd,
3726
+ classification: (flagValue(args, "--classification") ?? "") as UltragoalBlockerClassification,
3727
+ evidence: flagValue(args, "--evidence") ?? "",
3728
+ goalId: flagValue(args, "--goal-id"),
3729
+ });
3730
+ return {
3731
+ status: 0,
3732
+ stdout: json
3733
+ ? renderCliWriteReceipt({
3734
+ ok: true,
3735
+ event: "blocker_classified",
3736
+ classification: event.classification,
3737
+ })
3738
+ : `Recorded blocker classification: ${String(event.classification)}.\n`,
3739
+ };
3740
+ }
3605
3741
  default:
3606
3742
  return { status: 1, stderr: `Unknown gjc ultragoal command: ${command}\n` };
3607
3743
  }
@@ -3619,6 +3755,7 @@ const RECONCILE_COMMANDS = new Set([
3619
3755
  "steer",
3620
3756
  "record-review-blockers",
3621
3757
  "review",
3758
+ "classify-blocker",
3622
3759
  ]);
3623
3760
 
3624
3761
  /**
@@ -3632,9 +3769,9 @@ const RECONCILE_COMMANDS = new Set([
3632
3769
  * beyond that reconcile-failure audit event.
3633
3770
  */
3634
3771
  async function reconcileUltragoalState(cwd: string): Promise<void> {
3635
- const sessionId = process.env.GJC_SESSION_ID?.trim() || undefined;
3772
+ const sessionId = currentUltragoalSessionId(cwd);
3636
3773
  try {
3637
- const summary = await getUltragoalStatus(cwd);
3774
+ const summary = await getUltragoalStatus(cwd, sessionId);
3638
3775
  const status = summary.status;
3639
3776
  const active = summary.exists && status !== "complete";
3640
3777
  const payload: Record<string, unknown> = {
@@ -3653,15 +3790,44 @@ async function reconcileUltragoalState(cwd: string): Promise<void> {
3653
3790
  const ledgerText = await Bun.file(summary.paths.ledgerPath)
3654
3791
  .text()
3655
3792
  .catch(() => "");
3656
- const latestLedger = latestUltragoalLedgerEventFromText(ledgerText);
3793
+ const latestLedger = ledgerText
3794
+ .split(/\r?\n/)
3795
+ .map(line => line.trim())
3796
+ .filter(Boolean)
3797
+ .toReversed()
3798
+ .map(line => {
3799
+ try {
3800
+ const row = JSON.parse(line) as Record<string, unknown>;
3801
+ const event =
3802
+ typeof row.event === "string" ? row.event : typeof row.type === "string" ? row.type : undefined;
3803
+ return event ? { ...row, event } : undefined;
3804
+ } catch {
3805
+ return undefined;
3806
+ }
3807
+ })
3808
+ .find((row): row is Record<string, unknown> & { event: string } => Boolean(row));
3657
3809
  if (latestLedger) {
3658
3810
  payload.latestLedgerEvent = {
3659
3811
  event: latestLedger.event,
3660
3812
  ...(latestLedger.goalId ? { goalId: latestLedger.goalId } : {}),
3661
3813
  ...(latestLedger.timestamp ? { timestamp: latestLedger.timestamp } : {}),
3814
+ ...(typeof latestLedger.kind === "string" ? { kind: latestLedger.kind } : {}),
3815
+ ...(typeof latestLedger.evidence === "string" ? { evidence: latestLedger.evidence } : {}),
3662
3816
  };
3663
3817
  }
3664
- await reconcileWorkflowSkillState({ cwd, mode: "ultragoal", sessionId, active, phase: status, payload });
3818
+ const sourceRevision = Math.max(
3819
+ persistedStateRevision(await readUltragoalPlan(cwd, sessionId)),
3820
+ ledgerText.split(/\r?\n/).filter(line => line.trim().length > 0).length,
3821
+ );
3822
+ await reconcileWorkflowSkillState({
3823
+ cwd,
3824
+ mode: "ultragoal",
3825
+ sessionId,
3826
+ active,
3827
+ phase: status,
3828
+ payload,
3829
+ ...(sourceRevision > 0 ? { sourceRevision } : {}),
3830
+ });
3665
3831
  } catch (error) {
3666
3832
  const message = error instanceof Error ? error.message : String(error);
3667
3833
  process.stderr.write(`ultragoal state reconciliation failed: ${message}\n`);
@@ -1,5 +1,4 @@
1
- import type { CanonicalGjcWorkflowSkill } from "../skill-state/active-state";
2
- import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../skill-state/active-state";
1
+ import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill } from "../skill-state/canonical-skills";
3
2
 
4
3
  export type CommandRefVisibility = "public" | "hidden" | "planned";
5
4
  export type CommandRefIncludeWhen = "implemented-only" | "planned";
@@ -1443,7 +1443,8 @@
1443
1443
  "appliesToVerbs": [
1444
1444
  "checkpoint",
1445
1445
  "record-review-blockers",
1446
- "steer"
1446
+ "steer",
1447
+ "classify-blocker"
1447
1448
  ],
1448
1449
  "name": "evidence",
1449
1450
  "required": true,
@@ -1471,6 +1472,25 @@
1471
1472
  "name": "goal-id",
1472
1473
  "type": "string"
1473
1474
  },
1475
+ {
1476
+ "appliesToVerbs": [
1477
+ "classify-blocker"
1478
+ ],
1479
+ "name": "goal-id",
1480
+ "type": "string"
1481
+ },
1482
+ {
1483
+ "appliesToVerbs": [
1484
+ "classify-blocker"
1485
+ ],
1486
+ "enumValues": [
1487
+ "human_blocked",
1488
+ "resolvable"
1489
+ ],
1490
+ "name": "classification",
1491
+ "required": true,
1492
+ "type": "enum"
1493
+ },
1474
1494
  {
1475
1495
  "appliesToVerbs": [
1476
1496
  "steer"
@@ -1570,7 +1590,8 @@
1570
1590
  "review",
1571
1591
  "checkpoint",
1572
1592
  "record-review-blockers",
1573
- "steer"
1593
+ "steer",
1594
+ "classify-blocker"
1574
1595
  ],
1575
1596
  "name": "json",
1576
1597
  "type": "boolean"
@@ -1651,6 +1672,10 @@
1651
1672
  "name": "steer",
1652
1673
  "surface": "command-positional"
1653
1674
  },
1675
+ {
1676
+ "name": "classify-blocker",
1677
+ "surface": "command-positional"
1678
+ },
1654
1679
  {
1655
1680
  "name": "graph",
1656
1681
  "planned": true,