@gajae-code/coding-agent 0.4.5 → 0.5.1

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 (185) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/types/async/job-manager.d.ts +26 -0
  3. package/dist/types/cli/args.d.ts +1 -0
  4. package/dist/types/cli/list-models.d.ts +6 -0
  5. package/dist/types/commands/gc.d.ts +26 -0
  6. package/dist/types/commands/harness.d.ts +3 -0
  7. package/dist/types/config/file-lock-gc.d.ts +5 -0
  8. package/dist/types/config/file-lock.d.ts +7 -0
  9. package/dist/types/config/model-profile-activation.d.ts +11 -2
  10. package/dist/types/config/model-profiles.d.ts +7 -0
  11. package/dist/types/config/model-registry.d.ts +3 -0
  12. package/dist/types/config/model-resolver.d.ts +2 -0
  13. package/dist/types/config/models-config-schema.d.ts +30 -0
  14. package/dist/types/config/settings-schema.d.ts +4 -3
  15. package/dist/types/coordinator/contract.d.ts +1 -1
  16. package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
  17. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
  18. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
  19. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
  20. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
  21. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
  22. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
  23. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
  24. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
  25. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
  26. package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
  27. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  28. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
  29. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
  30. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
  31. package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
  32. package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
  33. package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
  34. package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
  35. package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
  36. package/dist/types/gjc-runtime/team-runtime.d.ts +5 -1
  37. package/dist/types/gjc-runtime/tmux-common.d.ts +14 -0
  38. package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
  39. package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
  40. package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
  41. package/dist/types/harness-control-plane/owner.d.ts +8 -1
  42. package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
  43. package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
  44. package/dist/types/harness-control-plane/storage.d.ts +20 -0
  45. package/dist/types/harness-control-plane/types.d.ts +4 -0
  46. package/dist/types/hindsight/mental-models.d.ts +5 -5
  47. package/dist/types/modes/components/hook-selector.d.ts +7 -1
  48. package/dist/types/modes/components/model-selector.d.ts +1 -12
  49. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  50. package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
  51. package/dist/types/modes/rpc/rpc-mode.d.ts +16 -1
  52. package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
  53. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
  54. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
  55. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
  56. package/dist/types/sdk.d.ts +5 -0
  57. package/dist/types/session/agent-session.d.ts +3 -1
  58. package/dist/types/session/blob-store.d.ts +59 -4
  59. package/dist/types/session/session-manager.d.ts +24 -6
  60. package/dist/types/session/streaming-output.d.ts +3 -2
  61. package/dist/types/session/tool-choice-queue.d.ts +6 -0
  62. package/dist/types/skill-state/workflow-hud.d.ts +14 -0
  63. package/dist/types/task/receipt.d.ts +1 -0
  64. package/dist/types/task/types.d.ts +7 -0
  65. package/dist/types/thinking-metadata.d.ts +16 -0
  66. package/dist/types/thinking.d.ts +3 -12
  67. package/dist/types/tools/ask.d.ts +15 -1
  68. package/dist/types/tools/index.d.ts +2 -0
  69. package/dist/types/tools/resolve.d.ts +0 -10
  70. package/dist/types/tools/subagent.d.ts +6 -0
  71. package/dist/types/utils/tool-choice.d.ts +14 -1
  72. package/package.json +7 -7
  73. package/src/async/job-manager.ts +52 -0
  74. package/src/cli/args.ts +3 -0
  75. package/src/cli/auth-broker-cli.ts +1 -0
  76. package/src/cli/list-models.ts +13 -1
  77. package/src/cli.ts +9 -4
  78. package/src/commands/gc.ts +22 -0
  79. package/src/commands/harness.ts +43 -5
  80. package/src/commands/launch.ts +2 -2
  81. package/src/commands/session.ts +3 -1
  82. package/src/config/file-lock-gc.ts +181 -0
  83. package/src/config/file-lock.ts +14 -0
  84. package/src/config/model-profile-activation.ts +15 -3
  85. package/src/config/model-profiles.ts +264 -56
  86. package/src/config/model-resolver.ts +9 -6
  87. package/src/config/models-config-schema.ts +1 -0
  88. package/src/config/settings-schema.ts +6 -3
  89. package/src/coordinator/contract.ts +1 -0
  90. package/src/coordinator-mcp/server.ts +513 -26
  91. package/src/cursor.ts +16 -2
  92. package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
  93. package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
  94. package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
  95. package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
  96. package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
  97. package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
  98. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
  99. package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
  100. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
  101. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
  102. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
  103. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
  104. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
  105. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
  106. package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
  107. package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
  108. package/src/defaults/gjc/skills/team/SKILL.md +3 -2
  109. package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
  110. package/src/defaults/gjc-defaults.ts +7 -0
  111. package/src/defaults/gjc-grok-cli.ts +22 -0
  112. package/src/export/html/index.ts +13 -9
  113. package/src/extensibility/extensions/index.ts +1 -0
  114. package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
  115. package/src/gjc-runtime/deep-interview-recorder.ts +417 -0
  116. package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
  117. package/src/gjc-runtime/deep-interview-state.ts +324 -0
  118. package/src/gjc-runtime/gc-render.ts +70 -0
  119. package/src/gjc-runtime/gc-runtime.ts +403 -0
  120. package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
  121. package/src/gjc-runtime/ralplan-runtime.ts +58 -7
  122. package/src/gjc-runtime/state-renderer.ts +12 -3
  123. package/src/gjc-runtime/state-runtime.ts +46 -29
  124. package/src/gjc-runtime/team-gc.ts +49 -0
  125. package/src/gjc-runtime/team-runtime.ts +211 -8
  126. package/src/gjc-runtime/tmux-common.ts +29 -0
  127. package/src/gjc-runtime/tmux-gc.ts +176 -0
  128. package/src/gjc-runtime/tmux-sessions.ts +68 -12
  129. package/src/gjc-runtime/ultragoal-runtime.ts +517 -41
  130. package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
  131. package/src/gjc-runtime/workflow-manifest.ts +16 -1
  132. package/src/harness-control-plane/gc-adapter.ts +184 -0
  133. package/src/harness-control-plane/owner.ts +89 -27
  134. package/src/harness-control-plane/receipt-spool.ts +128 -0
  135. package/src/harness-control-plane/state-machine.ts +27 -6
  136. package/src/harness-control-plane/storage.ts +93 -0
  137. package/src/harness-control-plane/types.ts +4 -0
  138. package/src/hindsight/mental-models.ts +17 -16
  139. package/src/internal-urls/docs-index.generated.ts +14 -8
  140. package/src/main.ts +7 -2
  141. package/src/modes/components/assistant-message.ts +26 -14
  142. package/src/modes/components/diff.ts +97 -0
  143. package/src/modes/components/hook-selector.ts +19 -0
  144. package/src/modes/components/model-selector.ts +370 -181
  145. package/src/modes/components/status-line/segments.ts +1 -1
  146. package/src/modes/components/tool-execution.ts +30 -13
  147. package/src/modes/controllers/command-controller.ts +25 -6
  148. package/src/modes/controllers/extension-ui-controller.ts +3 -0
  149. package/src/modes/controllers/selector-controller.ts +34 -42
  150. package/src/modes/rpc/rpc-client.ts +3 -2
  151. package/src/modes/rpc/rpc-mode.ts +187 -39
  152. package/src/modes/rpc/rpc-types.ts +5 -2
  153. package/src/modes/shared/agent-wire/command-dispatch.ts +279 -257
  154. package/src/modes/shared/agent-wire/command-validation.ts +11 -0
  155. package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
  156. package/src/modes/shared/agent-wire/session-registry.ts +109 -0
  157. package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
  158. package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
  159. package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
  160. package/src/sdk.ts +46 -5
  161. package/src/secrets/obfuscator.ts +102 -27
  162. package/src/session/agent-session.ts +179 -25
  163. package/src/session/blob-store.ts +148 -6
  164. package/src/session/session-manager.ts +311 -60
  165. package/src/session/streaming-output.ts +185 -122
  166. package/src/session/tool-choice-queue.ts +23 -0
  167. package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
  168. package/src/skill-state/workflow-hud.ts +106 -10
  169. package/src/slash-commands/builtin-registry.ts +3 -2
  170. package/src/task/executor.ts +78 -6
  171. package/src/task/receipt.ts +5 -0
  172. package/src/task/render.ts +21 -1
  173. package/src/task/types.ts +8 -0
  174. package/src/thinking-metadata.ts +51 -0
  175. package/src/thinking.ts +26 -46
  176. package/src/tools/ask.ts +56 -1
  177. package/src/tools/bash.ts +1 -1
  178. package/src/tools/index.ts +2 -0
  179. package/src/tools/job.ts +3 -2
  180. package/src/tools/monitor.ts +36 -1
  181. package/src/tools/resolve.ts +93 -18
  182. package/src/tools/subagent-render.ts +9 -0
  183. package/src/tools/subagent.ts +26 -2
  184. package/src/utils/edit-mode.ts +1 -1
  185. package/src/utils/tool-choice.ts +45 -16
@@ -4,6 +4,7 @@ import type { WorkflowHudSummary } from "../skill-state/active-state";
4
4
  import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
5
5
  import { renderCliWriteReceipt } from "./cli-write-receipt";
6
6
  import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
7
+ import { latestUltragoalLedgerEventFromText } from "./ledger-event-renderer";
7
8
  import { renderUltragoalStatusMarkdown } from "./state-renderer";
8
9
  import { reconcileWorkflowSkillState } from "./state-runtime";
9
10
  import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
@@ -116,6 +117,27 @@ const GJC_GOAL_SNAPSHOT_MAX_AGE_MILLISECONDS = 10 * 60 * 1000;
116
117
  const GJC_GOAL_SNAPSHOT_MAX_FUTURE_SKEW_MILLISECONDS = 60 * 1000;
117
118
 
118
119
  const SCHEDULABLE_STATUSES = new Set<UltragoalGoalStatus>(["pending", "active", "failed"]);
120
+ const NATIVE_STEERING_KINDS = [
121
+ "add_subgoal",
122
+ "split_subgoal",
123
+ "reorder_pending",
124
+ "revise_pending_wording",
125
+ "annotate_ledger",
126
+ "mark_blocked_superseded",
127
+ ] as const;
128
+ type UltragoalSteeringKind = (typeof NATIVE_STEERING_KINDS)[number];
129
+ const NATIVE_STEERING_KIND_SET = new Set<string>(NATIVE_STEERING_KINDS);
130
+
131
+ interface ReplacementSpec {
132
+ title: string;
133
+ objective: string;
134
+ }
135
+
136
+ interface SteeringCommandResult {
137
+ kind: UltragoalSteeringKind;
138
+ message: string;
139
+ receipt: JsonObject;
140
+ }
119
141
 
120
142
  function stableStructuredValue(value: unknown): unknown {
121
143
  if (Array.isArray(value)) return value.map(item => stableStructuredValue(item));
@@ -1263,44 +1285,372 @@ export async function checkpointAndContinueUltragoalGoal(input: {
1263
1285
  };
1264
1286
  }
1265
1287
 
1266
- export async function addUltragoalSubgoal(input: {
1288
+ function nextUltragoalGoalId(plan: UltragoalPlan, offset = 1): string {
1289
+ return `G${String(plan.goals.length + offset).padStart(3, "0")}`;
1290
+ }
1291
+
1292
+ function requireSteeringText(value: string, label: string, kind: UltragoalSteeringKind): string {
1293
+ const trimmed = value.trim();
1294
+ if (!trimmed) throw new Error(`steer --${label} is required for ${kind}`);
1295
+ return trimmed;
1296
+ }
1297
+
1298
+ function requireSteeringEvidence(input: { kind: UltragoalSteeringKind; evidence: string; rationale: string }): {
1299
+ evidence: string;
1300
+ rationale: string;
1301
+ } {
1302
+ return {
1303
+ evidence: requireSteeringText(input.evidence, "evidence", input.kind),
1304
+ rationale: requireSteeringText(input.rationale, "rationale", input.kind),
1305
+ };
1306
+ }
1307
+
1308
+ function findGoalOrThrow(plan: UltragoalPlan, goalId: string, kind: UltragoalSteeringKind): UltragoalGoal {
1309
+ const id = goalId.trim();
1310
+ if (!id) throw new Error(`steer --goal-id is required for ${kind}`);
1311
+ const goal = plan.goals.find(item => item.id === id);
1312
+ if (!goal) throw new Error(`No ultragoal goal found for ${id}.`);
1313
+ return goal;
1314
+ }
1315
+
1316
+ function requireGoalStatus(
1317
+ goal: UltragoalGoal,
1318
+ allowed: readonly UltragoalGoalStatus[],
1319
+ kind: UltragoalSteeringKind,
1320
+ ): void {
1321
+ if (!allowed.includes(goal.status)) {
1322
+ throw new Error(`steer ${kind} requires goal ${goal.id} status ${allowed.join(" or ")}; found ${goal.status}`);
1323
+ }
1324
+ }
1325
+
1326
+ function parseJsonFlag(value: string, label: string, kind: UltragoalSteeringKind): unknown {
1327
+ const trimmed = requireSteeringText(value, label, kind);
1328
+ try {
1329
+ return JSON.parse(trimmed) as unknown;
1330
+ } catch (error) {
1331
+ const message = error instanceof Error ? error.message : String(error);
1332
+ throw new Error(`steer --${label} must be valid JSON for ${kind}: ${message}`);
1333
+ }
1334
+ }
1335
+
1336
+ function parseReplacementSpecs(value: string, kind: UltragoalSteeringKind): ReplacementSpec[] {
1337
+ const raw = parseJsonFlag(value, "replacements-json", kind);
1338
+ if (!Array.isArray(raw) || raw.length < 2) {
1339
+ throw new Error("steer --replacements-json must be an array with at least two replacements");
1340
+ }
1341
+ const seen = new Set<string>();
1342
+ return raw.map((item, index) => {
1343
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
1344
+ throw new Error(`steer --replacements-json[${index}] must be an object`);
1345
+ }
1346
+ const record = item as Record<string, unknown>;
1347
+ const title = typeof record.title === "string" ? record.title.trim() : "";
1348
+ const objective = typeof record.objective === "string" ? record.objective.trim() : "";
1349
+ if (!title || !objective) {
1350
+ throw new Error(`steer --replacements-json[${index}] requires non-empty title and objective`);
1351
+ }
1352
+ const key = `${title}\u0000${objective}`;
1353
+ if (seen.has(key)) throw new Error(`steer --replacements-json[${index}] duplicates an earlier replacement`);
1354
+ seen.add(key);
1355
+ return { title, objective };
1356
+ });
1357
+ }
1358
+
1359
+ function parsePendingOrder(value: string, kind: UltragoalSteeringKind): string[] {
1360
+ const raw = parseJsonFlag(value, "order-json", kind);
1361
+ if (!Array.isArray(raw) || raw.length === 0) {
1362
+ throw new Error("steer --order-json must be a non-empty array of goal ids");
1363
+ }
1364
+ const seen = new Set<string>();
1365
+ return raw.map((item, index) => {
1366
+ if (typeof item !== "string" || item.trim().length === 0) {
1367
+ throw new Error(`steer --order-json[${index}] must be a non-empty goal id string`);
1368
+ }
1369
+ const id = item.trim();
1370
+ if (seen.has(id)) throw new Error(`steer --order-json contains duplicate goal id ${id}`);
1371
+ seen.add(id);
1372
+ return id;
1373
+ });
1374
+ }
1375
+
1376
+ async function appendSteeringRejected(input: {
1377
+ cwd: string;
1378
+ kind: UltragoalSteeringKind;
1379
+ reason: string;
1380
+ goalId?: string;
1381
+ evidence?: string;
1382
+ rationale?: string;
1383
+ payload?: JsonObject;
1384
+ }): Promise<void> {
1385
+ await appendLedger(input.cwd, {
1386
+ event: "steering_rejected",
1387
+ kind: input.kind,
1388
+ goalId: input.goalId?.trim() || undefined,
1389
+ reason: input.reason,
1390
+ evidence: input.evidence?.trim() || undefined,
1391
+ rationale: input.rationale?.trim() || undefined,
1392
+ payload: input.payload,
1393
+ });
1394
+ }
1395
+
1396
+ function steeringPayloadSummary(args: readonly string[]): JsonObject {
1397
+ return {
1398
+ goalId: flagValue(args, "--goal-id"),
1399
+ title: flagValue(args, "--title"),
1400
+ objective: flagValue(args, "--objective"),
1401
+ replacementsJson: flagValue(args, "--replacements-json"),
1402
+ orderJson: flagValue(args, "--order-json"),
1403
+ };
1404
+ }
1405
+
1406
+ function parseNativeSteeringKind(value: string | undefined): UltragoalSteeringKind {
1407
+ if (typeof value === "string" && NATIVE_STEERING_KIND_SET.has(value)) return value as UltragoalSteeringKind;
1408
+ throw new Error(`native steering currently supports --kind ${NATIVE_STEERING_KINDS.join(", ")}`);
1409
+ }
1410
+
1411
+ async function addUltragoalSubgoalToPlan(input: {
1267
1412
  cwd: string;
1413
+ plan: UltragoalPlan;
1268
1414
  title: string;
1269
1415
  objective: string;
1270
1416
  evidence: string;
1271
1417
  rationale: string;
1272
- }): Promise<UltragoalPlan> {
1273
- const plan = await readUltragoalPlan(input.cwd);
1274
- if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
1275
- for (const [label, value] of [
1276
- ["title", input.title],
1277
- ["objective", input.objective],
1278
- ["evidence", input.evidence],
1279
- ["rationale", input.rationale],
1280
- ] as const) {
1281
- if (!value.trim()) throw new Error(`steer --${label} is required for add_subgoal`);
1282
- }
1418
+ }): Promise<{ plan: UltragoalPlan; goalId: string }> {
1419
+ const kind = "add_subgoal";
1420
+ const title = requireSteeringText(input.title, "title", kind);
1421
+ const objective = requireSteeringText(input.objective, "objective", kind);
1422
+ const { evidence, rationale } = requireSteeringEvidence({
1423
+ kind,
1424
+ evidence: input.evidence,
1425
+ rationale: input.rationale,
1426
+ });
1283
1427
  const now = new Date().toISOString();
1284
- const nextId = `G${String(plan.goals.length + 1).padStart(3, "0")}`;
1285
- plan.goals.push({
1428
+ const nextId = nextUltragoalGoalId(input.plan);
1429
+ input.plan.goals.push({
1286
1430
  id: nextId,
1287
- title: input.title.trim(),
1288
- objective: input.objective.trim(),
1431
+ title,
1432
+ objective,
1289
1433
  status: "pending",
1290
1434
  createdAt: now,
1291
1435
  updatedAt: now,
1292
- steering: { kind: "add_subgoal", evidence: input.evidence.trim(), rationale: input.rationale.trim() },
1436
+ steering: { kind, evidence, rationale },
1293
1437
  });
1294
- plan.updatedAt = now;
1295
- await writePlan(input.cwd, plan);
1438
+ input.plan.updatedAt = now;
1439
+ await writePlan(input.cwd, input.plan);
1296
1440
  await appendLedger(input.cwd, {
1297
1441
  event: "steering_accepted",
1298
- kind: "add_subgoal",
1442
+ kind,
1299
1443
  goalId: nextId,
1300
- evidence: input.evidence.trim(),
1301
- rationale: input.rationale.trim(),
1444
+ evidence,
1445
+ rationale,
1302
1446
  });
1303
- return plan;
1447
+ return { plan: input.plan, goalId: nextId };
1448
+ }
1449
+
1450
+ export async function addUltragoalSubgoal(input: {
1451
+ cwd: string;
1452
+ title: string;
1453
+ objective: string;
1454
+ evidence: string;
1455
+ rationale: string;
1456
+ }): Promise<UltragoalPlan> {
1457
+ const plan = await readUltragoalPlan(input.cwd);
1458
+ if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
1459
+ return (await addUltragoalSubgoalToPlan({ ...input, plan })).plan;
1460
+ }
1461
+
1462
+ async function splitUltragoalSubgoal(input: {
1463
+ cwd: string;
1464
+ plan: UltragoalPlan;
1465
+ goalId: string;
1466
+ replacementsJson: string;
1467
+ evidence: string;
1468
+ rationale: string;
1469
+ }): Promise<{ plan: UltragoalPlan; goalId: string; replacementGoalIds: string[] }> {
1470
+ const kind = "split_subgoal";
1471
+ const { evidence, rationale } = requireSteeringEvidence({
1472
+ kind,
1473
+ evidence: input.evidence,
1474
+ rationale: input.rationale,
1475
+ });
1476
+ const target = findGoalOrThrow(input.plan, input.goalId, kind);
1477
+ requireGoalStatus(target, ["pending"], kind);
1478
+ const replacements = parseReplacementSpecs(input.replacementsJson, kind);
1479
+ const now = new Date().toISOString();
1480
+ const replacementGoalIds = replacements.map((_, index) => nextUltragoalGoalId(input.plan, index + 1));
1481
+ target.status = "superseded";
1482
+ target.evidence = evidence;
1483
+ target.updatedAt = now;
1484
+ target.steering = { kind, evidence, rationale, replacementGoalIds };
1485
+ const replacementGoals = replacements.map(
1486
+ (replacement, index): UltragoalGoal => ({
1487
+ id: replacementGoalIds[index]!,
1488
+ title: replacement.title,
1489
+ objective: replacement.objective,
1490
+ status: "pending",
1491
+ createdAt: now,
1492
+ updatedAt: now,
1493
+ steering: { kind: "split_replacement", sourceGoalId: target.id, evidence, rationale },
1494
+ }),
1495
+ );
1496
+ const targetIndex = input.plan.goals.findIndex(goal => goal.id === target.id);
1497
+ input.plan.goals.splice(targetIndex + 1, 0, ...replacementGoals);
1498
+ input.plan.updatedAt = now;
1499
+ await writePlan(input.cwd, input.plan);
1500
+ await appendLedger(input.cwd, {
1501
+ event: "steering_accepted",
1502
+ kind,
1503
+ goalId: target.id,
1504
+ replacementGoalIds,
1505
+ evidence,
1506
+ rationale,
1507
+ });
1508
+ return { plan: input.plan, goalId: target.id, replacementGoalIds };
1509
+ }
1510
+
1511
+ async function reorderPendingUltragoalGoals(input: {
1512
+ cwd: string;
1513
+ plan: UltragoalPlan;
1514
+ orderJson: string;
1515
+ evidence: string;
1516
+ rationale: string;
1517
+ }): Promise<{ plan: UltragoalPlan; pendingGoalIds: string[] }> {
1518
+ const kind = "reorder_pending";
1519
+ const { evidence, rationale } = requireSteeringEvidence({
1520
+ kind,
1521
+ evidence: input.evidence,
1522
+ rationale: input.rationale,
1523
+ });
1524
+ const pendingGoalIds = input.plan.goals.filter(goal => goal.status === "pending").map(goal => goal.id);
1525
+ const requestedOrder = parsePendingOrder(input.orderJson, kind);
1526
+ const pendingSet = new Set(pendingGoalIds);
1527
+ for (const id of requestedOrder) {
1528
+ const goal = input.plan.goals.find(item => item.id === id);
1529
+ if (!goal) throw new Error(`steer --order-json references unknown goal id ${id}`);
1530
+ if (goal.status !== "pending") throw new Error(`steer --order-json references non-pending goal id ${id}`);
1531
+ }
1532
+ const missing = pendingGoalIds.filter(id => !requestedOrder.includes(id));
1533
+ if (missing.length > 0) throw new Error(`steer --order-json missing pending goal id(s): ${missing.join(", ")}`);
1534
+ if (requestedOrder.length !== pendingSet.size)
1535
+ throw new Error("steer --order-json must include every pending goal exactly once");
1536
+ const pendingById = new Map(input.plan.goals.map(goal => [goal.id, goal]));
1537
+ const remaining = [...requestedOrder];
1538
+ input.plan.goals = input.plan.goals.map(goal =>
1539
+ goal.status === "pending" ? pendingById.get(remaining.shift()!)! : goal,
1540
+ );
1541
+ input.plan.updatedAt = new Date().toISOString();
1542
+ await writePlan(input.cwd, input.plan);
1543
+ await appendLedger(input.cwd, {
1544
+ event: "steering_accepted",
1545
+ kind,
1546
+ previousPendingGoalIds: pendingGoalIds,
1547
+ pendingGoalIds: requestedOrder,
1548
+ evidence,
1549
+ rationale,
1550
+ });
1551
+ return { plan: input.plan, pendingGoalIds: requestedOrder };
1552
+ }
1553
+
1554
+ async function revisePendingUltragoalWording(input: {
1555
+ cwd: string;
1556
+ plan: UltragoalPlan;
1557
+ goalId: string;
1558
+ title?: string;
1559
+ objective?: string;
1560
+ evidence: string;
1561
+ rationale: string;
1562
+ }): Promise<{ plan: UltragoalPlan; goalId: string; changedFields: string[] }> {
1563
+ const kind = "revise_pending_wording";
1564
+ const { evidence, rationale } = requireSteeringEvidence({
1565
+ kind,
1566
+ evidence: input.evidence,
1567
+ rationale: input.rationale,
1568
+ });
1569
+ const goal = findGoalOrThrow(input.plan, input.goalId, kind);
1570
+ requireGoalStatus(goal, ["pending"], kind);
1571
+ const title = input.title === undefined ? undefined : input.title.trim();
1572
+ const objective = input.objective === undefined ? undefined : input.objective.trim();
1573
+ if (input.title !== undefined && !title)
1574
+ throw new Error("steer --title must be non-empty for revise_pending_wording");
1575
+ if (input.objective !== undefined && !objective)
1576
+ throw new Error("steer --objective must be non-empty for revise_pending_wording");
1577
+ if (!title && !objective) throw new Error("revise_pending_wording requires --title and/or --objective");
1578
+ const changedFields: string[] = [];
1579
+ if (title !== undefined) {
1580
+ goal.title = title;
1581
+ changedFields.push("title");
1582
+ }
1583
+ if (objective !== undefined) {
1584
+ goal.objective = objective;
1585
+ changedFields.push("objective");
1586
+ }
1587
+ const now = new Date().toISOString();
1588
+ goal.updatedAt = now;
1589
+ goal.steering = { kind, evidence, rationale, changedFields };
1590
+ input.plan.updatedAt = now;
1591
+ await writePlan(input.cwd, input.plan);
1592
+ await appendLedger(input.cwd, {
1593
+ event: "steering_accepted",
1594
+ kind,
1595
+ goalId: goal.id,
1596
+ changedFields,
1597
+ evidence,
1598
+ rationale,
1599
+ });
1600
+ return { plan: input.plan, goalId: goal.id, changedFields };
1601
+ }
1602
+
1603
+ async function annotateUltragoalLedger(input: {
1604
+ cwd: string;
1605
+ plan: UltragoalPlan;
1606
+ evidence: string;
1607
+ rationale: string;
1608
+ }): Promise<{ plan: UltragoalPlan }> {
1609
+ const kind = "annotate_ledger";
1610
+ const { evidence, rationale } = requireSteeringEvidence({
1611
+ kind,
1612
+ evidence: input.evidence,
1613
+ rationale: input.rationale,
1614
+ });
1615
+ await appendLedger(input.cwd, { event: "steering_accepted", kind, evidence, rationale });
1616
+ return { plan: input.plan };
1617
+ }
1618
+
1619
+ async function markBlockedUltragoalSuperseded(input: {
1620
+ cwd: string;
1621
+ plan: UltragoalPlan;
1622
+ goalId: string;
1623
+ evidence: string;
1624
+ rationale: string;
1625
+ }): Promise<{ plan: UltragoalPlan; goalId: string }> {
1626
+ const kind = "mark_blocked_superseded";
1627
+ const { evidence, rationale } = requireSteeringEvidence({
1628
+ kind,
1629
+ evidence: input.evidence,
1630
+ rationale: input.rationale,
1631
+ });
1632
+ const goal = findGoalOrThrow(input.plan, input.goalId, kind);
1633
+ requireGoalStatus(goal, ["blocked", "review_blocked"], kind);
1634
+ const remainingRequiredGoals = requiredUltragoalGoals(input.plan).filter(item => item.id !== goal.id);
1635
+ if (remainingRequiredGoals.length === 0) {
1636
+ throw new Error(`steer ${kind} cannot supersede ${goal.id} because it is the only remaining required goal`);
1637
+ }
1638
+ const now = new Date().toISOString();
1639
+ goal.status = "superseded";
1640
+ goal.evidence = evidence;
1641
+ goal.updatedAt = now;
1642
+ goal.steering = { kind, evidence, rationale, noReplacementRequired: true };
1643
+ input.plan.updatedAt = now;
1644
+ await writePlan(input.cwd, input.plan);
1645
+ await appendLedger(input.cwd, {
1646
+ event: "steering_accepted",
1647
+ kind,
1648
+ goalId: goal.id,
1649
+ noReplacementRequired: true,
1650
+ evidence,
1651
+ rationale,
1652
+ });
1653
+ return { plan: input.plan, goalId: goal.id };
1304
1654
  }
1305
1655
 
1306
1656
  export async function recordUltragoalReviewBlockers(input: {
@@ -1365,6 +1715,8 @@ const FLAGS_WITH_VALUES = new Set([
1365
1715
  "--title",
1366
1716
  "--objective",
1367
1717
  "--rationale",
1718
+ "--replacements-json",
1719
+ "--order-json",
1368
1720
  ]);
1369
1721
 
1370
1722
  function isHelpArg(arg: string): boolean {
@@ -1523,6 +1875,135 @@ function renderCheckpointContinuation(
1523
1875
  return lines.join("\n");
1524
1876
  }
1525
1877
 
1878
+ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: string): Promise<SteeringCommandResult> {
1879
+ const kind = parseNativeSteeringKind(flagValue(args, "--kind"));
1880
+ const plan = await readUltragoalPlan(cwd);
1881
+ if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
1882
+ const evidence = flagValue(args, "--evidence") ?? "";
1883
+ const rationale = flagValue(args, "--rationale") ?? "";
1884
+ try {
1885
+ switch (kind) {
1886
+ case "add_subgoal": {
1887
+ const result = await addUltragoalSubgoalToPlan({
1888
+ cwd,
1889
+ plan,
1890
+ title: flagValue(args, "--title") ?? "",
1891
+ objective: flagValue(args, "--objective") ?? "",
1892
+ evidence,
1893
+ rationale,
1894
+ });
1895
+ return {
1896
+ kind,
1897
+ message: "Accepted add_subgoal steering.\n",
1898
+ receipt: { ok: true, kind, goal_id: result.goalId, goals_path: getUltragoalPaths(cwd).goalsPath },
1899
+ };
1900
+ }
1901
+ case "split_subgoal": {
1902
+ const result = await splitUltragoalSubgoal({
1903
+ cwd,
1904
+ plan,
1905
+ goalId: flagValue(args, "--goal-id") ?? "",
1906
+ replacementsJson: flagValue(args, "--replacements-json") ?? "",
1907
+ evidence,
1908
+ rationale,
1909
+ });
1910
+ return {
1911
+ kind,
1912
+ message: "Accepted split_subgoal steering.\n",
1913
+ receipt: {
1914
+ ok: true,
1915
+ kind,
1916
+ goal_id: result.goalId,
1917
+ replacement_goal_ids: result.replacementGoalIds,
1918
+ goals_path: getUltragoalPaths(cwd).goalsPath,
1919
+ },
1920
+ };
1921
+ }
1922
+ case "reorder_pending": {
1923
+ const result = await reorderPendingUltragoalGoals({
1924
+ cwd,
1925
+ plan,
1926
+ orderJson: flagValue(args, "--order-json") ?? "",
1927
+ evidence,
1928
+ rationale,
1929
+ });
1930
+ return {
1931
+ kind,
1932
+ message: "Accepted reorder_pending steering.\n",
1933
+ receipt: {
1934
+ ok: true,
1935
+ kind,
1936
+ pending_goal_ids: result.pendingGoalIds,
1937
+ goals_path: getUltragoalPaths(cwd).goalsPath,
1938
+ },
1939
+ };
1940
+ }
1941
+ case "revise_pending_wording": {
1942
+ const result = await revisePendingUltragoalWording({
1943
+ cwd,
1944
+ plan,
1945
+ goalId: flagValue(args, "--goal-id") ?? "",
1946
+ title: flagValue(args, "--title"),
1947
+ objective: flagValue(args, "--objective"),
1948
+ evidence,
1949
+ rationale,
1950
+ });
1951
+ return {
1952
+ kind,
1953
+ message: "Accepted revise_pending_wording steering.\n",
1954
+ receipt: {
1955
+ ok: true,
1956
+ kind,
1957
+ goal_id: result.goalId,
1958
+ changed_fields: result.changedFields,
1959
+ goals_path: getUltragoalPaths(cwd).goalsPath,
1960
+ },
1961
+ };
1962
+ }
1963
+ case "annotate_ledger": {
1964
+ await annotateUltragoalLedger({ cwd, plan, evidence, rationale });
1965
+ return {
1966
+ kind,
1967
+ message: "Accepted annotate_ledger steering.\n",
1968
+ receipt: { ok: true, kind, ledger_path: getUltragoalPaths(cwd).ledgerPath },
1969
+ };
1970
+ }
1971
+ case "mark_blocked_superseded": {
1972
+ const result = await markBlockedUltragoalSuperseded({
1973
+ cwd,
1974
+ plan,
1975
+ goalId: flagValue(args, "--goal-id") ?? "",
1976
+ evidence,
1977
+ rationale,
1978
+ });
1979
+ return {
1980
+ kind,
1981
+ message: "Accepted mark_blocked_superseded steering.\n",
1982
+ receipt: {
1983
+ ok: true,
1984
+ kind,
1985
+ goal_id: result.goalId,
1986
+ no_replacement_required: true,
1987
+ goals_path: getUltragoalPaths(cwd).goalsPath,
1988
+ },
1989
+ };
1990
+ }
1991
+ }
1992
+ } catch (error) {
1993
+ const reason = error instanceof Error ? error.message : String(error);
1994
+ await appendSteeringRejected({
1995
+ cwd,
1996
+ kind,
1997
+ reason,
1998
+ goalId: flagValue(args, "--goal-id"),
1999
+ evidence,
2000
+ rationale,
2001
+ payload: steeringPayloadSummary(args),
2002
+ });
2003
+ throw error;
2004
+ }
2005
+ }
2006
+
1526
2007
  async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<UltragoalCommandResult> {
1527
2008
  const help = renderUltragoalHelp(args);
1528
2009
  if (help) return { status: 0, stdout: help };
@@ -1577,26 +2058,10 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
1577
2058
  };
1578
2059
  }
1579
2060
  case "steer": {
1580
- const kind = flagValue(args, "--kind");
1581
- if (kind !== "add_subgoal") throw new Error("native steering currently supports --kind add_subgoal");
1582
- const plan = await addUltragoalSubgoal({
1583
- cwd,
1584
- title: flagValue(args, "--title") ?? "",
1585
- objective: flagValue(args, "--objective") ?? "",
1586
- evidence: flagValue(args, "--evidence") ?? "",
1587
- rationale: flagValue(args, "--rationale") ?? "",
1588
- });
1589
- const goal = plan.goals.at(-1);
2061
+ const result = await executeUltragoalSteeringCommand(args, cwd);
1590
2062
  return {
1591
2063
  status: 0,
1592
- stdout: json
1593
- ? renderCliWriteReceipt({
1594
- ok: true,
1595
- kind,
1596
- goal_id: goal?.id,
1597
- goals_path: getUltragoalPaths(cwd).goalsPath,
1598
- })
1599
- : "Accepted add_subgoal steering.\n",
2064
+ stdout: json ? renderCliWriteReceipt(result.receipt) : result.message,
1600
2065
  };
1601
2066
  }
1602
2067
  case "record-review-blockers": {
@@ -1663,6 +2128,17 @@ async function reconcileUltragoalState(cwd: string): Promise<void> {
1663
2128
  goals_path: summary.paths.goalsPath,
1664
2129
  };
1665
2130
  if (summary.gjcObjective) payload.gjc_objective = summary.gjcObjective;
2131
+ const ledgerText = await Bun.file(summary.paths.ledgerPath)
2132
+ .text()
2133
+ .catch(() => "");
2134
+ const latestLedger = latestUltragoalLedgerEventFromText(ledgerText);
2135
+ if (latestLedger) {
2136
+ payload.latestLedgerEvent = {
2137
+ event: latestLedger.event,
2138
+ ...(latestLedger.goalId ? { goalId: latestLedger.goalId } : {}),
2139
+ ...(latestLedger.timestamp ? { timestamp: latestLedger.timestamp } : {}),
2140
+ };
2141
+ }
1666
2142
  await reconcileWorkflowSkillState({ cwd, mode: "ultragoal", sessionId, active, phase: status, payload });
1667
2143
  } catch (error) {
1668
2144
  const message = error instanceof Error ? error.message : String(error);
@@ -1464,12 +1464,24 @@
1464
1464
  "name": "quality-gate-json",
1465
1465
  "type": "string"
1466
1466
  },
1467
+ {
1468
+ "appliesToVerbs": [
1469
+ "steer"
1470
+ ],
1471
+ "name": "goal-id",
1472
+ "type": "string"
1473
+ },
1467
1474
  {
1468
1475
  "appliesToVerbs": [
1469
1476
  "steer"
1470
1477
  ],
1471
1478
  "enumValues": [
1472
- "add_subgoal"
1479
+ "add_subgoal",
1480
+ "split_subgoal",
1481
+ "reorder_pending",
1482
+ "revise_pending_wording",
1483
+ "annotate_ledger",
1484
+ "mark_blocked_superseded"
1473
1485
  ],
1474
1486
  "name": "kind",
1475
1487
  "type": "enum"
@@ -1497,6 +1509,20 @@
1497
1509
  "name": "rationale",
1498
1510
  "type": "string"
1499
1511
  },
1512
+ {
1513
+ "appliesToVerbs": [
1514
+ "steer"
1515
+ ],
1516
+ "name": "replacements-json",
1517
+ "type": "string"
1518
+ },
1519
+ {
1520
+ "appliesToVerbs": [
1521
+ "steer"
1522
+ ],
1523
+ "name": "order-json",
1524
+ "type": "string"
1525
+ },
1500
1526
  {
1501
1527
  "appliesToVerbs": [
1502
1528
  "status",
@@ -267,10 +267,25 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
267
267
  },
268
268
  { name: "gjc-goal-json", type: "string", appliesToVerbs: ["checkpoint", "record-review-blockers"] },
269
269
  { name: "quality-gate-json", type: "string", appliesToVerbs: ["checkpoint"] },
270
- { name: "kind", type: "enum", enumValues: ["add_subgoal"], appliesToVerbs: ["steer"] },
270
+ { name: "goal-id", type: "string", appliesToVerbs: ["steer"] },
271
+ {
272
+ name: "kind",
273
+ type: "enum",
274
+ enumValues: [
275
+ "add_subgoal",
276
+ "split_subgoal",
277
+ "reorder_pending",
278
+ "revise_pending_wording",
279
+ "annotate_ledger",
280
+ "mark_blocked_superseded",
281
+ ],
282
+ appliesToVerbs: ["steer"],
283
+ },
271
284
  { name: "title", type: "string", appliesToVerbs: ["record-review-blockers", "steer"] },
272
285
  { name: "objective", type: "string", appliesToVerbs: ["record-review-blockers", "steer"] },
273
286
  { name: "rationale", type: "string", appliesToVerbs: ["steer"] },
287
+ { name: "replacements-json", type: "string", appliesToVerbs: ["steer"] },
288
+ { name: "order-json", type: "string", appliesToVerbs: ["steer"] },
274
289
  {
275
290
  name: "json",
276
291
  type: "boolean",